问题
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!
回答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)
回答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'})
});
来源:https://stackoverflow.com/questions/28049898/415-exception-cherrypy-webservice