Ajouter un préfixe à toutes les routes Flask

98

J'ai un préfixe que je veux ajouter à chaque itinéraire. En ce moment, j'ajoute une constante à l'itinéraire à chaque définition. Existe-t-il un moyen de le faire automatiquement?

PREFIX = "/abc/123"

@app.route(PREFIX + "/")
def index_page():
  return "This is a website about burritos"

@app.route(PREFIX + "/about")
def about_page():
  return "This is a website about burritos"
Evan Hahn
la source

Réponses:

75

La réponse dépend de la manière dont vous diffusez cette application.

Sous-monté à l'intérieur d'un autre conteneur WSGI

En supposant que vous allez exécuter cette application à l'intérieur d'un conteneur WSGI (mod_wsgi, uwsgi, gunicorn, etc.); vous devez réellement monter, à ce préfixe, l'application en tant que sous-partie de ce conteneur WSGI (tout ce qui parle WSGI fera l'affaire) et définir votre APPLICATION_ROOTvaleur de configuration sur votre préfixe:

app.config["APPLICATION_ROOT"] = "/abc/123"

@app.route("/")
def index():
    return "The URL for this page is {}".format(url_for("index"))

# Will return "The URL for this page is /abc/123/"

La définition de la APPLICATION_ROOTvaleur de configuration limite simplement le cookie de session de Flask à ce préfixe d'URL. Tout le reste sera automatiquement géré pour vous par les excellentes capacités de gestion WSGI de Flask et Werkzeug.

Un exemple de sous-montage correct de votre application

Si vous n'êtes pas sûr de la signification du premier paragraphe, jetez un œil à cet exemple d'application avec Flask monté à l'intérieur:

from flask import Flask, url_for
from werkzeug.serving import run_simple
from werkzeug.wsgi import DispatcherMiddleware

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/abc/123'

@app.route('/')
def index():
    return 'The URL for this page is {}'.format(url_for('index'))

def simple(env, resp):
    resp(b'200 OK', [(b'Content-Type', b'text/plain')])
    return [b'Hello WSGI World']

app.wsgi_app = DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app})

if __name__ == '__main__':
    app.run('localhost', 5000)

Demande de proxy vers l'application

