Добавить префикс ко всем маршрутам Flask

104

У меня есть префикс, который я хочу добавить к каждому маршруту. Прямо сейчас я добавляю константу к маршруту при каждом определении. Есть ли способ сделать это автоматически?

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"
Эван Хан
источник

Ответы:

78

Ответ зависит от того, как вы обслуживаете это приложение.

Подмонтирован внутри другого контейнера WSGI

Предполагая, что вы собираетесь запустить это приложение внутри контейнера WSGI (mod_wsgi, uwsgi, gunicorn и т. Д.); вам нужно на самом деле смонтировать с этим префиксом приложение как часть этого контейнера WSGI (все, что говорит WSGI, будет делать) и установить APPLICATION_ROOTзначение конфигурации для вашего префикса:

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

Установка APPLICATION_ROOTзначения конфигурации просто ограничивает cookie сеанса Flask этим префиксом URL. Все остальное будет автоматически обрабатываться за вас с помощью превосходных возможностей обработки WSGI Flask и Werkzeug.

Пример правильного подмонтирования вашего приложения

Если вы не уверены, что означает первый абзац, взгляните на этот пример приложения с установленным внутри него Flask:

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)

Проксирование запросов к приложению

Если, с другой стороны, вы будете запускать приложение Flask в корне его контейнера WSGI и передавать ему запросы проксирования (например, если оно выполняется FastCGI, или если nginx выполняет proxy_passзапросы -ing для субконечной точки на свой автономный uwsgi/ geventсервер, тогда вы можете:

  • Используйте Blueprint, как указывает Мигель в своем ответе .
  • или использовать DispatcherMiddlewareиз werkzeug(или PrefixMiddlewareот ответа su27 игровых ) к югу от монтажа приложения в автономном сервере WSGI вы используете. (См. Пример правильного подмонтирования вашего приложения выше, чтобы узнать, какой код использовать).
Шон Виейра
источник
@jknupp - глядя flask.Flask#create_url_adapterи werkzeug.routing.Map#bind_to_environ, похоже , он должен работать - как ты выполнение кода? (На самом деле приложение должно быть смонтировано на дополнительном пути в среде WSGI, url_forчтобы возвращать ожидаемое значение.)
Шон Виейра,
Я выполнил именно то, что вы написали, но добавил app = Flask ( name ) и app.run (debug = True)
jeffknupp
4
@jknupp - вот в чем проблема - вам нужно будет смонтировать приложение как часть большого приложения (подойдет все, что говорит WSGI). Я увеличил примерную суть и обновил свой ответ, чтобы прояснить, что я предполагаю субмонтированную среду WSGI, а не автономную среду WSGI за прокси, которая только пересылает запросы субпуть.
Шон Виейра
3
Это работает, используя DispatcherMiddlewareподход, когда запускается только flask. Кажется, не получается заставить это работать, когда вы бежите за Gunicorn.
Джастин
1
Путь монтирования к подпутью в uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. подробности см. (документ uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
todaynowork
101

Вы можете поместить свои маршруты в план:

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"

Затем вы регистрируете чертеж в приложении, используя префикс:

app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
Мигель
источник
2
Привет, Мигель! знаете ли вы разницу между регистрацией url_prefix для чертежа, как вы это делали ниже, app.register_blueprintи между регистрацией его при создании экземпляра объекта Blueprint выше, передавая url_prefix='/abc/123? Спасибо!
aralar
4
Разница в том, что наличие префикса URL в register_blueprintвызове дает приложению свободу «монтировать» схему в любом месте или даже монтировать одну и ту же схему несколько раз по разным URL. Если вы поместите префикс в сам план, вы упростите приложение, но у вас будет меньше гибкости.
Мигель
Спасибо!! Это очень полезно. Меня смутила очевидная избыточность, но я вижу компромисс между двумя вариантами.
aralar
И на самом деле я никогда этого не пробовал, но вполне вероятно, что вы можете комбинировать префиксы URL как в чертеже, так и в приложении, с префиксом приложения, за которым следует префикс чертежа.
Мигель
5
Обратите внимание, что необходимо зарегистрировать blueprint после украшенных функций blueprint.route.
Quint
54

Обратите внимание, что APPLICATION_ROOTэто НЕ для этой цели.

Все, что вам нужно сделать, это написать промежуточное ПО, чтобы внести следующие изменения:

  1. изменить PATH_INFOдля обработки URL-адреса с префиксом.
  2. изменить, SCRIPT_NAMEчтобы сгенерировать URL-адрес с префиксом.

Как это:

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

Оберните ваше приложение промежуточным программным обеспечением, например:

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)

Посетите http://localhost:9010/foo/bar,

Вы получите правильный результат: The URL for this page is /foo/bar

И не забудьте установить домен cookie, если вам нужно.

Это решение дает сущность Ларивакта . APPLICATION_ROOTНе для этой работы, хотя это выглядит как быть. Это действительно сбивает с толку.

вс27
источник
4
Спасибо, что добавили этот ответ. Пробовал другие решения, размещенные здесь, но это единственное, что сработало для меня. A +++ Я развернут на IIS с помощью wfastcgi.py
sytech
«Это APPLICATION_ROOTне для этой работы» - вот где я ошибся. Я хочу Blueprint«s url_prefixпараметр и APPLICATION_ROOTбыл объединено по умолчанию, так что я мог бы APPLICATION_ROOTобласть видимости URLs для всего приложения и url_prefixобласть действия URLs в пределах APPLICATION_ROOTтолько для индивидуального плана. Вздох
Monkpit
См. Эту суть для примера того, что я пытался использовать APPLICATION_ROOT.
Monkpit
2
Если вы используете Gunicorn, SCRIPT_NAME уже поддерживается. Задайте его как переменную среды или передайте как http-заголовок: docs.gunicorn.org/en/stable/faq.html
blurrcat
1
Код в его нынешнем виде у меня не работал. После некоторого исследования я придумал это после else in the __call__method: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)usingfrom werkzeug.wrappers import BaseResponse as Response
Луи Беккер
10

