Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

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

Author:
Phillip J. Eby <pje at telecommunity.com>
Discussions-To:
Web-SIG list
Status:
Final
Type:
Informational
Created:
26-Sep-2010
Post-History:
26-Sep-2010, 04-Oct-2010
Replaces:
333

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

PEP 333 독자를 위한 서문

이는 PEP 333을 업데이트한 버전으로, Python 3에서의 사용성을 개선하고 WSGI 프로토콜에 오랫동안 사실상 적용되어 온 여러 개정 사항을 반영하기 위해 약간 수정되었습니다. (코드 샘플도 Python 3으로 포팅되었습니다.)

절차상의 이유로 [6] 이는 별도의 PEP여야 하지만, Python 2.x에서 이전에 호환되던 서버나 애플리케이션을 무효화하는 변경은 이루어지지 않았습니다. 2.x 애플리케이션이나 서버가 PEP 333을 준수한다면, 이 PEP도 준수합니다.

그러나 Python 3에서는 A Note On String TypesUnicode Issues라는 제목의 아래 섹션에 설명된 규칙도 애플리케이션이나 서버가 따라야 합니다.

이 문서와 PEP 333 사이의 자세한 줄 단위 차이점을 확인하려면, 리비전 84854 이후의 SVN 리비전 기록 [7]을 볼 수 있습니다.

초록

이 문서는 웹 서버와 Python 웹 애플리케이션 또는 프레임워크 사이의 제안된 표준 인터페이스를 지정하여, 다양한 웹 서버에서 웹 애플리케이션의 이식성을 높이는 것을 목적으로 합니다.

원래의 근거와 목표(PEP 333에서 발췌)

현재 Python에는 Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Web과 같은 매우 다양한 웹 애플리케이션 프레임워크가 있습니다. 몇 가지만 예로 들면 [1] 이와 같습니다. 이러한 다양한 선택지는 새로운 Python 사용자에게 문제가 될 수 있습니다. 일반적으로 웹 프레임워크의 선택이 사용할 수 있는 웹 서버의 선택을 제한하며, 그 반대도 마찬가지이기 때문입니다.

반면 Java에는 그만큼 많은 웹 애플리케이션 프레임워크가 제공되지만, Java의 “servlet” API를 사용하면 어떤 Java 웹 애플리케이션 프레임워크로 작성된 애플리케이션이든 servlet API를 지원하는 모든 웹 서버에서 실행할 수 있습니다.

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

따라서 이 PEP는 웹 서버와 웹 애플리케이션 또는 프레임워크 사이의 단순하고 범용적인 인터페이스인 Python 웹 서버 게이트웨이 인터페이스(WSGI)를 제안합니다.

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

그러나 기존 서버나 프레임워크 중 WSGI를 지원하는 것이 없으므로, WSGI 지원을 구현하는 작성자가 즉시 얻을 수 있는 보상은 거의 없습니다. 따라서 WSGI는 반드시 구현하기 쉬워야 하며, 이를 통해 작성자가 인터페이스에 처음 투자하는 비용을 합리적으로 낮출 수 있어야 합니다.

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

그러나 프레임워크 작성자가 구현하기 쉽다는 것과 웹 애플리케이션 작성자가 사용하기 쉽다는 것은 같지 않다는 점에 유의하십시오. WSGI는 프레임워크 작성자에게 절대적으로 “꾸밈없는” 인터페이스를 제공합니다. 응답 객체나 쿠키 처리와 같은 부가 기능은 기존 프레임워크가 이러한 문제를 처리하는 데 오히려 방해가 될 뿐이기 때문입니다. 다시 말해 WSGI의 목표는 새로운 웹 프레임워크를 만드는 것이 아니라, 기존 서버와 애플리케이션 또는 프레임워크를 쉽게 연결하는 것입니다.

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

기존 및 향후 프레임워크와 서버에서 쉽게 구현할 수 있어야 할 뿐만 아니라, 요청 전처리기, 응답 후처리기 및 기타 WSGI 기반 “미들웨어” 구성 요소도 쉽게 만들 수 있어야 합니다. 이러한 구성 요소는 자신을 포함하는 서버에는 애플리케이션처럼 보이면서, 자신이 포함하는 애플리케이션에는 서버로 동작합니다.

미들웨어가 단순하면서도 견고할 수 있고 WSGI가 서버와 프레임워크에서 널리 사용된다면, 느슨하게 결합된 WSGI 미들웨어 구성 요소로 이루어진 완전히 새로운 종류의 Python 웹 애플리케이션 프레임워크가 가능해집니다. 실제로 기존 프레임워크 작성자는 프레임워크의 기존 서비스를 이러한 방식으로 제공하도록 리팩터링하여, 모놀리식 프레임워크보다는 WSGI와 함께 사용되는 라이브러리에 가까운 형태로 만들 수도 있습니다. 그러면 애플리케이션 개발자는 하나의 프레임워크가 가진 모든 장단점을 감수해야 하는 대신, 특정 기능에 가장 적합한 구성 요소를 선택할 수 있습니다.

물론 이 글을 작성하는 현재로서는 그날이 아마도 상당히 멀리 있을 것입니다. 그때까지 WSGI가 어떤 프레임워크든 어떤 서버에서든 사용할 수 있게 하는 것은 충분한 단기 목표입니다.

마지막으로, 현재 WSGI 버전은 웹 서버나 서버 게이트웨이에서 사용할 애플리케이션을 “배포”하기 위한 특정 메커니즘을 규정하지 않는다는 점을 언급해야 합니다. 현재로서는 이 사항이 반드시 서버나 게이트웨이의 구현에 따라 정의됩니다. 충분한 수의 서버와 프레임워크가 다양한 배포 요구 사항에 대한 실무 경험을 제공할 수 있도록 WSGI를 구현한 후에는, WSGI 서버와 애플리케이션 프레임워크를 위한 배포 표준을 설명하는 또 다른 PEP를 작성하는 것이 의미 있을 수 있습니다.

