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

3

我有一段可以上传二进制文件到一个我没有shell访问权限的远程服务器的PHP代码。以下是这段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

我正在尝试使用requests将此内容转换为Python代码,但是我无法让服务器接受我的POST请求。我已经尝试了各种方法来使用requests.post,但是头部信息无法模仿上述内容。
这会成功地将二进制文件传输到服务器(可以通过观察Wireshark进行判断),但由于头部信息不符合服务器的预期,它被拒绝了。然而,响应代码为200。
files = {'bulk_test2.mov': ('bulk_test2.mov', open('bulk_test2.mov', 'rb'))}
response = requests.post(url, files=files)

请求代码的结果是一个标题:
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--

有什么想法可以使请求与PHP代码生成的标头匹配吗?

2
被拒绝 响应代码为200?可能会返回一个错误消息页面? - Martijn Pieters
2
我注意到你的 Content-Length 只有160个字节。这恰好是多部分边界、元数据和换行符的大小。你的文件似乎是空的。 - Martijn Pieters
所以,如果我使用res = requests.post(url, data=open_file, headers={'Content-Type':'multipart/form-data; boundary=----------------------------360aaccde050'}),我得到的Content-Length: 208045390是准确的。但是头部信息与服务器期望的不同。 - user3524641
1个回答

6
有两个重大区别:
  1. PHP代码提交了一个名为file的字段,而您的Python代码提交了一个名为bulk_test2.mov的字段。

  2. 您的Python代码提交了一个文件。Content-Length头是160字节,正好等于多部分边界和Content-Disposition部分头占用的空间。要么bulk_test2.mov文件确实为空,要么您尝试多次提交文件而没有回绕或重新打开文件对象。

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

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 RFC,并且您将面临更大的问题。当然,这不是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()

为了保险起见。


以上代码创建了一个头部,类似于: Content-Length: 208045540 Content-Type: multipart/form-data; boundary=bd19b64db83e4ebbaadc4835f9727856 Accept-Encoding: gzip, deflate, compress Accept: / User-Agent: python-requests/2.2.1 CPython/2.7.5 Darwin/13.1.0--bd19b64db83e4ebbaadc4835f9727856 Content-Disposition: form-data; name="file"; filename="bulk_test2.mov"但这与我需要的不同(如下所示): Content-Length: 208045596 Expect: 100-continue Content-Type: multipart/fo... - user3524641
不要过于固执于“精确”的标头! Expect: 100-continue 不是必需的标头,它唯一的作用是暂停上传主体,直到服务器表示可以上传主体为止。如果您的 HTTP 上传因为缺少该标头而失败,那么您的服务器存在比这篇文章更大的问题。 - Martijn Pieters
16字节的内容长度差异完全可以解释为PHP和Python之间正确生成的多部分边界相差8个字符。这永远不会是问题的原因。 - Martijn Pieters
结果表明这与请求未关闭连接有关,这是服务器启动摄取过程的方式。现在正在处理这个问题。 - user3524641
1
@user3524641:然后在会话中将 keep_alive 设置为 False - Martijn Pieters
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接