Fügen Sie allen Flask-Routen ein Präfix hinzu

97

Ich habe ein Präfix, das ich jeder Route hinzufügen möchte. Im Moment füge ich der Route bei jeder Definition eine Konstante hinzu. Gibt es eine Möglichkeit, dies automatisch zu tun?

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
quelle

Antworten:

74

Die Antwort hängt davon ab, wie Sie diese Anwendung bereitstellen.

In einem anderen WSGI-Container untermontiert

Angenommen, Sie führen diese Anwendung in einem WSGI-Container aus (mod_wsgi, uwsgi, gunicorn usw.). Sie müssen die Anwendung an diesem Präfix tatsächlich als Unterteil dieses WSGI-Containers bereitstellen (alles, was WSGI spricht, funktioniert) und Ihren APPLICATION_ROOTKonfigurationswert auf Ihr Präfix setzen:

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

Durch Festlegen des APPLICATION_ROOTKonfigurationswerts wird das Sitzungscookie von Flask einfach auf dieses URL-Präfix beschränkt. Alles andere wird automatisch von Flask und Werkzeugs hervorragenden WSGI-Handhabungsfunktionen für Sie erledigt.

Ein Beispiel für die ordnungsgemäße Untermontage Ihrer App

Wenn Sie sich nicht sicher sind, was der erste Absatz bedeutet, sehen Sie sich diese Beispielanwendung mit darin montiertem Kolben an:

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)

Proxying-Anfragen an die App

Wenn Sie andererseits Ihre Flask-Anwendung im Stammverzeichnis des WSGI-Containers ausführen und Anforderungen an ihn weiterleiten (z. B. wenn FastCGI-Anforderungen erfüllt werden oder wenn nginx proxy_passAnforderungen für einen Subendpunkt sendet zu Ihrem Standalone uwsgi/ geventServer können Sie dann entweder:

  • Verwenden Sie eine Blaupause, wie Miguel in seiner Antwort betont .
  • Oder verwenden Sie das DispatcherMiddlewarefrom werkzeug(oder die AntwortPrefixMiddleware von su27 ), um Ihre Anwendung auf dem von Ihnen verwendeten eigenständigen WSGI-Server zu unterbinden . ( Informationen zur Verwendung des Codes finden Sie oben unter Ein Beispiel für die ordnungsgemäße Untermontage Ihrer App .)
Sean Vieira
quelle
@jknupp - anschauen flask.Flask#create_url_adapterund werkzeug.routing.Map#bind_to_environes sieht so aus, als ob es funktionieren sollte - wie hast du den Code ausgeführt? (Die App muss tatsächlich in einer WSGI-Umgebung auf dem Unterpfad bereitgestellt werden url_for, um den erwarteten Wert zurückzugeben.)
Sean Vieira
Ich habe genau das ausgeführt, was Sie geschrieben haben, aber app = Flask ( Name ) und app.run (Debug = True)
hinzugefügt
4
@jknupp - das ist das Problem - Sie müssen die Anwendung tatsächlich als Teil einer größeren Anwendung bereitstellen (alles, was WSGI spricht, reicht aus). Ich habe ein Beispiel zusammengestellt und meine Antwort aktualisiert, um klarer zu machen, dass ich von einer untergeordneten WSGI-Umgebung ausgehe, nicht von einer eigenständigen WSGI-Umgebung hinter einem Proxy, der nur Unterpfadanforderungen weiterleitet.
Sean Vieira
3
Dies funktioniert unter Verwendung des DispatcherMiddlewareAnsatzes, wenn der Kolben von selbst betrieben wird. Ich kann nicht scheinen, dass dies funktioniert, wenn ich hinter Gunicorn renne.
Justin
1
Der Weg zum Unterpfad in uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. Details siehe (uwsgi-Dokument) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
todaynowork
94

Sie können Ihre Routen in eine Blaupause einfügen:

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"

Anschließend registrieren Sie den Entwurf bei der Anwendung mit einem Präfix:

app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
Miguel
quelle
2
Hallo Miguel; Kennen Sie den Unterschied zwischen der Registrierung eines URL-Präfixes für eine Blaupause wie unten app.register_blueprintund der Registrierung, wenn Sie das Blaupausenobjekt oben instanziieren, durch Übergeben url_prefix='/abc/123? Danke dir!
Aralar
4
Der Unterschied besteht darin, dass die register_blueprintAnwendung mit dem URL-Präfix im Aufruf die Freiheit hat, die Blaupause an einer beliebigen Stelle zu "mounten" oder sogar dieselbe Blaupause mehrmals an verschiedenen URLs zu mounten. Wenn Sie das Präfix in die Blaupause selbst einfügen, wird dies für die Anwendung einfacher, Sie haben jedoch weniger Flexibilität.
Miguel
Danke dir!! Das ist sehr hilfreich. Ich war verwirrt von der offensichtlichen Redundanz, aber ich sehe den Kompromiss zwischen den beiden Optionen.
Aralar
Eigentlich habe ich das nie versucht, aber es ist wahrscheinlich, dass Sie URL-Präfixe sowohl in der Blaupause als auch in der App mit der Präfixfaust der App, gefolgt vom Präfix der Blaupause, kombinieren können.
Miguel
4
Beachten Sie, dass die Blaupause nach den mit blueprint.route dekorierten Funktionen registriert werden muss.
Quint
52

Sie sollten beachten, dass das APPLICATION_ROOTNICHT für diesen Zweck ist.

Sie müssen lediglich eine Middleware schreiben, um die folgenden Änderungen vorzunehmen:

  1. Ändern Sie PATH_INFO, um die vorangestellte URL zu behandeln.
  2. Ändern SCRIPT_NAME, um die vorangestellte URL zu generieren.

So was:

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

Wickeln Sie Ihre App folgendermaßen mit der Middleware ein:

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)

