What do I need to change so my tornado code can post successfully?

Question:

I have the following (with some strings modified) which works when using the requests library.

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 )

The answer printed is what I expect.

The following usage of curl also works:

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/

However, when I try to do the same using Tornado and AsyncHTTPClient, I get a "Bad Request" response. Some sample Tornado code is:

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)

If I had to guess, I believe the issue is with how I am sending the bodyData.

What do I need to change in the Tornado code so this will work…?

Asked By: Eric G

||

Answers:

By default, the requests library and Tornado’s AsyncHTTPClient, both, send the body data as form encoded data (i.e. with Content-Type: application/x-www-form-urlencoded).

The requests library automatically encodes the data dict with the correct content type.

But with Tornado’s client, you’re requried to manually encode the data as follows:

from urllib.parse import urlencode

bodyData = urlencode(data)

...


Cause of the error:

You’re getting the Bad Request error because you’re sending the data as JSON but the Content-Type header (sent automatically by Tornado) still says www-form-urlencoded. That means the server can’t decode your supplied data because it doesn’t know that it’s JSON.

Answered By: xyres