使用Python请求发送二进制(视频)文件

3 投票
1 回答
10793 浏览
提问于 2025-04-18 02:23

我有一段能正常工作的PHP代码,它可以把一个二进制文件上传到一个我没有shell访问权限的远程服务器。这个PHP代码是:

function upload($uri, $filename) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, array('file' => '@' . $filename));
curl_exec($ch);
curl_close($ch);
}

这段代码会生成一个这样的请求头:

HTTP/1.1
Host: XXXXXXXXX
Accept: */*
Content-Length: 208045596
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------360aaccde050

我现在想把这个功能转到Python上,使用requests库,但我无法让服务器接受我的POST请求。我尝试了各种方法来使用requests.post,但生成的请求头和上面的不一样。

虽然我能成功把二进制文件传输到服务器(通过观察wireshark可以看到),但因为请求头不符合服务器的预期,所以请求被拒绝了。不过,返回的状态码是200,这表示请求是成功的。

files = {'bulk_test2.mov': ('bulk_test2.mov', open('bulk_test2.mov', 'rb'))}
response = requests.post(url, files=files)

使用requests库的代码生成的请求头是:

HTTP/1.1
Host: XXXX
Content-Length: 160
Content-Type: multipart/form-data; boundary=250852d250b24399977f365f35c4e060
Accept-Encoding: gzip, deflate, compress
Accept: */*
User-Agent: python-requests/2.2.1 CPython/2.7.5 Darwin/13.1.0

--250852d250b24399977f365f35c4e060
Content-Disposition: form-data; name="bulk_test2.mov"; filename="bulk_test2.mov"


--250852d250b24399977f365f35c4e060--

有没有什么建议可以让requests生成的请求头和PHP代码生成的请求头一致呢?

1 个回答

6

这里有两个主要的问题:

  1. 你的PHP代码发送了一个叫 file 的字段,而你的Python代码发送的是一个叫 bulk_test2.mov 的字段。

  2. 你的Python代码发送的是一个文件。它的内容长度(Content-Length)是160字节,正好是多部分边界和 Content-Disposition 部分头所占的空间。要么 bulk_test2.mov 文件确实是空的,要么你在没有重置或重新打开文件对象的情况下多次尝试发送这个文件。

要解决第一个问题,使用 'file' 作为你 files 字典中的键:

files = {'file': open('bulk_test2.mov', 'rb')}
response = requests.post(url, files=files)

我只用了打开的文件对象作为值;在这种情况下,requests 会直接从文件对象中获取文件名。

第二个问题只有你能解决。确保在重复发送时不要 重用 files。你可以重新打开文件,或者使用 files['file'].seek(0) 将读取位置重置到开始。

Expect: 100-continue 这个头部是一个可选的客户端功能,它请求服务器 确认可以继续上传主体;这并不是一个必需的头部,任何文件对象发送失败都不是因为 requests 是否使用了这个功能。如果一个HTTP服务器因为你不使用这个功能而表现不正常,那它就违反了HTTP的标准,你会面临更大的问题。这绝对不是 requests 能为你解决的。

如果你成功发送了实际的文件数据,Content-Length 的小变化是因为Python和PHP之间的(随机)边界长度不同。这是正常的,不会导致上传问题,除非你的目标服务器极其糟糕。再次强调,不要试图用Python来修复这种糟糕。

不过,我猜你可能忽略了一些更简单的事情。比如,服务器可能会将某些 User-Agent 头部列入黑名单。你可以通过使用 Session 对象 来清除一些 requests 默认设置的头部:

files = {'file': open('bulk_test2.mov', 'rb')}
session = requests.Session()
del session.headers['User-Agent']
del session.headers['Accept-Encoding']
response = session.post(url, files=files)

看看这样是否会有所不同。

如果服务器无法处理你的请求,因为它无法处理 HTTP持久连接,你可以尝试将会话作为上下文管理器使用,以确保所有会话连接都被关闭:

files = {'file': open('bulk_test2.mov', 'rb')}
with requests.Session() as session:
    response = session.post(url, files=files, stream=True)

你还可以添加:

response.raw.close()

以确保万无一失。

撰写回答