Skip to content

Instantly share code, notes, and snippets.

@mminer
Created April 26, 2013 02:36
Show Gist options
  • Save mminer/5464753 to your computer and use it in GitHub Desktop.
Save mminer/5464753 to your computer and use it in GitHub Desktop.
A JSON request handler for Tornado.
import json
import tornado.web
class JsonHandler(BaseHandler):
"""Request handler where requests and responses speak JSON."""
def prepare(self):
# Incorporate request JSON into arguments dictionary.
if self.request.body:
try:
json_data = json.loads(self.request.body)
self.request.arguments.update(json_data)
except ValueError:
message = 'Unable to parse JSON.'
self.send_error(400, message=message) # Bad Request
# Set up response dictionary.
self.response = dict()
def set_default_headers(self):
self.set_header('Content-Type', 'application/json')
def write_error(self, status_code, **kwargs):
if 'message' not in kwargs:
if status_code == 405:
kwargs['message'] = 'Invalid HTTP method.'
else:
kwargs['message'] = 'Unknown error.'
self.response = kwargs
self.write_json()
def write_json(self):
output = json.dumps(self.response)
self.write(output)
@pconerly
Copy link

There's a bug in this-- tornado expects the argument dictionary to be in the form of:

{
key: ['list', 'of', 'values'],
key: ['list', 'of', 'values'],
}

Even if there's only one value for each key. This is to handle situations where we can pass multiple key-value pairs with the same key into a url querypath.

So if you post with {'message':'derp'} and use the built-in tornado function for getting arguments, you'll get this:

message = self.get_argument('message')
print message
> 'p'

This is because tornado always gives you the last argument. -_-;

Additionally I pop the raw json out of the arguments dictionary. In the original version it stays there and it's ugly.

Here's my version of prepare()

    def prepare(self):
        '''Incorporate request JSON into arguments dictionary.'''
        if self.request.body:
            try:
                json_data = json.loads(self.request.body)
                for k, v in json_data.items():
                    # Tornado expects values in the argument dict to be lists.
                    # in tornado.web.RequestHandler._get_argument the last argument is returned.
                    json_data[k] = [v]
                self.request.arguments.pop(self.request.body)
                self.request.arguments.update(json_data)
            except ValueError, e:
                logger.debug(e.message)
                message = 'Unable to parse JSON.'
                self.send_error(400, message=message) # Bad Request

        # Set up response dictionary.
        self.response = dict()

But overall: @mminer this is awesome and thanks for publishing this! As a tornado-noob it helped me a bunch.

@benjaminwilson
Copy link

Thanks for this. I am confused about the pop()? The body shouldn't be a key in the arguments dict. Also the logger fails without an import.

@JohnBrodie
Copy link

In Python 3, you might want to do json_data = tornado.escape.json_decode(self.request.body). self.request.body returns bytes in Python 3, and json.loads will not handle it automatically.

Thanks for the code.

@amitripshtos
Copy link

@pconerly , The problem is that a number/float variables will not work well using your method.

@weaming
Copy link

weaming commented May 30, 2016

@JohnBrodie Help me so much, but still no work on python3

This works for me:

for k,v in self.request.body_arguments.items():
    body_kw[k] = str(v[0], encoding='utf-8')
    if body_kw[k] == 'true':
        body_kw[k] = True
    if body_kw[k] == 'false':
        body_kw[k] = False
print(body_kw)

@root42
Copy link

root42 commented Dec 1, 2017

Also note, that BaseHandler needs to be defined. Instead one can probably use tornado.web.RequestHandler.

@AlexanderSov
Copy link

From documentation: "write() is used for non-template-based output; it accepts strings, bytes, and dictionaries (dicts will be encoded as JSON)." So, may be better send self.response to self.write()?
One moment more is about writing json_data into self.request.arguments. Also like in documentation, may be better write its in self.json_args and use tornado.escape.json_decode(self.request.body) instead json.loads(self.request.body)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment