使用Python请求发布到CloudApp API(AWS)

时间:2013-06-10 20:03:51

标签: python api post python-requests

我花了几天时间试图找出如何使用请求访问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参数的几种排列,而不是使用单独的datafiles字典(如建议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是否可能成为问题。

对这么长的帖子道歉,这让我很生气,感谢您提供的任何帮助。

1 个答案:

答案 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'])