文章

Python使用socket完成流式分片上传文件

Python使用socket完成流式分片上传文件

Python使用socket完成流式分片上传文件

Python使用socket完成流式分片上传文件

起因

有一个非常大的文件需要上传到网站,直接全部读取文件会导致内存占用过大、POST请求无法完成等问题,所以想到了类似stream方式上传文件的解决方法

在查找了诸多解决方案后,发现都不适合我的问题,他们大多都使用了MultipartEncoder来解决,但是这会导致上传的data中带有boundary,看上去像这样:

源文件内容:

1
nihao

上传文件的内容:

1
2
3
4
5
--66cc99952f5f4bffbd2c282f3aac4baf
Content-Disposition: form-data; name="file"

nihao
--66cc99952f5f4bffbd2c282f3aac4baf--

在查找了很多方案无果后,想到了直接建立socket,这样就可以直观的控制data的读取和上传了

解决方案

首先先来看下URL,我这里使用的是PUT方法:

1
http://127.0.0.1/api/fs/put

这里我们想要向127.0.0.1这个远程主机建立连接,对/api/fs/put这个资源路径进行操作

所以来建立一个这样的套接字:

1
2
3
4
5
6
7
def stream_upload(host, path, header:dict, src_path):
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 建立TCP套接字
    tcp_socket.connect((host, 80))
    # 连接至远程主机的80端口(没有指定端口就是80)
    body = f'PUT {path} HTTP/1.1\r\n'
    # 这一部分定义了请求的方式(PUT/GET/POST...)和请求的资源路径(传入的path)

由于我们有在请求中使用header,所以在后面加上header

1
2
3
4
    for key in header.keys():
        body += f'{key}:{header[key]}\r\n'
    # 在最后加\r\n,表示header已经加完了
    body += f'\r\n'

之后将这一部分发送

1
    tcp_socket.send(body.encode())

然后就来处理data的问题,上传文件时读1024就发送1024,直到把文件读完

1
2
3
4
5
6
7
8
9
     with open(src_path, 'rb') as file_stream:
        #打开文件
        chunk = file_stream.read(1024)
        #先读1024
        while chunk:
            tcp_socket.send(chunk)
            #读到的1024大小的文件就会被发送
            chunk = file_stream.read(1024)
            #如果没读完就接着读,读完会返回None跳出循环

收尾(由于返回的body很死,所以偷个懒检测body里有没有成功的200字样)

1
2
3
4
5
6
    result = tcp_socket.recv(8192).decode()
    print(result)
    if '"code":200' in str(result):
        return True
    else:
        return False

最终代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def stream_upload(host, param, header:dict, src_path):
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_socket.connect((host, 80))
    body = f'PUT {param} HTTP/1.1\r\n'
    for key in header.keys():

        body += f'{key}:{header[key]}\r\n'
    # end header
    body += f'\r\n'
    tcp_socket.send(body.encode())

    with open(src_path, 'rb') as file_stream:
        chunk = file_stream.read(1024)
        while chunk:
            tcp_socket.send(chunk)
            chunk = file_stream.read(1024)
    # end data
    result = tcp_socket.recv(8192).decode()
    print(result)
    if '"code":200' in str(result):
        return True
    else:
        return False

使用(带有header,向http://127.0.0.1/api/fs/put上传./test.zip文件):

1
2
3
4
5
6
header = {
    'Accept': 'application/json, text/plain, */*',
    'Accept-Encoding': 'gzip, deflate',
    'Authorization': '',
}
stream_upload("127.0.0.1", '/api/fs/put', header, "./test.zip")

DONE!

想要实现查看进度只需要在read方面计算已经上传的大小,凌晨两点了就先不写了

本文由作者按照 CC BY 4.0 进行授权