Dodaj przedrostek do wszystkich tras Flask


103

Mam prefiks, który chcę dodać do każdej trasy. W tej chwili dodaję stałą do trasy przy każdej definicji. Czy istnieje sposób, aby zrobić to automatycznie?

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"

Odpowiedzi:


77

Odpowiedź zależy od tego, jak obsługujesz tę aplikację.

Zamontowany w innym pojemniku WSGI

Zakładając, że zamierzasz uruchomić tę aplikację wewnątrz kontenera WSGI (mod_wsgi, uwsgi, gunicorn itp.); musisz faktycznie zamontować, w tym prefiksie aplikację jako część podrzędną tego kontenera WSGI (wszystko, co mówi WSGI) i ustawić APPLICATION_ROOTwartość konfiguracyjną na swój prefiks:

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

Ustawienie APPLICATION_ROOTwartości config ogranicza plik cookie sesji Flask do tego prefiksu adresu URL. Wszystko inne będzie obsługiwane automatycznie przez doskonałe możliwości obsługi WSGI firmy Flask i Werkzeug.

Przykład prawidłowego montażu aplikacji

Jeśli nie jesteś pewien, co oznacza pierwszy akapit, spójrz na tę przykładową aplikację z zamontowanym w niej Flaskiem:

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)

Przesyłanie żądań do aplikacji

Jeśli, z drugiej strony, będziesz uruchamiać aplikację Flask w katalogu głównym jej kontenera WSGI i przekazywać do niej żądania (na przykład, jeśli jest to FastCGI, lub jeśli nginx proxy_passwysyła żądania dla punktu końcowego na swój samodzielny uwsgi/ geventserwer, możesz:

  • Użyj schematu, jak wskazuje Miguel w swojej odpowiedzi .
  • lub użyj DispatcherMiddlewarefrom werkzeug(lub odpowiedzi PrefixMiddlewarefrom su27 ), aby zamontować swoją aplikację w autonomicznym serwerze WSGI, którego używasz. (Zobacz przykład prawidłowego montażu aplikacji poniżej, aby uzyskać kod do użycia).

@jknupp - patrząc flask.Flask#create_url_adapteri werkzeug.routing.Map#bind_to_environwygląda na to, że powinno działać - jak uruchomiłeś kod? (W rzeczywistości aplikacja musi być zamontowana na ścieżce podrzędnej w środowisku WSGI, url_foraby zwrócić oczekiwaną wartość.)
— Sean Vieira

Uruchomiłem dokładnie to, co napisałeś, ale dodałem app = Flask ( nazwa ) i app.run (debug = True)
— jeffknupp

4
@jknupp - na tym polega problem - będziesz musiał faktycznie zamontować aplikację jako część podrzędną większej aplikacji (wszystko, co mówi WSGI, zrobi). Przygotowałem przykładowy fragment i zaktualizowałem moją odpowiedź, aby było jaśniejsze, że zakładam podmontowane środowisko WSGI, a nie samodzielne środowisko WSGI za proxy, które przekazuje tylko żądania podścieżek.
— Sean Vieira

3
Działa to, stosując DispatcherMiddlewarepodejście, gdy działa samodzielnie. Nie wydaje się, żeby to działało, gdy działa za Gunicornem.
— Justin

1
Sposób montażu do ścieżki pomocniczej w uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. szczegóły patrz (dokument uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
— Todaynowork

101

Możesz umieścić swoje trasy w planie:

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"

Następnie rejestrujesz projekt w aplikacji za pomocą prefiksu:

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

2
Cześć Miguel; Czy znasz różnicę między rejestracją url_prefix dla blueprinta, jak to zrobiłeś poniżej, app.register_blueprinta między rejestracją go podczas tworzenia instancji obiektu Blueprint powyżej, przez przejście url_prefix='/abc/123? Dziękuję Ci!
— aralar

4
Różnica polega na tym, że posiadanie prefiksu adresu URL w register_blueprintwywołaniu daje aplikacji swobodę „montowania” planu w dowolnym miejscu, a nawet wielokrotnego montowania tego samego schematu na różnych adresach URL. Jeśli umieścisz prefiks w samym schemacie, ułatwiasz aplikację, ale masz mniejszą elastyczność.
— Miguel,

Dziękuję Ci!! To jest bardzo pomocne. Byłem zdezorientowany pozorną redundancją, ale widzę kompromis między tymi dwiema opcjami.
— aralar

Właściwie nigdy tego nie próbowałem, ale jest prawdopodobne, że możesz połączyć prefiksy adresów URL zarówno w schemacie, jak iw aplikacji, z prefiksem aplikacji, a następnie prefiksem schematu.
— Miguel

5
Zauważ, że konieczne jest zarejestrowanie planu po funkcjach dekorowanych blueprint.route.
— Quint

53

Należy pamiętać, że plik APPLICATION_ROOT NIE jest do tego celu.

Wszystko, co musisz zrobić, to napisać oprogramowanie pośredniczące, aby wprowadzić następujące zmiany:

  1. zmodyfikuj, PATH_INFOaby obsłużyć prefiksowany adres URL.
  2. zmodyfikuj, SCRIPT_NAMEaby wygenerować prefiksowany adres URL.

Lubię to:

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

Otocz swoją aplikację oprogramowaniem pośredniczącym, na przykład:

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)

Odwiedzić http://localhost:9010/foo/bar ,

Uzyskasz właściwy wynik: The URL for this page is /foo/bar

I nie zapomnij ustawić domeny cookie, jeśli zajdzie taka potrzeba.

Rozwiązanie to wynika z istoty Larivacta . To APPLICATION_ROOTnie jest do tej pracy, chociaż wygląda na to. To naprawdę zagmatwane.


4
Dzięki za dodanie tej odpowiedzi. Wypróbowałem inne zamieszczone tutaj rozwiązania, ale to jedyne, które działało dla mnie. A +++ Jestem wdrożony w usługach IIS przy użyciu wfastcgi.py
— sytech

„To APPLICATION_ROOTnie jest do tej pracy” - tutaj popełniłem błąd. Życzę Blueprint„s url_prefixparametr i APPLICATION_ROOTłączy się domyślnie, tak że mogę mieć APPLICATION_ROOTadresy URL zakres całej aplikacji i url_prefixURL-e zakresów ciągu APPLICATION_ROOTtylko dla indywidualnego planu. Westchnienie
— Monkpit

Zobacz ten sedno, aby zobaczyć przykład tego, co próbowałem zrobić, używając APPLICATION_ROOT.
— Monkpit

2
Jeśli używasz Gunicorn, SCRIPT_NAME jest już obsługiwany. Ustaw jako zmienną środowiskową lub przekaż jako http nagłówek: docs.gunicorn.org/en/stable/faq.html
— blurrcat

1
Kod w obecnej postaci nie działał dla mnie. Po kilku badaniach wymyśliłem to po innym w __call__metodzie: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)używającfrom werkzeug.wrappers import BaseResponse as Response
— Louis Becker