Besuchen Sie http://localhost:9010/foo/bar,

Sie erhalten das richtige Ergebnis: The URL for this page is /foo/bar

Und vergessen Sie nicht, die Cookie-Domain festzulegen, wenn Sie müssen.

Diese Lösung wird von Larivacts Kern gegeben . Das APPLICATION_ROOTist nicht für diesen Job, obwohl es so aussieht. Es ist wirklich verwirrend.

su27
quelle
3
Vielen Dank für das Hinzufügen dieser Antwort. Versuchte die anderen hier veröffentlichten Lösungen, aber dies ist die einzige, die für mich funktioniert hat. A +++ Ich werde auf IIS mit wfastcgi.py
sytech
"Das APPLICATION_ROOTist nicht für diesen Job" - hier habe ich mich geirrt. Ich wünschte Blueprintden url_prefixParameter und APPLICATION_ROOTwurde standardmäßig kombiniert, so dass ich APPLICATION_ROOTBereichs-URLs für die gesamte App und url_prefixBereichs-URLs APPLICATION_ROOTnur für den einzelnen Entwurf haben konnte. Sigh
Monkpit
In diesem Kern finden Sie ein Beispiel dafür, was ich mit versucht habe APPLICATION_ROOT.
Monkpit
2
Wenn Sie Gunicorn verwenden, wird SCRIPT_NAME bereits unterstützt. Legen Sie es als Umgebungsvariable fest oder übergeben Sie es als http-Header: docs.gunicorn.org/en/stable/faq.html
blurrcat
1
Der Code in seiner jetzigen Form hat bei mir nicht funktioniert. Nach einigen Recherchen fand ich dies nach dem anderen in der __call__Methode: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)mitfrom werkzeug.wrappers import BaseResponse as Response
Louis Becker
10

Dies ist eher eine Python-Antwort als eine Flask / Werkzeug-Antwort. aber es ist einfach und funktioniert.

