[Final] PEP 333 - Python Web Server Gateway Interface v1.0

원문 링크: PEP 333 - Python Web Server Gateway Interface v1.0

상태: Final 유형: Informational 작성일: 07-Dec-2003

PEP 333 – Python Web Server Gateway Interface v1.0

서문

참고: Python 3.x를 지원하며 커뮤니티의 정오표, 추가 사항 및 명확화가 포함된 업데이트된 사양은 대신 PEP 3333을 참조하십시오.

초록

이 문서는 웹 서버와 Python 웹 애플리케이션 또는 프레임워크 간의 표준 인터페이스를 제안하며, 다양한 웹 서버에서 웹 애플리케이션의 이식성을 촉진하는 것을 목표로 합니다.

배경 및 목표

Python은 현재 Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Web 등 다양한 웹 애플리케이션 프레임워크를 자랑합니다. 이러한 다양한 선택지는 새로운 Python 사용자들에게 문제를 야기할 수 있는데, 일반적으로 웹 프레임워크의 선택이 사용할 수 있는 웹 서버의 선택을 제한하며 그 반대도 마찬가지이기 때문입니다.

대조적으로, Java는 많은 웹 애플리케이션 프레임워크를 가지고 있지만, Java의 “서블릿 (servlet)” API 덕분에 어떤 Java 웹 애플리케이션 프레임워크로 작성된 애플리케이션이든 서블릿 API를 지원하는 모든 웹 서버에서 실행될 수 있습니다.

Python 웹 애플리케이션을 위한 웹 서버(Python으로 작성된 서버(예: Medusa), Python을 내장한 서버(예: mod_python), 또는 게이트웨이 프로토콜(예: CGI, FastCGI 등)을 통해 Python을 호출하는 서버)에서 이러한 API가 널리 사용 가능해진다면, 프레임워크 선택과 웹 서버 선택이 분리되어 사용자는 자신에게 적합한 조합을 자유롭게 선택할 수 있게 될 것입니다. 또한 프레임워크 및 서버 개발자들은 자신이 선호하는 전문 분야에 집중할 수 있게 될 것입니다.

따라서 이 PEP는 웹 서버와 웹 애플리케이션 또는 프레임워크 간의 Python Web Server Gateway Interface (WSGI)라는 간단하고 보편적인 인터페이스를 제안합니다.

WSGI 사양의 존재만으로는 Python 웹 애플리케이션을 위한 서버 및 프레임워크의 현재 상태를 해결할 수 없습니다. 어떠한 효과가 있으려면 서버 및 프레임워크 작성자와 관리자가 실제로 WSGI를 구현해야 합니다.

그러나 기존 서버나 프레임워크 중 WSGI를 지원하는 것이 없으므로, WSGI 지원을 구현하는 작성자에게는 즉각적인 보상이 거의 없습니다. 따라서 WSGI는 구현하기 쉬워야 하며, 작성자의 초기 인터페이스 투자가 합리적으로 낮아야 합니다.

이 인터페이스의 양쪽인 서버와 프레임워크 측면에서 구현의 단순성은 WSGI 인터페이스의 유용성에 절대적으로 중요하며, 따라서 모든 설계 결정의 주요 기준이 됩니다.

참고: 프레임워크 작성자를 위한 구현의 단순성이 웹 애플리케이션 작성자를 위한 사용 용이성과 같은 것은 아닙니다. WSGI는 프레임워크 작성자에게 “불필요한 기능 없는 (no frills)” 인터페이스를 제공합니다. 이는 응답 객체 및 쿠키 처리와 같은 부가 기능이 기존 프레임워크의 이러한 문제 처리 방식에 방해가 될 수 있기 때문입니다. 다시 말해, WSGI의 목표는 기존 서버와 애플리케이션 또는 프레임워크 간의 쉬운 상호 연결을 촉진하는 것이지, 새로운 웹 프레임워크를 만드는 것이 아닙니다.

또한 이 목표는 WSGI가 현재 배포된 Python 버전에서 아직 사용할 수 없는 것을 요구하지 않도록 합니다. 따라서 새로운 표준 라이브러리 모듈은 이 사양에서 제안되거나 요구되지 않으며, WSGI의 어떤 부분도 Python 2.2 버전보다 높은 버전을 요구하지 않습니다. (다만, 향후 Python 버전이 표준 라이브러리에서 제공되는 웹 서버에 이 인터페이스에 대한 지원을 포함하는 것이 좋을 것입니다.)

기존 및 미래의 프레임워크와 서버에 대한 구현 용이성 외에도, 요청 전처리기, 응답 후처리기 및 기타 WSGI 기반 “미들웨어 (middleware)” 구성 요소를 쉽게 생성할 수 있어야 합니다. 이러한 구성 요소는 포함하는 서버에게는 애플리케이션처럼 보이고, 포함된 애플리케이션에게는 서버 역할을 합니다.

미들웨어가 간단하고 견고하며, WSGI가 서버와 프레임워크에서 널리 사용 가능하다면, 완전히 새로운 종류의 Python 웹 애플리케이션 프레임워크, 즉 느슨하게 결합된 WSGI 미들웨어 구성 요소로 구성된 프레임워크가 가능해집니다. 실제로 기존 프레임워크 작성자들은 자신의 프레임워크의 기존 서비스를 이러한 방식으로 제공하도록 리팩토링하여, 모놀리식 프레임워크보다는 WSGI와 함께 사용되는 라이브러리에 더 가깝게 만들 수 있습니다. 이렇게 되면 애플리케이션 개발자들은 단일 프레임워크의 모든 장단점에 얽매이지 않고 특정 기능에 대해 “최고의 구성 요소 (best-of-breed)”를 선택할 수 있게 될 것입니다.

물론, 이 글을 쓰는 시점에는 그 날이 아직 멀었을 것입니다. 그 동안 WSGI의 충분한 단기 목표는 모든 프레임워크를 모든 서버와 함께 사용할 수 있도록 하는 것입니다.

마지막으로, WSGI의 현재 버전은 웹 서버 또는 서버 게이트웨이와 함께 애플리케이션을 “배포 (deploy)”하는 특정 메커니즘을 규정하지 않는다는 점을 언급해야 합니다. 현재로서는 이는 서버 또는 게이트웨이에 의해 구현 정의될 수밖에 없습니다. 충분한 수의 서버와 프레임워크가 WSGI를 구현하여 다양한 배포 요구 사항에 대한 현장 경험을 제공한 후에는 WSGI 서버 및 애플리케이션 프레임워크에 대한 배포 표준을 설명하는 또 다른 PEP를 만드는 것이 합리적일 수 있습니다.

사양 개요

WSGI 인터페이스는 “서버 (server)” 또는 “게이트웨이 (gateway)” 측과 “애플리케이션 (application)” 또는 “프레임워크 (framework)” 측의 두 가지 측면을 가집니다. 서버 측은 애플리케이션 측에서 제공하는 호출 가능한 (callable) 객체를 호출합니다. 해당 객체가 제공되는 구체적인 방법은 서버 또는 게이트웨이에 달려 있습니다. 일부 서버 또는 게이트웨이는 애플리케이션 배포자가 서버 또는 게이트웨이의 인스턴스를 생성하고 애플리케이션 객체를 제공하기 위한 짧은 스크립트를 작성해야 할 것이라고 가정합니다. 다른 서버 및 게이트웨이는 구성 파일 또는 기타 메커니즘을 사용하여 애플리케이션 객체를 가져올 위치를 지정할 수 있습니다.

“순수한 (pure)” 서버/게이트웨이 및 애플리케이션/프레임워크 외에도, 이 사양의 양쪽을 모두 구현하는 “미들웨어 (middleware)” 구성 요소를 생성하는 것도 가능합니다. 이러한 구성 요소는 포함하는 서버에게는 애플리케이션 역할을 하고, 포함된 애플리케이션에게는 서버 역할을 하며, 확장된 API, 콘텐츠 변환, 탐색 및 기타 유용한 기능을 제공하는 데 사용될 수 있습니다.

이 사양 전체에서 “호출 가능한 객체 (a callable)”는 “함수, 메서드, 클래스, 또는 __call__ 메서드를 가진 인스턴스”를 의미합니다. 호출 가능한 객체를 구현하는 서버, 게이트웨이 또는 애플리케이션은 자신들의 필요에 맞는 적절한 구현 기술을 선택할 수 있습니다. 반대로, 호출 가능한 객체를 호출하는 서버, 게이트웨이 또는 애플리케이션은 어떤 종류의 호출 가능한 객체가 제공되었는지에 대해 의존해서는 안 됩니다. 호출 가능한 객체는 호출만 되어야 하며, 내부를 검사해서는 안 됩니다.

애플리케이션/프레임워크 측

애플리케이션 객체는 단순히 두 개의 인수를 받는 호출 가능한 객체입니다. “객체”라는 용어는 실제 객체 인스턴스를 요구하는 것으로 오해되어서는 안 됩니다. 함수, 메서드, 클래스, 또는 __call__ 메서드를 가진 인스턴스 모두 애플리케이션 객체로 사용할 수 있습니다. 애플리케이션 객체는 한 번 이상 호출될 수 있어야 합니다. (CGI를 제외한) 거의 모든 서버/게이트웨이가 이러한 반복적인 요청을 할 것이기 때문입니다.

(참고: 이를 “애플리케이션” 객체라고 부르지만, 이는 애플리케이션 개발자가 WSGI를 웹 프로그래밍 API로 사용할 것이라는 의미로 해석되어서는 안 됩니다! 애플리케이션 개발자들은 기존의 고수준 프레임워크 서비스를 사용하여 애플리케이션을 계속 개발할 것이라고 가정합니다. WSGI는 프레임워크 및 서버 개발자를 위한 도구이며, 애플리케이션 개발자를 직접 지원하기 위한 것이 아닙니다.)

다음은 두 가지 예제 애플리케이션 객체입니다. 하나는 함수이고 다른 하나는 클래스입니다.

def simple_app(environ, start_response):
    """가장 간단한 애플리케이션 객체"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

class AppClass:
    """동일한 출력을 생성하지만 클래스를 사용합니다.
    ('AppClass'가 여기서는 "애플리케이션"이므로, 호출하면 'AppClass'의 인스턴스를 반환하며,
    이것이 사양에서 요구하는 "애플리케이션 호출 가능 객체"의 반복 가능한 반환 값입니다.
    만약 'AppClass'의 *인스턴스*를 애플리케이션 객체로 사용하고 싶다면,
    애플리케이션을 실행하기 위해 호출될 '__call__' 메서드를 구현해야 하며,
    서버 또는 게이트웨이에서 사용할 인스턴스를 생성해야 합니다.
    """
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

서버/게이트웨이 측

서버 또는 게이트웨이는 HTTP 클라이언트로부터 애플리케이션으로 전달되는 각 요청에 대해 애플리케이션 호출 가능 객체를 한 번 호출합니다. 예를 들어, 애플리케이션 객체를 인수로 받는 함수로 구현된 간단한 CGI 게이트웨이가 있습니다. 이 간단한 예제는 제한된 오류 처리를 가지고 있음을 유의하십시오. 기본적으로 처리되지 않은 예외는 sys.stderr로 덤프되고 웹 서버에 의해 로깅됩니다.

import os, sys

def run_with_cgi(application):
    environ = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1, 0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True

    if environ.get('HTTPS', 'off') in ('on', '1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set = []
    headers_sent = []

    def write(data):
        if not headers_set:
            raise AssertionError("write() before start_response()")
        elif not headers_sent:
            # 첫 번째 출력 전에 저장된 헤더를 보냅니다.
            status, response_headers = headers_sent[:] = headers_set
            sys.stdout.write('Status: %s\r\n' % status)
            for header in response_headers:
                sys.stdout.write('%s: %s\r\n' % header)
            sys.stdout.write('\r\n')
        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # 헤더가 이미 전송되었으면 원래 예외를 다시 발생시킵니다.
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None # 순환 참조 방지
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]
        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # 본문이 나타나기 전에는 헤더를 보내지 않습니다.
                write(data)
        if not headers_sent:
            write('')   # 본문이 비어있으면 지금 헤더를 보냅니다.
    finally:
        if hasattr(result, 'close'):
            result.close()

미들웨어: 양쪽 역할을 수행하는 구성 요소

단일 객체가 일부 애플리케이션에 대해서는 서버 역할을 하면서, 동시에 일부 서버에 대해서는 애플리케이션 역할을 할 수 있습니다. 이러한 “미들웨어 (middleware)” 구성 요소는 다음과 같은 기능을 수행할 수 있습니다.

  • 대상 URL에 따라 요청을 다른 애플리케이션 객체로 라우팅하고, environ을 그에 따라 재작성합니다.
  • 동일한 프로세스에서 여러 애플리케이션 또는 프레임워크가 나란히 실행되도록 허용합니다.
  • 네트워크를 통해 요청과 응답을 전달하여 로드 밸런싱 및 원격 처리를 수행합니다.
  • XSL 스타일시트 적용과 같은 콘텐츠 후처리를 수행합니다.

일반적으로 미들웨어의 존재는 인터페이스의 “서버/게이트웨이” 측과 “애플리케이션/프레임워크” 측 모두에게 투명하며, 특별한 지원을 요구하지 않습니다. 애플리케이션에 미들웨어를 통합하려는 사용자는 미들웨어 구성 요소를 마치 애플리케이션인 것처럼 서버에 제공하고, 미들웨어 구성 요소를 마치 서버인 것처럼 애플리케이션을 호출하도록 구성하기만 하면 됩니다. 물론, 미들웨어가 감싸는 “애플리케이션”은 실제로 또 다른 애플리케이션을 감싸는 또 다른 미들웨어 구성 요소일 수 있으며, 이러한 방식으로 “미들웨어 스택”이라고 불리는 것을 생성합니다.

대부분의 경우 미들웨어는 WSGI의 서버 및 애플리케이션 측의 제한 사항과 요구 사항을 모두 준수해야 합니다. 그러나 일부 경우에는 미들웨어에 대한 요구 사항이 “순수한” 서버 또는 애플리케이션보다 더 엄격하며, 이러한 점들은 사양에서 언급될 것입니다.

다음은 Joe Strout의 piglatin.py를 사용하여 text/plain 응답을 피그 라틴어로 변환하는 미들웨어 구성 요소의 (우스개 소리 같은) 예입니다. (참고: “실제” 미들웨어 구성 요소는 콘텐츠 유형을 확인하는 더 견고한 방법을 사용하고 콘텐츠 인코딩도 확인해야 할 것입니다. 또한 이 간단한 예제는 단어가 블록 경계에 걸쳐 분할될 수 있는 가능성을 무시합니다.)

from piglatin import piglatin

class LatinIter:
    """반복된 출력을 피그 라틴어로 변환합니다. 변환이 가능한 경우에만.
    "변환 가능성"은 애플리케이션이 첫 번째 비어 있지 않은 문자열을 생성할 때까지 변경될 수 있으므로,
    'transform_ok'는 변경 가능한 참 값이어야 합니다.
    """
    def __init__(self, result, transform_ok):
        if hasattr(result, 'close'):
            self.close = result.close
        self._next = iter(result).next
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def next(self):
        if self.transform_ok:
            return piglatin(self._next())
        else:
            return self._next()

class Latinator:
    # 기본적으로 출력은 변환하지 않습니다.
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        transform_ok = []
        def start_latin(status, response_headers, exc_info=None):
            # 반복 호출일 경우를 대비하여 ok 플래그를 재설정합니다.
            del transform_ok[:]
            for name, value in response_headers:
                if name.lower() == 'content-type' and value == 'text/plain':
                    transform_ok.append(True)
                    # Content-Length가 있으면 제거합니다. 그렇지 않으면 잘못됩니다.
                    response_headers = [(name, value) for name, value in response_headers
                                        if name.lower() != 'content-length']
                    break
            write = start_response(status, response_headers, exc_info)
            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ, start_latin), transform_ok)

# foo_app을 Latinator의 제어 하에 예제 CGI 게이트웨이를 사용하여 실행합니다.
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))

사양 세부 사항

애플리케이션 객체는 두 개의 위치 인수를 받아야 합니다. 설명을 위해 이들을 environstart_response라고 이름 붙였지만, 이러한 이름이 필수적인 것은 아닙니다. 서버 또는 게이트웨이는 위치 인수를 사용하여 (키워드 인수가 아닌) 애플리케이션 객체를 호출해야 합니다. (예: 위에서 보인 것처럼 result = application(environ, start_response)를 호출하여).

environ 매개변수는 CGI 스타일 환경 변수를 포함하는 딕셔너리 객체입니다. 이 객체는 내장 Python 딕셔너리여야 하며 (하위 클래스, UserDict 또는 다른 딕셔너리 에뮬레이션이 아님), 애플리케이션은 딕셔너리를 원하는 방식으로 수정할 수 있습니다. 딕셔너리에는 특정 WSGI 필수 변수 (나중에 설명)도 포함되어야 하며, 아래에서 설명할 규칙에 따라 이름이 지정된 서버별 확장 변수도 포함될 수 있습니다.

start_response 매개변수는 두 개의 필수 위치 인수와 하나의 선택적 인수를 받는 호출 가능한 객체입니다. 설명을 위해 이 인수들을 status, response_headers, exc_info라고 이름 붙였지만, 이러한 이름이 필수적인 것은 아니며, 애플리케이션은 위치 인수를 사용하여 start_response 호출 가능한 객체를 호출해야 합니다 (예: start_response(status, response_headers)).

status 매개변수는 “999 Message here” 형식의 상태 문자열이며, response_headers는 HTTP 응답 헤더를 설명하는 (header_name, header_value) 튜플 목록입니다. 선택적 exc_info 매개변수는 아래 “The start_response() Callable” 및 “Error Handling” 섹션에서 설명합니다. 이는 애플리케이션이 오류를 포착하고 브라우저에 오류 메시지를 표시하려고 할 때만 사용됩니다.

start_response 호출 가능 객체는 HTTP 응답 본문의 일부로 기록될 문자열을 인수로 받는 write(body_data) 호출 가능 객체를 반환해야 합니다. (참고: write() 호출 가능 객체는 특정 기존 프레임워크의 명령형 출력 API를 지원하기 위해 제공됩니다. 가능하다면 새로운 애플리케이션이나 프레임워크에서는 사용하지 않아야 합니다. 자세한 내용은 “Buffering and Streaming” 섹션을 참조하십시오.)

서버에 의해 호출될 때, 애플리케이션 객체는 0개 이상의 문자열을 생성하는 이터러블 (iterable)을 반환해야 합니다. 이는 문자열 목록을 반환하거나, 문자열을 생성하는 제너레이터 (generator) 함수이거나, 인스턴스가 이터러블인 클래스이거나 하는 다양한 방식으로 달성될 수 있습니다. 어떻게 달성되든 관계없이, 애플리케이션 객체는 항상 0개 이상의 문자열을 생성하는 이터러블을 반환해야 합니다.

서버 또는 게이트웨이는 생성된 문자열을 버퍼링되지 않은 방식으로 클라이언트에 전송해야 하며, 다른 문자열을 요청하기 전에 각 문자열의 전송을 완료해야 합니다. (즉, 애플리케이션은 자체 버퍼링을 수행해야 합니다. 애플리케이션 출력이 어떻게 처리되어야 하는지에 대한 자세한 내용은 아래 “Buffering and Streaming” 섹션을 참조하십시오.)

서버 또는 게이트웨이는 생성된 문자열을 이진 바이트 시퀀스로 처리해야 합니다. 특히, 줄 끝이 변경되지 않도록 해야 합니다. 애플리케이션은 작성될 문자열이 클라이언트에 적합한 형식인지 확인해야 합니다. (서버 또는 게이트웨이는 HTTP 전송 인코딩을 적용하거나 바이트 범위 전송과 같은 HTTP 기능을 구현하기 위한 다른 변환을 수행할 수 있습니다. 자세한 내용은 아래 “Other HTTP Features”를 참조하십시오.)

len(iterable) 호출이 성공하면 서버는 결과가 정확하다고 신뢰할 수 있어야 합니다. 즉, 애플리케이션이 반환한 이터러블이 작동하는 __len__() 메서드를 제공하는 경우 정확한 결과를 반환해야 합니다. (“Handling the Content-Length Header” 섹션에서 이것이 일반적으로 어떻게 사용되는지에 대한 정보를 참조하십시오.)

애플리케이션이 반환한 이터러블에 close() 메서드가 있는 경우, 서버 또는 게이트웨이는 현재 요청 완료 시 (요청이 정상적으로 완료되었든 오류로 인해 일찍 종료되었든 관계없이) 해당 메서드를 호출해야 합니다 (이는 애플리케이션에 의한 리소스 해제를 지원하기 위함입니다). 이 프로토콜은 PEP 325의 제너레이터 지원 및 close() 메서드를 가진 다른 일반적인 이터러블을 보완하기 위한 것입니다.

(참고: 애플리케이션은 이터러블이 첫 번째 본문 문자열을 생성하기 전에 start_response() 호출 가능 객체를 호출해야 합니다. 그래야 서버가 본문 내용이 전송되기 전에 헤더를 보낼 수 있습니다. 그러나 이 호출은 이터러블의 첫 번째 이터레이션에서 수행될 수 있으므로, 서버는 이터러블을 반복하기 시작하기 전에 start_response()가 호출되었다고 가정해서는 안 됩니다.)

마지막으로, 서버와 게이트웨이는 애플리케이션이 반환한 이터러블의 다른 속성을 직접 사용해서는 안 됩니다. wsgi.file_wrapper가 반환하는 “파일 래퍼”와 같이 해당 서버 또는 게이트웨이에 특정한 유형의 인스턴스가 아닌 한 (선택적 플랫폼별 파일 처리 참조). 일반적인 경우, 여기에 지정되었거나 PEP 234 반복 API를 통해 액세스되는 속성만 허용됩니다.

environ 변수

environ 딕셔너리에는 Common Gateway Interface (CGI) 사양에 정의된 다음 CGI 환경 변수가 포함되어야 합니다. 다음 변수는 값이 빈 문자열이 되는 경우를 제외하고는 항상 존재해야 하며, 아래에 달리 명시된 경우를 제외하고는 생략될 수 있습니다.

  • REQUEST_METHOD: “GET” 또는 “POST”와 같은 HTTP 요청 메서드. 빈 문자열이 될 수 없으므로 항상 필수입니다.
  • SCRIPT_NAME: 애플리케이션 객체에 해당하는 요청 URL “경로”의 초기 부분으로, 애플리케이션이 가상 “위치”를 알 수 있도록 합니다. 애플리케이션이 서버의 “루트”에 해당하는 경우 빈 문자열일 수 있습니다.
  • PATH_INFO: 요청 URL “경로”의 나머지 부분으로, 애플리케이션 내 요청 대상의 가상 “위치”를 지정합니다. 요청 URL이 애플리케이션 루트를 대상으로 하고 후행 슬래시가 없는 경우 빈 문자열일 수 있습니다.
  • QUERY_STRING: 요청 URL에서 “?” 뒤에 오는 부분 (있는 경우). 비어 있거나 없을 수 있습니다.
  • CONTENT_TYPE: HTTP 요청의 Content-Type 필드 내용. 비어 있거나 없을 수 있습니다.
  • CONTENT_LENGTH: HTTP 요청의 Content-Length 필드 내용. 비어 있거나 없을 수 있습니다.
  • SERVER_NAME, SERVER_PORT: SCRIPT_NAMEPATH_INFO와 결합하여 이러한 변수는 URL을 완성하는 데 사용될 수 있습니다. 그러나 HTTP_HOST가 존재하는 경우 요청 URL 재구성에는 SERVER_NAME보다 HTTP_HOST를 우선적으로 사용해야 합니다. 자세한 내용은 아래 “URL Reconstruction” 섹션을 참조하십시오. SERVER_NAMESERVER_PORT는 빈 문자열이 될 수 없으므로 항상 필수입니다.
  • SERVER_PROTOCOL: 클라이언트가 요청을 보내는 데 사용한 프로토콜 버전. 일반적으로 “HTTP/1.0” 또는 “HTTP/1.1”과 같으며, 애플리케이션이 HTTP 요청 헤더를 어떻게 처리할지 결정하는 데 사용될 수 있습니다. (이 변수는 요청에 사용된 프로토콜을 나타내므로 REQUEST_PROTOCOL이라고 불려야 하지만, CGI와의 호환성을 위해 기존 이름을 유지해야 합니다.)
  • HTTP_ 변수: 클라이언트가 제공한 HTTP 요청 헤더에 해당하는 변수 (즉, 이름이 “HTTP_“로 시작하는 변수). 이러한 변수의 존재 여부는 요청에 적절한 HTTP 헤더의 존재 여부와 일치해야 합니다.

서버 또는 게이트웨이는 적용 가능한 한 많은 다른 CGI 변수를 제공하려고 시도해야 합니다. 또한 SSL이 사용 중인 경우, 서버 또는 게이트웨이는 HTTPS=onSSL_PROTOCOL과 같은 적용 가능한 Apache SSL 환경 변수를 가능한 한 많이 제공해야 합니다. 그러나 위에 나열된 변수 외에 다른 CGI 변수를 사용하는 애플리케이션은 관련 확장을 지원하지 않는 웹 서버에서는 이식성이 없습니다. (예: 파일을 게시하지 않는 웹 서버는 의미 있는 DOCUMENT_ROOT 또는 PATH_TRANSLATED를 제공할 수 없습니다.)

WSGI 호환 서버 또는 게이트웨이는 제공하는 변수와 필요에 따라 그 정의를 문서화해야 합니다. 애플리케이션은 필요한 변수의 존재를 확인하고, 해당 변수가 없을 경우 대체 계획을 가지고 있어야 합니다.

참고: 누락된 변수 (예: 인증이 발생하지 않았을 때의 REMOTE_USER)는 environ 딕셔너리에서 제외해야 합니다. 또한 CGI 정의 변수는 존재하는 경우 문자열이어야 합니다. CGI 변수의 값이 str 유형이 아닌 다른 유형인 것은 이 사양의 위반입니다.

CGI 정의 변수 외에도, environ 딕셔너리에는 임의의 운영 체제 “환경 변수”가 포함될 수 있으며, 다음 WSGI 정의 변수가 포함되어야 합니다.

변수 값 ```tuple (tuple) of WSGI version (e.g. (1,0)).
wsgi.url_scheme 애플리케이션이 호출되는 URL의 “scheme” 부분을 나타내는 문자열. 일반적으로 “http” 또는 “https” 값을 가집니다.
wsgi.input HTTP 요청 본문을 읽을 수 있는 입력 스트림 (파일과 유사한 객체). (서버 또는 게이트웨이는 애플리케이션의 요청에 따라 주문형 읽기를 수행하거나, 클라이언트의 요청 본문을 미리 읽어 메모리 또는 디스크에 버퍼링하거나, 선호도에 따라 이러한 입력 스트림을 제공하기 위한 다른 기술을 사용할 수 있습니다.)
wsgi.errors 프로그램 또는 기타 오류를 표준화되고 잠재적으로 중앙 집중화된 위치에 기록하기 위해 오류 출력을 쓸 수 있는 출력 스트림 (파일과 유사한 객체). 이는 “텍스트 모드” 스트림이어야 합니다. 즉, 애플리케이션은 줄 끝으로 \n을 사용해야 하며, 서버/게이트웨이가 올바른 줄 끝으로 변환할 것이라고 가정해야 합니다.

많은 서버의 경우, wsgi.errors는 서버의 주요 오류 로그가 될 것입니다. 또는 sys.stderr 또는 일종의 로그 파일일 수 있습니다. 서버의 문서는 이를 구성하는 방법 또는 기록된 출력을 찾는 방법에 대한 설명을 포함해야 합니다. 서버 또는 게이트웨이는 원하는 경우 다른 애플리케이션에 다른 오류 스트림을 제공할 수 있습니다.

변수
wsgi.multithread 애플리케이션 객체가 동일 프로세스 내의 다른 스레드에 의해 동시에 호출될 수 있는 경우 참으로 평가되어야 하고, 그렇지 않으면 거짓으로 평가되어야 합니다.
wsgi.multiprocess 동등한 애플리케이션 객체가 다른 프로세스에 의해 동시에 호출될 수 있는 경우 참으로 평가되어야 하고, 그렇지 않으면 거짓으로 평가되어야 합니다.
wsgi.run_once 서버 또는 게이트웨이가 애플리케이션이 포함 프로세스의 수명 동안 이 한 번만 호출될 것으로 예상하는 (그러나 보장하지는 않는!) 경우 참으로 평가되어야 합니다. 일반적으로 이는 CGI (또는 유사한 것) 기반 게이트웨이에서만 참입니다.

마지막으로, environ 딕셔너리에는 서버 정의 변수도 포함될 수 있습니다. 이러한 변수는 소문자, 숫자, 점, 밑줄만 사용하여 이름이 지정되어야 하며, 정의하는 서버 또는 게이트웨이에 고유한 이름으로 접두사가 붙어야 합니다. 예를 들어, mod_pythonmod_python.some_variable과 같은 이름의 변수를 정의할 수 있습니다.

입력 및 오류 스트림

서버가 제공하는 입력 및 오류 스트림은 다음 메서드를 지원해야 합니다.

| 메서드 | 스트림 | 참고 사항 ```

PEP 333 – Python 웹 서버 게이트웨이 인터페이스 v1.0

서문

참고: Python 3.x를 지원하고 커뮤니티의 오류 정정, 추가 사항 및 명확화가 포함된 이 사양의 업데이트된 버전은 대신 PEP 3333을 참조하십시오.

초록

이 문서는 웹 서버와 Python 웹 애플리케이션 또는 프레임워크 간의 제안된 표준 인터페이스를 명시하여, 다양한 웹 서버에서 웹 애플리케이션의 이식성을 촉진합니다.

배경 및 목표

Python은 현재 Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Web 등 다양한 웹 애플리케이션 프레임워크를 자랑합니다. 이러한 다양한 선택지는 새로운 Python 사용자에게 문제가 될 수 있는데, 일반적으로 웹 프레임워크의 선택이 사용할 수 있는 웹 서버의 선택을 제한하고 그 반대도 마찬가지이기 때문입니다.

이와 대조적으로, Java는 비슷한 수의 웹 애플리케이션 프레임워크를 가지고 있지만, Java의 “서블릿 (servlet)” API를 통해 모든 Java 웹 애플리케이션 프레임워크로 작성된 애플리케이션이 서블릿 API를 지원하는 모든 웹 서버에서 실행될 수 있습니다.

Python용 웹 서버(예: Medusa와 같이 Python으로 작성되거나, mod_python과 같이 Python을 내장하거나, CGI, FastCGI 등과 같은 게이트웨이 프로토콜을 통해 Python을 호출하는 서버)에서 이러한 API가 널리 사용 가능하고 사용된다면, 프레임워크 선택과 웹 서버 선택이 분리되어 사용자는 자신에게 적합한 조합을 자유롭게 선택할 수 있게 될 것입니다. 또한 프레임워크 및 서버 개발자는 자신이 선호하는 전문 분야에 집중할 수 있게 될 것입니다.

따라서 이 PEP는 웹 서버와 웹 애플리케이션 또는 프레임워크 간의 간단하고 보편적인 인터페이스인 Python Web Server Gateway Interface (WSGI)를 제안합니다.

WSGI 사양의 존재만으로는 Python 웹 애플리케이션을 위한 서버 및 프레임워크의 현 상태를 해결할 수 없습니다. 효과를 보려면 서버 및 프레임워크 작성자와 유지 관리자가 실제로 WSGI를 구현해야 합니다.

그러나 현재 WSGI를 지원하는 기존 서버나 프레임워크가 없으므로, WSGI 지원을 구현하는 작성자에게는 즉각적인 보상이 거의 없습니다. 따라서 WSGI는 구현하기 쉬워야 하며, 작성자의 인터페이스에 대한 초기 투자가 합리적으로 낮아야 합니다.

따라서 인터페이스의 서버 및 프레임워크 양측에서 구현의 단순성은 WSGI 인터페이스의 유용성에 절대적으로 중요하며, 모든 설계 결정의 주요 기준입니다.

참고: 프레임워크 작성자를 위한 구현의 단순성이 웹 애플리케이션 작성자를 위한 사용 용이성과 같은 것은 아닙니다. WSGI는 응답 객체 및 쿠키 처리와 같은 부가 기능이 기존 프레임워크의 이러한 문제 처리 방식에 방해가 될 수 있으므로, 프레임워크 작성자에게 “불필요한 기능 없는 (no frills)” 인터페이스를 제공합니다. 다시 말해, WSGI의 목표는 기존 서버와 애플리케이션 또는 프레임워크 간의 쉬운 상호 연결을 촉진하는 것이지, 새로운 웹 프레임워크를 만드는 것이 아닙니다.

또한 이 목표는 WSGI가 현재 배포된 Python 버전에서 아직 사용할 수 없는 것을 요구하지 않도록 합니다. 따라서 새로운 표준 라이브러리 모듈은 이 사양에서 제안되거나 요구되지 않으며, WSGI의 어떤 부분도 Python 2.2 버전보다 높은 버전을 요구하지 않습니다. (다만, 향후 Python 버전이 표준 라이브러리에서 제공되는 웹 서버에 이 인터페이스에 대한 지원을 포함하는 것이 좋을 것입니다.)

기존 및 미래의 프레임워크와 서버에 대한 구현 용이성 외에도, 요청 전처리기, 응답 후처리기 및 기타 WSGI 기반 “미들웨어 (middleware)” 구성 요소를 쉽게 생성할 수 있어야 합니다. 이러한 구성 요소는 자신을 포함하는 서버에게는 애플리케이션처럼 보이고, 자신이 포함하는 애플리케이션에게는 서버 역할을 합니다.

미들웨어가 간단하고 견고하며, WSGI가 서버와 프레임워크에서 널리 사용 가능하다면, 완전히 새로운 종류의 Python 웹 애플리케이션 프레임워크, 즉 느슨하게 결합된 WSGI 미들웨어 구성 요소로 구성된 프레임워크가 가능해집니다. 실제로 기존 프레임워크 작성자들은 자신의 프레임워크의 기존 서비스를 이러한 방식으로 제공하도록 리팩토링하여, 모놀리식 프레임워크보다는 WSGI와 함께 사용되는 라이브러리에 더 가깝게 만들 수 있습니다. 이렇게 되면 애플리케이션 개발자들은 단일 프레임워크의 모든 장단점에 얽매이지 않고 특정 기능에 대해 “최고의 구성 요소 (best-of-breed)”를 선택할 수 있게 될 것입니다.

물론, 이 글을 쓰는 시점에는 그 날이 아직 멀었을 것입니다. 그 동안 WSGI의 충분한 단기 목표는 모든 프레임워크를 모든 서버와 함께 사용할 수 있도록 하는 것입니다.

마지막으로, WSGI의 현재 버전은 웹 서버 또는 서버 게이트웨이와 함께 애플리케이션을 “배포 (deploy)”하는 특정 메커니즘을 규정하지 않는다는 점을 언급해야 합니다. 현재로서는 이는 서버 또는 게이트웨이에 의해 구현 정의될 수밖에 없습니다. 충분한 수의 서버와 프레임워크가 WSGI를 구현하여 다양한 배포 요구 사항에 대한 현장 경험을 제공한 후에는 WSGI 서버 및 애플리케이션 프레임워크에 대한 배포 표준을 설명하는 또 다른 PEP를 만드는 것이 합리적일 수 있습니다.

사양 개요

WSGI 인터페이스는 “서버 (server)” 또는 “게이트웨이 (gateway)” 측과 “애플리케이션 (application)” 또는 “프레임워크 (framework)” 측의 두 가지 측면을 가집니다. 서버 측은 애플리케이션 측에서 제공하는 호출 가능한 (callable) 객체를 호출합니다. 해당 객체가 제공되는 구체적인 방법은 서버 또는 게이트웨이에 달려 있습니다. 일부 서버 또는 게이트웨이는 애플리케이션 배포자가 서버 또는 게이트웨이의 인스턴스를 생성하고 애플리케이션 객체를 제공하기 위한 짧은 스크립트를 작성해야 할 것이라고 가정합니다. 다른 서버 및 게이트웨이는 구성 파일 또는 다른 메커니즘을 사용하여 애플리케이션 객체를 가져올 위치를 지정하거나 얻을 수 있습니다.

“순수한” 서버/게이트웨이 및 애플리케이션/프레임워크 외에도, 이 사양의 양쪽을 모두 구현하는 “미들웨어 (middleware)” 구성 요소를 생성하는 것도 가능합니다. 이러한 구성 요소는 자신을 포함하는 서버에게는 애플리케이션 역할을 하고, 자신이 포함하는 애플리케이션에게는 서버 역할을 하며, 확장된 API, 콘텐츠 변환, 탐색 및 기타 유용한 기능을 제공하는 데 사용될 수 있습니다.

이 사양 전체에서 “호출 가능한 객체 (a callable)”는 “함수, 메서드, 클래스, 또는 __call__ 메서드를 가진 인스턴스”를 의미합니다. 호출 가능한 객체를 구현하는 서버, 게이트웨이 또는 애플리케이션은 자신들의 필요에 맞는 적절한 구현 기술을 선택할 수 있습니다. 반대로, 호출 가능한 객체를 호출하는 서버, 게이트웨이 또는 애플리케이션은 어떤 종류의 호출 가능한 객체가 제공되었는지에 대해 의존해서는 안 됩니다. 호출 가능한 객체는 호출만 되어야 하며, 내부를 검사해서는 안 됩니다.

애플리케이션/프레임워크 측

애플리케이션 객체는 단순히 두 개의 인수를 받는 호출 가능한 객체입니다. “객체”라는 용어는 실제 객체 인스턴스를 요구하는 것으로 오해되어서는 안 됩니다. 함수, 메서드, 클래스, 또는 __call__ 메서드를 가진 인스턴스 모두 애플리케이션 객체로 사용할 수 있습니다. 애플리케이션 객체는 한 번 이상 호출될 수 있어야 합니다. (CGI를 제외한) 거의 모든 서버/게이트웨이가 이러한 반복적인 요청을 할 것이기 때문입니다.

(참고: 이를 “애플리케이션” 객체라고 부르지만, 이는 애플리케이션 개발자가 WSGI를 웹 프로그래밍 API로 사용할 것이라는 의미로 해석되어서는 안 됩니다! 애플리케이션 개발자들은 기존의 고수준 프레임워크 서비스를 사용하여 애플리케이션을 계속 개발할 것이라고 가정합니다. WSGI는 프레임워크 및 서버 개발자를 위한 도구이며, 애플리케이션 개발자를 직접 지원하기 위한 것이 아닙니다.)

다음은 두 가지 예제 애플리케이션 객체입니다. 하나는 함수이고 다른 하나는 클래스입니다.

def simple_app(environ, start_response):
    """가장 간단한 애플리케이션 객체"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

class AppClass:
    """동일한 출력을 생성하지만 클래스를 사용합니다.
    ('AppClass'가 여기서는 "애플리케이션"이므로, 호출하면 'AppClass'의 인스턴스를 반환하며,
    이것이 사양에서 요구하는 "애플리케이션 호출 가능 객체"의 반복 가능한 반환 값입니다.
    만약 'AppClass'의 *인스턴스*를 애플리케이션 객체로 사용하고 싶다면,
    애플리케이션을 실행하기 위해 호출될 '__call__' 메서드를 구현해야 하며,
    서버 또는 게이트웨이에서 사용할 인스턴스를 생성해야 합니다.
    """
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

서버/게이트웨이 측

서버 또는 게이트웨이는 HTTP 클라이언트로부터 애플리케이션으로 전달되는 각 요청에 대해 애플리케이션 호출 가능 객체를 한 번 호출합니다. 예를 들어, 애플리케이션 객체를 인수로 받는 함수로 구현된 간단한 CGI 게이트웨이가 있습니다. 이 간단한 예제는 제한된 오류 처리를 가지고 있음을 유의하십시오. 기본적으로 처리되지 않은 예외는 sys.stderr로 덤프되고 웹 서버에 의해 로깅됩니다.

import os, sys

def run_with_cgi(application):
    environ = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1, 0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True

    if environ.get('HTTPS', 'off') in ('on', '1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set = []
    headers_sent = []

    def write(data):
        if not headers_set:
            raise AssertionError("write() before start_response()")
        elif not headers_sent:
            # 첫 번째 출력 전에 저장된 헤더를 보냅니다.
            status, response_headers = headers_sent[:] = headers_set
            sys.stdout.write('Status: %s\r\n' % status)
            for header in response_headers:
                sys.stdout.write('%s: %s\r\n' % header)
            sys.stdout.write('\r\n')
        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # 헤더가 이미 전송되었으면 원래 예외를 다시 발생시킵니다.
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None # 순환 참조 방지
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]
        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # 본문이 나타나기 전에는 헤더를 보내지 않습니다.
                write(data)
        if not headers_sent:
            write('')   # 본문이 비어있으면 지금 헤더를 보냅니다.
    finally:
        if hasattr(result, 'close'):
            result.close()

미들웨어: 양쪽 역할을 수행하는 구성 요소

단일 객체가 일부 애플리케이션에 대해서는 서버 역할을 하면서, 동시에 일부 서버에 대해서는 애플리케이션 역할을 할 수 있습니다. 이러한 “미들웨어 (middleware)” 구성 요소는 다음과 같은 기능을 수행할 수 있습니다.

  • 대상 URL에 따라 요청을 다른 애플리케이션 객체로 라우팅하고, environ을 그에 따라 재작성합니다.
  • 동일한 프로세스에서 여러 애플리케이션 또는 프레임워크가 나란히 실행되도록 허용합니다.
  • 네트워크를 통해 요청과 응답을 전달하여 로드 밸런싱 및 원격 처리를 수행합니다.
  • XSL 스타일시트 적용과 같은 콘텐츠 후처리를 수행합니다.

일반적으로 미들웨어의 존재는 인터페이스의 “서버/게이트웨이” 측과 “애플리케이션/프레임워크” 측 모두에게 투명하며, 특별한 지원을 요구하지 않습니다. 애플리케이션에 미들웨어를 통합하려는 사용자는 미들웨어 구성 요소를 마치 애플리케이션인 것처럼 서버에 제공하고, 미들웨어 구성 요소를 마치 서버인 것처럼 애플리케이션을 호출하도록 구성하기만 하면 됩니다. 물론, 미들웨어가 감싸는 “애플리케이션”은 실제로 또 다른 애플리케이션을 감싸는 또 다른 미들웨어 구성 요소일 수 있으며, 이러한 방식으로 “미들웨어 스택”이라고 불리는 것을 생성합니다.

대부분의 경우 미들웨어는 WSGI의 서버 및 애플리케이션 측의 제한 사항과 요구 사항을 모두 준수해야 합니다. 그러나 일부 경우에는 미들웨어에 대한 요구 사항이 “순수한” 서버 또는 애플리케이션보다 더 엄격하며, 이러한 점들은 사양에서 언급될 것입니다.

다음은 Joe Strout의 piglatin.py를 사용하여 text/plain 응답을 피그 라틴어로 변환하는 미들웨어 구성 요소의 (우스개 소리 같은) 예입니다. (참고: “실제” 미들웨어 구성 요소는 콘텐츠 유형을 확인하는 더 견고한 방법을 사용하고 콘텐츠 인코딩도 확인해야 할 것입니다. 또한 이 간단한 예제는 단어가 블록 경계에 걸쳐 분할될 수 있는 가능성을 무시합니다.)

from piglatin import piglatin

class LatinIter:
    """반복된 출력을 피그 라틴어로 변환합니다. 변환이 가능한 경우에만.
    "변환 가능성"은 애플리케이션이 첫 번째 비어 있지 않은 문자열을 생성할 때까지 변경될 수 있으므로,
    'transform_ok'는 변경 가능한 참 값이어야 합니다.
    """
    def __init__(self, result, transform_ok):
        if hasattr(result, 'close'):
            self.close = result.close
        self._next = iter(result).next
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def next(self):
        if self.transform_ok:
            return piglatin(self._next())
        else:
            return self._next()

class Latinator:
    # 기본적으로 출력은 변환하지 않습니다.
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        transform_ok = []
        def start_latin(status, response_headers, exc_info=None):
            # 반복 호출일 경우를 대비하여 ok 플래그를 재설정합니다.
            del transform_ok[:]
            for name, value in response_headers:
                if name.lower() == 'content-type' and value == 'text/plain':
                    transform_ok.append(True)
                    # Content-Length가 있으면 제거합니다. 그렇지 않으면 잘못됩니다.
                    response_headers = [(name, value) for name, value in response_headers
                                        if name.lower() != 'content-length']
                    break
            write = start_response(status, response_headers, exc_info)
            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ, start_latin), transform_ok)