사양 개요

WSGI 인터페이스에는 두 측면이 있습니다. “서버” 또는 “게이트웨이” 측면과 “애플리케이션” 또는 “프레임워크” 측면입니다. 서버 측은 애플리케이션 측에서 제공하는 호출 가능 객체를 호출합니다. 해당 객체를 제공하는 방법의 구체적인 사항은 서버나 게이트웨이에 달려 있습니다. 일부 서버나 게이트웨이는 애플리케이션 배포자가 서버나 게이트웨이의 인스턴스를 만들고 애플리케이션 객체를 전달하는 짧은 스크립트를 작성해야 한다고 가정합니다. 다른 서버와 게이트웨이는 애플리케이션 객체를 어디에서 가져와야 하는지, 또는 이를 다른 방식으로 어떻게 얻어야 하는지를 지정하기 위해 구성 파일이나 다른 메커니즘을 사용할 수 있습니다.

“순수한” 서버/게이트웨이와 애플리케이션/프레임워크 외에도, 이 사양의 양쪽 측면을 모두 구현하는 “미들웨어” 구성 요소를 만들 수도 있습니다. 이러한 구성 요소는 자신을 포함하는 서버에 대해서는 애플리케이션으로, 내부에 포함된 애플리케이션에 대해서는 서버로 동작하며, 확장된 API, 콘텐츠 변환, 탐색 및 기타 유용한 기능을 제공하는 데 사용할 수 있습니다.

이 사양 전체에서 “호출 가능 객체”라는 용어는 “함수, 메서드, 클래스 또는 __call__ 메서드를 가진 인스턴스”를 의미하는 데 사용합니다. 호출 가능 객체를 구현하는 서버, 게이트웨이 또는 애플리케이션은 필요에 맞는 적절한 구현 기법을 선택합니다. 반대로, 호출 가능 객체를 호출하는 서버, 게이트웨이 또는 애플리케이션은 제공된 호출 가능 객체의 종류에 대해 must not어떠한 의존성도 가져서는 안 됩니다. 호출 가능 객체는 호출만 해야 하며, 내부 검사를 해서는 안 됩니다.

문자열 유형에 관한 참고 사항

일반적으로 HTTP는 바이트를 다루므로, 이 사양은 대부분 바이트 처리를 다룹니다.

그러나 이러한 바이트의 내용에는 흔히 어떤 형태로든 텍스트 해석이 적용되며, Python에서는 문자열이 텍스트를 처리하는 가장 편리한 방법입니다.

하지만 많은 Python 버전과 구현에서 문자열은 바이트가 아니라 유니코드입니다. 따라서 HTTP의 맥락에서 사용 가능한 API와 바이트 및 텍스트 간의 올바른 변환 사이에 신중한 균형이 필요하며… 특히 서로 다른 str유형을 사용하는 Python 구현 간에 코드를 이식할 수 있도록 해야 합니다.

따라서 WSGI는 “문자열”을 두 종류로 정의합니다.

  • 요청/응답 헤더와 메타데이터에 사용되는 “네이티브” 문자열(항상 str이라는 유형을 사용해 구현됨)
  • 요청과 응답의 본문(예: POST/PUT 입력 데이터와 HTML 페이지 출력)에 사용되는 “바이트 문자열”(Python 3에서는 bytes유형으로, 그 외의 환경에서는 str유형으로 구현됨)

그러나 혼동해서는 안 됩니다. Python의 str유형이 실제로 “내부적으로” 유니코드라 하더라도, 네이티브 문자열의 content는 여전히 Latin-1 인코딩을 통해 바이트로 변환할 수 있어야 합니다! (자세한 내용은 이 문서 뒷부분의 Unicode Issues섹션을 참조하십시오.)

요약하면, 이 문서에서 “문자열”이라는 단어가 보이면 내부적으로 바이트로 구현되었는지 유니코드로 구현되었는지와 관계없이 str유형의 객체인 “네이티브” 문자열을 의미합니다. “바이트 문자열”에 대한 참조는 Python 3에서는 bytes유형의 객체로, Python 2에서는 str유형의 객체로 읽어야 합니다.

따라서 HTTP가 어떤 의미에서는 “실제로는 단지 바이트”라 하더라도, Python의 기본 str유형을 사용하면 많은 API 편의 기능을 활용할 수 있습니다.

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

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

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

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

HELLO_WORLD = b"Hello world!\n"

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [HELLO_WORLD]

class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

    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

서버/게이트웨이 측

서버 또는 게이트웨이는 애플리케이션을 대상으로 하는 HTTP 클라이언트의 각 요청을 받을 때마다 애플리케이션 호출 가능 객체를 한 번씩 호출합니다. 예를 들어, 다음은 애플리케이션 객체를 인자로 받는 함수로 구현된 간단한 CGI 게이트웨이입니다. 이 간단한 예제는 오류 처리가 제한적이라는 점에 유의하십시오. 기본적으로 처리되지 않은 예외는 sys.stderr에 출력되고 웹 서버에 기록되기 때문입니다.

import os, sys

enc, esc = sys.getfilesystemencoding(), 'surrogateescape'

def unicode_to_wsgi(u):
    # Convert an environment variable to a WSGI "bytes-as-unicode" string
    return u.encode(enc, esc).decode('iso-8859-1')

def wsgi_to_bytes(s):
    return s.encode('iso-8859-1')