Это скорее ответ Python, чем ответ Flask / werkzeug; но все просто и работает.

Если, как и я, вы хотите, чтобы настройки вашего приложения (загруженные из .iniфайла) также содержали префикс вашего приложения Flask (таким образом, чтобы значение не устанавливалось во время развертывания, а во время выполнения), вы можете выбрать следующее:

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

Возможно, это несколько хаком и опирается на тот факт , что функция маршрута колба требуетroute в качестве первого позиционного аргумента.

Вы можете использовать это так:

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

NB: ничего не стоит, если можно использовать переменную в префиксе (например, задав ее значение /<prefix>), а затем обрабатывать этот префикс в функциях, которые вы украшаете своим @app.route(...). Если вы это сделаете, вам, очевидно, придется объявить prefixпараметр в вашей украшенной функции (ах). Кроме того, вы можете проверить отправленный префикс на соответствие некоторым правилам и вернуть 404, если проверка не удалась. Чтобы избежать повторной реализации 404, пожалуйста, from werkzeug.exceptions import NotFoundа затем, raise NotFound()если проверка не удалась.

7heo.tk
источник
Это проще и эффективнее, чем использовать Blueprint. Спасибо, что поделился!
Мальчик из
5

Итак, я считаю, что правильный ответ на это: префикс должен быть настроен в реальном серверном приложении, которое вы используете после завершения разработки. Apache, nginx и т. Д.

Однако, если вы хотите, чтобы это работало во время разработки при запуске приложения Flask в отладке, взгляните на эту суть .

Flask спешит DispatcherMiddlewareна помощь!

Скопирую код сюда для потомков:

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

Теперь при запуске приведенного выше кода в качестве автономного приложения Flask http://localhost:5000/spam/отобразится Hello, world!.

В комментарии к другому ответу я сказал, что хотел бы сделать что-то вроде этого:

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/

