애플리케이션 디스패칭

애플리케이션 디스패칭은 WSGI 수준에서 여러 Flask 애플리케이션을 결합하는 과정을 말합니다. Flask 애플리케이션뿐만 아니라 모든 WSGI 애플리케이션을 결합할 수 있습니다. 예를 들어, Django와 Flask 애플리케이션을 동일한 인터프리터에서 나란히 실행할 수도 있습니다. 이러한 방식이 가지는 유용성은, 애플리케이션이 내부적으로 어떻게 작동하는지에 따라 달라집니다.

이 기능은 Large Applications as Packages 방식과 근본적으로 다릅니다. 애플리케이션 디스패칭은 서로 완전히 독립된 동일하거나 다른 Flask 애플리케이션들을 실행하는 데 사용됩니다. 이러한 Flask 애플리케이션들은 각각 고유한 설정을 가지며, WSGI 수준에서 요청이 디스패치됩니다.

이 문서와 함께 작업하기

아래의 기법과 예제는 모두 WSGI 서버로 실행할 수 있는 application 객체를 생성합니다. 개발 중에는 flask run 명령을 사용하여 개발 서버를 시작할 수 있습니다. 프로덕션 환경에 대한 자세한 내용은 :doc:`/deploying/index`를 참고하세요.

from flask import Flask

app = Flask(__name__)

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

애플리케이션 결합하기

완전히 분리된 애플리케이션을 동일한 Python 인터프리터 프로세스에서 함께 작동하게 하려면 :class:`werkzeug.wsgi.DispatcherMiddleware`를 사용할 수 있습니다. 이 접근법의 핵심은 각각의 Flask 애플리케이션들이 유효한 WSGI 애플리케이션들이며, 디스패치를 담당하는 미들웨어가 접두사를 기반으로 각각의 Flask 애플리케이션들을 더 큰 애플리케이션으로 디스패치한다는 점입니다.

예를 들어, 메인 애플리케이션은 / 경로에서 실행하고, 백엔드 인터페이스는 /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
})

서브도메인에 의한 디스패칭

때로는 동일한 애플리케이션을 여러 개의 다른 설정으로 실행하고 싶을 때가 있습니다. 애플리케이션이 함수 내에서 생성되고, 해당 함수를 호출하여 새로운 인스턴스를 만들 수 있다면, 이를 구현하는 것은 매우 간단합니다. 이를 위해 애플리케이션을 함수에서 새 인스턴스를 생성할 수 있도록 설계하려면 Application Factories 패턴을 참고하세요.

가장 일반적인 예로는 서브도메인별로 애플리케이션을 생성하는 경우가 있습니다. 예를 들어, 웹 서버를 설정하여 모든 서브도메인 요청을 하나의 애플리케이션으로 디스패칭한 다음, 서브도메인 정보를 사용하여 사용자별 애플리케이션 인스턴스를 생성할 수 있습니다. 서버가 모든 서브도메인을 수신하도록 설정한 후에는 간단한 WSGI 애플리케이션을 사용하여 동적으로 애플리케이션을 생성할 수 있습니다.

이와 같은 작업을 추상화하는 데 적합한 수준은 WSGI 계층입니다. 요청을 검사하고 이를 Flask 애플리케이션에 위임하는 WSGI 애플리케이션을 작성합니다. 해당 애플리케이션이 아직 존재하지 않을 경우, 동적으로 생성하고 이를 저장합니다.

from threading import Lock

class SubdomainDispatcher:

    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:
        # if there is no user for that subdomain we still have
        # to return a WSGI application that handles that request.
        # We can then just return the NotFound() exception as
        # application which will render a default 404 page.
        # You might also redirect the user to the main page then
        return NotFound()

    # otherwise create the application for the specific user
    return create_app(user)

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

경로에 의한 디스패칭

URL의 경로를 기반으로 디스패칭하는 방식도 매우 유사합니다. 서브도메인을 판별하기 위해 Host 헤더를 참조하는 대신, 요청 경로를 첫 번째 슬래시(/)까지 검사합니다.

from threading import Lock
from wsgiref.util import shift_path_info

class PathDispatcher:

    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:
            shift_path_info(environ)
        else:
            app = self.default_app
        return app(environ, start_response)

def _peek_path_info(environ):
    segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1)
    if segments:
        return segments[0]

    return None

서브도메인에 의한 디스패칭과 이 방식의 큰 차이점은, 경로에 의한 디스패칭 방식에서는 생성 함수가 ``None``을 반환할 경우 다른 애플리케이션으로 요청을 넘긴다는 점입니다.

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)