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

Python 개선 제안 한국어 번역

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

Author:
Phillip J. Eby <pje at telecommunity.com>
Discussions-To:
Web-SIG list
Status:
Final
Type:
Informational
Created:
07-Dec-2003
Post-History:
07-Dec-2003, 08-Aug-2004, 20-Aug-2004, 27-Aug-2004, 27-Sep-2010
Superseded-By:
3333

Table of Contents

번역·라이선스 안내

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

서문

참고: Python 3.x를 지원하고 커뮤니티 정오표, 부록, 설명을 포함한 이 명세의 개정판은 대신 PEP 3333을 참조하십시오.

초록

이 문서는 다양한 웹 서버 간에 웹 애플리케이션의 이식성을 높이기 위해, 웹 서버와 Python 웹 애플리케이션 또는 프레임워크 사이의 표준 인터페이스를 제안하고 명세합니다.

근거와 목표

현재 Python에는 Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Web 등 몇 가지만 꼽아도 매우 다양한 웹 애플리케이션 프레임워크가 있습니다 [1]. 이처럼 선택지가 다양하다는 점은 새로운 Python 사용자에게 문제가 될 수 있습니다. 일반적으로 어떤 웹 프레임워크를 선택하느냐에 따라 사용할 수 있는 웹 서버의 선택이 제한되고, 그 반대도 마찬가지이기 때문입니다.

이와 달리 Java에도 웹 애플리케이션 프레임워크가 그만큼 많지만, Java의 “서블릿(servlet)” API 덕분에 어떤 Java 웹 애플리케이션 프레임워크로 작성한 애플리케이션이든 서블릿 API를 지원하는 웹 서버라면 어디서나 실행할 수 있습니다.

파이썬용 웹 서버에서도 이러한 API를 사용할 수 있고 널리 쓰이게 된다면, 서버가 파이썬으로 작성되었든(예: Medusa), 파이썬을 내장하든(예: mod_python), 게이트웨이 프로토콜(예: CGI, FastCGI 등)로 파이썬을 호출하든 상관없이 프레임워크 선택과 웹 서버 선택이 분리됩니다. 그러면 사용자는 자신에게 맞는 조합을 자유롭게 고를 수 있고, 프레임워크 개발자와 서버 개발자는 각자 선호하는 전문 분야에 집중할 수 있습니다.

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

그러나 WSGI 명세가 존재한다는 것만으로는 Python 웹 애플리케이션을 위한 서버와 프레임워크의 현재 상황이 전혀 개선되지 않습니다. 실제로 효과가 있으려면 서버와 프레임워크의 작성자 및 유지 관리자가 WSGI를 실제로 구현해야 합니다.

하지만 WSGI를 지원하는 기존 서버나 프레임워크가 없으므로, WSGI 지원을 구현하는 작성자가 당장 얻는 이득은 거의 없습니다. 따라서 작성자가 처음 이 인터페이스에 들이는 투자가 적당히 적도록, WSGI는 반드시 구현하기 쉬워야 합니다.

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

그러나 프레임워크 작성자에게 구현이 단순하다는 것과 웹 애플리케이션 작성자에게 사용이 쉽다는 것은 같지 않다는 점에 유의하십시오. WSGI는 프레임워크 작성자에게 군더더기가 전혀 없는 인터페이스를 제공합니다. 응답 객체나 쿠키 처리 같은 부가 기능은 기존 프레임워크가 이런 문제를 처리하는 데 방해만 되기 때문입니다. 다시 말해 WSGI의 목표는 새로운 웹 프레임워크를 만드는 것이 아니라, 기존 서버와 애플리케이션 또는 프레임워크를 쉽게 연결하도록 돕는 것입니다.

또한 이 목표로 인해 WSGI는 이미 배포된 Python 버전에서 사용할 수 없는 것은 어떤 것도 요구할 수 없다는 점에 유의하십시오. 따라서 이 명세는 새로운 표준 라이브러리 모듈을 제안하거나 요구하지 않으며, WSGI의 어떤 부분도 2.2.2보다 높은 Python 버전을 요구하지 않습니다. (다만 향후 Python 버전에서는 표준 라이브러리가 제공하는 웹 서버에 이 인터페이스에 대한 지원을 포함하는 것이 좋을 것입니다.)

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

미들웨어가 단순하면서도 견고할 수 있고 WSGI가 서버와 프레임워크에서 널리 사용 가능해진다면, 완전히 새로운 종류의 Python 웹 애플리케이션 프레임워크, 즉 느슨하게 결합된 WSGI 미들웨어 구성 요소로 이루어진 프레임워크가 등장할 가능성이 열립니다. 실제로 기존 프레임워크 작성자들이 프레임워크의 기존 서비스를 이런 방식으로 제공하도록 리팩터링할 수도 있으며, 그러면 그 프레임워크는 단일체 프레임워크보다는 WSGI와 함께 쓰는 라이브러리에 더 가까워집니다. 그러면 애플리케이션 개발자는 단일 프레임워크의 모든 장단점을 떠안는 데 커밋할 필요 없이, 특정 기능마다 “최고 수준의” 컴포넌트를 선택할 수 있게 됩니다.

물론 이 글을 쓰는 현재로서는 그날이 분명 아직 한참 멀었습니다. 그동안은 어떤 프레임워크든 어떤 서버에서나 쓸 수 있게 하는 것만으로도 WSGI의 단기 목표로 충분합니다.

마지막으로, 현재 버전의 WSGI는 웹 서버나 서버 게이트웨이에서 사용하도록 애플리케이션을 “배포”하는 특정 메커니즘을 규정하지 않는다는 점을 밝혀 둡니다. 현재로서는 이 부분을 서버나 게이트웨이가 구현에 따라 정할 수밖에 없습니다. 충분한 수의 서버와 프레임워크가 WSGI를 구현하여 다양한 배포 요구 사항에 대한 필드 경험이 쌓이면, WSGI 서버와 애플리케이션 프레임워크를 위한 배포 표준을 기술하는 또 다른 PEP를 작성하는 것이 타당할 수도 있습니다.

명세 개요

WSGI 인터페이스는 “서버” 또는 “게이트웨이” 측과 “애플리케이션” 또는 “프레임워크” 측, 두 부분으로 이루어져 있습니다. 서버 측은 애플리케이션 측이 제공하는 호출 가능 객체를 호출합니다. 그 객체를 어떤 방식으로 제공할지는 서버나 게이트웨이가 정합니다. 일부 서버나 게이트웨이는 애플리케이션 배포자에게 짧은 스크립트를 작성하도록 요구할 것으로 가정합니다. 이 스크립트는 서버나 게이트웨이의 인스턴스를 생성하고 거기에 애플리케이션 객체를 넘겨줍니다. 다른 서버와 게이트웨이는 설정 파일이나 다른 메커니즘을 사용하여 애플리케이션 객체를 어디에서 임포트하거나 다른 방법으로 얻어야 하는지 지정할 수 있습니다.

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

이 명세 전체에서 “호출 가능 객체”라는 용어는 “함수, 메서드, 클래스 또는 __call__ 메서드를 가진 인스턴스”를 뜻합니다. 호출 가능 객체를 구현하는 서버, 게이트웨이 또는 애플리케이션이 자신의 필요에 맞는 구현 기법을 직접 선택합니다. 반대로, 호출 가능 객체를 호출하는 서버, 게이트웨이 또는 애플리케이션은 자신에게 어떤 종류의 호출 가능 객체가 제공되었는지에 대해 어떠한 의존성도 가져서는 안 됩니다. 호출 가능 객체는 호출하기만 해야 하며, 그 내부를 들여다보아서는 안 됩니다.

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

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

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

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

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!\n']


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!\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:
             # Before the first output, send the stored headers
             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:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None     # avoid dangling circular ref
        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:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

미들웨어: 양쪽 역할을 모두 하는 컴포넌트

하나의 객체가 어떤 애플리케이션에 대해서는 서버 역할을 하면서, 동시에 어떤 서버에 대해서는 애플리케이션으로 동작할 수도 있다는 점에 유의하십시오. 이러한 “미들웨어” 컴포넌트는 다음과 같은 함수 역할을 수행할 수 있습니다:

  • 대상 URL에 따라 environ을 적절히 재작성한 후, 요청을 서로 다른 애플리케이션 객체로 라우팅합니다.
  • 여러 애플리케이션이나 프레임워크가 같은 프로세스에서 나란히 실행되도록 합니다
  • 네트워크를 통해 요청과 응답을 전달하여 부하 분산과 원격 처리를 수행합니다
  • 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 string, 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):
        if self.transform_ok:
            return piglatin(self._next())
        else:
            return self._next()

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))
                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))

명세 세부 사항

