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 Types와 Unicode Issues 절에 설명된 규칙도 따라야 합니다.
이 문서와 PEP 333 사이의 자세한 줄 단위 차이는 리비전 84854부터의 SVN 리비전 기록 [7]에서 확인할 수 있습니다.
초록
이 문서는 다양한 웹 서버 사이에서 웹 애플리케이션의 이식성을 높이기 위해 웹 서버와 Python 웹 애플리케이션 또는 프레임워크 사이의 표준 인터페이스를 제안합니다.
원래의 근거와 목표 (PEP 333에서 발췌)
현재 Python에는 Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Web 등 매우 다양한 웹 애플리케이션 프레임워크가 있으며, 이는 일부에 불과합니다 [1]. 선택지가 이렇게 다양하면 새로운 Python 사용자에게는 문제가 될 수 있습니다. 일반적으로 웹 프레임워크를 선택하면 사용할 수 있는 웹 서버가 제한되고, 그 반대도 마찬가지이기 때문입니다.
반면 Java에도 웹 애플리케이션 프레임워크가 그만큼 많지만, Java의 “서블릿(servlet)” API 덕분에 어떤 Java 웹 애플리케이션 프레임워크로 작성한 애플리케이션이든 서블릿 API를 지원하는 모든 웹 서버에서 실행할 수 있습니다.
Python용 웹 서버에서 이러한 API를 사용할 수 있고 널리 쓰인다면, 서버가 Python으로 작성되었든(예: Medusa), Python을 내장하든(예: mod_python), 게이트웨이 프로토콜을 통해 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를 여러 서버와 프레임워크에서 널리 사용할 수 있다면, 느슨하게 결합된 WSGI 미들웨어 컴포넌트로 구성된 완전히 새로운 종류의 Python 웹 애플리케이션 프레임워크가 나올 수 있습니다. 실제로 기존 프레임워크 작성자들이 자기 프레임워크의 기존 서비스를 이런 방식으로 제공하도록 리팩터링할 수도 있습니다. 그러면 프레임워크는 모놀리식(monolithic) 프레임워크보다는 WSGI와 함께 쓰는 라이브러리에 더 가까워질 것입니다. 그러면 애플리케이션 개발자는 단일 프레임워크의 모든 장단점에 커밋할 필요 없이 특정 기능에 대해 “최고 수준(best-of-breed)”의 컴포넌트를 선택할 수 있습니다.
물론 이 글을 쓰는 현재로서는 그날이 틀림없이 아직 한참 멀었습니다. 그때까지는 WSGI로 어떤 프레임워크든 어떤 서버에서나 쓸 수 있게 하는 것만으로도 단기 목표로 충분합니다.
마지막으로, 현재 버전의 WSGI는 웹 서버나 서버 게이트웨이에서 사용할 애플리케이션을 “배포”하는 특정 메커니즘을 규정하지 않는다는 점을 언급해 두어야 합니다. 현재로서는 이 부분을 서버나 게이트웨이가 구현에 따라 정할 수밖에 없습니다. 충분한 수의 서버와 프레임워크가 WSGI를 구현하여 다양한 배포 요구 사항에 대한 필드 경험이 쌓이고 나면, WSGI 서버와 애플리케이션 프레임워크를 위한 배포 표준을 기술하는 또 다른 PEP를 만드는 것이 타당할 수 있습니다.
명세 개요
WSGI 인터페이스에는 두 측면이 있습니다. 하나는 “서버” 또는 “게이트웨이” 측이고, 다른 하나는 “애플리케이션” 또는 “프레임워크” 측입니다. 서버 측은 애플리케이션 측이 제공하는 호출 가능 객체를 호출합니다. 그 객체를 제공하는 구체적인 방식은 서버나 게이트웨이가 정합니다. 일부 서버나 게이트웨이는 애플리케이션 배포자에게 서버나 게이트웨이의 인스턴스를 생성하고 거기에 애플리케이션 객체를 전달하는 짧은 스크립트를 작성하도록 요구할 것으로 예상됩니다. 다른 서버와 게이트웨이는 설정 파일이나 그 밖의 메커니즘을 사용하여 애플리케이션 객체를 어디에서 임포트하거나 달리 어떻게 얻어야 하는지 지정할 수 있습니다.
“순수한” 서버/게이트웨이와 애플리케이션/프레임워크 외에도, 이 명세의 양쪽을 모두 구현하는 “미들웨어” 컴포넌트를 만들 수도 있습니다. 이러한 컴포넌트는 자신을 담고 있는 서버에 대해서는 애플리케이션으로, 자신이 담고 있는 애플리케이션에 대해서는 서버로 동작하며, 확장된 API, 콘텐츠 변환, 내비게이션 및 그 밖의 유용한 함수를 제공하는 데 사용될 수 있습니다.
이 명세 전체에서 “호출 가능 객체”라는 용어는 “함수, 메서드, 클래스 또는 __call__ 메서드가 있는 인스턴스”를 뜻합니다. 호출 가능 객체를 구현하는 서버, 게이트웨이 또는 애플리케이션은 자신의 필요에 맞는 적절한 구현 기법을 선택하면 됩니다. 반대로, 호출 가능 객체를 호출하는 서버, 게이트웨이 또는 애플리케이션은 자신에게 제공된 호출 가능 객체가 어떤 종류인지에 대해 어떠한 의존성도 가져서는 안 됩니다. 호출 가능 객체는 호출하기만 해야 하며, 인트로스펙션의 대상이 되어서는 안 됩니다.
문자열 타입에 관한 참고
일반적으로 HTTP는 바이트를 다루므로, 이 명세도 대부분 바이트 처리에 관한 것입니다.
하지만 그 바이트의 내용은 흔히 어떤 식으로든 텍스트로 해석되며, Python에서는 문자열이 텍스트를 다루는 가장 편리한 방법입니다.
그런데 많은 Python 버전과 구현에서 문자열은 바이트가 아니라 유니코드입니다. 따라서 사용하기 편한 API와 HTTP 맥락에서의 바이트와 텍스트 사이의 올바른 변환 사이에서 신중하게 균형을 잡아야 합니다… 특히 str 타입이 서로 다른 Python 구현 간의 코드 이식을 지원하려면 더욱 그렇습니다.
그래서 WSGI는 두 종류의 “문자열”을 정의합니다.
- 요청/응답 헤더와 메타데이터에 사용하는 “네이티브” 문자열(항상
str이라는 이름의 타입으로 구현됩니다) - 요청과 응답의 본문(예: POST/PUT 입력 데이터와 HTML 페이지 출력)에 사용하는 “바이트 문자열”(Python 3에서는
bytes타입으로, 그 밖의 환경에서는str로 구현됩니다)
하지만 혼동하지 마십시오. Python의 str 타입이 “내부적으로” 실제로는 유니코드이더라도, 네이티브 문자열의 내용은 여전히 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()
미들웨어: 양쪽 역할을 모두 하는 컴포넌트
하나의 객체가 어떤 애플리케이션에 대해서는 서버 역할을 하면서, 동시에 어떤 서버에 대해서는 애플리케이션 역할을 할 수도 있다는 점에 유의하십시오. 이러한 “미들웨어” 컴포넌트는 다음과 같은 함수를 수행할 수 있습니다:
- 대상 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 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))
명세 세부 사항
애플리케이션 객체는 두 개의 위치 인자를 받아야 합니다. 설명을 위해 이 인자들을 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() 메서드가 있다면, 서버나 게이트웨이는 현재 요청이 정상적으로 완료되었든, 반복 중 애플리케이션 오류나 브라우저의 조기 연결 끊김으로 인해 일찍 종료되었든 관계없이 요청이 끝날 때 그 메서드를 호출해야 합니다. (close() 메서드 요구 사항은 애플리케이션의 자원 해제를 지원하기 위한 것입니다. 이 프로토콜은 PEP 342의 제너레이터 지원 및 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_PORTHTTP_HOST가 설정되지 않은 경우, 이 변수들을 조합하여 기본값을 결정할 수 있습니다. 자세한 내용은 아래 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 환경 변수 [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 For many servers, |
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 라이브러리 레퍼런스에 문서화된 것과 같지만, 위 표에 표시된 다음 참고 사항은 예외입니다:
- 서버는 클라이언트가 지정한
Content-Length를 넘어서까지 읽을 필요는 없으며, 애플리케이션이 그 지점을 넘어 읽으려고 하면 파일 끝(end-of-file) 상태를 흉내 내야 합니다. 애플리케이션은CONTENT_LENGTH변수에 지정된 것보다 많은 데이터를 읽으려고 시도해서는 안 됩니다.서버는
read()가 인자 없이 호출되는 것을 허용해야 하며, 이 경우 클라이언트 입력 스트림의 나머지를 반환해야 합니다.서버는 비어 있거나 소진된 입력 스트림에서 읽으려는 모든 시도에 대해 빈 바이트열을 반환해야 합니다.
- 서버는
readline()의 선택적 “size” 인자를 지원해야 하지만, WSGI 1.0에서와 마찬가지로 이에 대한 지원을 생략하는 것도 허용됩니다.(WSGI 1.0에서는 구현이 복잡할 수 있고 실제로 자주 쓰이지 않는다는 이유로 size 인자를 지원하지 않았습니다… 그런데 이후
cgi모듈이 이를 사용하기 시작했고, 그래서 실제 서버들은 결국 이를 지원해야 했습니다!) readlines()의hint인자는 호출자와 구현자 모두에게 선택 사항이라는 점에 유의하십시오. 애플리케이션은 이를 전달하지 않아도 되며, 서버나 게이트웨이는 이를 무시해도 됩니다.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-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가 호출되는 시점에 헤더의 오류를 검사하는 것이 좋습니다.
그러나 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[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도 출력에 절대로 적용해서는 안 됩니다. 이러한 인코딩은 “홉 단위(hop-by-hop)” 연산으로서 실제 웹 서버/게이트웨이의 영역입니다. 자세한 내용은 아래의 Other HTTP Features를 참조하십시오.)
버퍼링과 스트리밍
일반적으로 애플리케이션은 (적당한 크기의) 출력을 버퍼링했다가 한꺼번에 전송할 때 가장 좋은 처리량을 얻습니다. 이는 Zope와 같은 기존 프레임워크에서 흔히 쓰이는 방식입니다. 출력은 StringIO나 이와 유사한 객체에 버퍼링된 다음, 응답 헤더와 함께 한꺼번에 전송됩니다.
WSGI에서 이에 대응하는 방식은 애플리케이션이 응답 본문을 하나의 바이트열로 담은 단일 요소 이터러블(예: 리스트)을 그냥 반환하는 것입니다. 이는 텍스트가 메모리에 쉽게 들어가는 HTML 페이지를 렌더링하는 대다수의 애플리케이션 함수에 권장되는 방식입니다.
그러나 큰 파일이나 (멀티파트 “서버 푸시”와 같은) 특수한 HTTP 스트리밍 용도의 경우, 애플리케이션은 (예를 들어 큰 파일을 메모리에 올리는 것을 피하기 위해) 출력을 더 작은 블록으로 나누어 제공해야 할 수도 있습니다. 또한 응답의 일부를 생성하는 데 시간이 오래 걸리는 경우도 있는데, 이때는 그 앞에 오는 응답 부분을 미리 보내 두는 것이 유용합니다.
이러한 경우 애플리케이션은 보통 출력을 블록 단위로 생성하는 이터레이터(흔히 제너레이터-이터레이터)를 반환합니다. 이러한 블록은 (“서버 푸시”를 위해) 멀티파트 경계에 맞추어 나누거나, (디스크에 있는 파일의 다른 블록을 읽는 것과 같은) 시간이 오래 걸리는 작업 직전에 나눌 수 있습니다.
WSGI 서버, 게이트웨이, 미들웨어는 어떤 블록의 전송도 지연해서는 안 됩니다. 블록을 클라이언트에 완전히 전송하거나, 애플리케이션이 다음 블록을 생성하는 동안에도 전송을 계속할 것을 반드시 보장해야 합니다. 서버/게이트웨이나 미들웨어는 다음 세 가지 방법 중 하나로 이를 보장할 수 있습니다.
- 애플리케이션에 제어를 돌려주기 전에 블록 전체를 운영 체제에 보냅니다(그리고 모든 O/S 버퍼를 플러시하도록 요청합니다). 또는
- 별도의 스레드를 사용하여 애플리케이션이 다음 블록을 생성하는 동안에도 블록이 계속 전송되도록 합니다.
- (미들웨어만 해당) 블록 전체를 상위 게이트웨이/서버로 보냅니다.
이러한 보장을 제공함으로써 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는 유니코드를 직접 지원하지 않으며, 이 인터페이스도 마찬가지입니다. 모든 인코딩/디코딩은 애플리케이션이 처리해야 합니다. 서버로 전달되거나 서버에서 전달되는 모든 문자열은 str 또는 bytes 타입이어야 하며, 절대 unicode여서는 안 됩니다. 문자열 객체가 필요한 곳에 unicode 객체를 사용한 결과는 정의되지 않습니다.
또한 start_response()에 상태나 응답 헤더로 전달되는 문자열은 인코딩과 관련하여 RFC 2616을 반드시 따라야 한다는 점에 유의하십시오. 즉, 이 문자열은 ISO-8859-1 문자이거나 RFC 2047 MIME 인코딩을 사용해야 합니다.
str 또는 StringType 타입이 실제로 유니코드 기반인 파이썬 플랫폼(예: Jython, IronPython, Python 3 등)에서는, 이 명세에서 언급하는 모든 “문자열”이 ISO-8859-1 인코딩으로 표현할 수 있는 코드 포인트(\u0000부터 \u00FF까지, 양 끝 포함)만 포함해야 합니다. 애플리케이션이 그 밖의 유니코드 문자나 코드 포인트를 포함하는 문자열을 제공하는 것은 치명적인 오류입니다. 마찬가지로, 서버와 게이트웨이는 그 밖의 유니코드 문자를 포함하는 문자열을 애플리케이션에 제공해서는 안 됩니다.
다시 말하지만, 이 명세에서 “문자열”이라고 언급하는 모든 객체는 반드시 str 또는 StringType 타입이어야 하며, unicode 또는 UnicodeType 타입이어서는 안 됩니다. 그리고 특정 플랫폼이 str/StringType 객체에서 문자당 8비트보다 많은 비트를 허용하더라도, 이 명세에서 “문자열”이라고 언급하는 모든 값에는 하위 8비트만 사용할 수 있습니다.
이 명세에서 “바이트열”이라고 언급하는 값(즉, wsgi.input에서 읽은 값, write()에 전달된 값, 또는 애플리케이션이 yield한 값)은 Python 3에서는 반드시 bytes 타입이어야 하고, 그 이전 버전의 파이썬에서는 str 타입이어야 합니다.
오류 처리
일반적으로 애플리케이션은 자체 내부 오류를 가로채서 브라우저에 도움이 되는 메시지를 표시하도록 노력해야 합니다. (이 맥락에서 무엇이 “도움이 되는” 것인지는 애플리케이션이 결정할 문제입니다.)
그러나 그런 메시지를 표시하려면 애플리케이션이 아직 브라우저에 실제로 어떤 데이터도 보내지 않은 상태여야 하며, 그렇지 않으면 응답이 손상될 위험이 있습니다. 따라서 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()를 호출)하게 되고, 미들웨어는 오류 출력을 포착하여 수정할 수 있습니다. 이러한 기법은 애플리케이션 작성자가 다음을 지키는 한 동작합니다:
- 오류 응답을 시작할 때는 항상
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 기능
일반적으로 서버와 게이트웨이는 “모르는 척”하고, 애플리케이션이 자신의 출력을 완전히 제어하도록 해야 합니다. 서버와 게이트웨이는 애플리케이션 응답의 실질적인 의미를 바꾸지 않는 변경만 해야 합니다. 애플리케이션 개발자는 언제든지 미들웨어 컴포넌트를 추가하여 부가 기능을 제공할 수 있으므로, 서버/게이트웨이 개발자는 보수적으로 구현해야 합니다. 어떤 의미에서 서버는 자신을 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-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 부분을 사용해 호출되도록 해당 API를 반드시 설계해야 합니다. 예를 들어 HTTP 요청 헤더에 접근하는 확장 API는 애플리케이션이 현재 environ을 넘겨주도록 요구해야 합니다. 그래야 서버/게이트웨이가 API로 접근할 수 있는 HTTP 헤더를 미들웨어가 변경하지 않았는지 확인할 수 있습니다. 확장 API가 HTTP 헤더의 내용에 관해 항상 environ과 일치한다고 보장할 수 없다면, 오류를 발생시키거나 헤더 컬렉션 대신 None을 반환하는 등 해당 API에 알맞은 방법으로 애플리케이션에 대한 서비스를 거부해야 합니다.
마찬가지로 확장 API가 응답 데이터나 헤더를 쓰는 다른 수단을 제공한다면, 애플리케이션이 확장 서비스를 얻기 전에 start_response 호출 가능 객체를 넘겨주도록 요구해야 합니다. 넘겨받은 객체가 서버/게이트웨이가 처음에 애플리케이션에 제공한 객체와 같지 않다면 확장 API는 올바른 동작을 보장할 수 없으므로, 애플리케이션에 확장 서비스를 제공하기를 거부해야 합니다.
이 지침은 파싱된 쿠키, 폼 변수, 세션 등의 정보를 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.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 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, '')를 반환한 경우와 같아야 합니다. 다시 말해, 전송은 전송이 시작되는 시점의 “파일” 내 현재 위치에서 시작하여 끝에 도달하거나 Content-Length 바이트가 기록될 때까지 계속되어야 합니다. (애플리케이션이 Content-Length를 제공하지 않으면, 서버는 기반 파일 구현에 대한 지식을 활용하여 파일로부터 이를 생성할 수 있습니다.)
물론 플랫폼별 파일 전송 API는 보통 임의의 “파일류” 객체를 받아들이지 않습니다. 따라서 wsgi.file_wrapper는 파일류 객체가 자신이 지원하는 플랫폼별 API와 함께 사용하기에 적합한지 판단하기 위해, 제공된 객체에 fileno() (Unix 계열 OS)나 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()
질문과 답변
environ은 왜 딕셔너리여야 합니까? 서브클래스를 사용하면 무엇이 문제입니까?딕셔너리를 요구하는 근거는 서버 간 이식성을 최대화하기 위함입니다. 다른 방법으로는 딕셔너리 메서드의 일부만 표준적이고 이식 가능한 인터페이스로 정의하는 방법이 있습니다. 그러나 실제로는 대부분의 서버가 딕셔너리로 충분하다고 여길 것입니다. 또한 딕셔너리의 모든 기능이 제공되는 경우가 대부분일 것이므로, 프레임워크 작성자들은 그 모든 기능을 쓸 수 있다고 기대하게 될 것입니다. 그런데 어떤 서버가 딕셔너리를 사용하지 않기로 한다면, 그 서버가 명세를 “준수”하더라도 상호 운용성 문제가 생길 것입니다. 따라서 딕셔너리를 필수로 하면 명세가 단순해지고 상호 운용성이 보장됩니다.
그렇다고 해서 서버나 프레임워크 개발자가
environ딕셔너리 안에 사용자 정의 변수를 두어 특수한 서비스를 제공하지 못하는 것은 아니라는 점에 유의하십시오. 이러한 부가 서비스를 제공할 때는 이 방법을 권장합니다.- 왜
write()를 호출할 수도 있고 동시에 바이트 문자열을 yield하거나 이터러블을 반환할 수도 있습니까? 한 가지 방법만 골라야 하지 않습니까?반복 방식만 지원한다면 “push”를 쓸 수 있다고 전제하는 기존 프레임워크들이 어려움을 겪습니다. 반대로
write()를 통한 push만 지원한다면, 예컨대 큰 파일을 전송할 때 서버 성능이 떨어집니다(모든 출력을 보내기 전까지 작업자 스레드가 새 요청을 처리하기 시작할 수 없는 경우). 그래서 이 절충안을 쓰면 애플리케이션 프레임워크는 상황에 맞게 두 방식을 모두 지원할 수 있습니다. 게다가 서버 구현자의 부담은 push만 지원하는 방식보다 조금 늘어날 뿐입니다. close()는 무엇을 위한 것입니까?애플리케이션 객체가 실행되는 동안 쓰기를 한다면, 애플리케이션은 try/finally 블록으로 리소스가 반드시 해제되도록 할 수 있습니다. 하지만 애플리케이션이 이터러블을 반환한다면, 사용한 리소스는 그 이터러블이 가비지 컬렉션될 때까지 해제되지 않습니다.
close()관용구를 쓰면 애플리케이션이 요청이 끝날 때 중요한 리소스를 해제할 수 있습니다. 또한 이 관용구는 PEP 325에서 제안한 제너레이터의 try/finally 지원과도 상위 호환됩니다.- 이 인터페이스는 왜 이렇게 저수준입니까? 기능 X가 필요합니다! (예: 쿠키, 세션, 영속성, …)
이것은 또 하나의 Python 웹 프레임워크가 아닙니다. 이것은 프레임워크가 웹 서버와 통신하고, 웹 서버가 프레임워크와 통신하는 방법일 뿐입니다. 이런 기능이 필요하다면 원하는 기능을 제공하는 웹 프레임워크를 골라야 합니다. 그리고 그 프레임워크로 WSGI 애플리케이션을 만들 수 있다면, 그 애플리케이션은 WSGI를 지원하는 대부분의 서버에서 실행할 수 있을 것입니다. 또한 일부 WSGI 서버는
environ딕셔너리에 넣어 주는 객체를 통해 추가 서비스를 제공할 수 있습니다. 자세한 내용은 해당 서버의 문서를 참조하십시오. (물론 이러한 확장을 사용하는 애플리케이션은 다른 WSGI 기반 서버로 이식할 수 없습니다.) - 왜 익숙한 HTTP 헤더 대신 CGI 변수를 사용합니까? 그리고 왜 CGI 변수를 WSGI가 정의한 변수와 섞어 둡니까?
기존의 많은 웹 프레임워크는 CGI 명세에 크게 의존하며, 기존 웹 서버는 CGI 변수를 생성할 줄 압니다. 반면에 들어오는 HTTP 정보를 표현하는 다른 방식들은 서로 제각각이고 널리 쓰이지도 않습니다. 따라서 CGI “표준”을 사용하면 기존 구현을 잘 활용할 수 있을 것으로 보입니다. WSGI 변수와 섞는 문제에 대해 말하자면, 둘을 분리하면 딕셔너리 인자 두 개를 주고받아야 할 뿐 실질적인 이점은 없습니다.
- 상태 문자열은 어떻습니까?
"200 OK"대신200을 전달하는 식으로 그냥 숫자를 쓰면 안 됩니까?이렇게 하면 서버나 게이트웨이가 숫자 상태 코드와 그에 대응하는 메시지의 표를 갖추어야 하므로 서버나 게이트웨이가 복잡해집니다. 반면 애플리케이션이나 프레임워크 작성자는 자신이 사용하는 특정 응답 코드에 덧붙일 추가 텍스트를 쉽게 입력할 수 있으며, 기존 프레임워크에는 필요한 메시지를 담은 표가 이미 있는 경우가 많습니다. 따라서 전체적으로 볼 때 서버나 게이트웨이보다는 애플리케이션/프레임워크가 이를 책임지게 하는 편이 더 나아 보입니다.
wsgi.run_once가 앱을 단 한 번만 실행한다고 보장하지 않는 이유는 무엇입니까?이는 애플리케이션에 “드물게 실행되는 상황에 대비하라”고 제안하는 것에 불과하기 때문입니다. 이는 캐싱, 세션 등을 위한 여러 동작 모드를 갖춘 애플리케이션 프레임워크를 위한 것입니다. “다중 실행” 모드에서 이러한 프레임워크는 캐시를 미리 로드할 수 있으며, 요청마다 로그나 세션 데이터 등을 디스크에 기록하지 않을 수도 있습니다. “단일 실행” 모드에서 이러한 프레임워크는 미리 로드하지 않으며, 요청마다 필요한 모든 쓰기를 플러시합니다.
그러나 후자의 모드에서 올바르게 동작하는지 확인하기 위해 애플리케이션이나 프레임워크를 테스트하려면, 이를 두 번 이상 호출해야 할 수도(또는 적어도 그 편이 편리할 수도) 있습니다. 따라서 애플리케이션은
wsgi.run_once가True로 설정된 채 호출되었다는 이유만으로 다시는 절대 실행되지 않을 것이라고 가정해서는 안 됩니다.- 기능 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 기능들을 중점적으로 검토해 주었습니다.
- Graham Dumpleton은 (저의 게으름과 어리석음에도 불구하고) 어떤 형태로든 Python 3용 WSGI를 내놓기 위해 지칠 줄 모르고 노력하였고, “네이티브 문자열” 대 “바이트 문자열” 개념을 제안하였으며, 수많은 HTTP,
wsgi.input, 그 밖의 수정 사항들과 사려 깊게 씨름해 주었습니다. 이 새 PEP의 공로는 전부는 아닐지라도 대부분 그에게 돌아가야 합니다.
참고 문헌
Copyright
This document has been placed in the public domain.