Wenn Sie wie ich möchten, dass Ihre Anwendungseinstellungen (aus einer .iniDatei geladen ) auch das Präfix Ihrer Flask-Anwendung enthalten (damit der Wert nicht während der Bereitstellung, sondern zur Laufzeit festgelegt wird), können Sie sich für Folgendes entscheiden:

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

Dies mag ein wenig hackish und stützt sich auf die Tatsache , dass der Kolben Route Funktion erfordert eine routeals ein erstes Positions Argument.

Sie können es so verwenden:

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

NB: Es ist nichts wert, dass es möglich ist, eine Variable im Präfix zu verwenden (z. B. indem Sie sie auf setzen /<prefix>) und dieses Präfix dann in den Funktionen zu verarbeiten, die Sie mit Ihrem dekorieren @app.route(...). Wenn Sie dies tun, müssen Sie den prefixParameter natürlich in Ihren dekorierten Funktionen deklarieren . Darüber hinaus möchten Sie möglicherweise das übermittelte Präfix anhand einiger Regeln überprüfen und eine 404 zurückgeben, wenn die Überprüfung fehlschlägt. Um eine benutzerdefinierte 404-Neuimplementierung zu vermeiden, klicken Sie bitte from werkzeug.exceptions import NotFounddarauf, raise NotFound()wenn die Prüfung fehlschlägt.

7heo.tk.
quelle
Es ist einfach und effizienter als die Verwendung Blueprint. Danke für das Teilen!
HK Junge
5

Daher glaube ich, dass eine gültige Antwort darauf lautet: Das Präfix sollte in der tatsächlichen Serveranwendung konfiguriert werden, die Sie nach Abschluss der Entwicklung verwenden. Apache, Nginx usw.

Wenn Sie jedoch möchten, dass dies während der Entwicklung funktioniert, während Sie die Flask-App im Debug ausführen, sehen Sie sich diese Übersicht an .

Flasche DispatcherMiddlewarezur Rettung!

Ich werde den Code hier für die Nachwelt kopieren:

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

Wenn Sie den obigen Code jetzt als eigenständige Flask-App ausführen, http://localhost:5000/spam/wird er angezeigt Hello, world!.

In einem Kommentar zu einer anderen Antwort habe ich zum Ausdruck gebracht, dass ich so etwas tun möchte:

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/

Anwendung DispatcherMiddlewareauf mein erfundenes Beispiel:

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
quelle
"Ich glaube also, dass eine gültige Antwort darauf lautet: Das Präfix sollte in der tatsächlichen Serveranwendung konfiguriert werden, die Sie nach Abschluss der Entwicklung verwenden. Apache, Nginx usw." Das Problem liegt in Weiterleitungen; Wenn Sie ein Präfix haben und es nicht in Flask einrichten, wird es bei der Umleitung anstelle von / yourprefix / path / to / url nur zu / path / to / url weitergeleitet. Gibt es eine Möglichkeit, in Nginx oder Apache das Präfix festzulegen?
Jordan Reiter
Die Art und Weise, wie ich dies wahrscheinlich tun würde, besteht darin, nur ein Konfigurationsmanagement-Tool wie Puppet oder Chef zu verwenden, das Präfix dort festzulegen und das Tool dann die Änderung an die Konfigurationsdateien weitergeben zu lassen, wo sie benötigt werden. Ich werde nicht einmal so tun, als wüsste ich, wovon ich für Apache oder Nginx spreche. Da diese Frage / Antwort spezifisch für Python war, würde ich Sie ermutigen, Ihr Szenario als separate Frage zu veröffentlichen. Wenn Sie dies tun, können Sie hier auf die Frage verlinken!
Monkpit
2

Ein anderer ganz anderer Weg ist mit Mountpoints in uwsgi.

Aus dem Dokument über das Hosten mehrerer Apps im selben Prozess ( Permalink ).

In deinem uwsgi.inifügst du hinzu

