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"
Evan Hahn
fonte

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).
Sean Vieira
fonte
@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')
Miguel
fonte
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.

su27
fonte
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.

7heo.tk
fonte
É 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/
Monkpit
fonte
"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.

luckydonald
fonte
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
fonte
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

dganesh2002
fonte
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)
Jayanta
fonte
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,
    )
Vishnugopal
fonte