Je travaille sur une API RESTful d'un service Web sur Bottle Web Framework et souhaite accéder aux ressources avec des appels jQuery AJAX.
Avec un client REST, les interfaces de ressources fonctionnent comme prévu et gèrent correctement les demandes GET, POST, .... Mais lors de l'envoi d'une demande jQuery AJAX POST, la demande de contrôle en amont OPTIONS résultante est simplement refusée en tant que "405: Méthode non autorisée".
J'ai essayé d'activer CORS sur le serveur Bottle - comme décrit ici: http://bottlepy.org/docs/dev/recipes.html#using-the-hooks-plugin Mais le after_request hook n'est jamais appelé pour la demande OPTIONS.
Voici un extrait de mon serveur:
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]"
[...]
L'appel 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);
}
});
Le serveur enregistre uniquement une erreur 405:
192.168.169.3 - - [23/Jun/2013 17:10:53] "OPTIONS /cors HTTP/1.1" 405 741
$ .post fonctionne, mais le fait de ne pas pouvoir envoyer de requêtes PUT irait à l’encontre du but d’un service RESTful. Alors, comment puis-je autoriser le traitement de la requête de contrôle en amont OPTIONS?
Installez un gestionnaire au lieu d'un crochet.
Je l'ai fait par le passé de deux manières complémentaires: décorateur ou plugin Bottle. Je vous montrerai les deux et vous pourrez décider si l'un (ou les deux) convient à vos besoins. Dans les deux cas, l’idée générale est la suivante: un gestionnaire intercepte la réponse avant qu’elle ne soit renvoyée au client, insère les en-têtes CORS, puis renvoie la réponse.
Cette méthode est préférable lorsque vous souhaitez uniquement exécuter le gestionnaire sur certains de vos itinéraires. Décorez simplement chaque itinéraire que vous souhaitez exécuter. Voici un exemple:
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)
Cette méthode est préférable si vous souhaitez que le gestionnaire s'exécute sur la totalité ou la plupart de vos itinéraires. Vous aurez juste définir un plugin Bottle une fois, et Bottle l'appellera automatiquement pour vous sur chaque route; pas besoin de spécifier un décorateur sur chacun. (Notez que vous pouvez utiliser le paramètre skip
d'une route pour éviter ce gestionnaire par route.) Voici un exemple qui correspond à celui ci-dessus:
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)
Voici une amélioration mineure de la méthode n ° 2 de @ ron.rothman pour l'installation globale du gestionnaire CORS. Sa méthode nécessite que vous spécifiiez que la méthode OPTIONS
est acceptée sur chaque route que vous déclarez. Cette solution installe un gestionnaire global pour toutes les demandes 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'
`` `
Envisagez de laisser votre serveur Web, et non Bottle, définir les en-têtes.
Je ne sais pas si cela s'applique dans votre cas, mais j'ai résolu le problème dans des projets antérieurs en définissant des en-têtes CORS pour mon application Bottle dans Apache. Il est facile à configurer, garde mon code Python agréable et propre, et est efficace.
Les informations sont disponibles de nombreuses sources , mais si vous utilisez Apache, voici à quoi ressemble ma configuration (plus ou moins):
<Location "/cors">
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"
</Location>
Aussi ne devriez-vous pas réellement utiliser cela?
response.set_header('Access-Control-Allow-Origin', '*')
response.add_header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS')