我花了几天时间试图找出如何使用请求访问CloudApp将图像发布到Python中的CloudApp's API。我可以使用pycloudapp使用Poster完成此操作,但我想学习如何使用请求。
我一直在尝试使用InspectB.in来比较我的脚本和pycloudapp发布的内容,以尝试找出差异。似乎没有很多,但显然存在的少数很重要。使用我当前的代码,我收到服务器端错误(500),这令人沮丧。因为基于海报的代码有效,所以我希望找到一种让Requests能够正常运行的方法,不过我认为这可能不太可行。
CloudApp使用亚马逊网络存储,我知道“文件”参数必须最后用于AWS。到目前为止,我已尝试使用data = collections.OrderedDict(sorted(upload_values)); data['file'] = open(last_pic, 'rb')
而不使用files
参数的几种排列,而不是使用单独的data
和files
字典(如建议here。我使用和不使用文件名标记了files
字典。
这是我的代码:
#!/usr/bin/env python
import requests
import os
last_pic = '/.../image.jpg'
USER = 'email@email.com'
PASS = 'mypass'
AUTH_URL = 'http://my.cl.ly'
API_URL = 'http://my.cl.ly/items/new'
s = requests.Session()
s.auth = requests.auth.HTTPDigestAuth(USER, PASS)
s.headers.update({'Accept': 'application/json'})
upload_request = s.get(API_URL)
upload_values = upload_request.json()['params']
filename = os.path.basename(last_pic)
upload_values['key'] = upload_values['key'].replace(r'${filename}', filename)
files = {'file': open(last_pic, 'rb')}
stuff = requests.post(upload_request.json()['url'], data=upload_values, files=files)
print(stuff.text)
根据InspectB.in,工作(pycloudapp)帖子和我的帖子之间的唯一区别是:
pycloudapp帖子正文中的每个参数都有Content-Type: text/plain; charset=utf-8
,但在我的代码中没有。例如:
--d5e0c013a6de4105b07ac844eea4da6e
Content-Disposition: form-data; name="acl"
Content-Type: text/plain; charset=utf-8
public-read
VS。矿:
--b1892e959d124887a61143dd2b468579
Content-Disposition: form-data; name="acl"
public-read
文件数据不同。
pycloudapp:
--d5e0c013a6de4105b07ac844eea4da6e
Content-Disposition: form-data; name="file"
Content-Type: text/plain; charset=utf-8
����JFIFHH���ICC_PROFILE�applmntrRGB XYZ �...
vs mine:
--b1892e959d124887a61143dd2b468579
Content-Disposition: form-data; name="file"; filename="20130608-ScreenShot-180.jpg"
Content-Type: image/jpeg
����JFIFHH���ICC_PROFILE�applmntrRGB XYZ �...
除了以下内容之外,标题基本相同:
pycloudapp:
Accept: application/json
Accept-Encoding: identity
矿:
Accept: */*
Accept-Encoding: gzip, deflate, compress
具体来说,两者都成功注册为Content-Type: multipart/form-data
认为接受标题可能是重要的区别,我已经尝试添加headers = {'accept': 'application/json', 'content-type': 'multipart/form-data'}
(以及两者都单独),没有运气。不幸的是,如果我修改标题,它会覆盖所有标题并丢失多部分编码。
我也想知道工作帖子中我的帖子vs Content-Type: image/jpeg
中的文件Content-Type: text/plain; charset=utf-8
是否可能成为问题。
对这么长的帖子道歉,这让我很生气,感谢您提供的任何帮助。
答案 0 :(得分:3)
几天后,我终于找到了(简单)问题。 CloudApp API需要对Amazon的响应中的“Location”标头发出“GET”请求。
Pycloudapp工作正常,因为它使用return json.load(self.upload_auth_opener.open(request))
正确验证了GET响应。
我不确定为什么我能够在没有任何身份验证的情况下使用Postman正确发布 - 不管怎么说,即使CloudApp API指定重定向{{3} }。
我无法正确地使用Requests跟踪重定向,因为我发布了未经身份验证的值(如果我继续使用s.post的Session(),则auth标头因为亚马逊不期望它们而抛出错误),因此随后的GET也未经过身份验证。令人困惑的一个令人困惑的部分是POSTed图像不出现在我的CloudApp帐户中。但是,后来我发现我可以手动将亚马逊的响应“位置”粘贴到浏览器窗口中,突然发布的图像出现在我的帐户中。这让我意识到POST不够;完成此过程需要经过身份验证的GET。
然后我发现我没有从requests.post.headers
获得任何帮助。花了几分钟才发现它正在响应重定向的头文件(来自GET的500错误),而不是来自POST。添加allow_redirects=False
后,我可以正确访问Amazon的响应“位置”标头。我只是将这个标题反馈给我经过身份验证的Session(),它终于奏效了。
另一件对这个过程有帮助的事情是requires authentication,它让我了解this SO thread。
希望这种解释有意义并帮助其他人。在过去的几天里,我确实学到了很多东西。我的代码可能仍然需要改进,我想用urllib.quote_plus进行更多的测试以及我是否需要UTF-8编码的东西,但我今天没有上传,所以必须等待。
我目前的代码:
#!/usr/bin/env python
import requests
from collections import OrderedDict
import keyring
import os
last_pic = '/path/to/image.jpg'
USER = 'myemail@email.com'
KEYCHAIN_SERVICE_NAME = 'cloudapp'
# replace with PASS = 'your_password' if you don't use keyring
PASS = keyring.backends.OS_X.Keyring.get_password(KEYCHAIN_SERVICE_NAME, USER)
AUTH_URL = 'http://my.cl.ly'
API_URL = 'http://my.cl.ly/items/new'
s = requests.Session()
s.auth = requests.auth.HTTPDigestAuth(USER, PASS)
s.headers.update({'Accept': 'application/json'})
upload_request = s.get(API_URL)
param_list = []
for key, value in upload_request.json()['params'].items():
param_list.append((key.encode('utf8'), value.encode('utf8')))
data = OrderedDict(sorted(param_list))
filename = (os.path.basename(last_pic)).encode('utf8')
data['key'] = data['key'].replace(r'${filename}', filename)
files = {'file': (filename, open(last_pic,'rb').read()) }
stuff = requests.post(upload_request.json()['url'], data=data, files=files, allow_redirects=False)
s.get(stuff.headers['Location'])