Skip to content Skip to sidebar Skip to footer

415 Exception Cherrypy Webservice

I'm trying to build a Cherrypy/Python webservice. I already spend the whole day on finding out how to make a cross domain ajax request possible. That's finally working, but now I h

Solution 1:

I've realised that the question is in fact about CORS preflight request. CORS specification defines the following condition for a simple CORS request:

  • Method: GET, HEAD, POST
  • Headers: Accept, Accept-Language, Content-Language, Content-Type
  • Cotent-type header value: application/x-www-form-urlencoded, multipart/form-data, text/plain

Otherwise CORS request isn't simple, and use preflight OPTIONS request before actual request to ensure it's eligible. Here is good CORS how-to.

So if you want to keep things simple you may want to revert to normal application/x-www-form-urlencoded. Otherwise you need to handle preflight requests correctly. Here's working example (don't forget to add localhost alias).

#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Add localhost alias, `proxy` , in /etc/hosts.
'''


import cherrypy


config = {
  'global' : {
    'server.socket_host' : '127.0.0.1',
    'server.socket_port' : 8080,
    'server.thread_pool' : 8
  }
}


def cors():
  if cherrypy.request.method == 'OPTIONS':
    # preflign request 
    # see http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
    cherrypy.response.headers['Access-Control-Allow-Methods'] = 'POST'
    cherrypy.response.headers['Access-Control-Allow-Headers'] = 'content-type'
    cherrypy.response.headers['Access-Control-Allow-Origin']  = '*'
    # tell CherryPy no avoid normal handler
    return True
  else:
    cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'

cherrypy.tools.cors = cherrypy._cptools.HandlerTool(cors)


class App:

  @cherrypy.expose
  def index(self):
    return '''<!DOCTYPE html>
      <html>
      <head>
      <meta content='text/html; charset=utf-8' http-equiv='content-type'>
      <title>CORS AJAX JSON request</title>
      <script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js'></script>
      <script type='text/javascript'>
        $(document).ready(function()
        {
          $('button').on('click', function()
          {
            $.ajax({
              'type'        : 'POST',
              'dataType'    : 'JSON',
              'contentType' : 'application/json',
              'url'         : 'http://proxy:8080/endpoint',
              'data'        : JSON.stringify({'foo': 'bar'}),
              'success'     : function(response)
              {
                console.log(response);  
              }
            });
          })
        });
      </script>
      </head>
      <body>
        <button>make request</button>
      </body>
      </html>
    '''

  @cherrypy.expose
  @cherrypy.config(**{'tools.cors.on': True})
  @cherrypy.tools.json_in()
  @cherrypy.tools.json_out()
  def endpoint(self):
    data = cherrypy.request.json
    return data.items()


if __name__ == '__main__':
  cherrypy.quickstart(App(), '/', config)

Solution 2:

In general if you have chosen a tool, then you're better using it, instead of fighting it. CherryPy tells you that for JSON input it expects request with application/json or text/javascript content-type.

Here's code of cherrypy.lib.jsontools.json_in:

def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
            force=True, debug=False, processor=json_processor):

    request = cherrypy.serving.request
    if isinstance(content_type, basestring):
        content_type = [content_type]

    if force:
        if debug:
            cherrypy.log('Removing body processors %s' %
                         repr(request.body.processors.keys()), 'TOOLS.JSON_IN')
        request.body.processors.clear()
        request.body.default_proc = cherrypy.HTTPError(
            415, 'Expected an entity of content type %s' %
            ', '.join(content_type))

    for ct in content_type:
        if debug:
            cherrypy.log('Adding body processor for %s' % ct, 'TOOLS.JSON_IN')
        request.body.processors[ct] = processor

force doesn't do anything more than removing existing body processors. If you set force to False, then you need to tell CherryPy how to treat the request body you send to it.

Or, better, use CherryPy and tell it correct content-type. With jQuery it's as simple as:

 jQuery.ajax({
    'type'        : 'POST',
    'dataType'    : 'JSON',
    'contentType' : 'application/json',
    'url'         : '/findRelated',
    'data'        : JSON.stringify({'foo': 'bar'})
 });

Post a Comment for "415 Exception Cherrypy Webservice"