데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      [네트워크] WSGI와 Python 2편

      Teus 23.10.30
      1,684 14 0
      DEVOTEE 요약
      이 포스팅은 Python에서의 WSGI 2탄으로, App과 requesthandler를 사용하는 response 전송 방법을 설명합니다. WSGI는 response를 보내기 위해 App을 만들어놓고 이를 기반으로 HTTP 메시지를 생성합니다. 이 메시지를 stdout에 쓴 후, 소켓을 통해 외부로 발송하는 것이 WSGI의 전체적인 동작 원리입니다.
      DEVOTEE 추천 블로그

      안녕하세요. Teus입니다.


      이번 포스팅은 지난시간에 다뤘던 Python WSGI 2탄 입니다.

      지난 포스팅에서 활용했던 WSGI를 사용하는 기초 예제를 기반으로 설명을 진행 합니다.


      1. WSGI의 Response처리

      WSGI는 기본적으로 아래 두가지로 구성됩니다.

      1. 무한루프를 돌면서 Request를 받아들이는 socket 서버

      2. socket서버가 받아들인 request를 처리하는 requesthandler

      이번 포스팅에서는 app과 requesthanlder를 활용해서 response를 보내는 방법을 알아 볼겁니다.

      대략적으로 response과정에서 app을 callback으로 활용해서 request를 처리할 것을 예상은 할 수 있습니다.

      실제로 그런지 알아 볼까요?


      2. WSGI의 Requesthanlder

      request handler는 아래의 순서로 동작하게 됩니다.

      1. request를받음

      2. 해당 request가 유효한지 판단

      3. 정해진 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)


      3. BaseRequestHandler

      이제 위 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):
              pass

      Class를 살펴보면 BaseRequestHandler에서 단순하게 생성자만을 정의하고 있습니다.

      이 생성자를 통해서 순서대로 req, client정보, server정보가 설정되고

      self.handle()을 통해서 request를 다루기 시작하게 됩니다.

      이때 handler이 pass로 비어있지요? 이거는 추후에 하위 class에서 overriden하게 됩니다.

      이때

      BaseRequestHandler

      ↑

      StremRequestHandler

      ↑

      BaseHTTPRequestHandler

      ↑

      WSGIRequestHandler

      순으로 상속이 되어, 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이므로 여기까지...)


      정리

      1. WSGI는 response를 보내기위한 App을 만들어놓고, 이 기반으로 Response HTTP Msg를 만듦

      2. WSGI는 stdout에 data를 쓰고 이 data를 socket을 통해서 외부로 송출함

      자 이제 Python의 Standard Server가 어떻게 돌아가시는지 이해가 되실까요😊?


      긴글 읽어주셔서 감사합니다 :)

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      Teus 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기