애플리케이션 객체는 두 개의 위치 인자를 받아야 합니다. 설명을 위해 이 인자들을 environ과 start_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개 이상의 문자열을 산출하는 이터러블을 반환해야 합니다. 이는 문자열의 리스트를 반환하거나, 애플리케이션을 문자열을 산출하는 제너레이터 함수로 만들거나, 애플리케이션을 인스턴스가 이터러블인 클래스로 만드는 등 다양한 방식으로 달성할 수 있습니다. 어떤 방식으로 달성하든, 애플리케이션 객체는 항상 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가 반환하는 “파일 래퍼”처럼 해당 서버나 게이트웨이에 고유한 타입의 인스턴스가 아닌 한, 서버와 게이트웨이는 그 이터러블의 다른 어떤 속성도 직접 사용해서는 안 됩니다(Optional Platform-Specific File Handling 참고). 일반적인 경우에는 여기에 명시된 속성이나, 예를 들어 PEP 234 반복 API를 통해 접근하는 속성만 허용됩니다.

environ 변수

environ 딕셔너리는 공통 게이트웨이 인터페이스(Common Gateway Interface) 명세 [2]에 정의된 다음 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_NAME 및 PATH_INFO와 함께 사용하면 이 변수들로 URL을 완성할 수 있습니다. 하지만 요청 URL을 재구성할 때는 HTTP_HOST가 있다면 SERVER_NAME보다 우선하여 사용해야 한다는 점에 유의하십시오. 자세한 내용은 아래의 URL Reconstruction 절을 참고하십시오. SERVER_NAME과 SERVER_PORT는 절대 빈 문자열일 수 없으므로 항상 필수입니다.
SERVER_PROTOCOL
클라이언트가 요청을 보낼 때 사용한 프로토콜의 버전입니다. 일반적으로 "HTTP/1.0" 또는 "HTTP/1.1"과 같은 값이며, 애플리케이션은 이 값을 사용해 HTTP 요청 헤더를 어떻게 처리할지 결정할 수 있습니다. (이 변수는 요청에 사용된 프로토콜을 나타내며 서버 응답에 사용될 프로토콜과 반드시 같지는 않으므로, REQUEST_PROTOCOL이라는 이름이 더 적절했을 것입니다. 그러나 CGI와의 호환성을 위해 기존 이름을 유지해야 합니다.)
HTTP_ 변수
클라이언트가 제공한 HTTP 요청 헤더에 대응하는 변수입니다(즉, 이름이 "HTTP_"로 시작하는 변수입니다). 이 변수들이 있는지 여부는 요청에 해당 HTTP 헤더가 있는지 여부와 일치해야 합니다.

서버나 게이트웨이는 해당되는 다른 CGI 변수를 가능한 한 많이 제공하려고 시도해야 합니다. 또한 SSL을 사용하는 경우, 서버나 게이트웨이는 HTTPS=on이나 SSL_PROTOCOL과 같은 Apache SSL 환경 변수 [3] 중 해당되는 것도 가능한 한 많이 제공해야 합니다. 다만 위에 나열된 것 이외의 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 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.

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_python은 mod_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를 넘어서 읽을 필요가 없습니다. 애플리케이션이 그 지점을 넘어서 읽으려고 하면 서버는 파일 끝(end-of-file) 상태를 흉내 내도 됩니다. 애플리케이션은 CONTENT_LENGTH 변수에 지정된 것보다 많은 데이터를 읽으려고 하지 않아야 합니다.
  2. readline()의 선택적 “size” 인자는 지원되지 않습니다. 서버 작성자가 구현하기 복잡할 수 있고, 실제로는 자주 사용되지 않기 때문입니다.
  3. readlines()의 hint 인자는 호출자와 구현자 모두에게 선택 사항이라는 점에 유의하십시오. 애플리케이션은 이 인자를 제공하지 않아도 되며, 서버나 게이트웨이는 이를 무시해도 됩니다.
  4. errors 스트림은 되감을 수 없을 수도 있으므로, 서버와 게이트웨이는 쓰기 작업을 버퍼링 없이 즉시 전달해도 됩니다. 이 경우 flush() 메서드는 아무 일도 하지 않는(no-op) 메서드일 수 있습니다. 그러나 이식 가능한 애플리케이션은 출력이 버퍼링되지 않는다거나 flush()가 no-op이라고 가정할 수 없습니다. 출력이 실제로 기록되었음을 보장해야 한다면 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-Code와 Reason-Phrase를 이 순서대로 공백 하나로 구분하여 구성하며, 앞뒤에 공백이나 다른 문자가 없어야 합니다. (자세한 내용은 RFC 2616의 6.1.1절을 참고하십시오.) 이 문자열은 제어 문자를 포함해서는 안 되며, 캐리지 리턴, 라인 피드 또는 그 조합으로 끝나서도 안 됩니다.

