Agregue un prefijo a todas las rutas de Flask

98

Tengo un prefijo que quiero agregar a cada ruta. Ahora mismo agrego una constante a la ruta en cada definición. ¿Hay alguna forma de hacer esto automáticamente?

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
fuente

Respuestas:

75

La respuesta depende de cómo esté sirviendo esta aplicación.

Submontado dentro de otro contenedor WSGI

Suponiendo que va a ejecutar esta aplicación dentro de un contenedor WSGI (mod_wsgi, uwsgi, gunicorn, etc.); realmente necesita montar, en ese prefijo la aplicación como una subparte de ese contenedor WSGI (cualquier cosa que hable WSGI servirá) y establecer su APPLICATION_ROOTvalor de configuración en su prefijo:

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

Establecer el APPLICATION_ROOTvalor de configuración simplemente limita la cookie de sesión de Flask a ese prefijo de URL. Todo lo demás será manejado automáticamente por usted por las excelentes capacidades de manejo de WSGI de Flask y Werkzeug.

Un ejemplo de submontaje adecuado de su aplicación

Si no está seguro de lo que significa el primer párrafo, eche un vistazo a esta aplicación de ejemplo con Flask montado en su interior:

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 solicitudes a la aplicación

Si, por otro lado, ejecutará su aplicación Flask en la raíz de su contenedor WSGI y le enviará solicitudes de proxy (por ejemplo, si está siendo FastCGI, o si nginx está proxy_passsolicitando un sub-endpoint a su stand-alone uwsgi/ geventservidor, entonces puede:

  • Use un plano, como Miguel señala en su respuesta .
  • o use la respuestaDispatcherMiddleware from werkzeug(o PrefixMiddlewarefrom su27 ) para submontar su aplicación en el servidor WSGI independiente que está usando. (Vea Un ejemplo de sub-montaje apropiado de su aplicación arriba para el código que debe usar).
Sean Vieira
fuente
@jknupp - mirando flask.Flask#create_url_adaptery werkzeug.routing.Map#bind_to_environparece que debería funcionar - ¿cómo estaba ejecutando el código? (La aplicación en realidad debe montarse en la ruta secundaria en un entorno WSGI para url_fordevolver el valor esperado).
Sean Vieira
Ejecuté exactamente lo que escribiste, pero agregué app = Flask ( name ) y app.run (debug = True)
jeffknupp
4
@jknupp, ese es el problema, necesitará montar la aplicación como una subparte de una aplicación más grande (cualquier cosa que hable WSGI servirá). He creado una esencia de ejemplo y he actualizado mi respuesta para que quede más claro que estoy asumiendo un entorno WSGI submontado, no un entorno WSGI independiente detrás de un proxy que solo reenvía solicitudes de subruta.
Sean Vieira
3
Esto funciona, usando el DispatcherMiddlewareenfoque, cuando se ejecuta el matraz solo. Parece que no puedo hacer que esto funcione cuando corro detrás de Gunicorn.
Justin
1
La forma de montar a subruta en uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. los detalles se refieren a (documento uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
todaynowork
94

Puede poner sus rutas en un plano:

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"

Luego, registra el plano con la aplicación usando un prefijo:

app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
Miguel
fuente
2
Hola Miguel; ¿Conoce la diferencia entre registrar un url_prefix para un blueprint como lo hizo a continuación con app.register_blueprinty entre registrarlo cuando crea una instancia del objeto Blueprint arriba, pasando url_prefix='/abc/123? ¡Gracias!
aralar
4
La diferencia es que tener el prefijo de URL en la register_blueprintllamada le da a la aplicación la libertad de "montar" el plano en cualquier lugar que desee, o incluso montar el mismo plano varias veces en diferentes URL. Si coloca el prefijo en el propio plano, facilita la aplicación, pero tiene menos flexibilidad.
Miguel
¡¡Gracias!! Eso es muy útil. Estaba confundido por la aparente redundancia, pero veo la compensación entre las dos opciones.
aralar
Y de hecho, nunca probé esto, pero es probable que puedas combinar los prefijos de URL tanto en el plano como en la aplicación, con el prefijo de la aplicación, seguido del prefijo del plano.
Miguel
4
Tenga en cuenta que es necesario registrar el plano después de las funciones decoradas blueprint.route.
Quint
53

Debe tener en cuenta que APPLICATION_ROOTNO es para este propósito.

Todo lo que tiene que hacer es escribir un middleware para realizar los siguientes cambios:

  1. modificar PATH_INFOpara manejar la URL prefijada.
  2. modificar SCRIPT_NAMEpara generar la URL prefijada.

Me gusta esto:

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

Envuelva su aplicación con el middleware, así:

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,

Obtendrá el resultado correcto: The URL for this page is /foo/bar

Y no olvide configurar el dominio de las cookies si es necesario.

Esta solución viene dada por la esencia de Larivact . No APPLICATION_ROOTes para este trabajo, aunque parece que sí. Es realmente confuso.

su27
fuente
4
Gracias por agregar esta respuesta. Probé las otras soluciones publicadas aquí, pero esta es la única que funcionó para mí. A +++ Estoy implementado en IIS usando wfastcgi.py
sytech
"El APPLICATION_ROOTno es para este trabajo" - aquí es donde me estaba equivocando. BlueprintEl url_prefixparámetro de deseo y APPLICATION_ROOTse combinaron de forma predeterminada, de modo que podría tener APPLICATION_ROOTURL de alcance para toda la aplicación y url_prefixURL de alcance APPLICATION_ROOTsolo para el plano individual. Suspiro
Monkpit
Vea esta esencia para ver un ejemplo de lo que estaba tratando de hacer usando APPLICATION_ROOT.
Monkpit
2
Si está utilizando gunicorn, SCRIPT_NAME ya es compatible. Configúrelo
blurrcat
1
El código tal como está no funcionó para mí. Después de investigar un poco, se me ocurrió esto después de lo demás en el __call__método: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)usarfrom werkzeug.wrappers import BaseResponse as Response
Louis Becker
10

Esta es más una respuesta de Python que una respuesta de Flask / werkzeug; pero es simple y funciona.

Si, como yo, desea que la configuración de su aplicación (cargada desde un .iniarchivo) también contenga el prefijo de su aplicación Flask (por lo tanto, no tener el valor establecido durante la implementación, sino durante el tiempo de ejecución), puede optar por lo siguiente:

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

Podría decirse que esto es algo hacker y se basa en el hecho de que la función de ruta Frasco requiere una routecomo primer argumento posicional.

Puedes usarlo así:

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

NB: No vale nada que sea posible usar una variable en el prefijo (por ejemplo, configurándola en /<prefix>), y luego procesar este prefijo en las funciones que decore con su @app.route(...). Si lo hace, obviamente debe declarar el prefixparámetro en sus funciones decoradas. Además, es posible que desee verificar el prefijo enviado con algunas reglas y devolver un 404 si la verificación falla. Para evitar una reimplementación 404 personalizada, por favor from werkzeug.exceptions import NotFoundy luego raise NotFound()si la verificación falla.

7heo.tk
fuente
Es simple y más eficiente que usarlo Blueprint. ¡Gracias por compartir!
HK boy
5

Entonces, creo que una respuesta válida a esto es: el prefijo debe configurarse en la aplicación de servidor real que usa cuando se completa el desarrollo. Apache, nginx, etc.

Sin embargo, si lo desea al trabajo durante el desarrollo mientras se ejecuta la aplicación en el frasco de depuración, echar un vistazo a este GIST .

¡Frasco está DispatcherMiddlewareal rescate!

Copiaré el código aquí para la posteridad:

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

Ahora, cuando se ejecuta el código anterior como una aplicación Flask independiente, http://localhost:5000/spam/se mostrará Hello, world!.

En un comentario sobre otra respuesta, expresé que deseaba hacer algo como esto:

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/

Aplicando DispatcherMiddlewarea mi ejemplo artificial:

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
fuente
"Entonces, creo que una respuesta válida a esto es: el prefijo debe configurarse en la aplicación de servidor real que usa cuando se completa el desarrollo. Apache, nginx, etc." El problema está en las redirecciones; si tiene un prefijo y no lo configura en Flask, entonces cuando redirige en lugar de ir a / yourprefix / path / to / url, simplemente va a / path / to / url. ¿Hay alguna forma de configurar, en nginx o Apache, cuál debe ser el prefijo?
Jordan Reiter
La forma en que probablemente haría esto es simplemente usar una herramienta de administración de configuración como puppet o chef, y establecer el prefijo allí y luego hacer que la herramienta propague el cambio a los archivos de configuración donde debe ir. Ni siquiera voy a fingir que sé de lo que estoy hablando para apache o nginx. Dado que esta pregunta / respuesta era específica de Python, le animo a que publique su escenario como una pregunta separada. Si hace esto, ¡no dude en vincular la pregunta aquí!
Monkpit
2

Otra forma completamente diferente es con los puntos de montaje en formatouwsgi .

Del documento sobre el alojamiento de varias aplicaciones en el mismo proceso ( enlace permanente ).

En tu uwsgi.initu agregas

[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 no llama a su archivo main.py, debe cambiar tanto el mountcomo elmodule

Tu main.pypodrías lucir así:

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

Y una configuración de nginx (nuevamente para completar):

server {
  listen 80;
  server_name example.com

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

Ahora la llamada example.com/foo/barse mostrará /foo/barcomo devuelta por el frasco url_for('bar'), ya que se adapta automáticamente. De esa manera, sus enlaces funcionarán sin problemas de prefijo.

luckydonald
fuente
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
fuente
1
Considere agregar una explicación.
jpp
1
Dos buenas explicaciones que encontré fueron en exploreflask y en los documentos oficiales
yuriploc
1

Necesitaba algo similar llamado "raíz de contexto". Lo hice en el archivo conf en /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>

Entonces ahora puedo acceder a mi aplicación como: http: // localhost: 5000 / myapp

Consulte la guía: http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

dganesh2002
fuente
1

Mi solución donde coexisten las aplicaciones flask y PHP nginx y PHP5.6

MANTENGA Flask en root y PHP en subdirectorios

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

Agregar 1 línea

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

USE UBICACIONES ANIDADAS para PHP y deje que FLASK permanezca en la raíz

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

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

Necesitamos entender la coincidencia de ubicación (ninguna): si no hay modificadores, la ubicación se interpreta como una coincidencia de prefijo. Esto significa que la ubicación proporcionada se comparará con el comienzo del URI de solicitud para determinar una coincidencia. =: Si se usa un signo igual, este bloque se considerará una coincidencia si el URI de la solicitud coincide exactamente con la ubicación proporcionada. ~: Si hay un modificador de tilde, esta ubicación se interpretará como una coincidencia de expresión regular sensible a mayúsculas y minúsculas. ~ *: Si se usa un modificador de tilde y asterisco, el bloque de ubicación se interpretará como una coincidencia de expresión regular que no distingue entre mayúsculas y minúsculas. ^ ~: Si hay un modificador de quilate y tilde, y si este bloque se selecciona como la mejor coincidencia de expresión no regular, no se realizará la coincidencia de expresión regular.

El orden es importante, según la descripción de la "ubicación" de nginx:

Para encontrar una ubicación que coincida con una solicitud dada, nginx primero verifica las ubicaciones definidas usando las cadenas de prefijo (ubicaciones de prefijo). Entre ellos, se selecciona y recuerda la ubicación con el prefijo coincidente más largo. A continuación, se comprueban las expresiones regulares, en el orden de aparición en el archivo de configuración. La búsqueda de expresiones regulares termina en la primera coincidencia y se utiliza la configuración correspondiente. Si no se encuentra ninguna coincidencia con una expresión regular, se utiliza la configuración de la ubicación del prefijo recordada anteriormente.

Significa:

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

Para las personas que todavía luchan con esto, el primer ejemplo funciona, pero el ejemplo completo está aquí si tiene una aplicación Flask que no está bajo su control:

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
fuente