def run_with_cgi(application):
    environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
    environ['wsgi.input']        = sys.stdin.buffer
    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):
        out = sys.stdout.buffer

        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             out.write(wsgi_to_bytes('Status: %s\r\n' % status))
             for header in response_headers:
                 out.write(wsgi_to_bytes('%s: %s\r\n' % header))
             out.write(wsgi_to_bytes('\r\n'))

        out.write(data)
        out.flush()

    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[1].with_traceback(exc_info[2])
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]

        # Note: error checking on the headers should happen here,
        # *after* the headers are set.  That way, if an error
        # occurs, start_response can only be re-called with
        # exc_info set.

        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write(b'')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

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

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

  • 그에 맞게 environ을 수정한 후, 대상 URL을 기준으로 요청을 서로 다른 애플리케이션 객체에 라우팅합니다.
  • 동일한 프로세스에서 여러 애플리케이션 또는 프레임워크가 나란히 실행되도록 합니다.
  • 네트워크를 통해 요청과 응답을 전달하여 부하를 분산하고 원격 처리를 수행합니다.
  • XSL 스타일시트를 적용하는 등의 콘텐츠 후처리를 수행합니다.

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

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

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

from piglatin import piglatin

class LatinIter:

    """Transform iterated output to piglatin, if it's okay to do so

    Note that the "okayness" can change until the application yields
    its first non-empty bytestring, so 'transform_ok' has to be a mutable
    truth value.
    """

    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):
        data = self._next()
        if self.transform_ok:
            return piglatin(data)   # call must be byte-safe on Py3
        else:
            return data

class Latinator:

    # by default, don't transform output
    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):

            # Reset ok flag, in case this is a repeat call
            del transform_ok[:]

            for name, value in response_headers:
                if name.lower() == 'content-type' and value == 'text/plain':
                    transform_ok.append(True)
                    # Strip content-length if present, else it'll be wrong
                    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))   # call must be byte-safe on Py3
                return write_latin
            else:
                return write

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


# Run foo_app under a Latinator's control, using the example CGI gateway
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_headersexc_info라고 명명했지만, 반드시 이러한 이름을 사용할 필요는 없으며, 애플리케이션은 위치 인자를 사용하여 start_response호출 가능 객체를 반드시 호출해야 합니다(예: start_response(status, response_headers)).

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

start_response 호출 가능 객체는 HTTP 응답 본문의 일부로 기록할 바이트 문자열이라는 위치 인자 하나를 받는 write(body_data)호출 가능 객체를 반환해야 합니다. (참고: write()호출 가능 객체는 기존 일부 프레임워크의 명령형 출력 API를 지원하기 위해서만 제공되므로, 피할 수 있다면 새 애플리케이션이나 프레임워크에서 사용해서는 안 됩니다. 자세한 내용은 Buffering and Streaming 절을 참조하십시오.)

서버가 호출하면 애플리케이션 객체는 0개 이상의 바이트 문자열을 생성하는 이터러블을 반환해야 합니다. 이는 바이트 문자열의 목록을 반환하거나, 바이트 문자열을 생성하는 제너레이터 함수로 애플리케이션을 구현하거나, 인스턴스가 이터러블인 클래스로 애플리케이션을 구현하는 등 다양한 방법으로 수행할 수 있습니다. 어떤 방법으로 구현하든 애플리케이션 객체는 항상 0개 이상의 바이트 문자열을 생성하는 이터러블을 반환해야 합니다.

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

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

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

애플리케이션이 반환한 이터러블에 close() 메서드가 있다면, 요청이 정상적으로 완료되었는지, 이터레이션 중 애플리케이션 오류 또는 브라우저의 조기 연결 해제로 인해 조기에 종료되었는지와 관계없이 서버 또는 게이트웨이는 현재 요청이 완료될 때 해당 메서드를 호출해야 합니다. (close()메서드 요구 사항은 애플리케이션이 리소스를 해제할 수 있도록 지원하기 위한 것입니다. 이 프로토콜은 PEP 342의 제너레이터 지원 및 close() 메서드를 사용하는 기타 일반적인 이터러블을 보완하기 위한 것입니다.)

제너레이터 또는 기타 사용자 지정 이터레이터를 반환하는 애플리케이션은 서버가 전체 이터레이터를 소비할 것이라고 가정해서는 안 됩니다. 서버가 이터레이터를 조기에 닫을 수 있기 때문입니다.

(참고: 서버가 본문 콘텐츠보다 먼저 헤더를 전송할 수 있도록 애플리케이션은 이터러블이 첫 번째 본문 바이트 문자열을 생성하기 전에 start_response() 호출 가능 객체를 호출해야 합니다. 그러나 이 호출은 이터러블의 첫 번째 이터레이션에서 수행될 수 있으므로, 서버는 이터러블에 대한 이터레이션을 시작하기 전에 start_response()가 호출되었다고 가정해서는 안 됩니다.)

마지막으로 서버 및 게이트웨이는 애플리케이션이 반환한 이터러블의 다른 속성을 직접 사용해서는 안 됩니다. 단, 해당 이터러블이 해당 서버 또는 게이트웨이에 특화된 타입의 인스턴스인 경우는 예외이며, 예를 들어 wsgi.file_wrapper가 반환하는 “파일 래퍼”가 이에 해당합니다(Optional Platform-Specific File Handling 참조). 일반적인 경우에는 여기에 지정된 속성 또는 예를 들어 PEP 234 이터레이션 API를 통해 액세스하는 속성만 허용됩니다.

environ 변수

environ 딕셔너리에는 공통 게이트웨이 인터페이스 사양 [2]에 정의된 다음 CGI 환경 변수가 포함되어야 합니다. 다음 변수는 그 값이 빈 문자열인 경우를 제외하고 반드시 존재해야 하며, 값이 빈 문자열이면 아래에 별도로 명시된 경우를 제외하고 생략할 수 있습니다.

REQUEST_METHOD
"GET" 또는 "POST"와 같은 HTTP 요청 메서드입니다. 이는 결코 빈 문자열일 수 없으므로 항상 필요합니다.
SCRIPT_NAME
애플리케이션 객체에 해당하는 요청 URL의 “path” 초기 부분으로, 애플리케이션이 자신의 가상 “위치”를 알 수 있도록 합니다. 애플리케이션이 서버의 “루트”에 해당하는 경우에는 빈 문자열일 수 있습니다.
PATH_INFO
애플리케이션 내에서 요청 대상의 가상 “위치”를 지정하는 요청 URL “경로”의 나머지 부분입니다. 이는 요청 URL이 애플리케이션 루트를 대상으로 하며 끝에 슬래시가 없는 경우 빈 문자열일 수 있습니다.
QUERY_STRING
요청 URL에서 "?" 뒤에 오는 부분입니다(있는 경우). 비어 있거나 존재하지 않을 수 있습니다.
CONTENT_TYPE
HTTP 요청의 Content-Type 필드에 포함된 내용입니다. 비어 있거나 존재하지 않을 수 있습니다.
CONTENT_LENGTH
HTTP 요청의 Content-Length 필드에 포함된 내용입니다. 비어 있거나 존재하지 않을 수 있습니다.
SERVER_NAME, SERVER_PORT
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 환경 변수 [5]를 가능한 한 많이 제공하도록 시도해야 합니다. 그러나 위에 나열된 변수 이외의 CGI 변수를 사용하는 애플리케이션은 관련 확장을 지원하지 않는 웹 서버로 이식할 수 없습니다. (예를 들어 파일을 게시하지 않는 웹 서버는 의미 있는 DOCUMENT_ROOT 또는 PATH_TRANSLATED를 제공할 수 없습니다.)

WSGI를 준수하는 서버 또는 게이트웨이는 제공하는 변수와 적절한 경우 그 정의를 문서화해야 합니다. 애플리케이션은 필요한 변수가 존재하는지 확인해야 하며, 해당 변수가 없을 경우를 대비한 대체 계획을 마련해야 합니다.

참고: 인증이 수행되지 않은 경우의 REMOTE_USER와 같은 누락된 변수는 environ 딕셔너리에서 제외해야 합니다. 또한 CGI에서 정의된 변수는 존재하는 경우 네이티브 문자열이어야 합니다. 모든 CGI 변수 값이 str 이외의 어떤 타입이든 되는 것은 이 사양을 위반하는 것입니다.

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

Variable Value
wsgi.version The tuple (1, 0), representing WSGI version 1.0.
wsgi.url_scheme A string representing the “scheme” portion of the URL at which the application is being invoked. Normally, this will have the value "http" or "https", as appropriate.
wsgi.input An input stream (file-like object) from which the HTTP request body bytes can be read. (The server or gateway may perform reads on-demand as requested by the application, or it may pre-read the client’s request body and buffer it in-memory or on disk, or use any other technique for providing such an input stream, according to its preference.)
wsgi.errors An output stream (file-like object) to which error output can be written, for the purpose of recording program or other errors in a standardized and possibly centralized location. This should be a “text mode” stream; i.e., applications should use "\n" as a line ending, and assume that it will be converted to the correct line ending by the server/gateway.

(On platforms where the str type is unicode, the error stream should accept and log arbitrary unicode without raising an error; it is allowed, however, to substitute characters that cannot be rendered in the stream’s encoding.)

For many servers, wsgi.errors will be the server’s main error log. Alternatively, this may be sys.stderr, or a log file of some sort. The server’s documentation should include an explanation of how to configure this or where to find the recorded output. A server or gateway may supply different error streams to different applications, if this is desired.

wsgi.multithread This value should evaluate true if the application object may be simultaneously invoked by another thread in the same process, and should evaluate false otherwise.
wsgi.multiprocess This value should evaluate true if an equivalent application object may be simultaneously invoked by another process, and should evaluate false otherwise.
wsgi.run_once This value should evaluate true if the server or gateway expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a gateway based on CGI (or something similar).

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

입력 및 오류 스트림

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

메서드 스트림 참고 사항
read(size) input 1
readline() input 1, 2
readlines(hint) input 1, 3
__iter__() input
flush() errors 4
write(str) errors
writelines(seq) errors

각 메서드의 의미는 Python 라이브러리 참조에 문서화된 내용과 같지만, 위 표에 나열된 다음 참고 사항은 예외입니다:

  1. 서버는 클라이언트가 지정한 Content-Length를 초과하여 읽을 필요는 없으며, 애플리케이션이 해당 지점을 넘어 읽으려고 하면 파일 끝 조건을 시뮬레이션해야 합니다. 애플리케이션은 CONTENT_LENGTH 변수에 지정된 양보다 많은 데이터를 읽으려고 시도해서는 안 됩니다.

    서버는 인수 없이 read() 를 호출할 수 있도록 허용해야 하며, 클라이언트 입력 스트림의 나머지를 반환해야 합니다.

    서버는 비어 있거나 소진된 입력 스트림에서 읽으려는 모든 시도에 대해 빈 바이트 문자열을 반환해야 합니다.

  2. 서버는 readline() 의 선택적 “size” 인자를 지원해야 하지만, WSGI 1.0과 마찬가지로 해당 지원을 생략할 수 있습니다.

    (WSGI 1.0에서는 크기 인자를 구현하기 복잡할 수 있고 실제로 자주 사용되지 않는다는 이유로 지원하지 않았습니다… 그러나 이후 cgi 모듈이 이를 사용하기 시작했고, 따라서 실제 서버는 결국 이를 지원하기 시작해야 했습니다!)

  3. readlines()hint 인자는 호출자와 구현자 모두에게 선택 사항이라는 점에 유의하십시오. 애플리케이션은 이를 제공하지 않아도 되며, 서버 또는 게이트웨이는 이를 무시해도 됩니다.
  4. errors 스트림은 되감지 못할 수 있으므로, 서버와 게이트웨이는 버퍼링하지 않고 쓰기 작업을 즉시 전달해도 됩니다. 이 경우 flush() 메서드는 아무 작업도 하지 않아도 됩니다. 그러나 이식 가능한 애플리케이션은 출력이 버퍼링되지 않거나 flush()가 아무 작업도 하지 않는다고 가정할 수 없습니다. 출력이 실제로 기록되었는지 확인해야 한다면 flush()를 호출해야 합니다. (예를 들어 동일한 오류 로그에 기록하는 여러 프로세스의 데이터가 서로 섞이는 것을 최소화하기 위해서입니다.)

위 표에 나열된 메서드는 이 사양을 준수하는 모든 서버에서 지원되어야 합니다. 이 사양을 준수하는 애플리케이션은 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의 Section 6.1.1을 참조하십시오.) 문자열에는 제어 문자가 포함되어서는 안 되며, 캐리지 리턴, 줄 바꿈 또는 그 조합으로 끝나서도 안 됩니다.

response_headers 인자는 (header_name, header_value) 튜플의 목록입니다. 이는 Python 목록이어야 합니다. 즉, type(response_headers) is ListType여야 하며, 서버는 원하는 방식으로 그 내용을 변경해도 됩니다. 각 header_name은 뒤에 콜론이나 다른 구두점이 없는 유효한 HTTP 헤더 필드 이름이어야 합니다(RFC 2616의 Section 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가 호출되는 시점에 헤더의 오류를 확인해야 합니다.

그러나 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[1].with_traceback(exc_info[2])

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

애플리케이션은 exc_info 인자가 제공된 경우에만 start_response 를 두 번 이상 호출할 수 있습니다. 더 정확히 말하면, 현재 응용 프로그램 호출 중에 start_response가 이미 호출된 경우 exc_info인자 없이 start_response를 호출하는 것은 치명적인 오류입니다. 여기에는 start_response에 대한 첫 번째 호출에서 오류가 발생한 경우도 포함됩니다. (올바른 로직의 예시는 위의 CGI 게이트웨이 예제를 참조하십시오.)

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

def start_response(status, response_headers, exc_info=None):
    if exc_info:
         try:
             # do stuff w/exc_info here
         finally:
             exc_info = None    # Avoid circular ref.

CGI 게이트웨이 예제는 이 기법을 보여 주는 또 다른 예시를 제공합니다.

Content-Length 헤더 처리

애플리케이션이 Content-Length 헤더를 제공하는 경우, 서버는 해당 헤더가 허용하는 것보다 많은 바이트를 클라이언트에 전송해서는 안 되며, 충분한 데이터가 전송되면 응답에 대한 반복을 중지해야 하고, 애플리케이션이 그 지점을 넘어 write() 하려고 하면 오류를 발생시켜야 합니다. (물론 애플리케이션이 명시한 Content-Length 를 충족할 만큼 충분한 데이터를 제공하지 않으면, 서버는 연결을 닫고 오류를 기록하거나 다른 방식으로 보고해야 합니다.)

응용 프로그램이 Content-Length 헤더를 제공하지 않는 경우, 서버 또는 게이트웨이는 이를 처리하기 위해 여러 방법 중 하나를 선택할 수 있습니다. 이러한 방법 중 가장 간단한 것은 응답이 완료될 때 클라이언트 연결을 닫는 것입니다.

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

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

(참고: 응용 프로그램과 미들웨어는 청크 분할이나 gzip 압축과 같은 어떠한 종류의 Transfer-Encoding도 출력에 적용해서는 안 됩니다. 이러한 인코딩은 “홉 간” 작업이므로 실제 웹 서버 또는 게이트웨이의 영역입니다. 자세한 내용은 아래의 Other HTTP Features 를 참조하십시오.)

버퍼링 및 스트리밍

일반적으로 응용 프로그램은 (적당한 크기의) 출력을 버퍼링한 후 한 번에 모두 전송할 때 최상의 처리량을 얻습니다. 이는 Zope와 같은 기존 프레임워크에서 일반적으로 사용하는 방식입니다. 출력은 StringIO 또는 유사한 객체에 버퍼링된 다음 응답 헤더와 함께 한 번에 모두 전송됩니다.

WSGI에서 이에 해당하는 방식은 응용 프로그램이 응답 본문을 하나의 바이트 문자열로 포함하는 단일 요소 이터러블(예: 리스트)을 간단히 반환하는 것입니다. 이는 텍스트가 메모리에 쉽게 들어가는 HTML 페이지를 렌더링하는 대부분의 응용 프로그램 함수에 권장되는 방식입니다.

그러나 대용량 파일이나 특수한 HTTP 스트리밍 용도(예: 멀티파트 “서버 푸시”)에서는 응용 프로그램이 더 작은 블록 단위로 출력을 제공해야 할 수 있습니다(예: 대용량 파일을 메모리에 로드하지 않기 위해). 또한 응답의 일부를 생성하는 데 시간이 오래 걸리지만, 그 앞에 있는 응답 부분을 먼저 전송하는 것이 유용한 경우도 있습니다.

이러한 경우 응용 프로그램은 일반적으로 블록 단위로 출력을 생성하는 이터레이터(대개 제너레이터-이터레이터)를 반환합니다. 이러한 블록은 멀티파트 경계(“서버 푸시”의 경우)에 맞추어 분할하거나, 시간이 오래 걸리는 작업(예: 디스크에 있는 파일의 다음 블록 읽기) 직전에 분할할 수 있습니다.

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

  1. 애플리케이션으로 제어를 반환하기 전에 전체 블록을 운영 체제로 전송하고(모든 O/S 버퍼를 플러시하도록 요청하면서) 그렇게 하십시오. OR
  2. 애플리케이션이 다음 블록을 생성하는 동안에도 블록이 계속 전송되도록 별도의 스레드를 사용하십시오.
  3. (미들웨어만 해당) 전체 블록을 상위 게이트웨이/서버로 전송하십시오.

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

블록 경계의 미들웨어 처리

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

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

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

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

write() 호출 가능 객체

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

안타깝게도 이러한 API는 스레드나 기타 특수 메커니즘을 사용하지 않는 한 WSGI의 “iterable” 애플리케이션 반환값을 기반으로 구현할 수 없습니다.

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

새로운 WSGI 애플리케이션과 프레임워크는 이를 피할 수 있다면 write()호출 가능 객체를 사용해서는 안 됩니다. write() 호출 가능 객체는 명령형 스트리밍 API를 지원하기 위한 엄밀한 의미의 임시 방편입니다. 일반적으로 애플리케이션은 반환된 이터러블을 통해 출력을 생성해야 합니다. 이렇게 하면 웹 서버가 동일한 Python 스레드에서 다른 작업을 인터리브할 수 있어 서버 전체의 처리량이 향상될 가능성이 있습니다.

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

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

유니코드 문제

HTTP는 유니코드를 직접 지원하지 않으며, 이 인터페이스도 마찬가지입니다. 모든 인코딩과 디코딩은 애플리케이션에서 처리해야 하며, 서버로 전달되거나 서버에서 전달되는 모든 문자열은 str 또는 bytes 유형이어야 하고, unicode 유형이어서는 안 됩니다. 문자열 객체가 필요한 곳에서 unicode 객체를 사용한 결과는 정의되지 않습니다.

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

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

다시 말해, 이 사양에서 “문자열”이라고 지칭하는 모든 객체는 str 또는 StringType 유형이어야 하며, unicode 또는 UnicodeType 유형이어서는 안 됩니다. 또한 특정 플랫폼에서 str/StringType 객체의 문자당 8비트 초과를 허용하더라도, 이 사양에서 “문자열”로 언급하는 모든 값에는 하위 8비트만 사용할 수 있습니다.

이 사양에서 “바이트 문자열”이라고 지칭하는 값(즉, wsgi.input 에서 읽거나 write() 에 전달하거나 애플리케이션이 산출하는 값)은 Python 3에서는 bytes 유형이어야 하며, 이전 Python 버전에서는 str 유형이어야 합니다.

오류 처리

일반적으로 애플리케이션은 자체 내부 오류를 가로채고 브라우저에 유용한 메시지를 표시하도록 시도해야 합니다. (여기서 “유용한”의 의미는 애플리케이션이 결정합니다.)

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

try:
    # regular application code here
    status = "200 Froody"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers)
    return ["normal body goes here"]