Si, en revanche, vous exécutez votre application Flask à la racine de son conteneur WSGI et lui transmettez les requêtes par proxy (par exemple, si elle est FastCGI, ou si nginx proxy_passenvoie des requêtes pour un sous-point de terminaison sur votre serveur uwsgi/ geventserveur autonome, vous pouvez soit:

  • Utilisez un plan, comme le souligne Miguel dans sa réponse .
  • ou utilisez DispatcherMiddlewarefrom werkzeug(ou la réponsePrefixMiddleware de su27 de from ) pour sous-monter votre application sur le serveur WSGI autonome que vous utilisez. (Voir un exemple de sous-montage correct de votre application ci-dessus pour le code à utiliser).
Sean Vieira
la source
@jknupp - en regardant flask.Flask#create_url_adapteret werkzeug.routing.Map#bind_to_environil semble que cela devrait fonctionner - comment exécutiez- vous le code? (L'application doit en fait être montée sur le sous-chemin dans un environnement WSGI pour url_forrenvoyer la valeur attendue.)
Sean Vieira
J'ai exécuté exactement ce que vous avez écrit, mais j'ai ajouté app = Flask ( nom ) et app.run (debug = True)
jeffknupp
4
@jknupp - c'est le problème - vous devrez monter l'application en tant que sous-partie d'une application plus grande (tout ce qui parle WSGI fera l'affaire). J'ai concocté un exemple et mis à jour ma réponse pour qu'il soit plus clair que je suppose un environnement WSGI sous-monté, pas un environnement WSGI autonome derrière un proxy qui ne transmet que les demandes de sous-chemin.
Sean Vieira
3
Cela fonctionne, en utilisant l' DispatcherMiddlewareapproche, lors de l'exécution de flask par lui-même. Je n'arrive pas à faire fonctionner cela en courant derrière Gunicorn.
Justin le
1
La façon dont monter au sous-chemin dans uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. détail se référer à (document uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
todaynowork
94

Vous pouvez mettre vos itinéraires dans un plan:

bp = Blueprint('burritos', __name__,
                        template_folder='templates')

@bp.route("/")
def index_page():
  return "This is a website about burritos"

@bp.route("/about")
def about_page():
  return "This is a website about burritos"

Ensuite, vous enregistrez le plan avec l'application en utilisant un préfixe:

app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
Miguel
la source
2
Salut Miguel; connaissez-vous la différence entre l'enregistrement d'un url_prefix pour un blueprint comme vous l'avez fait ci-dessous avec app.register_blueprintet entre son enregistrement lorsque vous instanciez l'objet Blueprint ci-dessus, en passant url_prefix='/abc/123? Je vous remercie!
aralar
4
La différence est que le fait d'avoir le préfixe d'URL dans l' register_blueprintappel donne à l'application la liberté de «monter» le plan où elle le souhaite, ou même de monter le même plan plusieurs fois sur différentes URL. Si vous mettez le préfixe dans le plan lui-même, vous facilitez les choses pour l'application, mais vous avez moins de flexibilité.
Miguel
Je vous remercie!! Cela est très utile. J'étais confus par la redondance apparente, mais je vois le compromis entre les deux options.
aralar
Et en fait, je n'ai jamais essayé cela, mais il est probable que vous puissiez combiner les préfixes d'URL dans le plan et dans l'application, avec le premier préfixe de l'application, suivi du préfixe du plan.
Miguel
4
Notez qu'il est nécessaire d'enregistrer le blueprint après les fonctions décorées blueprint.route.
Quint
53

Vous devez noter que le APPLICATION_ROOTn'est PAS à cette fin.

Tout ce que vous avez à faire est d'écrire un middleware pour apporter les modifications suivantes:

  1. modifier PATH_INFOpour gérer l'url préfixée.
  2. modifier SCRIPT_NAMEpour générer l'url préfixée.

Comme ça:

class PrefixMiddleware(object):

    def __init__(self, app, prefix=''):
        self.app = app
        self.prefix = prefix

    def __call__(self, environ, start_response):

        if environ['PATH_INFO'].startswith(self.prefix):
            environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
            environ['SCRIPT_NAME'] = self.prefix
            return self.app(environ, start_response)
        else:
            start_response('404', [('Content-Type', 'text/plain')])
            return ["This url does not belong to the app.".encode()]

Enveloppez votre application avec le middleware, comme ceci:

from flask import Flask, url_for

app = Flask(__name__)
app.debug = True
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix='/foo')


@app.route('/bar')
def bar():
    return "The URL for this page is {}".format(url_for('bar'))


if __name__ == '__main__':
    app.run('0.0.0.0', 9010)

Visite http://localhost:9010/foo/bar,

Vous obtiendrez le bon résultat: The URL for this page is /foo/bar

Et n'oubliez pas de définir le domaine du cookie si vous en avez besoin.

Cette solution est donnée par l'essentiel de Larivact . Ce APPLICATION_ROOTn'est pas pour ce travail, même si cela semble l'être. C'est vraiment déroutant.

su27
la source
4
Merci d'avoir ajouté cette réponse. J'ai essayé les autres solutions affichées ici, mais c'est la seule qui a fonctionné pour moi. A +++ Je suis déployé sur IIS en utilisant wfastcgi.py
sytech
"Ce APPLICATION_ROOTn'est pas pour ce travail" - c'est là que je me suis trompé. Je souhaite Blueprintle url_prefixparamètre et APPLICATION_ROOTont été combinés par défaut, afin que je puisse avoir des APPLICATION_ROOTURL de portée pour toute l'application, et des url_prefixURL de portée APPLICATION_ROOTjuste pour le plan individuel. Sigh
Monkpit
Voir cet essentiel pour un exemple de ce que j'essayais de faire en utilisant APPLICATION_ROOT.
Monkpit
2
Si vous utilisez gunicorn, SCRIPT_NAME est déjà pris en charge. Définissez-le comme variable d'environnement ou passez-le en tant qu'en-tête http: docs.gunicorn.org/en/stable/faq.html
blurrcat
1
Le code tel qu'il est n'a pas fonctionné pour moi. Après quelques recherches, je suis venu avec ceci après l'autre dans le__call__ méthode: en response = Response('That url is not correct for this application', status=404) return response(environ, start_response)utilisantfrom werkzeug.wrappers import BaseResponse as Response
Louis Becker
10