# foo_app을 Latinator의 제어 하에 예제 CGI 게이트웨이를 사용하여 실행합니다.
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))

사양 세부 사항

애플리케이션 객체는 두 개의 위치 인수를 받아야 합니다. 설명을 위해 이들을 environstart_response라고 이름 붙였지만, 이러한 이름이 필수적인 것은 아닙니다. 서버 또는 게이트웨이는 위치 인수를 사용하여 (키워드 인수가 아닌) 애플리케이션 객체를 호출해야 합니다. (예: 위에서 보인 것처럼 result = application(environ, start_response)를 호출하여).

environ 매개변수는 CGI 스타일 환경 변수를 포함하는 딕셔너리 객체입니다. 이 객체는 내장 Python 딕셔너리여야 하며 (하위 클래스, UserDict 또는 다른 딕셔너리 에뮬레이션이 아님), 애플리케이션은 딕셔너리를 원하는 방식으로 수정할 수 있습니다. 딕셔너리에는 특정 WSGI 필수 변수 (나중에 설명)도 포함되어야 하며, 아래에서 설명할 규칙에 따라 이름이 지정된 서버별 확장 변수도 포함될 수 있습니다.

start_response 매개변수는 두 개의 필수 위치 인수와 하나의 선택적 인수를 받는 호출 가능한 객체입니다. 설명을 위해 이 인수들을 status, response_headers, exc_info라고 이름 붙였지만, 이러한 이름이 필수적인 것은 아니며, 애플리케이션은 위치 인수를 사용하여 start_response 호출 가능 객체를 호출해야 합니다 (예: start_response(status, response_headers)).