response_headers 인자는 (header_name, header_value) 튜플의 리스트입니다. 이 인자는 Python 리스트여야 합니다. 즉 type(response_headers) is 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() 호출 가능 객체를 처음 호출할 때 비로소 서버나 게이트웨이가 헤더를 전송할 수 있도록 헤더를 저장해 두어야 합니다. 다시 말해, 실제 본문 데이터가 준비되거나 애플리케이션이 반환한 이터러블이 소진될 때까지 응답 헤더를 보내서는 안 됩니다. (이 규칙에 대해 가능한 유일한 예외는 응답 헤더에 값이 0인 Content-Length가 명시적으로 포함된 경우입니다.)

이렇게 응답 헤더 전송을 늦추는 것은, 버퍼링을 사용하는 애플리케이션과 비동기 애플리케이션이 가능한 마지막 순간까지 원래 의도한 출력을 오류 출력으로 바꿀 수 있도록 하기 위함입니다. 예를 들어, 애플리케이션 버퍼 안에서 본문을 생성하는 도중 오류가 발생하면, 애플리케이션은 응답 상태를 “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:
             # do stuff w/exc_info here
         finally:
             exc_info = None    # Avoid circular ref.

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

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도 출력에 적용해서는 안 됩니다. 이러한 인코딩은 “hop-by-hop” 연산이므로 실제 웹 서버/게이트웨이가 다루는 영역입니다. 자세한 내용은 아래의 Other HTTP Features를 참고하십시오.)

버퍼링과 스트리밍

일반적으로 애플리케이션은 (적당한 크기의) 출력을 버퍼링했다가 한꺼번에 전송할 때 가장 높은 처리량을 얻습니다. 이는 Zope 같은 기존 프레임워크에서 흔히 쓰는 방식입니다. 출력을 StringIO나 그와 비슷한 객체에 버퍼링한 다음, 응답 헤더와 함께 한꺼번에 전송합니다.

WSGI에서 이에 해당하는 방식은 애플리케이션이 응답 본문을 하나의 문자열로 담은 단일 요소 이터러블(리스트 등)을 그냥 반환하는 것입니다. 텍스트가 메모리에 쉽게 들어가는 HTML 페이지를 렌더링하는 대다수 애플리케이션 함수에는 이 방식을 권장합니다.

그러나 큰 파일을 다루거나 HTTP 스트리밍을 특수한 용도(멀티파트 “서버 푸시” 등)로 사용할 때는 애플리케이션이 출력을 더 작은 블록으로 나누어 제공해야 할 수 있습니다(예: 큰 파일을 메모리에 통째로 올리지 않기 위해). 또한 응답의 일부를 만드는 데 시간이 오래 걸리지만, 그보다 앞선 부분은 미리 보내 두는 것이 유용한 경우도 있습니다.

이런 경우 애플리케이션은 보통 출력을 블록 단위로 생성하는 이터레이터(흔히 제너레이터-이터레이터)를 반환합니다. 이 블록은 (“서버 푸시”를 위해) 멀티파트 경계에 맞추어 나누거나, 시간이 오래 걸리는 작업(디스크에 있는 파일의 다음 블록을 읽는 작업 등) 직전에 나눌 수 있습니다.

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

  1. 애플리케이션에 제어를 돌려주기 전에 블록 전체를 운영 체제에 보냅니다(그리고 O/S 버퍼를 모두 플러시하도록 요청합니다). 또는
  2. 별도의 스레드를 사용하여, 애플리케이션이 다음 블록을 생성하는 동안에도 블록이 계속 전송되도록 합니다.
  3. (미들웨어만 해당) 블록 전체를 상위 게이트웨이/서버에 보냅니다

WSGI는 이를 보장함으로써, 애플리케이션이 출력 데이터의 임의 지점에서 전송이 멈추지 않도록 할 수 있게 합니다. 이는 멀티파트 경계 사이의 데이터를 클라이언트에 온전히 전송해야 하는 멀티파트 “서버 푸시” 스트리밍 등이 제대로 동작하는 데 매우 중요합니다.

미들웨어의 블록 경계 처리

