REST 아키텍처는 HTTP의 기본 메서드를 활용해 자원을 다루는 대표적인 설계 패턴이다. 이번 글에서는 표준 라이브러리만으로 경량 REST 서비스를 구현하고, 외부에서 이를 호출하는 방법을 살펴본다.
전체 흐름 설계
구현할 서비스는 다음 두 가지 엔드포인트를 제공한다.
/greet— 이름을 받아 인사말 HTML을 반환/now— 현재 시각 정보를 XML로 반환
핵심 구성 요소는 RouteRegistry라는 분배자(dispatch) 클래스이며, WSGI 규약을 따른다.
RouteRegistry: 요청 라우팅 처리
import cgi
def default_404(environ, start_response):
start_response('404 Not Found', [('Content-Type', 'text/plain')])
return [b'Resource unavailable']
class RouteRegistry:
def __init__(self):
self._routes = {}
def __call__(self, environ, start_response):
route_key = (environ['REQUEST_METHOD'].upper(), environ['PATH_INFO'])
payload = cgi.FieldStorage(environ['wsgi.input'], environ=environ)
environ['args'] = {k: payload.getvalue(k) for k in payload}
handler = self._routes.get(route_key, default_404)
return handler(environ, start_response)
def add(self, verb, uri, callback):
self._routes[(verb.upper(), uri)] = callback
return callback
RouteRegistry는 (HTTP 메서드, 경로) 쌍을 키로 하여 핸들러 함수를 관리한다. __call__ 메서드 덕분에 WSGI 애플리케이션으로 직접 사용할 수 있다.
비즈니스 로직: 인사말과 시각
import time
_GREET_TEMPLATE = '''<!DOCTYPE html>
<html>
<head><title>Greeting for {user}</title></head>
<body><h1>Welcome, {user}!</h1></body>
</html>'''
def greet_user(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])
user = environ['args'].get('user', 'Guest')
yield _GREET_TEMPLATE.format(user=user).encode('utf-8')
_TIMESTAMP_TEMPLATE = '''<?xml version="1.0"?>
<timestamp>
<year>{dt.tm_year}</year>
<month>{dt.tm_mon}</month>
<day>{dt.tm_mday}</day>
<hour>{dt.tm_hour}</hour>
<minute>{dt.tm_min}</minute>
<second>{dt.tm_sec}</second>
</timestamp>'''
def current_timestamp(environ, start_response):
start_response('200 OK', [('Content-Type', 'application/xml')])
yield _TIMESTAMP_TEMPLATE.format(dt=time.localtime()).encode('utf-8')
greet_user는 쿼리 파라미터 user를 받아 동적 HTML을, current_timestamp는 시스템 시계를 참조해 XML을 생성한다. 두 함수 모두 제너레이터를 사용해 바이트열을 반환한다.
서버 기동
if __name__ == '__main__':
from wsgiref.simple_server import make_server
app = RouteRegistry()
app.add('GET', '/greet', greet_user)
app.add('GET', '/now', current_timestamp)
srv = make_server('0.0.0.0', 8080, app)
print('WSGI server listening on http://0.0.0.0:8080')
srv.serve_forever()
0.0.0.0을 바인딩하면 동일 네트워크 내 다른 기기에서도 접근할 수 있다.
원격 호출 검증
별도 터미널에서 다음 스크립트를 실행해 서버를 테스트한다.
import urllib.request
with urllib.request.urlopen('http://localhost:8080/greet?user=Alice') as r:
print(r.read().decode('utf-8'))
with urllib.request.urlopen('http://localhost:8080/now') as r:
print(r.read().decode('utf-8'))
정상 응답 시 Alice를 위한 HTML 페이지와 현재 시각이 담긴 XML을 확인할 수 있다.
응답 예시
/greet?user=Alice 요청:
<!DOCTYPE html>
<html>
<head><title>Greeting for Alice</title></head>
<body><h1>Welcome, Alice!</h1></body>
</html>
/now 요청:
<?xml version="1.0"?>
<timestamp>
<year>2025</year>
<month>1</month>
<day>15</day>
<hour>9</hour>
<minute>42</minute>
<second>3</second>
</timestamp>
이 구조에 새로운 경로와 메서드를 추가하면 다양한 원격 제어 기능을 손쉽게 확장할 수 있다.