C'est plus une réponse python qu'une réponse Flask / werkzeug; mais c'est simple et ça marche.

Si, comme moi, vous souhaitez que les paramètres de votre application (chargés à partir d'un .inifichier) contiennent également le préfixe de votre application Flask (ainsi, ne pas avoir la valeur définie lors du déploiement, mais pendant l'exécution), vous pouvez opter pour ce qui suit:

def prefix_route(route_function, prefix='', mask='{0}{1}'):
  '''
    Defines a new route function with a prefix.
    The mask argument is a `format string` formatted with, in that order:
      prefix, route
  '''
  def newroute(route, *args, **kwargs):
    '''New function to prefix the route'''
    return route_function(mask.format(prefix, route), *args, **kwargs)
  return newroute

On peut soutenir que c'est quelque peu hackish et repose sur le fait que la fonction de route Flask nécessite un routecomme premier argument positionnel.

Vous pouvez l'utiliser comme ceci:

app = Flask(__name__)
app.route = prefix_route(app.route, '/your_prefix')

NB: Cela ne vaut rien qu'il soit possible d'utiliser une variable dans le préfixe (par exemple en la définissant sur /<prefix>), puis de traiter ce préfixe dans les fonctions que vous décorez avec votre @app.route(...). Si vous le faites, vous devez évidemment déclarer le prefixparamètre dans votre (vos) fonction (s) décorée (s). En outre, vous souhaiterez peut-être vérifier le préfixe soumis par rapport à certaines règles et renvoyer un 404 si la vérification échoue. Afin d'éviter une réimplémentation personnalisée 404, veuillez from werkzeug.exceptions import NotFoundet ensuite raise NotFound()si la vérification échoue.

7heo.tk
la source
C'est simple et plus efficace que d'utiliser Blueprint. Merci d'avoir partagé!
Garçon HK le
5

Donc, je pense qu'une réponse valable à cela est: le préfixe doit être configuré dans l'application serveur réelle que vous utilisez lorsque le développement est terminé. Apache, nginx, etc.

Cependant, si vous souhaitez que cela fonctionne pendant le développement lors de l'exécution de l'application Flask dans le débogage, jetez un œil à cet essentiel .

Flacon DispatcherMiddleware à la rescousse!

Je vais copier le code ici pour la postérité:

"Serve a Flask app on a sub-url during localhost development."

from flask import Flask


APPLICATION_ROOT = '/spam'


app = Flask(__name__)
app.config.from_object(__name__)  # I think this adds APPLICATION_ROOT
                                  # to the config - I'm not exactly sure how!
# alternatively:
# app.config['APPLICATION_ROOT'] = APPLICATION_ROOT


@app.route('/')
def index():
    return 'Hello, world!'


if __name__ == '__main__':
    # Relevant documents:
    # http://werkzeug.pocoo.org/docs/middlewares/
    # http://flask.pocoo.org/docs/patterns/appdispatch/
    from werkzeug.serving import run_simple
    from werkzeug.wsgi import DispatcherMiddleware
    app.config['DEBUG'] = True
    # Load a dummy app at the root URL to give 404 errors.
    # Serve app at APPLICATION_ROOT for localhost development.
    application = DispatcherMiddleware(Flask('dummy_app'), {
        app.config['APPLICATION_ROOT']: app,
    })
    run_simple('localhost', 5000, application, use_reloader=True)

Maintenant, lors de l'exécution du code ci-dessus en tant qu'application Flask autonome, http://localhost:5000/spam/s'afficheHello, world! .

Dans un commentaire sur une autre réponse, j'ai exprimé que je souhaitais faire quelque chose comme ceci:

from flask import Flask, Blueprint

# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
app.run()

# I now would like to be able to get to my route via this url:
# http://host:8080/api/some_submodule/record/1/

S'appliquant DispatcherMiddlewareà mon exemple artificiel:

from flask import Flask, Blueprint
from flask.serving import run_simple
from flask.wsgi import DispatcherMiddleware

# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
application = DispatcherMiddleware(Flask('dummy_app'), {
    app.config['APPLICATION_ROOT']: app
})
run_simple('localhost', 5000, application, use_reloader=True)

# Now, this url works!
# http://host:8080/api/some_submodule/record/1/
Monkpit
la source
"Donc, je pense qu'une réponse valable à ceci est: le préfixe doit être configuré dans l'application serveur réelle que vous utilisez lorsque le développement est terminé. Apache, nginx, etc." Le problème est dans les redirections; si vous avez un préfixe et ne le configurez pas dans Flask, alors quand il redirige au lieu d'aller vers / votre préfixe / chemin / vers / url, il va simplement vers / chemin / vers / url. Existe-t-il un moyen de configurer, dans nginx ou Apache, quel doit être le préfixe?
Jordan Reiter le
La façon dont je ferais probablement cela serait simplement d'utiliser un outil de gestion de la configuration comme puppet ou chef, et de définir le préfixe là-bas, puis de faire en sorte que l'outil propage le changement dans les fichiers de configuration où il doit aller. Je ne vais même pas prétendre que je sais de quoi je parle pour apache ou nginx. Étant donné que cette question / réponse était spécifique à python, je vous encourage à publier votre scénario sous forme de question distincte. Si vous faites cela, n'hésitez pas à créer un lien vers la question ici!
Monkpit
2

Une autre façon complètement différente est avec mountpoints dans uwsgi.

À partir de la documentation sur l' hébergement de plusieurs applications dans le même processus ( lien permanent ).

Dans votre uwsgi.inivous ajoutez

[uwsgi]
mount = /foo=main.py
manage-script-name = true

# also stuff which is not relevant for this, but included for completeness sake:    
module = main
callable = app
socket = /tmp/uwsgi.sock

Si vous n'appelez pas votre fichier main.py, vous devez modifier le mountet lemodule

Votre main.pypourrait ressembler à ceci:

from flask import Flask, url_for
app = Flask(__name__)
@app.route('/bar')
def bar():
  return "The URL for this page is {}".format(url_for('bar'))
# end def

