Aggiungi un prefisso a tutte le rotte Flask


98

Ho un prefisso che voglio aggiungere a ogni percorso. In questo momento aggiungo una costante al percorso ad ogni definizione. C'è un modo per farlo automaticamente?

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"

Risposte:


75

La risposta dipende da come stai servendo questa applicazione.

Sub-montato all'interno di un altro contenitore WSGI

Supponendo che tu stia per eseguire questa applicazione all'interno di un contenitore WSGI (mod_wsgi, uwsgi, gunicorn, ecc.); devi effettivamente montare, a quel prefisso l'applicazione come sotto-parte di quel contenitore WSGI (tutto ciò che parla WSGI lo farà) e impostare il tuo APPLICATION_ROOTvalore di configurazione sul tuo prefisso:

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/"

L'impostazione del APPLICATION_ROOTvalore di configurazione limita semplicemente il cookie di sessione di Flask a quel prefisso URL. Tutto il resto verrà gestito automaticamente per te dalle eccellenti capacità di gestione WSGI di Flask e Werkzeug.

Un esempio di sotto-montaggio corretto della tua app

Se non sei sicuro di cosa significhi il primo paragrafo, dai un'occhiata a questa applicazione di esempio con Flask montato al suo interno:

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)

Invio di richieste all'app

Se, d'altra parte, eseguirai la tua applicazione Flask alla radice del suo contenitore WSGI e invierai le richieste tramite proxy (ad esempio, se viene FastCGI o se nginx sta proxy_passinviando richieste per un sub-endpoint al tuo server uwsgi/ stand-alone geventquindi puoi:

  • Usa un progetto, come Miguel sottolinea nella sua risposta .
  • oppure usa DispatcherMiddlewarefrom werkzeug(o PrefixMiddlewarefrom su27's answer ) per sub-montare la tua applicazione nel server WSGI stand-alone che stai usando. (Vedi un esempio di sotto-montaggio corretto della tua app sopra per il codice da usare).

@jknupp - guardando flask.Flask#create_url_adaptere werkzeug.routing.Map#bind_to_environsembra che dovrebbe funzionare - come stavi eseguendo il codice? (L'app in realtà deve essere montata sul percorso secondario in un ambiente WSGI per url_forrestituire il valore previsto.)
Sean Vieira

Ho eseguito esattamente quello che hai scritto, ma ho aggiunto app = Flask ( name ) e app.run (debug = True)
jeffknupp

4
@jknupp - questo è il problema - dovrai effettivamente montare l'applicazione come una sottoparte di un'applicazione più grande (tutto ciò che parla WSGI andrà bene). Ho creato un esempio essenziale e aggiornato la mia risposta per rendere più chiaro che sto assumendo un ambiente WSGI sub-montato, non un ambiente WSGI autonomo dietro un proxy che inoltra solo richieste di sottopercorsi.
Sean Vieira

3
Questo funziona, usando l' DispatcherMiddlewareapproccio, quando si esegue il flask da solo. Non riesco a farlo funzionare quando corro dietro a Gunicorn.
Justin,

1
Il modo in cui monta il percorso secondario in uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. i dettagli si riferiscono a (documento uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
todaynowork

94

Puoi inserire i tuoi percorsi in un progetto:

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"

Quindi si registra il progetto con l'applicazione utilizzando un prefisso:

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

2
Ciao Miguel; conosci la differenza tra la registrazione di un url_prefix per un progetto come hai fatto di seguito app.register_blueprinte tra la registrazione quando installi l'oggetto Blueprint sopra, passando url_prefix='/abc/123? Grazie!
aralar

4
La differenza è che avere il prefisso URL nella register_blueprintchiamata dà all'applicazione la libertà di "montare" il progetto ovunque lo desideri, o anche montare lo stesso progetto più volte su URL diversi. Se metti il ​​prefisso nel progetto stesso, rendi più facile l'applicazione, ma hai meno flessibilità.
Miguel

Grazie!! Questo è molto utile. Ero confuso dall'apparente ridondanza, ma vedo il compromesso tra le due opzioni.
aralar

E in realtà, non l'ho mai provato, ma è probabile che tu possa combinare i prefissi URL sia nel progetto che nell'app, con il prefisso dell'app, seguito dal prefisso del progetto.
Miguel

4
Notare che è necessario registrare il progetto dopo le funzioni decorate blueprint.route.
Quint

53

Dovresti notare che APPLICATION_ROOTNON è per questo scopo.

Tutto quello che devi fare è scrivere un middleware per apportare le seguenti modifiche:

  1. modificare PATH_INFOper gestire l'URL prefissato.
  2. modificare SCRIPT_NAMEper generare l'URL prefissato.

Come questo:

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()]

Avvolgi la tua app con il middleware, in questo modo:

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)

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

