Adicione um prefixo a todas as rotas do Flask


98

Tenho um prefixo que quero adicionar a todas as rotas. No momento, adiciono uma constante à rota em cada definição. Existe uma maneira de fazer isso 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"

Respostas:


75

A resposta depende de como você está atendendo a este aplicativo.

Submontado dentro de outro contêiner WSGI

Supondo que você irá executar este aplicativo dentro de um contêiner WSGI (mod_wsgi, uwsgi, gunicorn, etc); você precisa realmente montar, nesse prefixo, o aplicativo como uma subparte desse contêiner WSGI (qualquer coisa que fale WSGI servirá) e definir seu APPLICATION_ROOTvalor de configuração para o seu prefixo:

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

Definir o APPLICATION_ROOTvalor de configuração simplesmente limita o cookie de sessão do Flask a esse prefixo de URL. Todo o resto será tratado automaticamente para você pelos excelentes recursos de manipulação de WSGI do Flask e Werkzeug.

Um exemplo de submontagem adequada do seu aplicativo

Se você não tiver certeza do que o primeiro parágrafo significa, dê uma olhada neste aplicativo de exemplo com o Flask montado dentro dele:

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)

Solicitações de proxy para o aplicativo

Se, por outro lado, você for executar seu aplicativo Flask na raiz de seu contêiner WSGI e fazer proxy de solicitações para ele (por exemplo, se estiver sendo FastCGI, ou se nginx estiver proxy_passenviando solicitações para um subendpoint para seu servidor uwsgi/ autônomo, gevententão você pode:

  • Use um Blueprint, como Miguel aponta em sua resposta .
  • ou usar a DispatcherMiddlewarepartir de werkzeug(ou a PrefixMiddlewarepartir de resposta das Su27 ) para sub-montagem de sua aplicação no servidor WSGI stand-alone você está usando. (Veja um exemplo de submontagem adequada de seu aplicativo acima para o código a ser usado).

@jknupp - olhando flask.Flask#create_url_adaptere werkzeug.routing.Map#bind_to_environparece que deve funcionar - como você estava executando o código? (O aplicativo realmente precisa ser montado no subcaminho em um ambiente WSGI para url_forretornar o valor esperado.)
— Sean Vieira

Corri exatamente o que você escreveu, mas adicionei app = Flask ( nome ) e app.run (debug = True)
— jeffknupp

4
@jknupp - esse é o problema - você precisará realmente montar o aplicativo como uma sub-parte de um aplicativo maior (qualquer coisa que fale WSGI servirá). Eu criei uma essência de exemplo e atualizei minha resposta para deixar mais claro que estou assumindo um ambiente WSGI submontado, não um ambiente WSGI autônomo atrás de um proxy que está apenas encaminhando solicitações de subcaminho.
— Sean Vieira

3
Isso funciona, usando a DispatcherMiddlewareabordagem, ao executar o frasco sozinho. Não consigo fazer isso funcionar ao correr atrás de Gunicorn.
— Justin

1
O caminho é montado em um caminho secundário em uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. detalhes consulte (documento uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
— todaynowork

94

Você pode colocar suas rotas em um 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"

Em seguida, você registra o blueprint com o aplicativo usando um prefixo:

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

2
Oi Miguel; você sabe a diferença entre registrar um url_prefix para um blueprint como fez a seguir app.register_blueprinte entre registrá-lo ao instanciar o objeto Blueprint acima, passando url_prefix='/abc/123? Obrigado!
— aralar

4
A diferença é que ter o prefixo de URL na register_blueprintchamada dá ao aplicativo a liberdade de "montar" o blueprint em qualquer lugar que desejar, ou até mesmo montar o mesmo blueprint várias vezes em URLs diferentes. Se você colocar o prefixo no próprio blueprint, ficará mais fácil para o aplicativo, mas terá menos flexibilidade.
— Miguel

Obrigado!! Isso é muito útil. Fiquei confuso com a aparente redundância, mas vejo a compensação entre as duas opções.
— aralar

E, na verdade, nunca tentei fazer isso, mas é provável que você possa combinar prefixos de URL tanto no blueprint quanto no aplicativo, com o prefixo do app fist, seguido pelo prefixo do blueprint.
— Miguel

4
Observe que é necessário registrar o blueprint após as funções decoradas blueprint.route.
— Quint

53

Você deve observar que APPLICATION_ROOTNÃO é para essa finalidade.

Tudo que você precisa fazer é escrever um middleware para fazer as seguintes alterações:

  1. modifique PATH_INFOpara lidar com o url prefixado.
  2. modifique SCRIPT_NAMEpara gerar o url prefixado.

Como isso:

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

Envolva seu aplicativo com o middleware, assim:

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 ,

Você obterá o resultado certo: The URL for this page is /foo/bar

E não se esqueça de definir o domínio do cookie, se necessário.

Esta solução é dada pela essência do Larivact . O APPLICATION_ROOTnão é para este trabalho, embora pareça ser. É muito confuso.


4
Obrigado por adicionar esta resposta. Tentei as outras soluções postadas aqui, mas esta é a única que funcionou para mim. A +++ Estou implantado no IIS usando wfastcgi.py
— sytech

"Não APPLICATION_ROOTé para este trabalho" - é aí que eu estava errado. BlueprintO url_prefixparâmetro de desejo e APPLICATION_ROOTforam combinados por padrão, para que eu pudesse ter APPLICATION_ROOTurls de escopo para o aplicativo inteiro e url_prefixurls de escopo APPLICATION_ROOTapenas para o blueprint individual. Sigh
— Monkpit

Veja esta essência para um exemplo do que eu estava tentando fazer usando APPLICATION_ROOT.
— Monkpit de

2
Se você estiver usando gunicorn, SCRIPT_NAME já é compatível. Defina-o como uma variável de ambiente ou passe-o como um cabeçalho http: docs.gunicorn.org/en/stable/faq.html
— blurrcat

1
O código, tal como está, não funcionou para mim. Depois de alguma pesquisa, descobri isso após o else no __call__método: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)usingfrom werkzeug.wrappers import BaseResponse as Response
— Louis Becker

10

Esta é mais uma resposta python do que uma resposta Flask / werkzeug; mas é simples e funciona.

Se, como eu, você deseja que as configurações do seu aplicativo (carregadas de um .iniarquivo) também contenham o prefixo do seu aplicativo Flask (portanto, não ter o valor definido durante a implantação, mas durante o tempo de execução), você pode optar pelo seguinte:

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

Indiscutivelmente, isso é um tanto hackeado e se baseia no fato de que a função de rota do Flask requer a routecomo primeiro argumento posicional.

Você pode usá-lo assim:

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

NB: Não vale a pena que seja possível usar uma variável no prefixo (por exemplo, definindo-o como /<prefix>), e depois processar esse prefixo nas funções que você decorar com o seu @app.route(...). Se você fizer isso, obviamente terá que declarar o prefixparâmetro em sua (s) função (ões) decorada (s). Além disso, você pode querer verificar o prefixo enviado em relação a algumas regras e retornar um 404 se a verificação falhar. A fim de evitar uma reimplementação 404 customizada, por favor from werkzeug.exceptions import NotFound, raise NotFound()se a verificação falhar.


É simples e mais eficiente do que usar Blueprint. Obrigado por compartilhar!
— HK menino

5

Portanto, acredito que uma resposta válida para isso é: o prefixo deve ser configurado no aplicativo de servidor real que você usa quando o desenvolvimento é concluído. Apache, nginx, etc.

No entanto, se você quiser que isso funcione durante o desenvolvimento enquanto executa o aplicativo Flask na depuração, dê uma olhada nesta essência .

O frasco está DispatcherMiddlewarepara o resgate!

Vou copiar o código aqui para a posteridade:

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

Agora, ao executar o código acima como um aplicativo Flask autônomo, http://localhost:5000/spam/será exibido Hello, world!.

Em um comentário sobre outra resposta, expressei que gostaria de fazer algo assim:

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 DispatcherMiddlewareao meu exemplo inventado:

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/

"Portanto, acredito que uma resposta válida para isso é: o prefixo deve ser configurado no aplicativo de servidor real que você usa quando o desenvolvimento é concluído. Apache, nginx, etc." O problema está nos redirecionamentos; se você tiver um prefixo e não configurá-lo no Flask, quando ele redirecionar em vez de ir para / yourprefix / path / to / url, ele apenas irá para / path / to / url. Existe uma maneira de configurar, no nginx ou no Apache, qual deve ser o prefixo?
— Jordan Reiter

Provavelmente, eu faria isso usando uma ferramenta de gerenciamento de configuração, como puppet ou chef, e definir o prefixo lá e, em seguida, fazer com que a ferramenta propague a mudança para os arquivos de configuração onde ela precisa ir. Não vou fingir que sei do que estou falando sobre apache ou nginx. Uma vez que esta pergunta / resposta era específica para python, eu encorajo você a postar seu cenário como uma pergunta separada. Se você fizer isso, fique à vontade para acessar a pergunta aqui!
— Monkpit

2

Outra maneira completamente diferente é com pontos de montagem em uwsgi.

Do documento sobre hospedagem de vários aplicativos no mesmo processo ( link permanente ).

No seu uwsgi.inivocê adiciona

[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 você não chamar seu arquivo main.py, será necessário alterar o mounte omodule

Você main.pypoderia ter esta aparência:

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 uma configuração nginx (novamente para integridade):

server {
  listen 80;
  server_name example.com

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

Agora, a chamada example.com/foo/barserá exibida /foo/barconforme retornada por flask's url_for('bar'), pois se adapta automaticamente. Dessa forma, seus links funcionarão sem problemas de prefixo.


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
Considere adicionar uma explicação.
— jpp de

1
Duas boas explicações que encontrei estavam em exploreflask e nos documentos oficiais
— yuriploc

1

Eu precisava de algo semelhante, chamado de "raiz de contexto". Fiz isso no arquivo conf em /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>

Agora posso acessar meu aplicativo como: http: // localhost: 5000 / myapp

Consulte o guia - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html


1

Minha solução em que aplicativos flask e PHP coexistem nginx e PHP5.6

MANTER Flask na raiz e PHP nos subdiretórios

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

Adicionar 1 linha

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 LOCALIZAÇÕES ANINHADAS para PHP e deixe FLASK permanecer na raiz

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

LEIA com atenção https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

Precisamos entender a correspondência de localização (nenhuma): Se nenhum modificador estiver presente, a localização é interpretada como uma correspondência de prefixo. Isso significa que o local fornecido será comparado ao início do URI de solicitação para determinar uma correspondência. =: Se um sinal de igual for usado, este bloco será considerado uma correspondência se o URI de solicitação corresponder exatamente ao local fornecido. ~: Se um modificador de til estiver presente, este local será interpretado como uma correspondência de expressão regular que diferencia maiúsculas de minúsculas. ~ *: Se um modificador de til e asterisco for usado, o bloco de localização será interpretado como uma correspondência de expressão regular que não diferencia maiúsculas de minúsculas. ^ ~: Se um modificador de caractere e til estiver presente, e se este bloco for selecionado como a melhor correspondência de expressão não regular, a correspondência de expressão regular não ocorrerá.

A ordem é importante, a partir da descrição de "localização" do nginx:

Para encontrar o local que corresponde a uma determinada solicitação, o nginx primeiro verifica os locais definidos usando as sequências de prefixo (locais de prefixo). Entre eles, o local com o prefixo correspondente mais longo é selecionado e lembrado. Em seguida, as expressões regulares são verificadas, na ordem em que aparecem no arquivo de configuração. A pesquisa de expressões regulares termina na primeira correspondência e a configuração correspondente é usada. Se nenhuma correspondência com uma expressão regular for encontrada, a configuração da localização do prefixo lembrada anteriormente é usada.

Isso significa:

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

1

Para as pessoas que ainda lutam com isso, o primeiro exemplo funciona, mas o exemplo completo está aqui se você tiver um aplicativo Flask que não está sob seu controle:

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,
    )
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.