status 매개변수는 “999 Message here” 형식의 상태 문자열이며, response_headers는 HTTP 응답 헤더를 설명하는 (header_name, header_value) 튜플 목록입니다. 선택적 exc_info 매개변수는 아래 “The start_response() Callable” 및 “Error Handling” 섹션에서 설명합니다. 이는 애플리케이션이 오류를 포착하고 브라우저에 오류 메시지를 표시하려고 할 때만 사용됩니다.

start_response 호출 가능 객체는 HTTP 응답 본문의 일부로 기록될 문자열을 인수로 받는 write(body_data) 호출 가능 객체를 반환해야 합니다. (참고: write() 호출 가능 객체는 특정 기존 프레임워크의 명령형 출력 API를 지원하기 위해 제공됩니다. 가능하다면 새로운 애플리케이션이나 프레임워크에서는 사용하지 않아야 합니다. 자세한 내용은 “Buffering and Streaming” 섹션을 참조하십시오.)

서버에 의해 호출될 때, 애플리케이션 객체는 0개 이상의 문자열을 생성하는 이터러블 (iterable)을 반환해야 합니다. 이는 문자열 목록을 반환하거나, 문자열을 생성하는 제너레이터 (generator) 함수이거나, 인스턴스가 이터러블인 클래스이거나 하는 다양한 방식으로 달성될 수 있습니다. 어떻게 달성되든 관계없이, 애플리케이션 객체는 항상 0개 이상의 문자열을 생성하는 이터러블을 반환해야 합니다.

서버 또는 게이트웨이는 생성된 문자열을 버퍼링되지 않은 방식으로 클라이언트에 전송해야 하며, 다른 문자열을 요청하기 전에 각 문자열의 전송을 완료해야 합니다. (즉, 애플리케이션은 자체 버퍼링을 수행해야 합니다. 애플리케이션 출력이 어떻게 처리되어야 하는지에 대한 자세한 내용은 아래 “Buffering and Streaming” 섹션을 참조하십시오.)

서버 또는 게이트웨이는 생성된 문자열을 이진 바이트 시퀀스로 처리해야 합니다. 특히, 줄 끝이 변경되지 않도록 해야 합니다. 애플리케이션은 작성될 문자열이 클라이언트에 적합한 형식인지 확인해야 합니다. (서버 또는 게이트웨이는 HTTP 전송 인코딩을 적용하거나 바이트 범위 전송과 같은 HTTP 기능을 구현하기 위한 다른 변환을 수행할 수 있습니다. 자세한 내용은 아래 “Other HTTP Features”를 참조하십시오.)

len(iterable) 호출이 성공하면 서버는 결과가 정확하다고 신뢰할 수 있어야 합니다. 즉, 애플리케이션이 반환한 이터러블이 작동하는 __len__() 메서드를 제공하는 경우 정확한 결과를 반환해야 합니다. (“Handling the Content-Length Header” 섹션에서 이것이 일반적으로 어떻게 사용되는지에 대한 정보를 참조하십시오.)

애플리케이션이 반환한 이터러블에 close() 메서드가 있는 경우, 서버 또는 게이트웨이는 현재 요청 완료 시 (요청이 정상적으로 완료되었든 오류로 인해 일찍 종료되었든 관계없이) 해당 메서드를 호출해야 합니다 (이는 애플리케이션에 의한 리소스 해제를 지원하기 위함입니다). 이 프로토콜은 PEP 325의 제너레이터 지원 및 close() 메서드를 가진 다른 일반적인 이터러블을 보완하기 위한 것입니다.

(참고: 애플리케이션은 이터러블이 첫 번째 본문 문자열을 생성하기 전에 start_response() 호출 가능 객체를 호출해야 합니다. 그래야 서버가 본문 내용이 전송되기 전에 헤더를 보낼 수 있습니다. 그러나 이 호출은 이터러블의 첫 번째 이터레이션에서 수행될 수 있으므로, 서버는 이터러블을 반복하기 시작하기 전에 start_response()가 호출되었다고 가정해서는 안 됩니다.)

마지막으로, 서버와 게이트웨이는 애플리케이션이 반환한 이터러블의 다른 속성을 직접 사용해서는 안 됩니다. wsgi.file_wrapper가 반환하는 “파일 래퍼”와 같이 해당 서버 또는 게이트웨이에 특정한 유형의 인스턴스가 아닌 한 (선택적 플랫폼별 파일 처리 참조). 일반적인 경우, 여기에 지정되었거나 PEP 234 반복 API를 통해 액세스되는 속성만 허용됩니다.

environ 변수

environ 딕셔너리에는 Common Gateway Interface (CGI) 사양에 정의된 다음 CGI 환경 변수가 포함되어야 합니다. 다음 변수는 값이 빈 문자열이 되는 경우를 제외하고는 항상 존재해야 하며, 아래에 달리 명시된 경우를 제외하고는 생략될 수 있습니다.

  • REQUEST_METHOD: “GET” 또는 “POST”와 같은 HTTP 요청 메서드. 빈 문자열이 될 수 없으므로 항상 필수입니다.
  • SCRIPT_NAME: 애플리케이션 객체에 해당하는 요청 URL “경로”의 초기 부분으로, 애플리케이션이 가상 “위치”를 알 수 있도록 합니다. 애플리케이션이 서버의 “루트”에 해당하는 경우 빈 문자열일 수 있습니다.
  • PATH_INFO: 요청 URL “경로”의 나머지 부분으로, 애플리케이션 내 요청 대상의 가상 “위치”를 지정합니다. 요청 URL이 애플리케이션 루트를 대상으로 하고 후행 슬래시가 없는 경우 빈 문자열일 수 있습니다.
  • QUERY_STRING: 요청 URL에서 “?” 뒤에 오는 부분 (있는 경우). 비어 있거나 없을 수 있습니다.
  • CONTENT_TYPE: HTTP 요청의 Content-Type 필드 내용. 비어 있거나 없을 수 있습니다.
  • CONTENT_LENGTH: HTTP 요청의 Content-Length 필드 내용. 비어 있거나 없을 수 있습니다.
  • SERVER_NAME, SERVER_PORT: SCRIPT_NAMEPATH_INFO와 결합하여 이러한 변수는 URL을 완성하는 데 사용될 수 있습니다. 그러나 HTTP_HOST가 존재하는 경우 요청 URL 재구성에는 SERVER_NAME보다 HTTP_HOST를 우선적으로 사용해야 합니다. 자세한 내용은 아래 “URL Reconstruction” 섹션을 참조하십시오. SERVER_NAMESERVER_PORT는 빈 문자열이 될 수 없으므로 항상 필수입니다.
  • SERVER_PROTOCOL: 클라이언트가 요청을 보내는 데 사용한 프로토콜 버전. 일반적으로 “HTTP/1.0” 또는 “HTTP/1.1”과 같으며, 애플리케이션이 HTTP 요청 헤더를 어떻게 처리할지 결정하는 데 사용될 수 있습니다. (이 변수는 요청에 사용된 프로토콜을 나타내므로 REQUEST_PROTOCOL이라고 불려야 하지만, CGI와의 호환성을 위해 기존 이름을 유지해야 합니다.)
  • HTTP_ 변수: 클라이언트가 제공한 HTTP 요청 헤더에 해당하는 변수 (즉, 이름이 “HTTP_“로 시작하는 변수). 이러한 변수의 존재 여부는 요청에 적절한 HTTP 헤더의 존재 여부와 일치해야 합니다.

서버 또는 게이트웨이는 적용 가능한 한 많은 다른 CGI 변수를 제공하려고 시도해야 합니다. 또한 SSL이 사용 중인 경우, 서버 또는 게이트웨이는 HTTPS=onSSL_PROTOCOL과 같은 적용 가능한 Apache SSL 환경 변수를 가능한 한 많이 제공해야 합니다. 그러나 위에 나열된 변수 외에 다른 CGI 변수를 사용하는 애플리케이션은 관련 확장을 지원하지 않는 웹 서버에서는 이식성이 없습니다. (예: 파일을 게시하지 않는 웹 서버는 의미 있는 DOCUMENT_ROOT 또는 PATH_TRANSLATED를 제공할 수 없습니다.)

WSGI 호환 서버 또는 게이트웨이는 제공하는 변수와 필요에 따라 그 정의를 문서화해야 합니다. 애플리케이션은 필요한 변수의 존재를 확인하고, 해당 변수가 없을 경우 대체 계획을 가지고 있어야 합니다.

참고: 누락된 변수 (예: 인증이 발생하지 않았을 때의 REMOTE_USER)는 environ 딕셔너리에서 제외해야 합니다. 또한 CGI 정의 변수는 존재하는 경우 문자열이어야 합니다. CGI 변수의 값이 str 유형이 아닌 다른 유형인 것은 이 사양의 위반입니다.

CGI 정의 변수 외에도, environ 딕셔너리에는 임의의 운영 체제 “환경 변수”가 포함될 수 있으며, 다음 WSGI 정의 변수가 포함되어야 합니다.

변수 값 WSGI 버전 1.0을 나타내는 튜플 (1, 0).
wsgi.url_scheme 애플리케이션이 호출되는 URL의 “scheme” 부분을 나타내는 문자열. 일반적으로 “http” 또는 “https” 값을 가집니다.
wsgi.input HTTP 요청 본문을 읽을 수 있는 입력 스트림 (파일과 유사한 객체). (서버 또는 게이트웨이는 애플리케이션의 요청에 따라 주문형 읽기를 수행하거나, 클라이언트의 요청 본문을 미리 읽어 메모리 또는 디스크에 버퍼링하거나, 선호도에 따라 이러한 입력 스트림을 제공하기 위한 다른 기술을 사용할 수 있습니다.)
wsgi.errors 프로그램 또는 기타 오류를 표준화되고 잠재적으로 중앙 집중화된 위치에 기록하기 위해 오류 출력을 쓸 수 있는 출력 스트림 (파일과 유사한 객체). 이는 “텍스트 모드” 스트림이어야 합니다. 즉, 애플리케이션은 줄 끝으로 \n을 사용해야 하며, 서버/게이트웨이가 올바른 줄 끝으로 변환할 것이라고 가정해야 합니다.

