web-dev-qa-db-fra.com

Problème interdomaine RESTful avec Angular: méthodes PUT, OPTIONS

J'ai développé un petit fichier en écriture seule REST api avec Flask Restful qui accepte la demande PUT d'une poignée de clients pouvant potentiellement avoir des adresses IP changeantes. Mes clients sont des clients Chromium intégrés exécutant un serveur AngularJS. ils s'authentifient avec mon API avec une simple clé magique - c'est suffisant pour mon échelle très limitée. 

Je teste le déploiement de mon API maintenant et je remarque que les clients angulaires tentent d'envoyer une méthode http OPTIONS à mon service Flask. Pendant ce temps, mon API répond avec un 404 (étant donné que je n’ai pas encore écrit de gestionnaire OPTIONS, il n’ya qu’un gestionnaire PUT). Il semble que lors de l'envoi de requêtes interdomaine qui ne sont pas POST ou GET, Angular enverra une méthode OPTIONS de contrôle en amont au serveur pour s'assurer que la requête interdomaine est acceptée avant d'envoyer la requête réelle. . Est-ce correct?

Quoi qu'il en soit, comment puis-je autoriser toutes les demandes PUT entre domaines à l'API Restful Flask? J'ai déjà utilisé des décorateurs cross-domaion avec une instance Flask (non reposante), mais dois-je également écrire un gestionnaire OPTIONS dans mon API?

32
b.b.

J'ai résolu le problème en réécrivant mon back-end Flask pour répondre avec un en-tête Access-Control-Allow-Origin dans ma réponse PUT. De plus, j'ai créé un gestionnaire OPTIONS dans mon application Flask pour répondre à la méthode des options en suivant ce que j'ai lu dans la RFC http. 

Le retour sur la méthode PUT ressemble à ceci:

return restful.request.form, 201, {'Access-Control-Allow-Origin': '*'} 

Mon gestionnaire de méthode OPTIONS ressemble à ceci:

def options (self):
    return {'Allow' : 'PUT' }, 200, \
    { 'Access-Control-Allow-Origin': '*', \
      'Access-Control-Allow-Methods' : 'PUT,GET' }

@tbicr a raison: Flask répond automatiquement à la méthode OPTIONS pour vous. Cependant, dans mon cas, l'en-tête Access-Control-Allow-Origin n'était pas transmis avec cette réponse. Mon navigateur recevait donc une réponse de l'API qui semblait impliquer que les requêtes inter-domaines n'étaient pas autorisées. J'ai surchargé la demande d'options dans mon cas et ajouté l'en-tête ACAO. Le navigateur semblait en être satisfait et j'ai ensuite suivi OPTIONS avec un PUT qui fonctionnait également. 

18
b.b.

Avec le module Flask-CORS , vous pouvez faire des requêtes entre domaines sans changer votre code.

from flask.ext.cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

Mettre à jour

Comme Eric suggéré, le module flask.ext.cors est maintenant obsolète, vous devriez plutôt utiliser le code suivant:

from flask_cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
45
Mathieu Rodic

Vous pouvez utiliser le hook after_request:

 @ app.after_request 
 def after_request (réponse): 
 response.headers.add ('Access-Control-Allow-Origin', '*') 
 response.headers.add ('Access-Control-Allow-Headers', 'Type de contenu, Autorisation') 
 response.headers.add ('Méthodes d'accès-contrôle-autoriser', 'GET, PUT, POST, DELETE') 
 retour réponse 

Consultez ce didacticiel pour plus de détails - http://tutsbucket.com/tutorials/building-a-blog-using-flask-and-angularjs-part-1/

19
John Kenn

Vous avez raison, la méthode OPTIONS est appelée à chaque fois avant la demande réelle dans le navigateur. OPTIONS response ont autorisé les méthodes et les en-têtes. Flask traite automatiquement les demandes OPTIONS.

Pour avoir accès à une requête interdomaine, votre API doit avoir l'en-tête Access-Control-Allow-Origin. Il peut contenir des domaines spécifiques, mais si vous souhaitez autoriser les demandes de tous les domaines, définissez-le sur Access-Control-Allow-Origin: *.

Pour configurer CORS pour flask, vous pouvez consulter un code d'extension ou essayer d'utiliser cette extension: https://github.com/wcdolphin/flask-cors/blob/master/flask_cors.py .

Pour configurer CORS pour flask-restful, consultez cette demande d'extraction: https://github.com/twilio/flask-restful/pull/122 et https://github.com/twilio/flask-restful/pull/131 . Mais il semble que flask-restful ne le supporte pas encore par défaut.

4
tbicr

Juste une mise à jour de ce commentaire. Flask CORS est la voie à suivre, mais flask.ext.cors est obsolète.

utiliser: from flask_cors import CORS

3
Eric

Que diriez-vous de cette solution de contournement: 

from flask import Flask
from flask.ext import restful
from flask.ext.restful import Api
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object('config')

#flask-sqlalchemy
db = SQLAlchemy(app)

#flask-restful
api = restful.Api(app)

@app.after_request

def after_request(response):
  response.headers.add('Access-Control-Allow-Origin', '*')
  response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
  response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
  return response

import views

J'ai pris ceci de this tutorial. Fonctionne très bien. en fait, je pense que c'est la meilleure approche que j'ai vue jusqu'à présent.

Renvoyer {'Access-Control-Allow-Origin': '*'} sur chaque noeud final ne semble pas être efficace car vous devez l'ajouter sur chaque noeud final. un peu angoissant ..., du moins pour moi.

J'ai essayé @cors.crossdomain(Origin='*') mais, on dirait que cela ne fonctionne qu'avec la requête GET

2

Pour autoriser les demandes CORS à distance sur votre API de service Web, vous pouvez simplement initialiser votre API reposante comme suit:

from flask import Flask
from flask_restful import reqparse, abort, Api, Resource
from flask_cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"*": {"origins": "*"}})
api = Api(app)

Cela ajoute l'en-tête CORS à votre instance api et autorise une demande CORS sur chaque chemin de chaque origine.

0
domih

J'aime utiliser une décoration pour résoudre.

def cross_Origin(origin="*"):
    def cross_Origin(func):
        @functools.wraps(func)
        def _decoration(*args, **kwargs):
            ret = func(*args, **kwargs)
            _cross_Origin_header = {"Access-Control-Allow-Origin": Origin,
                                    "Access-Control-Allow-Headers":
                                        "Origin, X-Requested-With, Content-Type, Accept"}
            if isinstance(ret, Tuple):
                if len(ret) == 2 and isinstance(ret[0], dict) and isinstance(ret[1], int):
                    # this is for handle response like: ```{'status': 1, "data":"ok"}, 200```
                    return ret[0], ret[1], _cross_Origin_header
                Elif isinstance(ret, basestring):
                    response = make_response(ret)
                    response.headers["Access-Control-Allow-Origin"] = Origin
                    response.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
                    return response
                Elif isinstance(ret, Response):
                    ret.headers["Access-Control-Allow-Origin"] = Origin
                    ret.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept"
                    return ret
                else:
                    raise ValueError("Cannot handle cross Origin, because the return value is not matched!")
            return ret

        return _decoration

    return cross_Origin

Et puis, utilisez la décoration dans votre api reposant.

class ExampleRestfulApi(Resource)
    @cross_Origin()
    def get(self):
        # allow all cross domain access
        pass

    @cross_Origin(origin="192.168.1.100")
    def post(self):
        # allow 192.168.1.100 access
        pass
0
gordonpro