[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

Wenn Sie Ihre Datei nicht aufrufen main.py, müssen Sie sowohl die mountals auch die ändernmodule

Du main.pykönntest so aussehen:

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

Und eine Nginx-Konfiguration (der Vollständigkeit halber noch einmal):

server {
  listen 80;
  server_name example.com

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

Jetzt wird der Anruf als von den Flaschen zurückgegeben example.com/foo/barangezeigt , da er sich automatisch anpasst. Auf diese Weise funktionieren Ihre Links ohne Präfixprobleme./foo/barurl_for('bar')

Glückydonald
quelle
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
quelle
1
Bitte fügen Sie eine Erklärung hinzu.
Jpp
1
Zwei nette Erklärungen, die ich gefunden habe, waren in Exploreflask und den offiziellen Dokumenten
Yuriploc
1

Ich brauchte eine ähnliche sogenannte "Kontextwurzel". Ich habe es in der conf-Datei unter /etc/httpd/conf.d/ mit WSGIScriptAlias ​​gemacht:

myapp.conf:

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

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

</VirtualHost>

Jetzt kann ich auf meine App zugreifen als: http: // localhost: 5000 / myapp

Siehe die Anleitung - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

dganesh2002
quelle
1

Meine Lösung, bei der Flask- und PHP-Apps neben Nginx und PHP5.6 existieren

KEEP Flask in root und PHP in Unterverzeichnissen

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

1 Zeile hinzufügen

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

VERWENDEN SIE NESTED LOCATIONS für PHP und lassen Sie FLASK im Stammverzeichnis

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

LESEN Sie sorgfältig https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

Wir müssen die Standortübereinstimmung verstehen (keine): Wenn keine Modifikatoren vorhanden sind, wird der Standort als Präfixübereinstimmung interpretiert. Dies bedeutet, dass der angegebene Speicherort mit dem Beginn des Anforderungs-URI abgeglichen wird, um eine Übereinstimmung zu ermitteln. =: Wenn ein Gleichheitszeichen verwendet wird, wird dieser Block als Übereinstimmung betrachtet, wenn der Anforderungs-URI genau mit dem angegebenen Ort übereinstimmt. ~: Wenn ein Tilde-Modifikator vorhanden ist, wird dieser Speicherort als Übereinstimmung zwischen regulären Ausdrücken und Groß- und Kleinschreibung interpretiert. ~ *: Wenn ein Tilde- und ein Sternchen-Modifikator verwendet werden, wird der Positionsblock als Übereinstimmung zwischen regulären Ausdrücken ohne Berücksichtigung der Groß- und Kleinschreibung interpretiert. ^ ~: Wenn ein Modifikator für Karat und Tilde vorhanden ist und dieser Block als beste Übereinstimmung mit nicht regulären Ausdrücken ausgewählt ist, findet keine Übereinstimmung mit regulären Ausdrücken statt.

Die Reihenfolge ist wichtig, aus der Beschreibung des "Standorts" von nginx:

Um einen Standort zu finden, der einer bestimmten Anforderung entspricht, überprüft nginx zunächst Standorte, die mithilfe der Präfixzeichenfolgen (Präfixpositionen) definiert wurden. Unter diesen wird der Ort mit dem längsten übereinstimmenden Präfix ausgewählt und gespeichert. Anschließend werden reguläre Ausdrücke in der Reihenfolge ihres Auftretens in der Konfigurationsdatei überprüft. Die Suche nach regulären Ausdrücken endet mit der ersten Übereinstimmung und die entsprechende Konfiguration wird verwendet. Wenn keine Übereinstimmung mit einem regulären Ausdruck gefunden wird, wird die Konfiguration des zuvor gespeicherten Präfixorts verwendet.

Es bedeutet:

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

Für Leute, die immer noch damit zu kämpfen haben, funktioniert das erste Beispiel, aber das vollständige Beispiel finden Sie hier, wenn Sie eine Flask-App haben, die nicht unter Ihrer Kontrolle steht:

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
quelle