많은 서버의 경우, wsgi.errors는 서버의 주요 오류 로그가 될 것입니다. 또는 sys.stderr 또는 일종의 로그 파일일 수 있습니다. 서버의 문서는 이를 구성하는 방법 또는 기록된 출력을 찾는 방법에 대한 설명을 포함해야 합니다. 서버 또는 게이트웨이는 원하는 경우 다른 애플리케이션에 다른 오류 스트림을 제공할 수 있습니다.

변수
wsgi.multithread 애플리케이션 객체가 동일 프로세스 내의 다른 스레드에 의해 동시에 호출될 수 있는 경우 참으로 평가되어야 하고, 그렇지 않으면 거짓으로 평가되어야 합니다.
wsgi.multiprocess 동등한 애플리케이션 객체가 다른 프로세스에 의해 동시에 호출될 수 있는 경우 참으로 평가되어야 하고, 그렇지 않으면 거짓으로 평가되어야 합니다.
wsgi.run_once 서버 또는 게이트웨이가 애플리케이션이 포함 프로세스의 수명 동안 이 한 번만 호출될 것으로 예상하는 (그러나 보장하지는 않는!) 경우 참으로 평가되어야 합니다. 일반적으로 이는 CGI (또는 유사한 것) 기반 게이트웨이에서만 참입니다.

마지막으로, environ 딕셔너리에는 서버 정의 변수도 포함될 수 있습니다. 이러한 변수는 소문자, 숫자, 점, 밑줄만 사용하여 이름이 지정되어야 하며, 정의하는 서버 또는 게이트웨이에 고유한 이름으로 접두사가 붙어야 합니다. 예를 들어, mod_pythonmod_python.some_variable과 같은 이름의 변수를 정의할 수 있습니다.

입력 및 오류 스트림

서버가 제공하는 입력 및 오류 스트림은 다음 메서드를 지원해야 합니다.

| 메서드 | 스트림 | 참고 사항 | | read(size) | input | 1. 서버는 클라이언트가 지정한 Content-Length를 초과하여 읽을 필요가 없으며, 애플리케이션이 해당 지점을 초과하여 읽으려고 시도하면 파일 끝 조건을 시뮬레이션할 수 있습니다. 애플리케이션은 CONTENT_LENGTH 변수에 지정된 것보다 많은 데이터를 읽으려고 시도해서는 안 됩니다. | readline() | input | 2. readline()의 선택적 “size” 인수는 서버 작성자가 구현하기 복잡할 수 있고 실제 사용 빈도가 낮기 때문에 지원되지 않습니다. | readlines(hint) | input | 3. readlines()hint 인수는 호출자 및 구현자 모두에게 선택 사항입니다. 애플리케이션은 이를 제공하지 않아도 되고, 서버 또는 게이트웨이는 이를 무시해도 됩니다. | iter() | input | | | flush() | errors | 4. errors 스트림은 되감을 수 없으므로, 서버와 게이트웨이는 버퍼링 없이 쓰기 작업을 즉시 전달할 수 있습니다. 이 경우 flush() 메서드는 아무런 작동도 하지 않을 수 있습니다. 그러나 휴대용 애플리케이션은 출력이 버퍼링되지 않거나 flush()가 아무런 작동도 하지 않는다고 가정할 수 없습니다. 출력이 실제로 기록되었는지 확인해야 하는 경우 flush()를 호출해야 합니다. (예: 동일한 오류 로그에 쓰는 여러 프로세스의 데이터 혼합을 최소화하기 위해.) | write(str) | errors | | | writelines(seq) | errors | | The semantics of each method are as documented in the Python Library Reference, except for these notes as listed in the table above:

  1. The server is not required to read past the client’s specified Content-Length, and is allowed to simulate an end-of-file condition if the application attempts to read past that point. The application should not attempt to read more data than is specified by the CONTENT_LENGTH variable.
  2. The optional “size” argument to readline() is not supported, as it may be complex for server authors to implement, and is not often used in practice.
  3. Note that the hint argument to readlines() is optional for both caller and implementer. The application is free not to supply it, and the server or gateway is free to ignore it.
  4. Since the errors stream may not be rewound, servers and gateways are free to forward write operations immediately, without buffering. In this case, the flush() method may be a no-op. Portable applications, however, cannot assume that output is unbuffered or that flush() is a no-op. They must call flush() if they need to ensure that output has in fact been written. (For example, to minimize intermingling of data from multiple processes writing to the same error log.)

위에 나열된 표의 참고 사항을 제외하고 각 메서드의 의미는 Python 라이브러리 참조에 문서화된 바와 같습니다.

이 사양을 준수하는 모든 서버는 위에 나열된 메서드를 지원해야 합니다. 이 사양을 준수하는 애플리케이션은 input 또는 errors 객체의 다른 메서드나 속성을 사용해서는 안 됩니다. 특히, 애플리케이션은 close() 메서드를 가지고 있더라도 이러한 스트림을 닫으려고 시도해서는 안 됩니다.

start_response() 호출 가능 객체

애플리케이션 객체에 전달되는 두 번째 매개변수는 start_response(status, response_headers, exc_info=None) 형식의 호출 가능한 객체입니다. (모든 WSGI 호출 가능 객체와 마찬가지로, 인수는 키워드가 아닌 위치 인수로 제공되어야 합니다.) start_response 호출 가능 객체는 HTTP 응답을 시작하는 데 사용되며, write(body_data) 호출 가능 객체 (아래 “Buffering and Streaming” 섹션 참조)를 반환해야 합니다.

status 인수는 “200 OK” 또는 “404 Not Found“와 같은 HTTP “상태 (status)” 문자열입니다. 즉, Status-Code와 Reason-Phrase로 구성된 문자열이며, 그 순서대로 단일 공백으로 구분되며 주변에 공백이나 다른 문자가 없습니다. (자세한 내용은 RFC 2616, 섹션 6.1.1을 참조하십시오.) 문자열은 제어 문자를 포함해서는 안 되며, 캐리지 리턴, 라인 피드 또는 그 조합으로 끝나서는 안 됩니다.

response_headers 인수는 (header_name, header_value) 튜플 목록입니다. 이는 Python 리스트여야 합니다. 즉, type(response_headers)ListType이며, 서버는 그 내용을 원하는 방식으로 변경할 수 있습니다. 각 header_name은 후행 콜론이나 다른 구두점 없이 유효한 HTTP 헤더 필드 이름이어야 합니다 (RFC 2616, 섹션 4.2에 정의된 바와 같이).

header_value는 포함되거나 끝에 캐리지 리턴 또는 라인 피드를 포함한 어떠한 제어 문자도 포함해서는 안 됩니다. (이러한 요구 사항은 응답 헤더를 검사하거나 수정해야 하는 서버, 게이트웨이 및 중간 응답 프로세서가 수행해야 하는 파싱의 복잡성을 최소화하기 위함입니다.)

일반적으로 서버 또는 게이트웨이는 클라이언트에 올바른 헤더가 전송되도록 할 책임이 있습니다. 애플리케이션이 HTTP (또는 시행 중인 기타 관련 사양)에서 요구하는 헤더를 생략하는 경우, 서버 또는 게이트웨이는 이를 추가해야 합니다. 예를 들어, HTTP Date:Server: 헤더는 일반적으로 서버 또는 게이트웨이에서 제공됩니다.

(서버/게이트웨이 작성자를 위한 알림: HTTP 헤더 이름은 대소문자를 구분하지 않으므로, 애플리케이션이 제공한 헤더를 검사할 때 이 점을 고려해야 합니다!)

애플리케이션 및 미들웨어는 HTTP/1.1 “hop-by-hop” 기능 또는 헤더, HTTP/1.0의 동등한 기능, 또는 클라이언트의 웹 서버 연결 지속성에 영향을 미치는 헤더를 사용하는 것이 금지됩니다. 이러한 기능은 실제 웹 서버의 전유물이며, 서버 또는 게이트웨이는 애플리케이션이 이를 보내려고 시도하는 것을 치명적인 오류로 간주해야 하며, start_response()에 제공될 경우 오류를 발생시켜야 합니다. (“hop-by-hop” 기능 및 헤더에 대한 자세한 내용은 아래 “Other HTTP Features” 섹션을 참조하십시오.)

start_response 호출 가능 객체는 실제로 응답 헤더를 전송해서는 안 됩니다. 대신, 서버 또는 게이트웨이가 애플리케이션 반환 값의 첫 번째 비어 있지 않은 문자열을 생성하는 첫 번째 이터레이션 이후에만, 또는 애플리케이션의 write() 호출 가능 객체의 첫 번째 호출 시에만 전송하도록 저장해야 합니다. 즉, 실제 본문 데이터가 사용 가능할 때까지 또는 애플리케이션이 반환한 이터러블이 소진될 때까지 응답 헤더는 전송되어서는 안 됩니다. (이 규칙의 유일한 예외는 응답 헤더에 명시적으로 Content-Length가 0으로 포함된 경우입니다.)

응답 헤더 전송을 지연시키는 것은 버퍼링되고 비동기식 애플리케이션이 원래 의도했던 출력을 마지막 순간까지 오류 출력으로 대체할 수 있도록 하기 위함입니다. 예를 들어, 애플리케이션 버퍼 내에서 본문이 생성되는 동안 오류가 발생하면 애플리케이션은 응답 상태를 “200 OK”에서 “500 Internal Error”로 변경해야 할 수 있습니다.

exc_info 인수가 제공되는 경우, Python sys.exc_info() 튜플이어야 합니다. 이 인수는 오류 핸들러가 start_response를 호출할 때만 애플리케이션에 의해 제공되어야 합니다. exc_info가 제공되고 아직 HTTP 헤더가 출력되지 않은 경우, start_response는 현재 저장된 HTTP 응답 헤더를 새로 제공된 헤더로 교체하여, 오류가 발생했을 때 애플리케이션이 출력에 대해 “마음을 바꿀” 수 있도록 허용해야 합니다.

그러나 exc_info가 제공되고 HTTP 헤더가 이미 전송된 경우, start_response는 오류를 발생시켜야 하며, exc_info 튜플을 발생시켜야 합니다. 즉:

raise exc_info[0], exc_info[1], exc_info[2]

이것은 애플리케이션에 의해 포착된 예외를 다시 발생시키며, 원칙적으로 애플리케이션을 중단시켜야 합니다. (HTTP 헤더가 이미 전송된 후에는 애플리케이션이 브라우저에 오류 출력을 시도하는 것이 안전하지 않습니다.) 애플리케이션은 exc_info와 함께 start_response를 호출한 경우 start_response에 의해 발생된 예외를 포착해서는 안 됩니다. 대신, 이러한 예외가 서버 또는 게이트웨이로 전파되도록 허용해야 합니다. 자세한 내용은 아래 “Error Handling”을 참조하십시오.

애플리케이션은 exc_info 인수가 제공되는 경우에만 start_response를 두 번 이상 호출할 수 있습니다. 더 정확하게는, 현재 애플리케이션 호출 내에서 start_response가 이미 호출된 경우 exc_info 인수 없이 start_response를 호출하는 것은 치명적인 오류입니다. (올바른 논리의 예는 위의 CGI 게이트웨이 예제를 참조하십시오.)

참고: start_response를 구현하는 서버, 게이트웨이 또는 미들웨어는 추적 및 관련 프레임을 통한 순환 참조 생성을 피하기 위해 함수 실행 기간을 넘어서 exc_info 매개변수에 대한 참조가 유지되지 않도록 해야 합니다. 이를 수행하는 가장 간단한 방법은 다음과 같습니다.

def start_response(status, response_headers, exc_info=None):
    if exc_info:
        try:
            # 여기에서 exc_info를 가지고 작업을 수행합니다.
        finally:
            exc_info = None # 순환 참조 방지

예제 CGI 게이트웨이는 이 기술의 또 다른 예를 제공합니다.

Content-Length 헤더 처리

애플리케이션이 Content-Length 헤더를 제공하지 않으면, 서버 또는 게이트웨이는 이를 처리하기 위해 여러 접근 방식 중 하나를 선택할 수 있습니다. 이 중 가장 간단한 방법은 응답이 완료되면 클라이언트 연결을 닫는 것입니다.

그러나 일부 상황에서는 서버 또는 게이트웨이가 Content-Length 헤더를 생성하거나, 적어도 클라이언트 연결을 닫을 필요를 피할 수 있습니다. 애플리케이션이 write() 호출 가능 객체를 호출하지 않고 len()이 1인 이터러블을 반환하면, 서버는 이터러블이 생성한 첫 번째 문자열의 길이를 통해 Content-Length를 자동으로 결정할 수 있습니다.

그리고 서버와 클라이언트 모두 HTTP/1.1 “청크 인코딩 (chunked encoding)”을 지원하는 경우, 서버는 청크 인코딩을 사용하여 각 write() 호출 또는 이터러블이 생성한 문자열에 대해 청크를 보낼 수 있으며, 따라서 각 청크에 대한 Content-Length 헤더를 생성할 수 있습니다. 이렇게 하면 서버가 원하는 경우 클라이언트 연결을 계속 유지할 수 있습니다. 이 작업을 수행할 때 서버는 RFC 2616을 완전히 준수해야 하며, 그렇지 않으면 Content-Length의 부재를 처리하기 위한 다른 전략 중 하나로 돌아가야 합니다.

(참고: 애플리케이션 및 미들웨어는 청킹 (chunking) 또는 gzip 압축과 같은 어떤 종류의 Transfer-Encoding도 출력에 적용해서는 안 됩니다. “hop-by-hop” 작업으로서, 이러한 인코딩은 실제 웹 서버/게이트웨이의 영역입니다. 자세한 내용은 아래 “Other HTTP Features”를 참조하십시오.)

버퍼링 및 스트리밍

일반적으로 애플리케이션은 (적당한 크기의) 출력을 버퍼링하고 한 번에 모두 전송함으로써 최상의 처리량을 달성합니다. 이는 Zope와 같은 기존 프레임워크에서 흔히 사용되는 접근 방식입니다. 출력은 StringIO 또는 유사한 객체에 버퍼링된 다음, 응답 헤더와 함께 한 번에 전송됩니다.

WSGI에서 해당하는 접근 방식은 애플리케이션이 응답 본문을 단일 문자열로 포함하는 단일 요소 이터러블 (예: 리스트)을 단순히 반환하는 것입니다. 이는 텍스트가 메모리에 쉽게 들어가는 HTML 페이지를 렌더링하는 대다수의 애플리케이션 기능에 권장되는 접근 방식입니다.