비동기 애플리케이션과 서버를 더 잘 지원하기 위해, 미들웨어 컴포넌트는 애플리케이션 이터러블에서 여러 값을 기다리느라 이터레이션을 블록해서는 안 됩니다. 미들웨어가 출력을 생성하기 전에 애플리케이션에서 데이터를 더 모아야 한다면, 빈 문자열을 yield해야 합니다.

이 요구 사항을 달리 표현하면, 미들웨어 컴포넌트는 하위 애플리케이션이 값을 하나 yield할 때마다 적어도 하나의 값을 yield해야 합니다. 미들웨어가 다른 값을 yield할 수 없다면 빈 문자열을 yield해야 합니다.

이 요구 사항 덕분에 비동기 애플리케이션과 서버가 서로 협력하여, 주어진 수의 애플리케이션 인스턴스를 동시에 실행하는 데 필요한 스레드 수를 줄일 수 있습니다.

또한 이 요구 사항은 하위 애플리케이션이 이터러블을 반환하는 즉시 미들웨어도 이터러블을 반환해야 한다는 뜻이기도 합니다. 미들웨어가 하위 애플리케이션이 yield한 데이터를 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 응답 본문의 일부로 쓰일 문자열이며, 출력 이터러블이 yield한 것과 정확히 똑같이 취급됩니다. 다시 말해, write()는 반환하기 전에 전달받은 문자열이 클라이언트로 완전히 전송되었거나, 애플리케이션이 계속 진행하는 동안 전송을 위해 버퍼에 저장되었음을 보장해야 합니다.

애플리케이션은 write()를 사용해 응답 본문의 전부 또는 일부를 생성하더라도 반드시 이터러블 객체를 반환해야 합니다. 반환된 이터러블은 비어 있을 수도 있지만(즉, 비어 있지 않은 문자열을 하나도 yield하지 않음), 비어 있지 않은 문자열을 실제로 yield한다면 서버나 게이트웨이는 그 출력을 평소처럼 처리해야 합니다(즉, 즉시 전송하거나 큐에 넣어야 합니다). 애플리케이션은 반환 이터러블 안에서 write()를 호출해서는 안 됩니다. 따라서 이터러블이 yield한 문자열은 모두 write()에 전달된 모든 문자열이 클라이언트로 전송된 후에 전송됩니다.

유니코드 문제

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

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

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

다시 말하지만, 이 명세에서 언급하는 모든 문자열은 반드시 str 또는 StringType 타입이어야 하며, unicode 또는 UnicodeType 타입이어서는 안 됩니다. 또한 특정 플랫폼이 str/StringType 객체에서 문자당 8비트를 초과하는 것을 허용하더라도, 이 명세에서 “문자열”이라고 부르는 모든 값에는 하위 8비트만 사용할 수 있습니다.

오류 처리

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

그러나 이러한 메시지를 표시하려면 애플리케이션이 아직 브라우저에 실제로 어떤 데이터도 보내지 않은 상태여야 하며, 그렇지 않으면 응답이 손상될 위험이 있습니다. 따라서 WSGI는 애플리케이션이 오류 메시지를 보낼 수 있게 하거나 자동으로 중단되도록 하는 메커니즘을 제공하며, 그것이 바로 start_response의 exc_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. 요청을 정상적으로 진행하되, 애플리케이션이 입력 스트림에서 처음 읽기를 시도할 때(그런 시도가 있다면) “100 Continue” 응답을 보내는 wsgi.input 스트림을 애플리케이션에 제공하십시오. 그런 다음 읽기 요청은 클라이언트가 응답할 때까지 차단된 상태로 유지되어야 합니다.
  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 서버는 해당하는 경우 청크 인코딩을 포함하여 들어오는 모든 Transfer-Encoding을 디코딩하는 등의 방식으로, 지원하는 수신 “hop-by-hop” 헤더를 반드시 직접 처리해야 합니다.

이러한 원칙을 다양한 HTTP 기능에 적용해 보면, 서버가 If-None-Match 및 If-Modified-Since 요청 헤더와 Last-Modified 및 ETag 응답 헤더를 통해 캐시 검증을 처리할 수 있다는 점은 분명합니다. 그러나 서버가 반드시 이렇게 해야 하는 것은 아니며, 서버/게이트웨이는 그러한 검증을 할 의무가 없으므로 애플리케이션이 해당 기능을 지원하려면 자체적으로 캐시 검증을 수행해야 합니다.

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

