415 exception Cherrypy webservice

2020-02-12 08:30发布

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 have the next issue. I think I already know the solution, but I don't know how to implement it. The problem is that when I'm sending the ajax request, the Cherrypy server responds with:

415 Unsupported Media Type

Expected an entity of content type application/json, text/javascript

Traceback (most recent call last):  File "/Library/Python/2.7/site-packages/cherrypy/_cprequest.py", line 663, in respond    self.body.process()  File "/Library/Python/2.7/site-packages/cherrypy/_cpreqbody.py", line 996, in process    super(RequestBody, self).process()  File "/Library/Python/2.7/site-packages/cherrypy/_cpreqbody.py", line 538, in process    self.default_proc()  File "/Library/Python/2.7/site-packages/cherrypy/_cperror.py", line 411, in __call__    raise selfHTTPError: (415, u'Expected an entity of content type application/json, text/javascript')    

The solution I found, and trying to test, is adding this line to the configuration:

'tools.json_in.force': False

So I tried to implement it in this code:

import cherrypy
import json
import sys

class RelatedDocuments:

def index(self):
    return "Hello World!"

@cherrypy.tools.json_out()
@cherrypy.tools.json_in()
def findRelated(self, **raw):
    #Get JSON message form request
    request = cherrypy.request.json
    result = []

    #SOME CODE...

    return result;

# Expose the index method through the web. CherryPy will never
# publish methods that don't have the exposed attribute set to True.
index.exposed = True
findRelated.exposed = True

def CORS():
    cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"

import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'webserver.conf')
config = {
    'global': {
        'server.socket_host':'127.0.0.1',
        'server.socket_port': 8080,
        'log.error_file' : 'Web.log',
        'log.access_file' : 'Access.log'
    },
    '/': {
        'tools.CORS.on': True
    }
}

if __name__ == '__main__':
    cherrypy.tools.CORS = cherrypy.Tool('before_finalize', CORS)

    cherrypy.quickstart(RelatedDocuments(),config=config)

I added the config line under the tools.CORS.on line, but that didn't work. Next i tried this:

cherrypy.config.update({
    'tools.json_in.force': False,
});

Didn't work eiter..next I tried to implement this right above the findRelated method:

@cherrypy.config(**{'tools.json_in.force': False})

All of the implementations gave me a 500 error, I really appreciate it if somebody can help me. Thanks in advance!

2条回答
做自己的国王
2楼-- · 2020-02-12 09:20

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'})
 });
查看更多
Root(大扎)
3楼-- · 2020-02-12 09:22

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)
查看更多
登录 后发表回答