Применим DispatcherMiddlewareк моему надуманному примеру:

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/
Яма для монахов
источник
«Итак, я считаю, что правильный ответ на этот вопрос: префикс должен быть настроен в реальном серверном приложении, которое вы используете после завершения разработки. Apache, nginx и т. Д.» Проблема в редиректах; если у вас есть префикс и вы не настраиваете его во Flask, то при перенаправлении вместо перехода к / yourprefix / path / to / url он просто переходит в / path / to / url. Есть ли способ настроить в nginx или Apache, какой должен быть префикс?
Джордан Рейтер,
Я бы, вероятно, сделал это, просто используя инструмент управления конфигурацией, такой как puppet или chef, и установил там префикс, а затем инструмент распространил изменение в файлы конфигурации, где оно должно быть. Я даже не собираюсь делать вид, что знаю, о чем говорю для apache или nginx. Поскольку этот вопрос / ответ относится к python, я рекомендую вам опубликовать свой сценарий как отдельный вопрос. Если вы это сделаете, не стесняйтесь ссылаться на вопрос здесь!
Monkpit 02
2

Другой совершенно другой способ - использовать точки монтирования в uwsgi.

Из документа о размещении нескольких приложений в одном процессе ( постоянная ссылка ).

В вашем uwsgi.iniвы добавляете

[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

Если вы не вызываете файл main.py, вам необходимо изменить как mountиmodule

Вы main.pyмогли бы выглядеть так:

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

И конфиг nginx (опять же для полноты):

server {
  listen 80;
  server_name example.com

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

Теперь вызов example.com/foo/barбудет отображаться /foo/barкак возвращенный flask url_for('bar'), поскольку он адаптируется автоматически. Таким образом, ваши ссылки будут работать без проблем с префиксом.

удачливый
источник
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
Пожалуйста, подумайте о добавлении объяснения.
jpp
1
Два хороших объяснения, которые я нашел, были в exploreflask и официальной документации
yuriploc
1

Мне понадобился аналогичный так называемый «контекстный корень». Я сделал это в файле conf в /etc/httpd/conf.d/, используя WSGIScriptAlias:

myapp.conf:

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

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

</VirtualHost>

Итак, теперь я могу получить доступ к своему приложению как: http: // localhost: 5000 / myapp

См. Руководство - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

dganesh2002
источник
1

Мое решение, в котором приложения flask и PHP сосуществуют с nginx и PHP5.6

СОХРАНИТЬ Flask в корне и PHP в подкаталогах

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

Добавить 1 строку

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

ИСПОЛЬЗУЙТЕ ВЛОЖЕННЫЕ МЕСТА для PHP и пусть FLASK останется в корне

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

ПРОЧИТАЙТЕ внимательно https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

Нам нужно понимать сопоставление местоположения (нет): если модификаторы отсутствуют, местоположение интерпретируется как совпадение префикса. Это означает, что указанное местоположение будет сопоставлено с началом URI запроса для определения совпадения. =: Если используется знак равенства, этот блок будет считаться совпадающим, если URI запроса точно соответствует указанному местоположению. ~: Если присутствует модификатор тильды, это местоположение будет интерпретировано как совпадение регулярного выражения с учетом регистра. ~ *: Если используются модификаторы тильды и звездочки, блок местоположения будет интерпретироваться как совпадение регулярного выражения без учета регистра. ^ ~: Если присутствует модификатор carat и тильда, и если этот блок выбран как лучшее совпадение нерегулярного выражения, сопоставление регулярного выражения не произойдет.

Порядок важен, исходя из описания "местоположения" nginx:

Чтобы найти местоположение, соответствующее заданному запросу, nginx сначала проверяет местоположения, определенные с помощью строк префикса (местоположения префиксов). Среди них выбирается и запоминается место с самым длинным совпадающим префиксом. Затем проверяются регулярные выражения в порядке их появления в файле конфигурации. Поиск регулярных выражений завершается при первом совпадении, и используется соответствующая конфигурация. Если совпадения с регулярным выражением не найдено, используется ранее запомненная конфигурация расположения префикса.

Это значит:

First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)
Джаянта
источник
1

Для людей, которые все еще борются с этим, первый пример работает, но полный пример здесь, если у вас есть приложение Flask, которое не находится под вашим контролем:

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,
    )
вишнугопал
источник