except:
    # XXX should trap runtime issues like MemoryError, KeyboardInterrupt
    #     in a separate handler before this 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()를 호출하게 되며, 미들웨어는 오류 출력을 캡처하고 수정할 수 있습니다. 이러한 기법은 애플리케이션 작성자가 다음을 준수하는 한 작동합니다.

  1. 오류 응답을 시작할 때 항상 exc_info를 제공하십시오.
  2. exc_info가 제공되는 동안 start_response가 발생시킨 오류를 절대 가로채지 마십시오.

HTTP 1.1 Expect/Continue

HTTP 1.1을 구현하는 서버와 게이트웨이는 HTTP 1.1의 “expect/continue” 메커니즘을 반드시 투명하게 지원해야 합니다. 이는 다음과 같은 여러 방법 중 하나로 수행할 수 있습니다.

  1. Expect: 100-continue 요청 헤더를 포함하는 요청에 즉시 “100 Continue” 응답으로 응답한 후 정상적으로 진행하십시오.
  2. 요청을 정상적으로 진행하되, 애플리케이션에 wsgi.input 스트림을 제공하십시오. 이 스트림은 애플리케이션이 입력 스트림에서 처음 읽기를 시도할 때 “100 Continue” 응답을 전송합니다. 그런 다음 읽기 요청은 클라이언트가 응답할 때까지 계속 차단된 상태여야 합니다.
  3. 클라이언트가 서버가 expect/continue를 지원하지 않는다고 판단하여 스스로 요청 본문을 보낼 때까지 기다리십시오. (이는 최적의 방법이 아니며 권장되지 않습니다.)

이러한 동작 제한은 HTTP 1.0 요청이나 애플리케이션 객체를 대상으로 하지 않는 요청에는 적용되지 않는다는 점에 유의하십시오. HTTP 1.1 Expect/Continue에 대한 자세한 내용은 RFC 2616의 8.2.3절과 10.1.1절을 참조하십시오.

기타 HTTP 기능

일반적으로 서버와 게이트웨이는 “멍청하게 동작하고” 애플리케이션이 출력에 대한 완전한 제어권을 갖도록 해야 합니다. 애플리케이션 응답의 실질적인 의미를 변경하지 않는 변경만 수행해야 합니다. 애플리케이션 개발자는 추가 기능을 제공하는 미들웨어 구성 요소를 언제든지 추가할 수 있으므로, 서버/게이트웨이 개발자는 구현에 신중해야 합니다. 어떤 의미에서 서버는 자신을 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 기능은 미들웨어 구성 요소로 부분적으로 또는 완전히 구현할 수 있으므로, 서버와 애플리케이션 작성자는 동일한 기능을 반복해서 구현하지 않아도 됩니다.