10

Jest to bardziej odpowiedź Pythona niż odpowiedź Flask / werkzeug; ale to proste i działa.

Jeśli, tak jak ja, chcesz, aby ustawienia aplikacji (ładowane z .inipliku) zawierały również prefiks Twojej aplikacji Flask (a zatem nie ustawiano wartości podczas wdrażania, ale w czasie wykonywania), możesz wybrać następujące opcje:

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

Prawdopodobnie jest to nieco hackish i opiera się na fakcie, że funkcja trasa Kolba wymagaroute jako pierwszego argumentu pozycyjnego.

Możesz go używać w ten sposób:

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

NB: Nic nie warte, że w prefiksie można użyć zmiennej (na przykład ustawiając ją na /<prefix>), a następnie przetworzyć ten prefiks w funkcjach, które dekorujesz swoim @app.route(...). Jeśli to zrobisz, musisz oczywiście zadeklarować prefixparametr w dekorowanej funkcji (funkcjach). Ponadto możesz chcieć sprawdzić przesłany prefiks pod kątem niektórych reguł i zwrócić 404, jeśli sprawdzenie się nie powiedzie. Aby uniknąć ponownej implementacji niestandardowej 404, prosimy, from werkzeug.exceptions import NotFounda następnie, raise NotFound()jeśli sprawdzenie się nie powiedzie.


To proste i wydajniejsze niż używanie Blueprint. Dzięki za udostępnienie!
— HK boy

5

Uważam więc, że prawidłowa odpowiedź na to pytanie brzmi: prefiks powinien być skonfigurowany w rzeczywistej aplikacji serwera, której używasz po zakończeniu programowania. Apache, Nginx itp.

Jeśli jednak chcesz, aby to działało podczas programowania podczas uruchamiania aplikacji Flask w trybie debugowania, spójrz na tę istotę .

Kolby DispatcherMiddleware na ratunek!

Skopiuję kod tutaj dla potomności:

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

Teraz, po uruchomieniu powyższego kodu jako samodzielnej aplikacji Flask, http://localhost:5000/spam/wyświetli sięHello, world! .

W komentarzu do innej odpowiedzi wyraziłem, że chciałbym zrobić coś takiego:

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/

Odnosząc się DispatcherMiddlewaredo mojego wymyślonego przykładu:

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/

„Więc uważam, że prawidłowa odpowiedź na to pytanie brzmi: prefiks powinien być skonfigurowany w rzeczywistej aplikacji serwera, której używasz po zakończeniu programowania. Apache, nginx itp.” Problem tkwi w przekierowaniach; jeśli masz prefiks i nie konfigurujesz go w Flasku, to gdy przekierowuje zamiast przechodzić do / twój prefiks / ścieżka / do / url, po prostu przechodzi do / ścieżka / do / url. Czy jest sposób, aby ustawić w nginx lub Apache, jaki musi być prefiks?
— Jordan Reiter

Sposób, w jaki prawdopodobnie bym to zrobił, to po prostu użycie narzędzia do zarządzania konfiguracją, takiego jak marionetka lub szef kuchni, i ustawienie tam prefiksu, a następnie narzędzie propaguje zmianę do plików konfiguracyjnych tam, gdzie ma się udać. Nie zamierzam nawet udawać, że wiem, o czym mówię w przypadku apache czy nginx. Ponieważ to pytanie / odpowiedź dotyczyło języka Python, zachęcam do opublikowania scenariusza jako osobnego pytania. Jeśli to zrobisz, możesz zamieścić link do pytania tutaj!
— Monkpit

2

Innym zupełnie innym sposobem są punkty montowania w uwsgi.

Z dokumentu o hostowaniu wielu aplikacji w tym samym procesie ( link bezpośredni ).

W twojej uwsgi.inidodasz

[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

Jeśli nie wywołasz swojego pliku main.py, musisz zmienić zarówno rozszerzenie, jak mountimodule

Twój main.pymógłby wyglądać tak:

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

I konfiguracja nginx (ponownie dla kompletności):

server {
  listen 80;
  server_name example.com

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

Teraz wywołanie example.com/foo/barbędzie wyświetlane /foo/barjako zwrócone przez kolby url_for('bar'), ponieważ dostosowuje się automatycznie. W ten sposób Twoje linki będą działać bez problemów z prefiksami.


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
Proszę rozważyć dodanie wyjaśnienia.
— jpp

1
Dwa fajne wyjaśnienia, które znalazłem, znajdują się w eksploratorze i oficjalnej dokumentacji
— yuriploc

1

Potrzebowałem podobnego tak zwanego „kontekstowego korzenia”. Zrobiłem to w pliku konfiguracyjnym w /etc/httpd/conf.d/ używając WSGIScriptAlias:

myapp.conf:

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

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

</VirtualHost>

Więc teraz mogę uzyskać dostęp do mojej aplikacji jako: http: // localhost: 5000 / myapp

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


1

Moje rozwiązanie, w którym aplikacje flask i PHP współistnieją nginx i PHP5.6

ZACHOWAJ Flask w katalogu głównym, a PHP w podkatalogach

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

Dodaj 1 linię

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

UŻYJ LOKALIZACJI ZAGNIEŻDŻONYCH dla PHP i pozwól FLASK pozostać w katalogu głównym

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

PRZECZYTAJ uważnie https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

Musimy zrozumieć dopasowanie lokalizacji (brak): jeśli nie ma żadnych modyfikatorów, lokalizacja jest interpretowana jako dopasowanie prefiksu. Oznacza to, że podana lokalizacja zostanie dopasowana do początku identyfikatora URI żądania w celu określenia dopasowania. =: Jeśli zostanie użyty znak równości, ten blok zostanie uznany za zgodny, jeśli identyfikator URI żądania dokładnie pasuje do podanej lokalizacji. ~: Jeśli obecny jest modyfikator tyldy, ta lokalizacja zostanie zinterpretowana jako dopasowanie wyrażenia regularnego z uwzględnieniem wielkości liter. ~ *: Jeśli używany jest modyfikator tyldy i gwiazdki, blok lokalizacji zostanie zinterpretowany jako dopasowanie wyrażenia regularnego bez rozróżniania wielkości liter. ^ ~: Jeśli obecny jest modyfikator karata i tyldy, a ten blok zostanie wybrany jako najlepsze dopasowanie inne niż wyrażenie regularne, dopasowanie wyrażenia regularnego nie nastąpi.

Porządek jest ważny, z opisu „lokalizacji” nginx:

Aby znaleźć lokalizację pasującą do danego żądania, nginx najpierw sprawdza lokalizacje zdefiniowane przy użyciu ciągów prefiksów (lokalizacji prefiksów). Wśród nich lokalizacja z najdłuższym pasującym prefiksem jest wybierana i zapamiętywana. Następnie sprawdzane są wyrażenia regularne, w kolejności ich występowania w pliku konfiguracyjnym. Wyszukiwanie wyrażeń regularnych kończy się przy pierwszym dopasowaniu i używana jest odpowiednia konfiguracja. Jeśli nie zostanie znalezione dopasowanie do wyrażenia regularnego, używana jest konfiguracja zapamiętanej wcześniej lokalizacji prefiksu.

To znaczy:

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

1

Dla osób, które wciąż mają z tym problemy, pierwszy przykład działa, ale pełny przykład znajduje się tutaj, jeśli masz aplikację Flask, nad którą nie masz kontroli:

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,
    )
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.