angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Два приложения, фронтенд и администратор, на одном домене с помощью диспетчерского программного обеспечения DispatcherMiddleware

Используя промежуточное программное обеспечение диспетчеров Веркзевга, мы объединяем два приложения в одно с отправкой на основе префикса в url.

9 октября 2019
post main image
unsplash.com/@ytcount

Flask Приложение, которое я пишу для запуска этого сайта, содержит весь код в одном "приложении". Я уже провел некоторую реорганизацию, так как хотел полностью разделить код фронтенда и код администрирования. Теперь пришло время для полного разделения, то есть сделать внешний интерфейс Flask приложением, а администратора другим приложением, работающим в одном и том же домене и находящимся в одном каталоге проекта. Поскольку мы не хотим дублировать код и данные, общие для обоих приложений, мы создаем "общую директорию", в которой находятся статические элементы, модель данных и т.д.

Промежуточное программное обеспечение диспетчера использует только один экземпляр гуникорна. Вероятно, есть и другие способы сделать это, например, иметь несколько случаев курильщика, каждый из которых служит приложению, но я не расследовал это дело.

Два приложения

У нас есть два приложения в одном каталоге проекта. Один из них называется front-end, а другой admin. Оба приложения работают на одном домене, и префикс 'admin' используется для отправки запросов либо в приложение front-end, либо в приложение admin. Предположим, порт 5000, затем запросим:

http://127.0.0.1:5000/

это отправить в фронтенд приложение и запросить:

http://127.0.0.1:5000/admin

Перед применением отправки приложения к самому приложению мы сначала хотим проверить, действительно ли оно работает. Для этого я создал виртуальную среду и установил и Gunicorn..:

pip3 install flask
pip3 install gunicorn

В виртуальной среде я создал следующую структуру каталогов. Это уже показывает файлы, которые я буду использовать:


│
├── flask_dispatch
│   ├── bin
│   ├── include
│   ├── lib
│   ├── lib64
│   ├── share
│   ├── pyvenv.cfg
│   │
│   ├── project
│   │   ├── app_admin
│   │   │   └── __init__.py
│   │   ├── app_frontend
│   │   │   └── __init__.py
│   │   ├── __init__.py
│   │   ├── run_admin.py
│   │   ├── run_both.py
│   │   ├── run_frontend.py
│   │   ├── wsgi_admin.py
│   │   ├── wsgi_both.py
│   │   └── wsgi_frontend.py

Есть два приложения: front-end и admin. Приложение для фронтенда находится в директории app_frontend. Он состоит только из одного файла __init__.py:

# app_frontend/__init__.py

from flask import Flask, request

def create_app():
    app_name = 'frontend'
    print('app_name = {}'.format(app_name))

    # create app
    app = Flask(__name__, instance_relative_config=True)

    @app.route("/")
    def hello():
        return 'Hello ' + app_name + '! request.url = ' + request.url
    
    # return app
    return app

Приложение для администрирования находится в директории app_admin. Он почти идентичен внешнему приложению. В обоих приложениях я жестко закодировал имя приложения, чтобы убедиться, что мы действительно видим нужное приложение:

# app_admin/__init__.py

from flask import Flask, request

def create_app():
    app_name = 'admin'
    print('app_name = {}'.format(app_name))

    # create app
    app = Flask(__name__, instance_relative_config=True)

    @app.route("/")
    def hello():
        return 'Hello ' + app_name + '! request.url = ' + request.url
    
    # return app
    return app

Запустите два приложения с сервером разработки.

Чтобы проверить, что они могут работать, я создал два файла, run_frontend.py и run_admin.py:

# run_frontend.py

# import frontend
from app_frontend import create_app as app_frontend_create_app
frontend = app_frontend_create_app()

if __name__ == '__main__':
    frontend.run(host='0.0.0.0')
# run_admin.py

# import admin
from app_admin import create_app as app_admin_create_app
admin = app_admin_create_app()

if __name__ == '__main__':
    admin.run(host='0.0.0.0')

В директории проекта введите следующую команду:

python3 run_frontend.py

Это запустит сервер разработки:

app_name = frontend
 * Serving Flask app "app_frontend" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Затем наведите курсор на ваш браузер:

http://127.0.0.1:5000/

и вы должны увидеть следующий текст:

Hello frontend! request.url = http://127.0.0.1:5000/

Чтобы проверить администратора, введите каталог проекта:

python3 run_admin.py

и навести на него браузер:

http://127.0.0.1:5000/

и тебе стоит посмотреть:

Hello admin! request.url = http://127.0.0.1:5000/

Это не было чем-то особенным, теперь у нас есть два рабочих приложения.

Запустите оба приложения с сервером Gunicorn WSGI.

Чтобы иметь возможность запускать их с Gunicorn сервером, я создал еще два файла:

# wsgi_frontend.py

from run_frontend import frontend
# wsgi_admin.py

from run_admin import admin

Теперь мы запустим Gunicorn сервер. Я не буду вдаваться в подробности, вы можете прочитать о опциях Gunicorn's конфигурации, включая рабочийPython каталог/путь. Самое главное здесь то, что мы должны начать использовать абсолютный путь.

/var/www/.../flask_dispatch/bin/gunicorn -b :5000 wsgi_frontend:frontend

Терминал должен показать:

[2019-10-09 11:07:31 +0200] [28073] [INFO] Starting gunicorn 19.9.0
[2019-10-09 11:07:31 +0200] [28073] [INFO] Listening at: http://0.0.0.0:5000 (28073)
[2019-10-09 11:07:31 +0200] [28073] [INFO] Using worker: sync
[2019-10-09 11:07:31 +0200] [28076] [INFO] Booting worker with pid: 28076
app_name = frontend

Наведите курсор на ваш браузер:

http://127.0.0.1:5000/

и тебе стоит посмотреть:

Hello frontend! request.url = http://127.0.0.1:5000/

Вы можете сделать то же самое для администратора. Ничего особенного, теперь у нас есть два приложения, которые можно обслуживать с помощью Gunicorn.

Диспетчеризация приложений с сервером разработки

Используя диспетчерскую программу Werkzeug's DispatcherMiddleware, очень легко объединить оба приложения в одно, которое может обслуживаться HTTP-сервером gunicorn WSGI. Это описано в Flask документе "Отправка заявления", см. ссылки ниже. Обратите внимание, что DispatcherMiddleware был перемещен из werkzeug.wsgi в werkzeug.middleware.dispatcher с версии Werkzeug 0.15. Мы снова хотим протестировать его сначала с помощью 's сервера Flaskразработки. Для этого я создал файл run_both.py:

# run_both.py

from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple

# import frontend
from app_frontend import create_app as app_frontend_create_app
frontend = app_frontend_create_app()

# import admin
from app_admin import create_app as app_admin_create_app
admin = app_admin_create_app()

# merge
application = DispatcherMiddleware(
    frontend, {
    '/admin': admin
})

if __name__ == '__main__':
    run_simple(
        hostname='localhost',
        port=5000,
        application=application,
        use_reloader=True,
        use_debugger=True,
        use_evalex=True)

Объект DispatcherMiddleware не имеет метода 'run'. Вместо этого мы можем использовать 'run_simple'.

После запуска сервера разработки:

python3 run_both.py

ты бы видел это:

app_name = frontend
app_name = admin
 * Running on http://localhost:5000/ (Press CTRL+C to quit)
 * Restarting with stat
app_name = frontend
app_name = admin
 * Debugger is active!
 * Debugger PIN: 136-162-082

На что указывает наш браузер:

http://127.0.0.1:5000/

мы видим:

Hello frontend! request.url = http://127.0.0.1:5000/

И когда мы направим наш браузер:

http://127.0.0.1:5000/admin/

мы видим:

Hello admin! request.url = http://127.0.0.1:5000/admin/

Отлично, у нас оба приложения работают на одном домене, 127.0.0.0.1, и префикс посылает запрос либо на приложение front-end, либо на приложение admin.

Диспетчеризация приложений с Gunicorn сервером

Для запуска обоих приложений с Gunicorn сервером я создал файл wsgi_both.py:

# wsgi_both.py

from run_both import application

После запуска Gunicorn сервера:

/var/www/.../flask_dispatch/bin/gunicorn -b :5000 wsgi_both:application

терминал показывает:

[2019-10-09 11:17:25 +0200] [28508] [INFO] Starting gunicorn 19.9.0
[2019-10-09 11:17:25 +0200] [28508] [INFO] Listening at: http://0.0.0.0:5000 (28508)
[2019-10-09 11:17:25 +0200] [28508] [INFO] Using worker: sync
[2019-10-09 11:17:25 +0200] [28511] [INFO] Booting worker with pid: 28511
app_name = frontend
app_name = admin

Теперь снова, указывая на браузер:

http://127.0.0.1:5000/

...и это видно..:

Hello frontend! request.url = http://127.0.0.1:5000/

и указать на него в браузере:

http://127.0.0.1:5000/admin/

...и это видно..:

Hello admin! request.url = http://127.0.0.1:5000/admin/

script_root и пути к нему

Важно понимать, что при вызове URL-администратора диспетчером сценарий_рут (request.script_root) приложения меняется с пустого на '/admin'. Также путь (request.path) не включает в себя '/admin'.

(См. ссылку ниже: ) "Путь - это путь внутри приложения, по которому выполняется маршрутизация. script_root находится вне вашего приложения, но обрабатывается url_for'.

Потому что обычно мы используем url_for() только для генерации url не будет никаких проблем. Однако, если вы используете Flask URL путь, например request.path, .static_url_path, в вашем приложении, вы должны добавить его скриптом_root. Пример использования пути в шаблоне, ранее:

    {{ request.path }}

после

    {{ request.script_root + request.path }}

Если вы не знаете, что делаете, старайтесь избегать использования пути Flask url непосредственно в коде и использовать url_for().

Совместное использование статических элементов

Сообщения в блоге могут иметь одно или несколько изображений. Фронтенд обслуживает изображения из своей статической папки. Администратор содержит функции для загрузки изображения и назначения изображения для записи в блоге. Чтобы все было проще, я решил переместить статическую папку в папку, где находятся папки app_frontend и app_admin, чтобы она не только была общей, но и выглядела общей.

Единственное, что нам нужно изменить, чтобы сделать эту работу, это передать папку static_folder при создании Flask объекта:

    app = Flask(__name__, 
        instance_relative_config=True,
        static_folder='/home/flask/project/shared/static')

Это делается только в целях развития.

Обмен константами и моделью данных

Никогда не дублируйте код. Константы и модель данных являются одними из первых, что мы разделяем между front-end и admin. Мы поместили app_constants.py и model.py в общий каталог. После этого мы заменяем ссылки на них в файлах приложений:

from shared.app_constants import *
from shared.models import <classes to import>

Обмен чертежами

Ряд чертежей может быть предоставлен для совместного использования фронтендом и администратором. Один из них - авторский чертеж, используемый для входа и выхода из системы. Другой - это страница Blueprint, на которой отображаются страницы. Обмениваться чертежами очень просто, мы просто создаем каталог 'shared/'blueprintsи помещаем его blueprints сюда. Во фронтенде и функции create_app() администратора в __init__.py мы меняем:

    from .blueprints.auth.views import auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/<lang_code>')

к..:

    from shared.blueprints.auth.views import auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/<lang_code>')

Функции просмотра в blueprints вызове, что означает, что мы должны убедиться, что шаблоны существуют как во внешнем интерфейсе, так и в администраторе. Позже мы также можем изменить каталог шаблонов для этих разделяемых файлов .

Проблемы с регистратором

Я использую логгер как во внешнем, так и в административном приложениях, фронтенд приложения записывает журналы в файл app_frontend.log, а админ приложения записывает в файл app_admin.log.

После использования DispatcherMiddleware выяснилось, что любое сообщение журнала всегда записывалось в оба файла журнала, сообщения из приложения front-end записывались в app_frontend.log и app_admin.log, а сообщения из приложения admin - в app_frontend.log и app_admin.log.

Похоже, это связано с тем, что app.logger всегда имеет имя flask.app. Хотя есть способы обойти это, лучше перейти Flask на версию 1.1 (или 1.1.1.1), где app.logger теперь имеет то же имя, что и app.name. После обновления регистрация событий велась отдельно для фронтенда и администратора.

Статический каталог для staging и производства

Я использую с Nginx обратной связью. Для страниц это работает нормально, но статический каталог не был корректно отображен. Изображения не отображались в режиме администрирования, т.е. на url '/admin'. Я не знаю другого способа, кроме добавления другой директивы location для Nginx компенсации /admin. Так что раньше:

  location /static/ {
    alias /var/www/.../static/;
  }

И после использования программы Диспетчер-Миддлворд:

  location /admin/static/ {
    alias /var/www/.../static/;
  }

  location /static/ {
    alias /var/www/.../static/;
  }

Резюме

Использование диспетчерской программы Werkzeug's DispatcherMiddleware упрощает запуск двух приложений на одном домене. Для разработки, вероятно, было бы неплохо использовать два сервера Flask разработки, один для внешнего приложения, а другой для администрирования.

Первые шаги по реализации этого в моем приложении дали мало того, что нужно было исправить. Я переместил папку приложения в app_frontend и скопировал app_frontend в app_admin. В режиме разработки необходимо было изменить имя хоста='localhost' на '0.0.0.0.0', но это проблема при работе с доккером. Затем мне пришлось изменить имена cookie файлов сессии, чтобы избежать конфликтов, удалить '/admin' из url_prefix вblueprint функции register_, переименовать файлы журнала и изменить расположение моделей, 'с моделей для импорта приложений' на 'модели для импорта приложений' из app_frontend'. Многие изменения могут быть сделаны с помощью рекурсивной замены, см. раздел "regexxer" для Linux/Ubuntu.

Потом он бежал. Всегда ли два приложения, работающие на одном домене и написанные самостоятельно, полностью разделены? Я так не думаю. Я представил общую папку, куда мы поместили статическую папку. Общая папка также используется для обмена моделью данных и некоторым кодом между внешним интерфейсом и администратором.

Время удалить код администратора из внешнего интерфейса и код внешнего интерфейса от администратора и поделиться тем, что должно быть общим!

Ссылки / кредиты

Add a prefix to all Flask routes
https://stackoverflow.com/questions/18967441/add-a-prefix-to-all-flask-routes

Application Dispatching
https://flask.palletsprojects.com/en/1.1.x/patterns/appdispatch/

DispatcherMiddleware with different loggers per app in flask 1.0 #2866
https://github.com/pallets/flask/issues/2866

Flask 1.1 Released
https://palletsprojects.com/blog/flask-1-1-released/

How do I run multiple python apps in 1 command line under 1 WSGI app?
https://www.slideshare.net/onceuponatimeforever/how-do-i-run-multiple-python-apps-in-1-command-line-under-1-wsgi-app

How to implement Flask Application Dispatching by Path with WSGI?
https://stackoverflow.com/questions/30906489/how-to-implement-flask-application-dispatching-by-path-with-wsgi

request.path doesn't include request.script_root when running under a subdirectory #3032
https://github.com/pallets/flask/issues/3032

Serving WSGI Applications
https://werkzeug.palletsprojects.com/en/0.15.x/serving/

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии (3)

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.

avatar

Very helpful. Thanks.
One thing:
http://127.0.0.1:8000/en/blog/two-flask-apps-frontend-and-admin-on-one-domain-using-dispatchermiddleware
should be
https://www.peterspython.com/en/blog/two-flask-apps-frontend-and-admin-on-one-domain-using-dispatchermiddleware

avatar
user59176594 3 года назад

I would like to integrate several DashApps into a website running under Flask (the html frame comes from Flask, the DashApp should be embedded in the html frame). I want to avoid the iframe method.
The DashApps should not create their own Flask instance, as they normally do, but should be transferred to the Flask instance of the running website.
Can that be done with the Dispatcher Middleware?

avatar
peter 3 года назад user59176594

I do not think this is possible (without iframes) because DispatcherMiddleware is used for a different purpose. Did you check this information on the plotly site: https://dash.plotly.com/integrating-dash