스레드 지원

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

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

서버 확장 API

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

가장 간단한 경우에는 mod_python.some_api와 같은 environ 변수를 정의하는 것만 필요합니다. 그러나 많은 경우 미들웨어가 존재할 가능성 때문에 이 작업이 어려워질 수 있습니다. 예를 들어 environ 변수에 있는 것과 동일한 HTTP 헤더에 대한 액세스를 제공하는 API는 미들웨어가 environ을 수정한 경우 다른 데이터를 반환할 수 있습니다.

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

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

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

이러한 지침은 파싱된 쿠키, 폼 변수, 세션 등과 같은 정보를 environ에 추가하는 미들웨어에도 적용됩니다. 구체적으로, 이러한 미들웨어는 값을 단순히 environ에 채워 넣기보다는 environ에서 작동하는 함수로 이러한 기능을 제공해야 합니다. 이렇게 하면 다른 미들웨어가 URL 재작성이나 그 밖의 environ수정 작업을 수행한 후에 environ에서 정보가 계산되도록 할 수 있습니다.

이러한 “안전한 확장” 규칙은 서버/게이트웨이 개발자와 미들웨어 개발자 모두가 반드시 따라야 합니다. 그래야 미들웨어 개발자가 확장 기능을 사용하는 애플리케이션에 의해 자신의 중재가 우회되지 않도록 향후 environ에서 모든 확장 API를 삭제해야 하는 상황을 피할 수 있습니다!

애플리케이션 구성

이 명세는 서버가 호출할 애플리케이션을 선택하거나 얻는 방법을 정의하지 않습니다. 이러한 구성 옵션과 그 밖의 구성 옵션은 서버별로 크게 달라지는 사항입니다. 서버/게이트웨이 작성자는 특정 애플리케이션 객체를 어떤 옵션(스레딩 옵션 등)으로 실행하도록 서버를 구성하는 방법을 문서화할 것으로 예상됩니다.

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

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

모든 서버가 이러한 변수의 간편한 구성을 지원하는 것은 아니므로, 애플리케이션은 이러한 필수 변수를 최소한으로 유지하도록 노력해야 합니다. 물론 최악의 경우에도 애플리케이션을 배포하는 사람은 필요한 구성 값을 제공하는 스크립트를 만들 수 있습니다.:

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.parse 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이 대상 플랫폼인 경우 특히 중요합니다. 이 글을 작성하는 시점에는 아직 제품 수준으로 사용할 수 있는 Jython 2.2 버전이 제공되지 않았기 때문입니다.

서버와 게이트웨이의 경우 이는 비교적 간단합니다. Python 2.2 이전 버전을 대상으로 하는 서버와 게이트웨이는 애플리케이션이 반환하는 모든 이터러블을 순회할 때 표준 “for” 루프만 사용하도록 제한하면 됩니다. 이는 아래에서 더 자세히 설명하는 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 대신 1과 0을 사용하는 등도 당연히 수행해야 합니다.)

플랫폼별 파일 처리(선택 사항)

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

“파일과 유사한” 객체로 간주되려면, 애플리케이션이 제공하는 객체에 선택적 크기 인자를 받는 read() 메서드가 있어야 합니다. 해당 객체에는 close() 메서드가 있을 수 있으며, 있는 경우 wsgi.file_wrapper 가 반환하는 이터러블에는 원래 파일과 유사한 객체의 close() 메서드를 호출하는 close() 메서드가 있어야 합니다. “파일과 유사한” 객체에 Python 기본 제공 파일 객체의 메서드 또는 특성과 이름이 일치하는 다른 메서드나 특성(예: fileno() )이 있는 경우, wsgi.file_wrapper 는 이러한 메서드 또는 특성이 기본 제공 파일 객체의 것과 동일한 의미를 가진다고 간주할 수 있습니다.

플랫폼별 파일 처리의 실제 구현은 애플리케이션이 반환한 에 수행되어야 하며, 서버 또는 게이트웨이는 래퍼 객체가 반환되었는지 확인합니다. (다시 말해, 미들웨어, 오류 처리기 등이 존재하므로 생성된 래퍼가 실제로 사용된다고 보장되지는 않습니다.)

close()처리를 제외하면, 애플리케이션에서 파일 래퍼를 반환하는 의미는 애플리케이션이 iter(filelike.read, '')를 반환한 경우와 같아야 합니다. 즉, 전송은 전송이 시작되는 시점의 “파일” 내 현재 위치에서 시작하여 끝에 도달하거나 Content-Length 바이트가 기록될 때까지 계속되어야 합니다. (애플리케이션이 Content-Length 를 제공하지 않는 경우, 서버는 기반 파일 구현에 관한 지식을 사용하여 파일에서 해당 값을 생성할 수 있습니다.)

물론 플랫폼별 파일 전송 API는 일반적으로 임의의 “파일과 유사한” 객체를 받아들이지 않습니다. 따라서 wsgi.file_wrapper는 파일과 유사한 객체가 지원하는 플랫폼별 API에 사용하기에 적합한지 확인하기 위해 제공된 객체에서 fileno()(Unix 계열 운영 체제) 또는 java.nio.FileChannel(Jython에서)과 같은 항목을 조사해야 합니다.

객체가 플랫폼 API에 적합하지 않더라도, wsgi.file_wrapper는 애플리케이션에서 파일 래퍼를 사용하는 경우 플랫폼 간 이식성을 보장할 수 있도록 read()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):
        # check if result.filelike is usable w/platform-specific
        # API, and if so, use that API to transmit the result.
        # If not, fall through to normal iterable handling
        # loop below.

    for data in result:
        # etc.

finally:
    if hasattr(result, 'close'):
        result.close()

질문과 답변

  1. environ은 왜 딕셔너리여야 합니까? 서브클래스를 사용하는 데 무엇이 문제입니까?

    딕셔너리를 요구하는 근거는 서버 간 이식성을 극대화하기 위한 것입니다. 대안은 딕셔너리 메서드의 일부를 표준이자 이식 가능한 인터페이스로 정의하는 것입니다. 그러나 실제로는 대부분의 서버가 딕셔너리만으로도 필요를 충족할 수 있다고 판단할 것이며, 따라서 프레임워크 작성자는 딕셔너리 기능 전체를 사용할 수 있을 것으로 기대하게 됩니다. 대부분의 경우 실제로 그러한 기능이 제공될 것이기 때문입니다. 그러나 어떤 서버가 딕셔너리를 사용하지 않기로 선택한다면, 해당 서버가 사양을 “준수”하더라도 상호 운용성 문제가 발생할 것입니다. 따라서 딕셔너리를 필수로 만들면 사양이 단순해지고 상호 운용성이 보장됩니다.

    서버 또는 프레임워크 개발자가 environ 딕셔너리 내부에 사용자 지정 변수로 특수 서비스를 제공하는 것을 이것이 막지는 않는다는 점에 유의하십시오. 이러한 부가 가치 서비스를 제공할 때 권장되는 방식입니다.

  2. write()를 호출하고 동시에 바이트 문자열을 yield하거나 이터러블을 반환할 수 있는 이유는 무엇입니까? 한 가지 방식만 선택해야 하지 않습니까?

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

  3. close()는 무엇을 위한 것입니까?

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

  4. 이 인터페이스는 왜 이렇게 저수준입니까? 기능 X를 원합니다! (예: 쿠키, 세션, 영속성 등)

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

  5. 기존의 좋은 HTTP 헤더 대신 CGI 변수를 사용하는 이유는 무엇입니까? 그리고 이를 WSGI에서 정의한 변수와 섞는 이유는 무엇입니까?

    기존 웹 프레임워크 중 다수는 CGI 사양을 기반으로 구축되었으며, 기존 웹 서버는 CGI 변수를 생성하는 방법을 알고 있습니다. 반면 인바운드 HTTP 정보를 표현하는 대체 방식은 파편화되어 있고 시장 점유율도 부족합니다. 따라서 CGI “표준”을 사용하는 것은 기존 구현을 활용하는 좋은 방법인 듯합니다. 이를 WSGI 변수와 섞는 경우에는 분리하려면 딕셔너리 인자를 두 개 전달해야 할 뿐이며, 실질적인 이점은 제공되지 않습니다.

  6. 상태 문자열은 어떻게 합니까? "200 OK" 대신 200을 전달하여 숫자만 사용하면 안 됩니까?

    이렇게 하면 서버나 게이트웨이가 숫자 상태와 그에 대응하는 메시지의 표를 갖도록 요구하게 되어 복잡해집니다. 반면 애플리케이션이나 프레임워크 작성자는 사용 중인 특정 응답 코드에 덧붙일 추가 텍스트를 작성하기만 하면 되며, 기존 프레임워크에는 필요한 메시지가 들어 있는 표가 이미 있는 경우가 많습니다. 따라서 종합적으로 볼 때 서버나 게이트웨이보다는 애플리케이션/프레임워크가 책임지도록 하는 편이 더 나아 보입니다.

  7. wsgi.run_once는 왜 앱을 한 번만 실행하도록 보장하지 않습니까?

    애플리케이션에 단지 “드물게 실행되도록 준비하십시오”라고 제안할 뿐이기 때문입니다. 이는 캐싱, 세션 등의 동작 모드가 여러 개인 애플리케이션 프레임워크를 위한 것입니다. “여러 번 실행” 모드에서는 이러한 프레임워크가 캐시를 미리 적재할 수 있으며, 각 요청 후에 예를 들어 로그나 세션 데이터를 디스크에 기록하지 않을 수도 있습니다. “한 번 실행” 모드에서는 이러한 프레임워크가 미리 적재하지 않고 각 요청 후에 필요한 모든 쓰기 작업을 플러시합니다.

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

  8. Feature X(딕셔너리, 호출 가능 객체 등)는 애플리케이션 코드에서 사용하기에 보기 좋지 않은데, 대신 객체를 사용하지 않는 이유는 무엇입니까?

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

    본질적으로 미들웨어는 “책임 연쇄” 패턴을 사용하여 일부 함수의 “핸들러”로 동작하는 동시에 다른 함수는 변경되지 않은 상태로 두기를 원합니다. 인터페이스의 확장성을 유지하려면 일반적인 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는 사양이 확정되기 훨씬 전부터 WSGI-on-Jython을 구현하려고 용감하게 시도했으며, 이는 “이전 버전의 Python 지원” 절과 선택적인 wsgi.file_wrapper 기능, 그리고 초기의 바이트/유니코드 결정 사항 일부를 형성하는 데 도움이 되었습니다.
  • Mark Nottingham은 HTTP RFC 준수 문제를 중심으로 사양을 광범위하게 검토했으며, 특히 그가 지적하기 전까지는 존재하는지도 몰랐던 HTTP/1.1 기능과 관련된 문제를 검토했습니다.
  • Graham Dumpleton은 제 게으름과 어리석음에도 굴하지 않고 어떤 형태로든 Python 3 버전의 WSGI를 출시하기 위해 끊임없이 노력했으며, “네이티브 문자열”과 “바이트 문자열”이라는 개념을 제안하고, 수많은 HTTP, wsgi.input 및 기타 수정 사항을 사려 깊게 해결했습니다. 이 새로운 PEP에 대한 공로의 대부분, 아니 전부는 그의 몫입니다.

참고 자료입니다.