angle-up arrow-clockwise arrow-counterclockwise arrow-down-up arrow-left at calendar card-list chat check envelope folder house info-circle pencil people person person-plus phone plus question-circle search tag trash x

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

9 октября 2019 возле Peter

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

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/

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

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

Комментарии

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

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