Bottle Py: habilitar CORS para solicitudes jQuery AJAX

Estoy trabajando en una API RESTful de un servicio web en Bottle Web Framework y quiero acceder a los recursos con llamadas jQuery AJAX.

Utilizando un cliente REST, las interfaces de recursos funcionan según lo previsto y manejan adecuadamente las solicitudes GET, POST, …. Pero cuando se envía una solicitud jQuery AJAX POST, la solicitud de verificación previa de OPTIONS resultante simplemente se deniega como ‘405: Método no permitido’.

Intenté habilitar CORS en el servidor de Bottle, como se describe aquí: http://bottlepy.org/docs/dev/recipes.html#using-the-hooks-plugin Pero el hook after_request nunca se llama para la solicitud OPTIONS.

Aquí hay un extracto de mi servidor:

from bottle import Bottle, run, request, response import simplejson as json app = Bottle() @app.hook('after_request') def enable_cors(): print "after_request hook" response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' @app.post('/cors') def lvambience(): response.headers['Content-Type'] = 'application/json' return "[1]" [...] 

La llamada jQuery AJAX:

 $.ajax({ type: "POST", url: "http://192.168.169.9:8080/cors", data: JSON.stringify( data ), contentType: "application/json; charset=utf-8", dataType: "json", success: function(data){ alert(data); }, failure: function(err) { alert(err); } }); 

El servidor solo registra un error 405:

 192.168.169.3 - - [23/Jun/2013 17:10:53] "OPTIONS /cors HTTP/1.1" 405 741 

$ .post funciona, pero el hecho de no poder enviar solicitudes PUT frustraría el propósito de un servicio RESTful. Entonces, ¿cómo puedo permitir que se maneje la solicitud de verificación previa de OPTIONS?

Instale un controlador en lugar de un gancho.

Hay dos formas complementarias en las que he hecho esto en el pasado: decorador o plugin de botella. Los mostraré a los dos y ustedes podrán decidir si uno (o ambos) se adaptan a sus necesidades. En ambos casos, la idea general es: un controlador intercepta la respuesta antes de enviarla al cliente, inserta los encabezados CORS y luego procede a devolver la respuesta.

Método 1: Instalar por ruta (decorador)

Este método es preferible cuando solo desea ejecutar el controlador en algunas de sus rutas. Simplemente decora cada ruta en la que quieras que se ejecute. Aquí hay un ejemplo:

 import bottle from bottle import response # the decorator def enable_cors(fn): def _enable_cors(*args, **kwargs): # set CORS headers response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' if bottle.request.method != 'OPTIONS': # actual request; reply with the actual response return fn(*args, **kwargs) return _enable_cors app = bottle.app() @app.route('/cors', method=['OPTIONS', 'GET']) @enable_cors def lvambience(): response.headers['Content-type'] = 'application/json' return '[1]' app.run(port=8001) 

Método 2: Instalar globalmente (Complemento de botella)

Este método es preferible si desea que el controlador se ejecute en todas o en la mayoría de sus rutas. Simplemente definirá un complemento de Bottle una vez, y Bottle lo llamará automáticamente en cada ruta; no es necesario especificar un decorador en cada uno. (Tenga en cuenta que puede usar el parámetro de skip una ruta para evitar este controlador por ruta). Aquí hay un ejemplo que corresponde al anterior:

 import bottle from bottle import response class EnableCors(object): name = 'enable_cors' api = 2 def apply(self, fn, context): def _enable_cors(*args, **kwargs): # set CORS headers response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' if bottle.request.method != 'OPTIONS': # actual request; reply with the actual response return fn(*args, **kwargs) return _enable_cors app = bottle.app() @app.route('/cors', method=['OPTIONS', 'GET']) def lvambience(): response.headers['Content-type'] = 'application/json' return '[1]' app.install(EnableCors()) app.run(port=8001) 

Aquí hay una mejora menor en el método # 2 de ron ronrrman para instalar el controlador CORS globalmente. Su método requiere que especifique que se acepta el método OPTIONS en cada ruta que declare. Esta solución instala un controlador global para todas las solicitudes OPTIONS .

 @bottle.route('/<:re:.*>', method='OPTIONS') def enable_cors_generic_route(): """ This route takes priority over all others. So any request with an OPTIONS method will be handled by this function. See: https://github.com/bottlepy/bottle/issues/402 NOTE: This means we won't 404 any invalid path that is an OPTIONS request. """ add_cors_headers() @bottle.hook('after_request') def enable_cors_after_request_hook(): """ This executes after every route. We use it to attach CORS headers when applicable. """ add_cors_headers() def add_cors_headers(): if SOME_CONDITION: # You don't have to gate this bottle.response.headers['Access-Control-Allow-Origin'] = '*' bottle.response.headers['Access-Control-Allow-Methods'] = \ 'GET, POST, PUT, OPTIONS' bottle.response.headers['Access-Control-Allow-Headers'] = \ 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' 

“ `

Considere dejar que su servidor web, no Bottle, configure los encabezados.

No estoy seguro de si esto se aplica a su situación, pero he resuelto el problema en proyectos anteriores al configurar los encabezados CORS para mi aplicación de botella en Apache. Es fácil de configurar, mantiene mi código Python agradable y limpio, y es eficiente.

La información está disponible de muchas fonts , pero si está usando Apache, así es como se ve mi configuración (más o menos):

  Header set Access-Control-Allow-Headers "Origin, Content-Type" Header set Access-Control-Allow-Methods "POST, GET, OPTIONS" Header set Access-Control-Allow-Origin "*" Header set Access-Control-Request-Headers "Origin, Content-Type"  

Además, ¿no deberías estar realmente usando esto?

 response.set_header('Access-Control-Allow-Origin', '*') response.add_header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS')