PEP 3333–Python Web Server Gateway Interface V1.0.1#
Python Web Server Gateway Interface (WSGI) 버전 1.0.1을 정의한 문서.
PEP 333의 개정판으로, Python 3 지원을 개선하고 몇 가지 오랜 de facto 수정사항을 반영
PEP 3333은 PEP 333을 Python 3 시대에 맞게 업데이트한 버전이다.
주요 변경사항과 특징#
- Python 3 지원:
- 문자열 처리가 유니코드로 변경됨
- environ 딕셔너리의 문자열은 str 타입이어야 함
- 응답 본문은 bytes 타입이어야 함
- 새로운 보안 고려사항:
- 미들웨어 체이닝:
- 여러 미들웨어를 연결하여 요청/응답 처리 파이프라인 구성 가능
- 파일 핸들링:
- wsgi.input과 wsgi.errors를 통한 표준화된 입출력 처리
WSGI (Web Server Gateway Interface)#
웹 서버와 Python 웹 어플리케이션 또는 프레임워크 간의 표준 인터페이스를 정의한다.
다양한 웹 서버와 Python 웹 어플리케이션 간의 호환성을 보장한다.
- 호환성과 이식성:
WSGI는 웹 서버와 Python 웹 애플리케이션/프레임워크 간의 표준 인터페이스를 제공합니다. 이를 통해 다양한 웹 서버와 프레임워크 간의 호환성이 보장되어 애플리케이션의 이식성이 크게 향상됩니다 - 유연성:
개발자는 코드 변경 없이 웹 스택 구성요소를 쉽게 변경할 수 있습니다. 이는 다양한 서버와 프레임워크를 조합하여 사용할 수 있게 해줍니다 - 확장성:
WSGI 서버는 수천 개의 동시 요청을 처리하고 라우팅할 수 있어 대규모 애플리케이션에 적합합니다 - 단순성:
WSGI의 학습 곡선이 완만하여 쉽게 익힐 수 있으며, 복잡한 설정이나 설치 과정이 필요 없습니다 - 재사용 가능한 미들웨어:
인증/인가, 캐싱, 필터링 등의 기능을 제공하는 재사용 가능한 미들웨어 컴포넌트를 활용할 수 있어 개발 시간을 단축할 수 있습니다 - Python 3 지원:
PEP 3333은 Python 3에서의 문자열과 유니코드 처리에 대한 규칙을 추가하여 Python 3 환경에서의 WSGI 사용을 개선했습니다
기본 구조#
WSGI 서버 (Server/Gateway)#
클라이언트로부터 HTTP 요청을 받아들이고, 이를 WSGI 애플리케이션으로 전달한 후 응답을 클라이언트에 다시 전송한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| import os, sys
def run_with_cgi(application):
environ = dict(os.environ.items()) # 환경 변수 복사
environ['wsgi.input'] = sys.stdin.buffer # 요청 본문을 읽기 위한 입력 스트림
environ['wsgi.errors'] = sys.stderr # 오류 출력을 위한 스트림
environ['wsgi.version'] = (1, 0) # WSGI 버전
environ['wsgi.multithread'] = False # 멀티스레드 지원 여부
environ['wsgi.multiprocess'] = True # 멀티프로세스 지원 여부
environ['wsgi.run_once'] = True # 한 번만 실행되는지 여부
environ['wsgi.url_scheme'] = 'http' # URL 스키마
headers_set = []
headers_sent = []
def write(data):
# 응답 헤더와 본문을 출력하는 함수
if not headers_set:
raise AssertionError("write() before start_response()")
elif not headers_sent:
status, response_headers = headers_sent[:] = headers_set
sys.stdout.buffer.write(f"Status: {status}\r\n".encode('iso-8859-1'))
for header in response_headers:
sys.stdout.buffer.write(f"{header[0]}: {header[1]}\r\n".encode('iso-8859-1'))
sys.stdout.buffer.write(b"\r\n")
sys.stdout.buffer.write(data)
sys.stdout.buffer.flush()
def start_response(status, response_headers, exc_info=None):
# 응답 시작을 처리하는 함수
if exc_info:
try:
if headers_sent:
raise exc_info[1].with_traceback(exc_info[2])
finally:
exc_info = None
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status, response_headers]
return write
result = application(environ, start_response) # 애플리케이션 실행
try:
for data in result:
if data:
write(data) # 응답 데이터 출력
if not headers_sent:
write(b'') # 빈 응답일 경우 헤더만 전송
finally:
if hasattr(result, 'close'):
result.close() # 리소스 정리
# 주석:
# - run_with_cgi: CGI 환경에서 WSGI 애플리케이션을 실행하는 함수
# - environ: WSGI 환경 변수 설정
# - write: 응답을 클라이언트에 전송하는 함수
# - start_response: 응답 헤더를 설정하는 함수
# - result: 애플리케이션의 응답 (이터러블)
|
WSGI 애플리케이션 (Application/Framework)#
서버로부터 전달받은 요청을 처리하고 응답을 생성하여 반환한다.
1
2
3
4
5
6
7
8
9
10
11
| def simple_app(environ, start_response):
"""가장 간단한 애플리케이션 객체"""
status = '200 OK' # HTTP 상태
response_headers = [('Content-type', 'text/plain')] # 응답 헤더
start_response(status, response_headers)
return [b"Hello world!\n"] # 응답 본문
# 주석:
# - environ: 환경 변수와 요청 정보를 담은 딕셔너리
# - start_response: 응답을 시작하는 함수
# - 반환값: 응답 본문을 포함하는 이터러블(iterable)
|
미들웨어 (Middleware)#
서버와 애플리케이션 사이에서 동작하며, 요청을 응답을 가로채어 추가적인 처리를 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| class Latinator:
"""평문 응답을 Pig Latin으로 변환하는 미들웨어"""
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
transform_ok = []
def start_latin(status, response_headers, exc_info=None):
# 응답 헤더를 확인하고 필요시 변환 설정
del transform_ok[:]
for name, value in response_headers:
if name.lower() == 'content-type' and value == 'text/plain':
transform_ok.append(True)
break
# Content-Length 헤더 제거 (변환으로 길이가 바뀔 수 있음)
response_headers = [(name, value) for name, value in response_headers
if name.lower() != 'content-length']
return start_response(status, response_headers, exc_info)
response = self.application(environ, start_latin)
if transform_ok:
return [self.piglatin(chunk) for chunk in response]
else:
return response
def piglatin(self, text):
# Pig Latin 변환 로직 (간단한 예시)
words = text.split()
return b' '.join([word[1:] + word[0:1] + b'ay' for word in words])
# 주석:
# - Latinator: 응답을 Pig Latin으로 변환하는 미들웨어 클래스
# - __call__: WSGI 호출 규약을 따르는 메서드
# - start_latin: 원본 start_response를 래핑하여 변환 여부 결정
# - piglatin: 텍스트를 Pig Latin으로 변환하는 메서드
|
작동 방식#
- 웹 서버가 요청을 받는다.
- 서버는 이 요청을 WSGI 애플리케이션에 전달한다.
- 애플리케이션은 요청을 처리하고 응답을 생성한다.
- 응답은 다시 서버를 통해 클라이언트에게 전송된다.
1
2
3
4
5
| def simple_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, headers)
return [b"Hello, World!"]
|
environ
: 요청 정보와 서버 환경 변수를 포함하는 딕셔너리start_response
: 응답 시작을 알리는 함수- 반환값: 응답 본문을 포함하는 이터러블 객체
주요 WSGI 서버/미들웨어#
- Gunicorn: UNIX 환경에서 사용하기 위해 만들어진 Python WSGI HTTP 서버
- uWSGI: 다양한 언어와 프로토콜을 지원하며, 풀스택 개발이 가능한 WSGI 서버
- mod_wsgi (Apache 모듈): Apache HTTP Server의 모듈로, Python 웹 애플리케이션을 위한 WSGI 인터페이스를 지원
대표적인 WSGI 프레임워크#
WSGI를 사용할 때 성능을 최적화하기 위한 몇 가지 방법#
- 프로덕션용 WSGI 서버 사용:
개발 서버 대신 Gunicorn이나 uWSGI 같은 프로덕션용 WSGI 서버를 사용하면 처리량과 응답성을 크게 향상시킬 수 있다. - WSGI 프로세스 수 조정:
서버의 CPU 코어 수에 맞춰 WSGI 프로세스 수를 조정한다. 일반적으로 CPU 코어 수와 동일하게 설정하는 것이 좋다. - 캐싱 구현:
Flask-Caching 같은 라이브러리를 사용해 자주 요청되는 데이터를 캐싱하여 중복 처리를 줄인다. - 데이터베이스 연결 풀링: SQLAlchemy의 연결 풀링을 활용하여 데이터베이스 연결 오버헤드를 줄인다.
- 응답 압축: Flask-Compress를 사용해 응답을 압축하여 전송 데이터량을 줄이고 페이지 로드 시간을 개선한다.
- 백그라운드 작업 활용: 이메일 전송이나 대용량 데이터 처리 같은 리소스 집약적 작업은 Celery를 사용해 백그라운드로 처리한다.
- 적절한 로깅 설정: 과도한 로깅은 성능에 영향을 줄 수 있으므로, 프로덕션 환경에서는 필요한 로그만 남기도록 설정한다.
참고 및 출처#