Сообщить об ошибке.

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

Содержание:


Что такое диспетчеризация нескольких приложений.

Диспетчеризация приложений построенных на фреймворке Flask - это процесс объединения нескольких приложений Flask на уровне WSGI. Можно комбинировать не только приложения Flask, но и любое приложение WSGI. Такое поведение позволяет запускать приложения Django и Flask в одном интерпретаторе бок о бок. Полезность этого зависит от того, как приложения взаимодействуют друг с другом внутри.

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

Каждый из приведенных ниже примеров приводит к созданию объекта приложения, который запускается с любым сервером WSGI. Для разработки Werkzeug предоставляет сервер через werkzeug.serving.run_simple():

from werkzeug.serving import run_simple
run_simple('localhost', 5000, application, use_reloader=True)

Обратите внимание, что run_simple() не предназначен для использования на боевом сервере.

Чтобы использовать интерактивный отладчик, функции отладки должны быть включены как в экземпляре приложения, так и в аргументах run_simple(). Смотрим пример:

from flask import Flask
from werkzeug.serving import run_simple

app = Flask(__name__)
# включаем режим отладки в приложении
app.debug = True

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    # передаем аргумент use_debugger=True
    run_simple('localhost', 5000, app,
               use_reloader=True, use_debugger=True, use_evalex=True)

Объединение приложений на уровне модуля werkzeug.

Если есть полностью разделенные приложения и необходимо, чтобы они работали рядом друг с другом в одном процессе интерпретатора Python, то можно воспользоваться классом werkzeug.wsgi.DispatcherMiddleware. Идея здесь в том, что каждое приложение Flask является допустимым приложением WSGI, и они объединяются промежуточным программным обеспечением (диспетчером) в более крупное приложение, которое задействуется на основе префикса URL-адреса.

Например, можно запустить основное приложение по адресу / и внутренний интерфейс по URL-адресу /backend:

from werkzeug.middleware.dispatcher import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend': backend
})

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

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

Очень распространенный пример - создание приложений для каждого поддомена. Например, есть веб-сервер, который настроен для проксирования всех запросов, всех поддоменов в приложение Flask, а в приложении используется информация о поддомене для создания экземпляров приложения, специфичных для пользователя. После того, как веб-сервер настроен для прослушивания всех поддоменов, можно использовать простой WSGI для создания динамического приложения Flask.

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

from threading import Lock

class SubdomainDispatcher(object):

    def __init__(self, domain, create_app):
        self.domain = domain
        self.create_app = create_app
        self.lock = Lock()
        self.instances = {}

    def get_application(self, host):
        host = host.split(':')[0]
        assert host.endswith(self.domain), 'Configuration error'
        subdomain = host[:-len(self.domain)].rstrip('.')
        with self.lock:
            app = self.instances.get(subdomain)
            if app is None:
                app = self.create_app(subdomain)
                self.instances[subdomain] = app
            return app

    def __call__(self, environ, start_response):
        app = self.get_application(environ['HTTP_HOST'])
        return app(environ, start_response)

Затем этот диспетчер можно использовать следующим образом:

from myapplication import create_app, get_user_for_subdomain
from werkzeug.exceptions import NotFound

def make_app(subdomain):
    user = get_user_for_subdomain(subdomain)
    if user is None:
    # Если для этого поддомена нет пользователя, то все равно нужно 
    # возвратить приложение WSGI, которое обрабатывает этот запрос.
    # Затем можно просто вернуть исключение `NotFound()` как приложение, 
    # которое будет отображать страницу 404 по умолчанию.
    # Можно также перенаправить пользователя на главную страницу, 
    # а затем вернуть `NotFound()`

    # в противном случае создается приложение для конкретного пользователя
    return create_app(user)

application = SubdomainDispatcher('example.com', make_app)

Запуск приложения по префиксу URL-адреса.

Запуск приложения по префиксу URL-адреса очень похожа. Для этого, вместо просмотра заголовка Host, просто будем просматривать текущий путь запроса до первой косой черты:

from threading import Lock
from werkzeug.wsgi import pop_path_info, peek_path_info

class PathDispatcher(object):

    def __init__(self, default_app, create_app):
        self.default_app = default_app
        self.create_app = create_app
        self.lock = Lock()
        self.instances = {}

    def get_application(self, prefix):
        with self.lock:
            app = self.instances.get(prefix)
            if app is None:
                app = self.create_app(prefix)
                if app is not None:
                    self.instances[prefix] = app
            return app

    def __call__(self, environ, start_response):
        app = self.get_application(peek_path_info(environ))
        if app is not None:
            pop_path_info(environ)
        else:
            app = self.default_app
        return app(environ, start_response)

Разница между запуском приложения на основе URL-адреса и поддомена заключается в том, что если функция make_app() возвращает None, то запускается приложение default_app:

from myapplication import create_app, default_app, get_user_for_prefix

def make_app(prefix):
    user = get_user_for_prefix(prefix)
    if user is not None:
        return create_app(user)

application = PathDispatcher(default_app, make_app)