애플리케이션에 대한 이러한 제한이 모든 애플리케이션이 모든 HTTP 기능을 다시 구현해야 한다는 뜻은 아니라는 점에 유의하십시오. 많은 HTTP 기능은 미들웨어 컴포넌트로 부분적으로 또는 완전히 구현할 수 있으므로, 서버 작성자와 애플리케이션 작성자 모두 같은 기능을 반복해서 구현하는 수고를 덜 수 있습니다.

스레드 지원

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

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

서버 확장 API

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

가장 단순한 경우에는 mod_python.some_api와 같은 environ 변수 하나를 정의하기만 하면 됩니다. 그러나 많은 경우 미들웨어가 존재할 가능성 때문에 이것이 어려워질 수 있습니다. 예를 들어, environ 변수에 들어 있는 것과 같은 HTTP 헤더에 대한 접근을 제공하는 API는 미들웨어가 environ을 수정한 경우 다른 데이터를 반환할 수 있습니다.

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

따라서 최대한의 호환성을 제공하기 위해, WSGI 기능의 일부를 대체하는 확장 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에 넣을 이름-값 쌍을 지정할 수 있도록 하여 이를 지원해야 합니다. 가장 단순한 경우, 이 지원은 운영 체제가 제공하는 모든 환경 변수를 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이 대상 플랫폼이라면 이 점이 특히 중요합니다.

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

(이 기법은 당연히 Python으로 작성된 서버, 게이트웨이 또는 미들웨어에만 적용된다는 점에 유의하십시오. 다른 언어에서 이터레이터 프로토콜을 올바르게 사용하는 방법에 대한 논의는 이 PEP의 범위를 벗어납니다.)

애플리케이션의 경우, 2.2 이전 버전의 Python을 지원하는 일은 조금 더 복잡합니다.

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

마지막으로, 2.2 이전 버전의 Python을 지원하려는 미들웨어가 애플리케이션의 반환값을 순회하거나 직접 이터러블을 반환한다면(또는 둘 다 한다면), 위의 해당 권고 사항을 따라야 합니다.

(참고: 말할 필요도 없이, 2.2 이전 버전의 Python을 지원하려면 모든 서버, 게이트웨이, 애플리케이션 또는 미들웨어는 대상 버전에서 사용할 수 있는 언어 기능만 사용해야 하고, True와 False 대신 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, '')를 반환한 것과 같아야 합니다. 다시 말해, 전송은 전송이 시작되는 시점의 “파일” 내 현재 위치에서 시작하여 끝에 도달할 때까지 계속되어야 합니다.

물론 플랫폼별 파일 전송 API는 보통 임의의 “파일류” 객체를 받지 않습니다. 따라서 wsgi.file_wrapper는 파일류 객체가 자신이 지원하는 플랫폼별 API에 사용하기에 적합한지 판단하기 위해, 제공된 객체에 fileno()(Unix 계열 OS)나 java.nio.FileChannel(Jython 환경) 같은 것이 있는지 조사해야 합니다.

객체가 플랫폼 API에 적합하지 않더라도, 파일 래퍼를 사용하는 애플리케이션이 여러 플랫폼에서 이식 가능하도록 wsgi.file_wrapper는 여전히 read()와 close()를 감싸는 이터러블을 반드시 반환해야 한다는 점에 유의하십시오. 다음은 오래된(2.2 이전) Python과 새로운 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하거나 이터러블을 반환할 수도 있는 이유는 무엇입니까? 한 가지 방식만 골라야 하지 않습니까?

    이터레이션 방식만 지원한다면 “푸시”를 사용할 수 있다고 가정하는 현재의 프레임워크들이 곤란해집니다. 그렇다고 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_once가 True로 설정된 채 호출되었다는 이유만으로 다시는 실행되지 않으리라고 가정해서는 안 됩니다.

  8. 기능 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는 (명세가 확정되기 훨씬 전에) WSGI-on-Jython을 구현하려고 용감하게 시도했으며, 이 시도는 “이전 버전의 Python 지원” 절과 선택 사항인 wsgi.file_wrapper 기능의 형태를 잡는 데 도움이 되었습니다.
  • Mark Nottingham은 HTTP RFC 준수 문제를 찾기 위해 명세를 폭넓게 검토했으며, 특히 그가 지적하기 전까지는 저도 존재조차 몰랐던 HTTP/1.1 기능들에 관해 많은 문제를 짚어 주었습니다.

참고 문헌