Додайте префікс до всіх маршрутів Flask


98

У мене є префікс, який я хочу додати до кожного маршруту. Зараз я додаю константу до маршруту при кожному визначенні. Чи є спосіб зробити це автоматично?

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"

Відповіді:


75

Відповідь залежить від того, як ви обслуговуєте цю програму.

Встановлено всередині іншого контейнера 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.

Приклад належної установки програми

Якщо ви не впевнені, що означає перший абзац, погляньте на цей приклад програми із встановленою всередині колбою:

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'd, або якщо nginx виконує proxy_pass-ing-запити на під-кінцеву точку на ваш автономний uwsgi/ geventсервер, тоді ви можете:

  • Використовуйте План, на що вказує Мігель у своїй відповіді .
  • або скористайтеся DispatcherMiddlewareвід werkzeug(або PrefixMiddlewareвід відповіді su27 ), щоб встановити додаток на автономному сервері WSGI, який ви використовуєте. (Див . Приклад правильного підмонтування програми вище, щоб дізнатися про код, який потрібно використовувати).

@jknupp - дивлячись flask.Flask#create_url_adapterі, werkzeug.routing.Map#bind_to_environсхоже, це має працювати - як ти запускав код? (Додаток насправді потрібно встановити на піддоріжці в середовищі WSGI, url_forщоб повернути очікуване значення.)
Шон Віейра

Я запустив саме те, що ви написали, але додав app = Flask ( ім'я ) та app.run (debug = True)
jeffknupp

4
@jknupp - ось у чому проблема - вам потрібно буде фактично змонтувати додаток як підрозділ більшого додатка (все, що говорить WSGI). Я підготував приклад суті та оновив свою відповідь, щоб зрозуміти, що я припускаю підмонтоване середовище WSGI, а не окреме середовище WSGI за проксі-сервером, яке лише пересилає запити про підпуть.
Шон Вієйра,

3
Це працює, використовуючи DispatcherMiddlewareпідхід, при запуску колби самостійно. Здається, це не спрацьовує, коли ти біжиш позаду Гунікорна.
Джастін

1
Шлях піднятися на піддоріжку uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. докладно див. (документ uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
сьогодніпрацює

94

Ви можете розмістити свої маршрути в проекті:

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? Дякую!
аралар

4
Різниця полягає в тому, що наявність префікса URL-адреси у register_blueprintвиклику надає програмі свободу «монтувати» план де завгодно або навіть монтувати один і той же проект кілька разів на різні URL-адреси. Якщо ви вкладете префікс у сам проект, ви полегшите програму, але у вас буде менша гнучкість.
Мігель

Дякую!! Це дуже корисно. Мене бентежила очевидна надмірність, але я бачу компроміс між двома варіантами.
аралар

І насправді, я ніколи цього не пробував, але цілком ймовірно, що ви можете поєднувати префікси URL-адрес як у проекті, так і в додатку, із префіксом кулаку програми, за яким слід префікс проекту.
Мігель

4
Зверніть увагу, що необхідно зареєструвати план після оформлених функцій blueprint.route.
Квінт

53

Слід зазначити, що 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, якщо вам потрібно.

Це рішення дано суттю Larivact . Це APPLICATION_ROOTне для цієї роботи, хоча, схоже, буде. Це справді бентежить.


4
Дякуємо, що додали цю відповідь. Спробував інші рішення, опубліковані тут, але це єдине, що працювало для мене. A +++ Я розгорнуто на IIS за допомогою wfastcgi.py
sytech

"Це APPLICATION_ROOTне для цієї роботи" - тут я помилявся. Я хочу Blueprint«s url_prefixпараметр і APPLICATION_ROOTбув об'єднано за замовчуванням, так що я міг би APPLICATION_ROOTобласть видимості URLs для всього програми і url_prefixобласть дії URLs в межах APPLICATION_ROOTтільки для індивідуального плану. Зітхання
Монкпіт

Дивіться цей суть для прикладу того, що я намагався зробити, використовуючи APPLICATION_ROOT.
Monkpit

2
Якщо ви використовуєте gunicorn, SCRIPT_NAME уже підтримується. Встановіть його як змінну середовища або передайте як заголовок http: docs.gunicorn.org/en/stable/faq.html
blurrcat

1
Код, як він є, у мене не спрацював. Після деяких досліджень, я придумав це після іншого в __call__методі: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)використанняfrom 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()якщо перевірка не вдасться.


Це просто і ефективніше, ніж використання Blueprint. Дякую, що поділились!
HK хлопчик

5

Отже, я вважаю, що на це справедлива відповідь: префікс повинен бути налаштований у фактичному сервері, який ви використовуєте, коли розробка завершена. Apache, nginx тощо.

Однак якщо ви хочете, щоб це працювало під час розробки під час запуску програми 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, яким повинен бути префікс?
Йорданія Рейтер

Я б, мабуть, зробив це, просто скориставшись інструментом управління конфігурацією, таким як маріонетка чи шеф-кухар, і встановити там префікс, а потім попросити інструмент розповсюдити зміну у файлах конфігурації, куди йому потрібно перейти. Я навіть не збираюся робити вигляд, що я знаю, про що кажу про apache чи nginx. Оскільки це питання / відповідь був специфічним для python, я б рекомендував розмістити свій сценарій як окреме питання. Якщо ви зробите це, сміливо посилайтеся на питання тут!
Monkpit

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як повернений у колбі 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
Дві приємні пояснення, які я знайшов, були в Exploflask та офіційних документах
yuriploc

1

Мені знадобився подібний так званий "context-root". Я робив це у конф-файлі під /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


1

Моє рішення, коли програми flask та PHP співіснують nginx та PHP5.6

Зберігайте колбу в кореневій системі та 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 запиту точно відповідає вказаному розташуванню. ~: Якщо присутній модифікатор тильди, це місце буде інтерпретовано як збіг регулярних виразів, чутливий до регістру. ~ *: Якщо використовується модифікатор тильди та зірочки, блок розташування буде інтерпретований як збіг регулярних виразів, що не враховує регістр. ^ ~: Якщо присутній модифікатор карат і тильда, і якщо цей блок вибрано як найкращий збіг нерегулярних виразів, збіг регулярних виразів не відбудеться.

Важливо замовлення, з опису "розташування" 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,
    )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.