Otterrai il risultato giusto: The URL for this page is /foo/bar

E non dimenticare di impostare il dominio dei cookie se necessario.

Questa soluzione è data dal succo di Larivact . Non APPLICATION_ROOTè per questo lavoro, anche se sembra esserlo. È davvero confuso.


4
Grazie per aver aggiunto questa risposta. Ho provato le altre soluzioni pubblicate qui, ma questa è l'unica che ha funzionato per me. A +++ Sono distribuito su IIS usando wfastcgi.py
sytech

"Non APPLICATION_ROOTè per questo lavoro" - è qui che stavo sbagliando. Vorrei che Blueprintil url_prefixparametro e APPLICATION_ROOTfossero combinati per impostazione predefinita, in modo da poter avere APPLICATION_ROOTURL di ambito per l'intera app e url_prefixURL di ambito APPLICATION_ROOTsolo per il singolo progetto. Sigh
Monkpit

Vedi questa sintesi per un esempio di ciò che stavo cercando di fare usando APPLICATION_ROOT.
Monkpit

2
Se stai usando gunicorn, SCRIPT_NAME è già supportato. Impostalo come variabile d'ambiente o passalo
blurrcat

1
Il codice così com'è non ha funzionato per me. Dopo alcune ricerche, mi è venuto in mente questo dopo l'altro nel __call__metodo: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)utilizzofrom werkzeug.wrappers import BaseResponse as Response
Louis Becker

10

Questa è più una risposta Python che una risposta Flask / werkzeug; ma è semplice e funziona.

Se, come me, vuoi che le impostazioni dell'applicazione (caricate da un .inifile) contengano anche il prefisso della tua applicazione Flask (quindi, non per avere il valore impostato durante la distribuzione, ma durante il runtime), puoi optare per quanto segue:

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

Probabilmente, questo è un po 'hacker e si basa sul fatto che la funzione di percorso Flask richiede una routecome primo argomento posizionale.

Puoi usarlo in questo modo:

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

NB: Non vale nulla che sia possibile utilizzare una variabile nel prefisso (ad esempio impostandola a /<prefix>), e poi elaborare questo prefisso nelle funzioni che decori con il tuo @app.route(...). Se lo fai, devi ovviamente dichiarare il prefixparametro nelle funzioni decorate. Inoltre, potresti voler controllare il prefisso inviato rispetto ad alcune regole e restituire un 404 se il controllo fallisce. Per evitare una reimplementazione personalizzata 404, per favore from werkzeug.exceptions import NotFounde poi raise NotFound()se il controllo fallisce.


È semplice e più efficiente dell'utilizzo Blueprint. Grazie per la condivisione!
HK boy

5

Quindi, credo che una risposta valida a questa domanda sia: il prefisso dovrebbe essere configurato nell'effettiva applicazione server che usi quando lo sviluppo è completato. Apache, nginx, ecc.

Tuttavia, se desideri che funzioni durante lo sviluppo mentre esegui l'app Flask nel debug, dai un'occhiata a questa sintesi .

Flask è DispatcherMiddlewarein soccorso!

Copierò qui il codice per i posteri:

"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)

Ora, quando si esegue il codice sopra come app Flask autonoma, http://localhost:5000/spam/verrà visualizzato Hello, world!.

In un commento su un'altra risposta, ho espresso che volevo fare qualcosa del genere:

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/

Applicando DispatcherMiddlewareal mio esempio artificioso:

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/

"Quindi, credo che una risposta valida a questa domanda sia: il prefisso dovrebbe essere configurato nell'applicazione server effettiva che usi quando lo sviluppo è completato. Apache, nginx, ecc." Il problema è nei reindirizzamenti; se si dispone di un prefisso e non lo si imposta in Flask, quando reindirizza invece di andare a / yourprefix / path / to / url va semplicemente a / path / to / url. C'è un modo per impostare, in nginx o Apache, quale deve essere il prefisso?
Jordan Reiter

Il modo in cui lo farei probabilmente è solo utilizzare uno strumento di gestione della configurazione come Puppet o Chef, e impostare il prefisso lì e quindi fare in modo che lo strumento propaghi la modifica ai file di configurazione dove deve andare. Non farò nemmeno finta di sapere di cosa parlo per Apache o Nginx. Poiché questa domanda / risposta era specifica per python, ti incoraggerei a pubblicare il tuo scenario come domanda separata. Se lo fai, non esitare a collegare la domanda qui!
Monkpit

2

Un altro modo completamente diverso è con i mountpoint in uwsgi.

Dal documento sull'hosting di più app nello stesso processo ( permalink ).

Nel tuo uwsgi.iniaggiungi

[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

Se non chiami il tuo file main.py, devi cambiare sia il mountche ilmodule

Il tuo main.pypotrebbe assomigliare a questo:

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

E una configurazione nginx (di nuovo per completezza):

server {
  listen 80;
  server_name example.com

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

Ora la chiamata example.com/foo/barverrà visualizzata /foo/barcome restituita da flask url_for('bar'), poiché si adatta automaticamente. In questo modo i tuoi link funzioneranno senza problemi di prefisso.


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"

1
Si prega di considerare l'aggiunta di una spiegazione.
jpp

1
Due belle spiegazioni che ho trovato erano in exploreflask e nei documenti ufficiali
yuriploc il

1

Avevo bisogno di simili cosiddetti "context-root". L'ho fatto nel file conf sotto /etc/httpd/conf.d/ usando WSGIScriptAlias:

myapp.conf:

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

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

</VirtualHost>

Quindi ora posso accedere alla mia app come: http: // localhost: 5000 / myapp

Consulta la guida - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html


1

La mia soluzione in cui le app flask e PHP coesistono nginx e PHP5.6

MANTIENI Flask in root e PHP nelle sottodirectory

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

Aggiungi 1 riga

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

USA NESTED LOCATIONS per PHP e lascia che FLASK rimanga nella root

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;
    #}
}

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

Dobbiamo capire la corrispondenza della posizione (nessuna): se non sono presenti modificatori, la posizione viene interpretata come una corrispondenza del prefisso. Ciò significa che la posizione fornita verrà confrontata con l'inizio dell'URI della richiesta per determinare una corrispondenza. =: Se viene utilizzato un segno di uguale, questo blocco sarà considerato una corrispondenza se l'URI della richiesta corrisponde esattamente alla posizione fornita. ~: Se è presente un modificatore di tilde, questa posizione verrà interpretata come una corrispondenza di espressioni regolari con distinzione tra maiuscole e minuscole. ~ *: Se viene utilizzato un modificatore di tilde e asterisco, il blocco di posizione verrà interpretato come una corrispondenza di espressioni regolari senza distinzione tra maiuscole e minuscole. ^ ~: Se è presente un modificatore di carati e tilde e se questo blocco è selezionato come la migliore corrispondenza di espressioni non regolari, la corrispondenza di espressioni regolari non avrà luogo.

L'ordine è importante, dalla descrizione della "posizione" di nginx:

Per trovare la posizione che corrisponde a una determinata richiesta, nginx controlla prima le posizioni definite utilizzando le stringhe del prefisso (posizioni del prefisso). Tra questi, viene selezionata e ricordata la posizione con il prefisso corrispondente più lungo. Quindi vengono controllate le espressioni regolari, nell'ordine in cui appaiono nel file di configurazione. La ricerca delle espressioni regolari termina alla prima corrispondenza e viene utilizzata la configurazione corrispondente. Se non viene trovata alcuna corrispondenza con un'espressione regolare, viene utilizzata la configurazione della posizione del prefisso ricordata in precedenza.

Significa:

First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)

1

Per le persone ancora alle prese con questo, il primo esempio funziona, ma l'esempio completo è qui se hai un'app Flask che non è sotto il tuo controllo:

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,
    )
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.