그러나 대용량 파일의 경우, 또는 HTTP 스트리밍의 특수 용도 (예: 다중 파트 “서버 푸시”)의 경우, 애플리케이션은 더 작은 블록으로 출력을 제공해야 할 수 있습니다 (예: 대용량 파일을 메모리로 로드하는 것을 피하기 위해). 때로는 응답의 일부를 생성하는 데 시간이 오래 걸릴 수 있지만, 그 앞에 오는 응답 부분을 미리 보내는 것이 유용할 때도 있습니다.

이러한 경우, 애플리케이션은 일반적으로 블록 단위로 출력을 생성하는 이터레이터 (종종 제너레이터-이터레이터)를 반환합니다. 이러한 블록은 다중 파트 경계와 일치하도록 분할되거나 (“서버 푸시”의 경우), 시간이 많이 걸리는 작업 (예: 디스크에 있는 파일의 다른 블록 읽기) 바로 전에 분할될 수 있습니다.

WSGI 서버, 게이트웨이 및 미들웨어는 어떠한 블록의 전송도 지연해서는 안 됩니다. 애플리케이션이 다음 블록을 생성하는 동안에도 블록을 클라이언트에 완전히 전송하거나, 전송을 계속할 것이라고 보장해야 합니다. 서버/게이트웨이 또는 미들웨어는 다음 세 가지 방법 중 하나로 이 보장을 제공할 수 있습니다.

  • 애플리케이션에 제어권을 반환하기 전에 전체 블록을 운영 체제로 보내고 (모든 O/S 버퍼를 플러시하도록 요청합니다).
  • 또는 애플리케이션이 다음 블록을 생성하는 동안 블록이 계속 전송되도록 다른 스레드를 사용합니다.
  • (미들웨어만 해당) 전체 블록을 부모 게이트웨이/서버로 보냅니다.

이러한 보장을 제공함으로써 WSGI는 애플리케이션이 출력 데이터의 임의의 지점에서 전송이 멈추지 않도록 할 수 있습니다. 이는 예를 들어, 다중 파트 경계 사이의 데이터가 클라이언트에 완전히 전송되어야 하는 다중 파트 “서버 푸시” 스트리밍의 적절한 작동에 중요합니다.

미들웨어의 블록 경계 처리

비동기 애플리케이션 및 서버를 더 잘 지원하기 위해 미들웨어 구성 요소는 애플리케이션 이터러블에서 여러 값을 기다리며 이터레이션을 차단해서는 안 됩니다. 미들웨어가 출력을 생성하기 전에 애플리케이션에서 더 많은 데이터를 축적해야 하는 경우, 빈 문자열을 생성해야 합니다.

이 요구 사항을 다른 방식으로 말하면, 미들웨어 구성 요소는 기본 애플리케이션이 값을 생성할 때마다 적어도 하나의 값을 생성해야 합니다. 미들웨어가 다른 값을 생성할 수 없는 경우, 빈 문자열을 생성해야 합니다.

이 요구 사항은 비동기 애플리케이션과 서버가 주어진 수의 애플리케이션 인스턴스를 동시에 실행하는 데 필요한 스레드 수를 줄이기 위해 협력할 수 있도록 보장합니다.

또한 이 요구 사항은 미들웨어가 기본 애플리케이션이 이터러블을 반환하는 즉시 이터러블을 반환해야 함을 의미합니다. 미들웨어가 기본 애플리케이션에 의해 생성된 데이터를 전송하기 위해 write() 호출 가능 객체를 사용하는 것도 금지됩니다. 미들웨어는 기본 애플리케이션이 미들웨어 제공 write() 호출 가능 객체를 사용하여 보낸 데이터를 전송하기 위해서만 부모 서버의 write() 호출 가능 객체를 사용할 수 있습니다.

write() 호출 가능 객체

일부 기존 애플리케이션 프레임워크 API는 WSGI와 다른 방식으로 버퍼링되지 않은 출력을 지원합니다. 구체적으로, 버퍼링되지 않은 데이터 블록을 작성하기 위한 “write” 함수 또는 메서드를 제공하거나, 버퍼링된 “write” 함수와 버퍼를 플러시하기 위한 “flush” 메커니즘을 제공합니다.

불행히도, 이러한 API는 스레드 또는 다른 특수 메커니즘이 사용되지 않는 한 WSGI의 “이터러블” 애플리케이션 반환 값으로 구현될 수 없습니다.

따라서 이러한 프레임워크가 명령형 API를 계속 사용할 수 있도록 WSGI는 start_response 호출 가능 객체가 반환하는 특별한 write() 호출 가능 객체를 포함합니다.

새로운 WSGI 애플리케이션 및 프레임워크는 가능하다면 write() 호출 가능 객체를 사용하지 않아야 합니다. write() 호출 가능 객체는 명령형 스트리밍 API를 지원하기 위한 엄격한 편법입니다. 일반적으로 애플리케이션은 반환된 이터러블을 통해 출력을 생성해야 합니다. 이는 웹 서버가 동일한 Python 스레드에서 다른 작업을 번갈아 수행할 수 있도록 하여, 전체 서버의 처리량을 잠재적으로 향상시킬 수 있기 때문입니다.

write() 호출 가능 객체는 start_response() 호출 가능 객체가 반환하며, HTTP 응답 본문의 일부로 작성될 문자열 하나를 매개변수로 받습니다. 이 문자열은 출력 이터러블이 생성한 것처럼 정확하게 처리됩니다. 즉, write()가 반환하기 전에 전달된 문자열이 클라이언트에 완전히 전송되었거나, 애플리케이션이 계속 진행되는 동안 전송을 위해 버퍼링되었음을 보장해야 합니다.

애플리케이션은 응답 본문의 전부 또는 일부를 생성하기 위해 write()를 사용하더라도 이터러블 객체를 반환해야 합니다. 반환된 이터러블은 비어 있을 수 있지만 (즉, 비어 있지 않은 문자열을 생성하지 않음), 비어 있지 않은 문자열을 생성하는 경우 해당 출력은 서버 또는 게이트웨이에 의해 정상적으로 처리되어야 합니다 (즉, 즉시 전송되거나 대기열에 추가되어야 함). 애플리케이션은 반환 이터러블 내에서 write()를 호출해서는 안 되며, 따라서 이터러블이 생성하는 모든 문자열은 write()에 전달된 모든 문자열이 클라이언트에 전송된 후에 전송됩니다.

유니코드 문제

HTTP는 유니코드를 직접 지원하지 않으며, 이 인터페이스도 마찬가지입니다. 모든 인코딩/디코딩은 애플리케이션이 처리해야 합니다. 서버로 또는 서버에서 전달되는 모든 문자열은 표준 Python 바이트 문자열이어야 하며, 유니코드 객체가 아니어야 합니다. 문자열 객체가 필요한 곳에 유니코드 객체를 사용하는 경우의 결과는 정의되지 않습니다.

또한 start_response()에 상태 또는 응답 헤더로 전달되는 문자열은 인코딩과 관련하여 RFC 2616을 따라야 합니다. 즉, ISO-8859-1 문자이거나 RFC 2047 MIME 인코딩을 사용해야 합니다.

str 또는 StringType 유형이 실제로 유니코드 기반인 Python 플랫폼 (예: Jython, IronPython, Python 3000 등)에서는 이 사양에서 언급된 모든 “문자열”은 ISO-8859-1 인코딩으로 표현 가능한 코드 포인트 (포함하여 \u0000부터 \u00FF까지)만 포함해야 합니다. 애플리케이션이 다른 유니코드 문자 또는 코드 포인트를 포함하는 문자열을 제공하는 것은 치명적인 오류입니다. 마찬가지로, 서버와 게이트웨이는 다른 유니코드 문자를 포함하는 문자열을 애플리케이션에 제공해서는 안 됩니다.

다시 한번 말하지만, 이 사양에서 언급된 모든 문자열은 str 또는 StringType 유형이어야 하며, unicode 또는 UnicodeType 유형이 아니어야 합니다. 그리고 주어진 플랫폼이 str/StringType 객체에서 문자당 8비트 이상을 허용하더라도, 이 사양에서 “문자열”로 언급된 모든 값에 대해 하위 8비트만 사용될 수 있습니다.

오류 처리

일반적으로 애플리케이션은 자체 내부 오류를 포착하고 브라우저에 유용한 메시지를 표시하려고 노력해야 합니다. (이러한 맥락에서 “유용한” 것이 무엇을 의미하는지는 애플리케이션이 결정할 문제입니다.)

그러나 이러한 메시지를 표시하려면 애플리케이션이 아직 브라우저에 데이터를 전송해서는 안 됩니다. 그렇지 않으면 응답이 손상될 위험이 있습니다. 따라서 WSGI는 애플리케이션이 오류 메시지를 보내거나 자동으로 중단될 수 있는 메커니즘을 제공합니다. 즉, start_responseexc_info 인수입니다. 다음은 그 사용 예입니다.

try:
    # 여기에 일반 애플리케이션 코드
    status = "200 Froody"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers)
    return ["normal body goes here"]
except:
    # XXX MemoryError, KeyboardInterrupt와 같은 런타임 문제는
    # 이 bare 'except:' 이전에 별도의 핸들러에서 포착해야 합니다.
    status = "500 Oops"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers, sys.exc_info())
    return ["error body goes here"]

예외가 발생했을 때 아무런 출력이 작성되지 않았다면, start_response 호출은 정상적으로 반환되며, 애플리케이션은 브라우저로 전송될 오류 본문을 반환할 것입니다. 그러나 이미 브라우저에 출력이 전송된 경우, start_response는 제공된 예외를 다시 발생시킬 것입니다. 이 예외는 애플리케이션에 의해 포착되어서는 안 되므로, 애플리케이션은 중단됩니다. 그러면 서버 또는 게이트웨이가 이 (치명적인) 예외를 포착하고 응답을 중단시킬 수 있습니다.

