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"
Evan Hahn
źródło

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).
Sean Vieira
źródło
@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')
Miguel
źródło
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.

su27
źródło
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.

7heo.tk
źródło
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/
Monkpit
źródło
„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.

Luckydonald
źródło
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
źródło
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

dganesh2002
źródło
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)
Jayanta
źródło
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,
    )
vishnugopal
źródło