Et une configuration nginx (encore une fois pour l'exhaustivité):

server {
  listen 80;
  server_name example.com

  location /foo {
    include uwsgi_params;
    uwsgi_pass unix:///temp/uwsgi.sock;
  }
}

L'appel maintenant example.com/foo/bars'affichera /foo/barcomme renvoyé par flaskurl_for('bar') , car il s'adapte automatiquement. De cette façon, vos liens fonctionneront sans problèmes de préfixe.

chanceydonald
la source
2
from flask import Flask

app = Flask(__name__)

app.register_blueprint(bp, url_prefix='/abc/123')

if __name__ == "__main__":
    app.run(debug='True', port=4444)


bp = Blueprint('burritos', __name__,
                        template_folder='templates')

@bp.route('/')
def test():
    return "success"
abhimanyu
la source
1
Pensez à ajouter une explication.
jpp
1
Deux belles explications que j'ai trouvées se trouvaient dans exploreflask et les documents officiels
yuriploc
1

J'avais besoin de ce qu'on appelle un "context-root". Je l'ai fait dans le fichier conf sous /etc/httpd/conf.d/ en utilisant WSGIScriptAlias:

myapp.conf:

<VirtualHost *:80>
    WSGIScriptAlias /myapp /home/<myid>/myapp/wsgi.py

    <Directory /home/<myid>/myapp>
        Order deny,allow
        Allow from all
    </Directory>

</VirtualHost>

Alors maintenant, je peux accéder à mon application en tant que: http: // localhost: 5000 / myapp

Voir le guide - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

dganesh2002
la source
1

Ma solution où les applications flask et PHP coexistent nginx et PHP5.6

GARDER Flask à la racine et PHP dans les sous-répertoires

sudo vi /etc/php/5.6/fpm/php.ini

Ajouter 1 ligne

cgi.fix_pathinfo=0
sudo vi /etc/php/5.6/fpm/pool.d/www.conf
listen = /run/php/php5.6-fpm.sock

uwsgi

sudo vi /etc/nginx/sites-available/default

UTILISEZ DES EMPLACEMENTS IMPRIMÉS pour PHP et laissez FLASK rester à la racine

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # SSL configuration
    #
    # listen 443 ssl default_server;
    # listen [::]:443 ssl default_server;
    #
    # Note: You should disable gzip for SSL traffic.
    # See: https://bugs.debian.org/773332
    #
    # Read up on ssl_ciphers to ensure a secure configuration.
    # See: https://bugs.debian.org/765782
    #
    # Self signed certs generated by the ssl-cert package
    # Don't use them in a production server!
    #
    # include snippets/snakeoil.conf;

    root /var/www/html;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.php index.nginx-debian.html;

    server_name _;

    # Serve a static file (ex. favico) outside static dir.
    location = /favico.ico  {    
        root /var/www/html/favico.ico;    
    }

    # Proxying connections to application servers
    location / {
        include            uwsgi_params;
        uwsgi_pass         127.0.0.1:5000;
    }

    location /pcdp {
        location ~* \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    location /phpmyadmin {
        location ~* \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #   include snippets/fastcgi-php.conf;
    #
    #   # With php7.0-cgi alone:
    #   fastcgi_pass 127.0.0.1:9000;
    #   # With php7.0-fpm:
    #   fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #   deny all;
    #}
}

LIRE attentivement https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

Nous devons comprendre la correspondance d'emplacement (aucun): si aucun modificateur n'est présent, l'emplacement est interprété comme une correspondance de préfixe. Cela signifie que l'emplacement indiqué sera comparé au début de l'URI de la demande pour déterminer une correspondance. =: Si un signe égal est utilisé, ce bloc sera considéré comme une correspondance si l'URI de la demande correspond exactement à l'emplacement indiqué. ~: Si un modificateur tilde est présent, cet emplacement sera interprété comme une correspondance d'expression régulière sensible à la casse. ~ *: Si un modificateur tilde et astérisque est utilisé, le bloc d'emplacement sera interprété comme une correspondance d'expression régulière insensible à la casse. ^ ~: Si un modificateur carat et tilde est présent et si ce bloc est sélectionné comme meilleure correspondance d'expression non régulière, la correspondance d'expression régulière n'aura pas lieu.

L'ordre est important, d'après la description de «location» de nginx:

Pour trouver l'emplacement correspondant à une demande donnée, nginx vérifie d'abord les emplacements définis à l'aide des chaînes de préfixe (emplacements de préfixe). Parmi eux, l'emplacement avec le préfixe correspondant le plus long est sélectionné et mémorisé. Ensuite, les expressions régulières sont vérifiées, dans l'ordre de leur apparition dans le fichier de configuration. La recherche d'expressions régulières se termine sur la première correspondance et la configuration correspondante est utilisée. Si aucune correspondance avec une expression régulière n'est trouvée, la configuration de l'emplacement du préfixe mémorisé précédemment est utilisée.

Ça veut dire:

First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)
Jayanta
la source
1

Pour les personnes toujours aux prises avec cela, le premier exemple fonctionne, mais l'exemple complet est ici si vous avez une application Flask qui n'est pas sous votre contrôle:

from os import getenv
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple
from custom_app import app

application = DispatcherMiddleware(
    app, {getenv("REBROW_BASEURL", "/rebrow"): app}
)

if __name__ == "__main__":
    run_simple(
        "0.0.0.0",
        int(getenv("REBROW_PORT", "5001")),
        application,
        use_debugger=False,
        threaded=True,
    )
vishnugopal
la source