如何修改Tornado代码以实现成功POST请求?
问题:Tornado AsyncHTTPClient调用接口返回Bad Request如何解决?
我用requests库调用接口能成功运行,代码如下:
import requests from pprint import pprint import json PEM = '/full/path/to/my.pem' client_id='cliendID' client_secret='clientSecret' USER='myuser' PSWD='mypwd' url = 'https://theurl.com/request/' data = { "grant_type": "password", "username": USER, "password": PSWD } auth = (client_id, client_secret) response = requests.post( url, auth=auth, data=data, verify=PEM ) answer = response.json()['answer'] print( answer )
打印结果符合预期。
用curl调用该接口同样成功:
curl -iv --user cliendID:clientSecret --key /full/path/to/server.key --cert /full/path/to/my.pem -F grant_type=password -F username=myuser -F password=mypwd https://theurl.com/request/
但用Tornado的AsyncHTTPClient实现相同功能时,收到“Bad Request”响应,示例代码如下:
from tornado.httpclient import AsyncHTTPClient from tornado.ioloop import IOLoop import json async def get_content(): PEM = '/full/path/to/my.pem' client_id='cliendID' client_secret='clientSecret' url = 'https://theurl.com/request/' USER='myuser' PSWD='mypwd' data = { "grant_type": "password", "username": USER, "password": PSWD } bodyData = json.dumps( data ) http_client = AsyncHTTPClient() response = await http_client.fetch( url, method = 'POST', body = bodyData, auth_username = client_id, auth_password = client_secret, ca_certs = PEM ) print( response.body.decode() ) async def main(): await get_content() if __name__ == "__main__": io_loop = IOLoop.current() io_loop.run_sync(main)
我猜测问题出在bodyData的传递方式上,请问需修改Tornado代码的哪些部分才能让请求成功?
解决办法
你的猜测正确,问题确实出在请求体的格式上。requests的data参数和curl的-F参数都是发送表单数据(application/x-www-form-urlencoded),但你用Tornado时把数据转成了JSON格式发送,接口无法识别,因此返回Bad Request。需要做以下修改:
- 替换请求体编码方式:不要用
json.dumps,改用urllib.parse.urlencode将数据编码为表单格式 - 设置正确的请求头:添加
Content-Type头,明确告诉服务器请求体是表单格式
修改后的Tornado代码如下:
from tornado.httpclient import AsyncHTTPClient from tornado.ioloop import IOLoop from urllib.parse import urlencode async def get_content(): PEM = '/full/path/to/my.pem' client_id='cliendID' client_secret='clientSecret' url = 'https://theurl.com/request/' USER='myuser' PSWD='mypwd' data = { "grant_type": "password", "username": USER, "password": PSWD } # 将数据编码为表单格式 bodyData = urlencode(data) http_client = AsyncHTTPClient() response = await http_client.fetch( url, method = 'POST', body = bodyData, auth_username = client_id, auth_password = client_secret, ca_certs = PEM, # 添加Content-Type请求头 headers = {"Content-Type": "application/x-www-form-urlencoded"} ) print( response.body.decode() ) async def main(): await get_content() if __name__ == "__main__": io_loop = IOLoop.current() io_loop.run_sync(main)
额外注意:
如果你的curl命令中的--key和--cert是用于双向认证的客户端证书,而当前Tornado代码仅设置了ca_certs(验证服务器证书),若接口要求客户端证书,还需补充添加client_key和client_cert参数:
response = await http_client.fetch( url, # 其他参数保持不变 client_key='/full/path/to/server.key', client_cert='/full/path/to/my.pem' )
不过从你的requests代码来看,仅设置了verify=PEM(仅验证服务器证书),未配置客户端证书,所以这一步可能不需要,除非接口实际要求双向认证。
内容的提问来源于stack exchange,提问作者Eric G
相关产品推荐
相关产品推荐

