23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
안녕하세요. Teus입니다.
이번 포스팅은 지난시간에 다뤘던 Python WSGI 2탄 입니다.
지난 포스팅에서 활용했던 WSGI를 사용하는 기초 예제를 기반으로 설명을 진행 합니다.
WSGI는 기본적으로 아래 두가지로 구성됩니다.
무한루프를 돌면서 Request를 받아들이는 socket 서버
socket서버가 받아들인 request를 처리하는 requesthandler
이번 포스팅에서는 app과 requesthanlder를 활용해서 response를 보내는 방법을 알아 볼겁니다.
대략적으로 response과정에서 app을 callback으로 활용해서 request를 처리할 것을 예상은 할 수 있습니다.
실제로 그런지 알아 볼까요?
request handler는 아래의 순서로 동작하게 됩니다.
request를받음
해당 request가 유효한지 판단
정해진 callback기반으로 response HTTP Msg생성
먼저 Python의 wsgiref 예제로 돌아가 보겠습니다.
예제에서는 Response를 어떻게 할지 사전에 정의된 simple_app이란것을 만들었고
이 app을 make_server에 집어넣었습니다.
#https://docs.python.org/ko/3/library/wsgiref.html#wsgiref.util.setup_testing_defaults
from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server
def simple_app(environ, start_response):
setup_testing_defaults(environ)
status = '200 OK'
headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, headers)
ret = [("%s: %s\n" % (key, value)).encode("utf-8")
for key, value in environ.items()]
return ret
with make_server('', 8000, simple_app) as httpd:
print("Serving on port 8000...")
httpd.serve_forever()그리고 이렇게 make_server로 전달된 simple_app같은 경우
server_class의 set_app을 통해서 application(callback)을 저장하게 되었습니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/simple_server.py#L150
def make_server(
host,
port,
app,
server_class=WSGIServer,
handler_class=WSGIRequestHandler
):
"""Create a new WSGI server listening on `host` and `port` for `app`"""
server = server_class((host, port), handler_class)
server.set_app(app)
return server그리고 지난 포스팅에서
HTTP Scoket Server는 request가 오기를 기다리며
request가 도착했을 때 serve_forever()함수에서 handle_request_noblock() 함수를 통해서 request를 처리를 한다고 했습니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/socketserver.py#L304
class BaseServer:
...
def _handle_request_noblock(self):
try:
request, client_address = self.get_request()
except OSError:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
...그럼 이제 여기서 중간에 self.process_request(request, client_address)를 통해서
request를 처리하게 됩니다.
그리고 이때 RequestHandlerClass에 request와 client정보를 제공하여 request를 처리하기 시작합니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/socketserver.py#L342
class BaseServer:
...
def process_request(self, request, client_address):
self.finish_request(request, client_address)
self.shutdown_request(request)
...
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)이제 위 requesthandlerClass에 Object를 넣었을때 어떤일이 일어나는지를 보겠습니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/socketserver.py#L731
class BaseRequestHandler:
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
def setup(self):
pass
def handle(self):
pass
def finish(self):
passClass를 살펴보면 BaseRequestHandler에서 단순하게 생성자만을 정의하고 있습니다.
이 생성자를 통해서 순서대로 req, client정보, server정보가 설정되고
self.handle()을 통해서 request를 다루기 시작하게 됩니다.
이때 handler이 pass로 비어있지요? 이거는 추후에 하위 class에서 overriden하게 됩니다.
이때
↑
↑
↑
순으로 상속이 되어, WSGIRequestHandler를 통해서 handle method가 구현됩니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/simple_server.py#L129C19-L129C32
class WSGIRequestHandler(BaseHTTPRequestHandler):
...
def handle(self):
"""Handle a single HTTP request"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
multithread=False,
)
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())여기서 self.parse_request()부분은 request를 검증하는 부분이구
문제가 없다면 ServerHandler Object를 만들고, 이 Object로 처리가 시작됩니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/simple_server.py#L28
class ServerHandler(SimpleHandler):
server_software = software_version
def close(self):
try:
self.request_handler.log_request(
self.status.split(' ',1)[0], self.bytes_sent
)
finally:
SimpleHandler.close(self)ServerHandler는 단순하게 loging만 하고
실제 Response를 만드는 동작은 상위 SImpleHanlder에서 진행합니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/handlers.py#L433
class SimpleHandler(BaseHandler):
...
def __init__(self,stdin,stdout,stderr,environ,
multithread=True, multiprocess=False
):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.base_env = environ
self.wsgi_multithread = multithread
self.wsgi_multiprocess = multiprocess이때 SimpleHandler는 생성자와I/O를 처리하기위한 method들이 정의되어있고
중요한 self.run() method같은 경우 상위 Class에서 정의되어 있습니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/handlers.py#L94
class BaseHandler:
"""Manage the invocation of a WSGI application"""
...
def run(self, application):
try:
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
...이제 보시면, BaseHandler의 run에서 application에다가 환경변수정보와 self.start_response callback을 넣어줍니다.
참고로 환경변수는 아래 self.setup_environ에서
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/handlers.py#L225
class BaseHandler:
...
def setup_environ(self):
"""Set up the environment for one request"""
env = self.environ = self.os_environ.copy()
self.add_cgi_vars()
env['wsgi.input'] = self.get_stdin()
env['wsgi.errors'] = self.get_stderr()
env['wsgi.version'] = self.wsgi_version
env['wsgi.run_once'] = self.wsgi_run_once
env['wsgi.url_scheme'] = self.get_scheme()
env['wsgi.multithread'] = self.wsgi_multithread
env['wsgi.multiprocess'] = self.wsgi_multiprocess
if self.wsgi_file_wrapper is not None:
env['wsgi.file_wrapper'] = self.wsgi_file_wrapper
if self.origin_server and self.server_software:
env.setdefault('SERVER_SOFTWARE',self.server_software)그리고 self.start_response는 아래와같이 정의 됩니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/handlers.py#L225C1-L251C26
class BaseHandler:
...
def start_response(self, status, headers,exc_info=None):
"""'start_response()' callable as specified by PEP 3333"""
if exc_info:
try:
if self.headers_sent:
raise
finally:
exc_info = None # avoid dangling circular ref
elif self.headers is not None:
raise AssertionError("Headers already set!")
self.status = status
self.headers = self.headers_class(headers)
status = self._convert_string_type(status, "Status")
...
return self.write사실 start_response는 status코드와 headers를 받고
해당 정보를 BaseHandler의 Property로 설정하기 위해서 사용되는 Callback에 불과합니다
이렇게 app을 사용하고, 최종적으로 self.finish_response를 통해서 response가 처리되게 됩니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/handlers.py#L173C1-L185C38
class BaseHandler:
...
def finish_response(self):
"""Send any iterable data, then close self and the iterable
"""
try:
if not self.result_is_file() or not self.sendfile():
for data in self.result:
self.write(data)
self.finish_content()현재 self.result에는 app을통해서 정의된 response를 보내기위한 data가 담겨 있습니다.(List형태로)
이때 반복문을 돌면서 self.write를 통해서 해당 data를 쓰는것을 볼 수 있습니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/handlers.py#L275
class BaseHandler:
...
def write(self, data):
...
elif not self.headers_sent:
# Before the first output, send the stored headers
self.bytes_sent = len(data) # make sure we know content-length
self.send_headers()
else:
self.bytes_sent += len(data)
self._write(data)
self._flush()이때 내부에서 self._write를 통해서 받아들일 data를 실제 활용하게 되고
이 self._write같은 경우 SimpleHandler에 정의되어 있습니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/wsgiref/handlers.py#L433C1-L433C1
class SimpleHandler(BaseHandler):
...
def _write(self,data):
result = self.stdout.write(data)
if result is None or result == len(data):
return
...
while True:
data = data[result:]
if not data:
break
result = self.stdout.write(data)
def _flush(self):
self.stdout.flush()
self._flush = self.stdout.flush위를 통해서 stdout에다가 write를 통해서 response로 보낼 data를 저장시켜놓은것을 볼 수 있습니다.
이제 self.handle()은 끝이 났으므로, self.finish()를 통해서 RequestHandle작업이 완료됩니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/socketserver.py#L731
class BaseRequestHandler:
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()그리고 이 self.finish()의 경우 중간인 StremRequestHandler에서 정의되어 있습니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/socketserver.py#L811
class StreamRequestHandler(BaseRequestHandler):
...
def finish(self):
if not self.wfile.closed:
try:
self.wfile.flush()
except socket.error:
# A final socket error may have occurred here, such as
pass
self.wfile.close()
self.rfile.close()이제 거의 다 왔습니다.
self.wfile같은 경우 StreamRequestHandler내부에 정의되어있고
wfile은 _SocketWriter Object입니다.
#https://github.com/python/cpython/blob/7dead6a33ac786465c3d927d5c2025caf0c1a71b/Lib/socketserver.py#L777C1-L798C21
class StreamRequestHandler(BaseRequestHandler):
...
def setup(self):
...
self.wfile = _SocketWriter(self.connection)
class _SocketWriter(BufferedIOBase):
"""Simple writable BufferedIOBase implementation for a socket
Does not hold data in a buffer, avoiding any need to call flush()."""
def __init__(self, sock):
self._sock = sock
def writable(self):
return True
def write(self, b):
self._sock.sendall(b)
with memoryview(b) as view:
return view.nbytes
def fileno(self):
return self._sock.fileno()결국 stdout에 저장된 Data를 _SocketWriter Object를 이용하고
이 결과 Socket을 통해서 Response를 위한 Data를 외부로 송출하는것을 예상할 수 있습니다.
(BufferdIOBase는 더 들어가면 CPython이므로 여기까지...)
정리
WSGI는 response를 보내기위한 App을 만들어놓고, 이 기반으로 Response HTTP Msg를 만듦
WSGI는 stdout에 data를 쓰고 이 data를 socket을 통해서 외부로 송출함
자 이제 Python의 Standard Server가 어떻게 돌아가시는지 이해가 되실까요😊?
긴글 읽어주셔서 감사합니다 :)
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.