서버는 애플리케이션 또는 반환 값의 이터레이션을 중단시키는 모든 예외를 포착하고 기록해야 합니다. 애플리케이션 오류가 발생했을 때 부분적인 응답이 이미 브라우저에 작성된 경우, 서버 또는 게이트웨이는 이미 전송된 헤더가 서버가 깨끗하게 수정할 수 있는 text/* 콘텐츠 유형을 나타내는 경우 출력에 오류 메시지를 추가하려고 시도할 수 있습니다.

일부 미들웨어는 추가 예외 처리 서비스를 제공하거나, 애플리케이션 오류 메시지를 가로채고 대체하기를 원할 수 있습니다. 이러한 경우, 미들웨어는 start_response에 제공된 exc_info를 다시 발생시키지 않고, 대신 미들웨어별 예외를 발생시키거나, 제공된 인수를 저장한 후 예외 없이 단순히 반환하기로 선택할 수 있습니다. 그러면 애플리케이션이 오류 본문 이터러블을 반환하거나 (write()를 호출함) 미들웨어가 오류 출력을 캡처하고 수정할 수 있도록 허용합니다. 이러한 기술은 애플리케이션 작성자가 다음을 준수하는 한 작동합니다.

  • 오류 응답을 시작할 때 항상 exc_info를 제공합니다.
  • exc_info가 제공될 때 start_response에 의해 발생된 오류를 절대로 포착하지 않습니다.

HTTP 1.1 Expect/Continue

HTTP 1.1을 구현하는 서버 및 게이트웨이는 HTTP 1.1의 “expect/continue” 메커니즘에 대한 투명한 지원을 제공해야 합니다. 이는 여러 가지 방법 중 하나로 수행될 수 있습니다.

  • Expect: 100-continue 요청을 포함하는 요청에 즉시 “100 Continue” 응답으로 응답하고 정상적으로 진행합니다.
  • 요청을 정상적으로 진행하지만, 애플리케이션이 입력 스트림에서 처음 읽으려고 시도할 때 “100 Continue” 응답을 보낼 wsgi.input 스트림을 애플리케이션에 제공합니다. 읽기 요청은 클라이언트가 응답할 때까지 계속 차단되어야 합니다.
  • 클라이언트가 서버가 expect/continue를 지원하지 않는다고 판단하고 자체적으로 요청 본문을 보낼 때까지 기다립니다. (이는 최적이 아니며 권장되지 않습니다.)

이러한 동작 제한은 HTTP 1.0 요청 또는 애플리케이션 객체로 직접 전달되지 않는 요청에는 적용되지 않습니다. HTTP 1.1 Expect/Continue에 대한 자세한 내용은 RFC 2616, 섹션 8.2.3 및 10.1.1을 참조하십시오.

기타 HTTP 기능

일반적으로 서버와 게이트웨이는 “바보처럼 (play dumb)” 행동하고 애플리케이션이 출력에 대한 완전한 제어권을 갖도록 허용해야 합니다. 그들은 애플리케이션 응답의 유효한 의미를 변경하지 않는 변경만 해야 합니다. 애플리케이션 개발자가 추가 기능을 제공하기 위해 미들웨어 구성 요소를 추가하는 것은 항상 가능하므로, 서버/게이트웨이 개발자는 구현에 있어 보수적이어야 합니다. 어떤 의미에서는 서버는 자신을 HTTP “게이트웨이 서버”처럼 간주해야 하며, 애플리케이션은 HTTP “오리진 서버”처럼 간주해야 합니다. (이러한 용어의 정의는 RFC 2616, 섹션 1.3을 참조하십시오.)

그러나 WSGI 서버와 애플리케이션은 HTTP를 통해 통신하지 않으므로, RFC 2616이 “hop-by-hop” 헤더라고 부르는 것은 WSGI 내부 통신에는 적용되지 않습니다. WSGI 애플리케이션은 “hop-by-hop” 헤더를 생성해서는 안 되며, 그러한 헤더를 생성해야 하는 HTTP 기능을 사용하려고 시도해서는 안 되며, environ 딕셔너리에 들어오는 “hop-by-hop” 헤더의 내용에 의존해서도 안 됩니다. WSGI 서버는 지원되는 모든 수신 “hop-by-hop” 헤더를 자체적으로 처리해야 합니다. 예를 들어, 청크 인코딩을 포함하여 모든 수신 Transfer-Encoding을 디코딩하는 방식입니다.

이러한 원칙을 다양한 HTTP 기능에 적용하면, 서버는 If-None-MatchIf-Modified-Since 요청 헤더와 Last-ModifiedETag 응답 헤더를 통해 캐시 유효성 검사를 처리할 수 있음이 분명해집니다. 그러나 서버는 이를 수행할 필요가 없으며, 서버/게이트웨이가 그러한 유효성 검사를 수행할 필요가 없으므로 애플리케이션이 해당 기능을 지원하려면 자체적으로 캐시 유효성 검사를 수행해야 합니다.

마찬가지로, 서버는 애플리케이션의 응답을 재인코딩하거나 전송 인코딩할 수 있지만, 애플리케이션은 자체적으로 적절한 콘텐츠 인코딩을 사용해야 하며, 전송 인코딩을 적용해서는 안 됩니다. 서버는 클라이언트가 요청하고 애플리케이션이 바이트 범위를 기본적으로 지원하지 않는 경우 애플리케이션 응답의 바이트 범위를 전송할 수 있습니다. 다시 말하지만, 애플리케이션은 원하는 경우 이 기능을 자체적으로 수행해야 합니다.

이러한 애플리케이션에 대한 제한은 모든 애플리케이션이 모든 HTTP 기능을 재구현해야 한다는 것을 의미하지는 않는다는 점에 유의하십시오. 많은 HTTP 기능은 미들웨어 구성 요소에 의해 부분적으로 또는 완전히 구현될 수 있으므로, 서버 및 애플리케이션 작성자 모두 동일한 기능을 반복해서 구현할 필요가 없습니다.

스레드 지원

스레드 지원 또는 그 부족은 서버에 따라 다릅니다. 여러 요청을 병렬로 실행할 수 있는 서버는 스레드 안전 (thread-safe)하지 않은 애플리케이션이나 프레임워크도 해당 서버와 함께 사용할 수 있도록 애플리케이션을 단일 스레드 방식으로 실행하는 옵션을 제공해야 합니다.

구현/애플리케이션 참고 사항

서버 확장 API

일부 서버 작성자는 애플리케이션 또는 프레임워크 작성자가 특수 목적을 위해 사용할 수 있는 더 고급 API를 노출하기를 원할 수 있습니다. 예를 들어, mod_python 기반의 게이트웨이는 Apache API의 일부를 WSGI 확장으로 노출하기를 원할 수 있습니다.

가장 간단한 경우에는 mod_python.some_api와 같은 environ 변수를 정의하는 것 외에 아무것도 필요하지 않습니다. 그러나 많은 경우 미들웨어의 존재로 인해 어려움을 겪을 수 있습니다. 예를 들어, environ 변수에서 찾을 수 있는 것과 동일한 HTTP 헤더에 대한 액세스를 제공하는 API는 environ이 미들웨어에 의해 수정된 경우 다른 데이터를 반환할 수 있습니다.

일반적으로 WSGI 기능의 일부를 복제, 대체 또는 우회하는 모든 확장 API는 미들웨어 구성 요소와 호환되지 않을 위험이 있습니다. 서버/게이트웨이 개발자는 아무도 미들웨어를 사용하지 않을 것이라고 가정해서는 안 됩니다. 일부 프레임워크 개발자는 프레임워크를 다양한 종류의 미들웨어로 거의 완전히 기능하도록 구성하거나 재구성할 의도가 있기 때문입니다.

따라서 최대의 호환성을 제공하기 위해 WSGI 기능의 일부를 대체하는 확장 API를 제공하는 서버 및 게이트웨이는 해당 API가 대체하는 API 부분을 사용하여 호출되도록 해당 API를 설계해야 합니다. 예를 들어, HTTP 요청 헤더에 액세스하는 확장 API는 애플리케이션이 현재 environ을 전달하도록 요구해야 합니다. 그래야 서버/게이트웨이가 API를 통해 액세스 가능한 HTTP 헤더가 미들웨어에 의해 변경되지 않았음을 확인할 수 있습니다. 확장 API가 HTTP 헤더의 내용에 대해 항상 environ과 일치할 것을 보장할 수 없다면, 해당 API는 애플리케이션에 서비스를 거부해야 합니다. 예를 들어, 오류를 발생시키거나, 헤더 컬렉션 대신 None을 반환하거나, API에 적절한 다른 조치를 취하는 방식입니다.

마찬가지로, 확장 API가 응답 데이터 또는 헤더를 작성하는 대체 수단을 제공하는 경우, 애플리케이션이 확장된 서비스를 얻기 전에 start_response 호출 가능 객체가 전달되도록 요구해야 합니다. 전달된 객체가 서버/게이트웨이가 원래 애플리케이션에 제공한 것과 동일하지 않은 경우, 올바른 작동을 보장할 수 없으며 애플리케이션에 확장된 서비스를 제공하는 것을 거부해야 합니다.

이러한 지침은 구문 분석된 쿠키, 폼 변수, 세션 등과 같은 정보를 environ에 추가하는 미들웨어에도 적용됩니다. 구체적으로, 이러한 미들웨어는 단순히 environ에 값을 채워넣는 대신 environ에서 작동하는 함수로 이러한 기능을 제공해야 합니다. 이는 미들웨어가 URL 재작성 또는 기타 environ 수정을 수행한 후에 정보가 environ에서 계산되도록 보장하는 데 도움이 됩니다.

미들웨어 개발자들이 이러한 확장을 사용하는 애플리케이션에 의해 중재가 우회되지 않도록 environ에서 모든 확장 API를 삭제해야 하는 미래를 피하기 위해서는 서버/게이트웨이 및 미들웨어 개발자 모두 이러한 “안전한 확장” 규칙을 따르는 것이 매우 중요합니다!

애플리케이션 구성

이 사양은 서버가 호출할 애플리케이션을 선택하거나 얻는 방법을 정의하지 않습니다. 이러한 및 기타 구성 옵션은 서버에 따라 매우 다릅니다. 서버/게이트웨이 작성자는 특정 애플리케이션 객체를 실행하도록 서버를 구성하는 방법과 어떤 옵션 (예: 스레딩 옵션)을 사용할지 문서화할 것으로 예상됩니다.

반면에 프레임워크 작성자는 프레임워크의 기능을 감싸는 애플리케이션 객체를 생성하는 방법을 문서화해야 합니다. 서버와 애플리케이션 프레임워크를 모두 선택한 사용자는 두 가지를 연결해야 합니다. 그러나 프레임워크와 서버 모두 이제 공통 인터페이스를 가지므로, 이는 새로운 서버/프레임워크 쌍마다 상당한 엔지니어링 노력이 필요한 대신 단순히 기계적인 문제가 되어야 합니다.

마지막으로, 일부 애플리케이션, 프레임워크 및 미들웨어는 environ 딕셔너리를 사용하여 간단한 문자열 구성 옵션을 받기를 원할 수 있습니다. 서버 및 게이트웨이는 애플리케이션 배포자가 environ에 배치될 이름-값 쌍을 지정할 수 있도록 허용함으로써 이를 지원해야 합니다. 가장 간단한 경우, 이 지원은 모든 운영 체제가 제공한 환경 변수를 os.environ에서 environ 딕셔너리로 복사하는 것으로 구성될 수 있습니다. 이는 배포자가 원칙적으로 서버 외부에서 또는 CGI의 경우 서버의 구성 파일을 통해 이러한 변수를 구성할 수 있기 때문입니다.

모든 서버가 이러한 변수의 쉬운 구성을 지원하지는 않으므로, 애플리케이션은 이러한 필수 변수를 최소한으로 유지하려고 노력해야 합니다. 물론, 최악의 경우에도 애플리케이션을 배포하는 사람들은 필요한 구성 값을 제공하기 위한 스크립트를 생성할 수 있습니다.

from the_app import application

def new_app(environ, start_response):
    environ['the_app.configval1'] = 'something'
    return application(environ, start_response)

하지만, 대부분의 기존 애플리케이션 및 프레임워크는 애플리케이션 또는 프레임워크별 구성 파일의 위치를 나타내기 위해 environ에서 단 하나의 구성 값만 필요할 것입니다. (물론, 애플리케이션은 각 호출 시 다시 읽는 것을 피하기 위해 이러한 구성을 캐시해야 합니다.)

URL 재구성

애플리케이션이 요청의 완전한 URL을 재구성하고자 하는 경우, Ian Bicking이 기여한 다음 알고리즘을 사용하여 재구성할 수 있습니다.

from urllib import quote

url = environ['wsgi.url_scheme']+'://'
if environ.get('HTTP_HOST'):
    url += environ['HTTP_HOST']
else:
    url += environ['SERVER_NAME']

if environ['wsgi.url_scheme'] == 'https':
    if environ['SERVER_PORT'] != '443':
        url += ':' + environ['SERVER_PORT']
else:
    if environ['SERVER_PORT'] != '80':
        url += ':' + environ['SERVER_PORT']

url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
    url += '?' + environ['QUERY_STRING']

이렇게 재구성된 URL이 클라이언트가 요청한 URI와 정확히 같지 않을 수 있다는 점에 유의하십시오. 예를 들어, 서버 재작성 규칙은 클라이언트가 원래 요청한 URL을 정규 형식으로 만들기 위해 수정했을 수 있습니다.

이전 (<2.2) Python 버전 지원

일부 서버, 게이트웨이 또는 애플리케이션은 이전 (<2.2) Python 버전을 지원하기를 원할 수 있습니다. 이는 이 글을 쓰는 시점에서 Jython 2.2의 프로덕션 준비 버전이 아직 제공되지 않으므로 Jython이 대상 플랫폼인 경우 특히 중요합니다.

서버 및 게이트웨이의 경우, 이는 비교적 간단합니다. Python 2.2 이전 버전을 대상으로 하는 서버 및 게이트웨이는 애플리케이션이 반환하는 모든 이터러블을 반복하기 위해 표준 “for” 루프만 사용하도록 제한해야 합니다. 이는 Python 2.2 이전 이터레이터 프로토콜 (아래에서 더 자세히 논의)과 “오늘날의” 이터레이터 프로토콜 (PEP 234 참조) 모두와의 소스 수준 호환성을 보장하는 유일한 방법입니다.

(참고: 이 기술은 Python으로 작성된 서버, 게이트웨이 또는 미들웨어에만 적용됩니다. 다른 언어에서 이터레이터 프로토콜을 올바르게 사용하는 방법에 대한 논의는 이 PEP의 범위 밖에 있습니다.)

애플리케이션의 경우, Python 2.2 이전 버전 지원은 약간 더 복잡합니다.

  • 파일 객체를 반환하고 이터러블로 작동할 것을 기대해서는 안 됩니다. Python 2.2 이전에는 파일이 이터러블이 아니었기 때문입니다. (일반적으로 이것은 대부분의 경우 매우 성능이 좋지 않으므로 어쨌든 이렇게 해서는 안 됩니다!) wsgi.file_wrapper 또는 애플리케이션별 파일 래퍼 클래스를 사용하십시오. (wsgi.file_wrapper에 대한 자세한 내용과 파일을 이터러블로 감싸는 데 사용할 수 있는 예제 클래스는 “Optional Platform-Specific File Handling”을 참조하십시오.)
  • 사용자 정의 이터러블을 반환하는 경우, Python 2.2 이전 이터레이터 프로토콜을 구현해야 합니다. 즉, 정수 키를 받아들이고 소진되면 IndexError를 발생시키는 __getitem__ 메서드를 제공해야 합니다. (내장 시퀀스 유형도 이 프로토콜을 구현하므로 허용됩니다.)

마지막으로, Python 2.2 이전 버전을 지원하기를 원하고 애플리케이션 반환 값을 반복하거나 자체적으로 이터러블을 반환하는 (또는 둘 다) 미들웨어는 위의 적절한 권장 사항을 따라야 합니다.

(참고: Python 2.2 이전 버전을 지원하려면 모든 서버, 게이트웨이, 애플리케이션 또는 미들웨어는 대상 버전에서 사용할 수 있는 언어 기능만 사용해야 하며, TrueFalse 대신 10을 사용해야 하는 등의 사항은 말할 필요도 없을 것입니다.)

선택적 플랫폼별 파일 처리

일부 운영 환경은 Unix의 sendfile() 호출과 같은 특별한 고성능 파일 전송 기능을 제공합니다. 서버 및 게이트웨이는 environ의 선택적 wsgi.file_wrapper 키를 통해 이 기능을 노출할 수 있습니다. 애플리케이션은 이 “파일 래퍼”를 사용하여 파일 또는 파일과 유사한 객체를 이터러블로 변환한 다음 이를 반환할 수 있습니다. 예를 들어:

if 'wsgi.file_wrapper' in environ:
    return environ['wsgi.file_wrapper'](filelike, block_size)
else:
    return iter(lambda: filelike.read(block_size), '')

서버 또는 게이트웨이가 wsgi.file_wrapper를 제공하는 경우, 이는 하나의 필수 위치 매개변수와 하나의 선택적 위치 매개변수를 받는 호출 가능한 객체여야 합니다. 첫 번째 매개변수는 전송될 파일과 유사한 객체이며, 두 번째 매개변수는 선택적 블록 크기 “제안”입니다 (서버/게이트웨이는 이를 사용할 필요가 없습니다). 호출 가능한 객체는 이터러블 객체를 반환해야 하며, 서버/게이트웨이가 애플리케이션에서 반환 값으로 이터러블을 실제로 받기 전까지는 어떠한 데이터 전송도 수행해서는 안 됩니다. (그렇지 않으면 미들웨어가 응답 데이터를 해석하거나 재정의할 수 없게 됩니다.)

“파일과 유사한” 것으로 간주되려면, 애플리케이션이 제공하는 객체는 선택적 size 인수를 받는 read() 메서드를 가지고 있어야 합니다. close() 메서드를 가질 수 있으며, 그렇다면 wsgi.file_wrapper가 반환하는 이터러블은 원래 파일과 유사한 객체의 close() 메서드를 호출하는 close() 메서드를 가지고 있어야 합니다. “파일과 유사한” 객체가 Python 내장 파일 객체와 이름이 일치하는 다른 메서드나 속성 (예: fileno())을 가지고 있다면, wsgi.file_wrapper는 이러한 메서드나 속성이 내장 파일 객체의 메서드나 속성과 동일한 의미를 가진다고 가정할 수 있습니다.

모든 플랫폼별 파일 처리의 실제 구현은 애플리케이션이 반환하고 서버 또는 게이트웨이가 래퍼 객체가 반환되었는지 확인한 후에 발생해야 합니다. (다시 말하지만, 미들웨어, 오류 핸들러 등이 존재하기 때문에 생성된 래퍼가 실제로 사용될 것이라는 보장은 없습니다.)

close() 처리 외에, 애플리케이션에서 파일 래퍼를 반환하는 의미는 애플리케이션이 iter(filelike.read, '')를 반환한 것과 동일해야 합니다. 즉, 전송은 전송이 시작될 때 “파일” 내의 현재 위치에서 시작하여 끝에 도달할 때까지 계속되어야 합니다.

물론, 플랫폼별 파일 전송 API는 일반적으로 임의의 “파일과 유사한” 객체를 받아들이지 않습니다. 따라서 wsgi.file_wrapper는 파일과 유사한 객체가 지원하는 플랫폼별 API와 함께 사용하기에 적합한지 확인하기 위해 fileno() (Unix와 유사한 OS) 또는 java.nio.FileChannel (Jython)과 같은 것을 검사해야 합니다.

객체가 플랫폼 API에 적합하지 않더라도, wsgi.file_wrapperread()close()를 감싸는 이터러블을 반환해야 합니다. 이는 파일 래퍼를 사용하는 애플리케이션이 플랫폼 간에 이식성을 가질 수 있도록 하기 위함입니다. 다음은 오래된 (2.2 이전) 및 새로운 Python 모두에 적합한 간단한 플랫폼 독립적인 파일 래퍼 클래스입니다.

class FileWrapper:
    def __init__(self, filelike, blksize=8192):
        self.filelike = filelike
        self.blksize = blksize
        if hasattr(filelike, 'close'):
            self.close = filelike.close

    def __getitem__(self, key):
        data = self.filelike.read(self.blksize)
        if data:
            return data
        raise IndexError

그리고 다음은 이를 사용하여 플랫폼별 API에 대한 액세스를 제공하는 서버/게이트웨이의 코드 스니펫입니다.

environ['wsgi.file_wrapper'] = FileWrapper
result = application(environ, start_response)
try:
    if isinstance(result, FileWrapper):
        # result.filelike가 플랫폼별 API에 사용할 수 있는지 확인하고,
        # 그렇다면 해당 API를 사용하여 결과를 전송합니다.
        # 그렇지 않다면 아래의 일반 이터러블 처리 루프로 넘어갑니다.
        # loop below.
        for data in result:
            # etc.
finally:
    if hasattr(result, 'close'):
        result.close()

질문 및 답변

environ은 왜 딕셔너리여야 하는가? 하위 클래스를 사용하면 무엇이 문제인가?

딕셔너리를 요구하는 이유는 서버 간의 이식성을 극대화하기 위함입니다. 대안은 딕셔너리의 메서드 중 일부를 표준 및 이식 가능한 인터페이스로 정의하는 것일 것입니다. 그러나 실제로는 대부분의 서버가 딕셔너리가 자신들의 필요에 충분하다고 생각할 것이고, 따라서 프레임워크 작성자들은 딕셔너리의 전체 기능 집합을 사용할 수 있을 것이라고 기대할 것입니다. 하지만 일부 서버가 딕셔너리를 사용하지 않기로 선택하면, 해당 서버가 사양을 “준수”하더라도 상호 운용성 문제가 발생할 것입니다. 따라서 딕셔너리를 필수로 만들면 사양이 단순화되고 상호 운용성이 보장됩니다.

이는 서버 또는 프레임워크 개발자가 environ 딕셔너리 내에 사용자 정의 변수로 특수 서비스를 제공하는 것을 방해하지 않습니다. 이는 그러한 부가가치 서비스를 제공하기 위한 권장되는 접근 방식입니다.

write()를 호출하고 문자열을 yield하거나 이터러블을 반환할 수 있는 이유는 무엇인가? 한 가지 방법만 선택해야 하는가?

반복 접근 방식만 지원한다면 “푸시 (push)”의 가용성을 가정하는 현재 프레임워크는 어려움을 겪을 것입니다. 그러나 write()를 통한 푸시만 지원한다면, 예를 들어 대용량 파일 전송 시 서버 성능이 저하됩니다 (모든 출력이 전송될 때까지 작업 스레드가 새 요청 작업을 시작할 수 없는 경우). 따라서 이러한 절충안은 애플리케이션 프레임워크가 적절하게 두 가지 접근 방식을 모두 지원할 수 있도록 하며, 푸시 전용 접근 방식보다 서버 구현자에게 약간 더 많은 부담만 줍니다.

close()는 무엇을 위한 것인가?

애플리케이션 객체 실행 중에 쓰기 작업이 수행될 때, 애플리케이션은 try/finally 블록을 사용하여 리소스가 해제되도록 할 수 있습니다. 그러나 애플리케이션이 이터러블을 반환하면, 사용된 모든 리소스는 이터러블이 가비지 컬렉션될 때까지 해제되지 않습니다. close() 관용구는 애플리케이션이 요청이 끝날 때 중요한 리소스를 해제할 수 있도록 하며, PEP 325에서 제안된 제너레이터의 try/finally 지원과 미래에 호환됩니다.

이 인터페이스는 왜 이렇게 저수준인가? 나는 X 기능 (예: 쿠키, 세션, 영속성 등)을 원한다!

이것은 또 다른 Python 웹 프레임워크가 아닙니다. 이는 단순히 프레임워크가 웹 서버와 통신하는 방법이며, 그 반대도 마찬가지입니다. 이러한 기능을 원한다면, 원하는 기능을 제공하는 웹 프레임워크를 선택해야 합니다. 그리고 해당 프레임워크가 WSGI 애플리케이션을 생성하도록 허용한다면, 대부분의 WSGI 지원 서버에서 이를 실행할 수 있을 것입니다. 또한 일부 WSGI 서버는 environ 딕셔너리에 제공된 객체를 통해 추가 서비스를 제공할 수 있습니다. 자세한 내용은 해당 서버 문서를 참조하십시오. (물론, 그러한 확장을 사용하는 애플리케이션은 다른 WSGI 기반 서버에는 이식성이 없을 것입니다.)

왜 일반적인 HTTP 헤더 대신 CGI 변수를 사용하는가? 그리고 왜 WSGI 정의 변수와 섞어 사용하는가?

많은 기존 웹 프레임워크는 CGI 사양에 크게 의존하고 있으며, 기존 웹 서버는 CGI 변수를 생성하는 방법을 알고 있습니다. 대조적으로, 수신 HTTP 정보를 나타내는 다른 방법들은 파편화되어 있고 시장 점유율이 낮습니다. 따라서 CGI “표준”을 사용하는 것이 기존 구현을 활용하는 좋은 방법으로 보입니다. WSGI 변수와 섞어 사용하는 것에 대해서는, 이를 분리하면 두 개의 딕셔너리 인수를 전달해야 하며, 실질적인 이점은 없습니다.

상태 문자열은 어떤가? “200 OK” 대신 200을 전달하는 식으로 숫자만 사용하면 안 되는가?

이렇게 하면 숫자 상태 코드와 해당 메시지 테이블을 서버 또는 게이트웨이가 갖도록 요구하여 복잡하게 만들 것입니다. 대조적으로, 애플리케이션 또는 프레임워크 작성자가 사용하는 특정 응답 코드에 추가 텍스트를 입력하는 것은 쉽고, 기존 프레임워크는 필요한 메시지를 포함하는 테이블을 이미 가지고 있는 경우가 많습니다. 따라서 전체적으로 애플리케이션/프레임워크가 서버 또는 게이트웨이보다 책임지는 것이 더 낫다고 보입니다.

wsgi.run_once가 앱을 한 번만 실행한다고 보장하지 않는 이유는 무엇인가?

이는 단지 애플리케이션에게 “자주 실행되지 않도록 준비하라”는 제안일 뿐이기 때문입니다. 이는 캐싱, 세션 등 다양한 작동 모드를 가진 애플리케이션 프레임워크를 위한 것입니다. “다중 실행” 모드에서는 이러한 프레임워크가 캐시를 미리 로드할 수 있으며, 각 요청 후에 로그나 세션 데이터를 디스크에 쓰지 않을 수 있습니다. “단일 실행” 모드에서는 이러한 프레임워크가 사전 로드를 피하고 각 요청 후에 필요한 모든 쓰기를 플러시합니다.

그러나 후자의 모드에서 올바른 작동을 확인하기 위해 애플리케이션 또는 프레임워크를 테스트하려면 여러 번 호출해야 할 수 있습니다 (또는 적어도 편리할 수 있음). 따라서 wsgi.run_onceTrue로 설정되었다는 이유만으로 애플리케이션이 다시 실행되지 않을 것이라고 가정해서는 안 됩니다.

기능 X (딕셔너리, 호출 가능 객체 등)가 애플리케이션 코드에서 사용하기에 보기 좋지 않다. 왜 객체를 대신 사용하지 않는가?

WSGI의 이러한 모든 구현 선택은 기능을 서로 분리하기 위한 것입니다. 이러한 기능을 캡슐화된 객체로 재결합하면 서버 또는 게이트웨이를 작성하기가 약간 더 어려워지고, 전체 기능 중 작은 부분만 대체하거나 수정하는 미들웨어를 작성하기가 훨씬 더 어려워집니다.

본질적으로 미들웨어는 “책임 연쇄 (Chain of Responsibility)” 패턴을 원하며, 이를 통해 일부 함수에 대해서는 “핸들러” 역할을 하면서 다른 함수는 변경되지 않은 상태로 유지할 수 있습니다. 인터페이스가 확장 가능해야 한다면 일반 Python 객체로는 이것이 어렵습니다. 예를 들어, 확장 (예: 미래 WSGI 버전에서 정의될 속성)이 전달되도록 하려면 __getattr__ 또는 __getattribute__ 오버라이드를 사용해야 합니다.

이러한 종류의 코드는 100% 정확하게 작성하기가 매우 어렵기로 악명이 높으며, 직접 작성하려는 사람은 거의 없을 것입니다. 따라서 다른 사람들의 구현을 복사할 것이지만, 복사한 사람이 또 다른 예외 상황을 수정했을 때 업데이트하지 않을 것입니다.

또한, 이 필수적인 상용구 코드는 순전히 추가 비용이 될 것이며, 애플리케이션 프레임워크 개발자를 위한 약간 더 예쁜 API를 지원하기 위해 미들웨어 개발자가 지불하는 개발자 세금이 될 것입니다. 그러나 애플리케이션 프레임워크 개발자는 일반적으로 전체 프레임워크의 매우 제한된 부분에서 WSGI를 지원하기 위해 하나의 프레임워크만 업데이트할 것입니다. 이는 그들의 첫 번째 (그리고 아마도 유일한) WSGI 구현이 될 가능성이 높으며, 따라서 이 사양을 참고하여 구현할 가능성이 높습니다. 따라서 객체 속성 등으로 API를 “더 예쁘게” 만드는 노력은 이러한 사용자에게는 낭비될 가능성이 높습니다.

(웹 프레임워크 개발과 반대로) 직접적인 웹 애플리케이션 프로그래밍에 사용하기 위해 더 예쁘거나 (또는 다른 방식으로 개선된) WSGI 인터페이스를 원하는 사람들에게는 애플리케이션 개발자가 편리하게 사용할 수 있도록 WSGI를 감싸는 API 또는 프레임워크를 개발할 것을 권장합니다. 이러한 방식으로 WSGI는 서버 및 미들웨어 작성자를 위해 편리하게 저수준으로 유지될 수 있으며, 애플리케이션 개발자에게 “보기 흉하지” 않을 수 있습니다.

제안/논의 중

현재 Web-SIG 및 다른 곳에서 논의 중이거나 PEP 작성자의 “할 일” 목록에 있는 항목입니다.

  • wsgi.input이 파일 대신 이터레이터여야 하는가? 이는 비동기 애플리케이션 및 청크 인코딩 입력 스트림에 도움이 될 것입니다.
  • 입력을 사용할 수 있거나 콜백이 발생할 때까지 애플리케이션 출력의 이터레이션을 일시 중지하기 위한 선택적 확장이 논의 중입니다.
  • 동기 대 비동기 앱 및 서버, 관련 스레딩 모델, 그리고 이러한 영역의 문제/설계 목표에 대한 섹션을 추가합니다.

감사의 글

이 개정된 초안을 가능하게 한 Web-SIG 메일링 리스트의 많은 분들의 사려 깊은 피드백에 감사드립니다. 특히:

  • mod_python의 작성자 Gregory “Grisha” Trubetskoy는 첫 번째 초안이 “평범한 CGI”보다 이점이 없다고 비판하여, 더 나은 접근 방식을 찾도록 격려했습니다.
  • Ian Bicking은 멀티스레딩 및 멀티프로세스 옵션을 적절하게 명시하도록 재촉하고, 서버가 애플리케이션에 사용자 정의 확장 데이터를 제공할 수 있는 메커니즘을 제공하도록 압력을 가했습니다.
  • Tony Lownds는 상태 및 헤더를 받아 write 함수를 반환하는 start_response 함수의 개념을 제시했습니다. 그의 의견은 또한 예외 처리 기능의 설계, 특히 애플리케이션 오류 메시지를 재정의하는 미들웨어를 허용하는 영역에서 지침이 되었습니다.
  • Alan Kennedy는 사양이 완성되기 훨씬 전에 Jython에서 WSGI를 구현하려는 용감한 시도를 통해 “이전 Python 버전 지원” 섹션과 선택적 wsgi.file_wrapper 기능의 형성에 기여했습니다.
  • Mark Nottingham은 HTTP RFC 준수 문제, 특히 그가 지적하기 전까지 존재조차 몰랐던 HTTP/1.1 기능과 관련하여 사양을 광범위하게 검토했습니다.

참조

  • The Python Wiki “Web Programming” topic (http://www.python.org/cgi-bin/moinmoin/WebProgramming)
  • The Common Gateway Interface Specification, v 1.1, 3rd Draft (https://datatracker.ietf.org/doc/html/draft-coar-cgi-v11-03)
  • mod_ssl Reference, “Environment Variables” (http://www.modssl.org/docs/2.8/ssl_reference.html#ToC25)

저작권

이 문서는 공개 도메인에 배치되었습니다.

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments