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

신고하기

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

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

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

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

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

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

      임시저장함

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

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

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

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

      회원정보 연결

      [네트워크] WSGI와 Python 1편

      Teus 23.09.20
      4,938 18 2
      DEVOTEE 요약
      CGI는 클라이언트로부터 HTTP 요청 메시지를 받아들이고 해당 응답을 출력으로 보내는 Common Gateway Interface를 말하며, WSGI는 Python의 내장된 요청 처리 API인 Web Server Gateway Interface를 의미한다. WSGI를 사용하면 low level에서 웹 서버를 구축할 수 있으며, WSGI 서버는 HTTP 요청을 받고 처리하는 로직을 포함하고 있다. 즉, WSGI는 요청을 받는 역할을 하고, 서버에서 요청을 처리하고 응답을 보내는 방법은 다음 시간에 알아보도록 하겠다.

      안녕하세요 Teus 입니다.


      오랜만에 RN 스터디가 아닌 글로 찾아왔습니다.

      이번시간에 다뤄볼 내용은 WGSI입니다.

      WSGI에 앞서, 모든 프로그래밍언어에서 사용되는 CGI에 대해서 간단하게 알아봅니다.


      0. CGI는 무엇인가?

      CGI는 Common Gate Interface로 Client로 부터 발송된 Request HTTP Msg를 가장 먼저 받아들이는 Layer입니다.

      Interface이기 때문에, 특정 언어에 귀속된게 아니라 다양한 프로그래밍 언어에서 구현이 되어 있습니다.

      과거 Web 프로그래밍이 난잡하던 시기에, 모든 언어에서 동일한 Interface로 HTTP Msg를 처리할 수 있게 만들기위해서 고안된 알고 있습니다.

      CGI의 특징으로는 HTTP Requests를 받고, 그에따른 HTTP Response를 stdout에 출력하면 이 출력이 HTTP Msg 형태로 Client에게 돌아갑니다.

      #출처 : https://ko.wikipedia.org/wiki/%EA%B3%B5%EC%9A%A9_%EA%B2%8C%EC%9D%B4%ED%8A%B8%EC%9B%A8%EC%9D%B4_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4
      =head1 DESCRIPTION
      
      printenv — 환경을 출력만 하는 CGI 프로그램
      
      =cut
      print "Content-type: text/plain\n\n";
      
      for my $var ( sort keys %ENV ) {
       printf "%s = \"%s\"\n", $var, $ENV{$var};
      }


      1. WSGI는 무엇인가?

      WSGI는 Web Server Gateway Interface로, Python의 built-in Requests 처리 API입니다.

      python에서는 wsgiref 패키지를 통해서 wsgi를 사용하여 low level로 웹서버 구축을 가능하게 해줍니다.

      아래는 Python 공식 홈페이지에서 제공하는 간단한 WSGI 웹서버 입니다.

      #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
      
      # A relatively simple WSGI application. It's going to print out the
      # environment dictionary after being updated by setup_testing_defaults
      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()


      2. 웹서버는 무엇인가?

      JAVA프래임워크를 보면 서버를 WAS(Web Application Server), Web Server등으로 나눕니다.

      하지만 예전 Requests 패키지의 분석에서 보았듯, 컴퓨터의 통신은 socket API를 활용해서 정해진 프로토콜의 Msg를 주고받으며 이뤄집니다.

      HTTP의 경우 HTTP(Hypertext Transfer Protocol) 입니다.

      때문에 HTTP 웹서버의 경우 Client로부터 HTTP Request Msg를 받고, 이에 대응되는 HTTP Response Msg를 전달하게 됩니다.

      그렇다면, WSGI도 어떻게 구성되어 있던지 HTTP Requests를 받고, 이를 처리하는 로직이 있을 것이란것 예상해볼 수 있습니다.


      3. wsgiref 패키지 파해치기

      위 예제에서는 make_server가 (host_name, port_number, callback)을 받은것을 볼 수 있습니다.

      먼저 wsgiref.simple_server의 make_server를 살펴볼 필요가 있습니다.

      #https://github.com/python/cpython/blob/main/Lib/wsgiref/simple_server.py#L146
      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

      이때 서버구축을 위해서 WSGIServer와 WSGIRequestHandler 클래스가 사용되고 있습니다.


      먼저 WSGISever 클래스를 살펴보겠습니다.

      #https://github.com/python/cpython/blob/main/Lib/wsgiref/simple_server.py#L42
      class WSGIServer(HTTPServer):
          """BaseHTTPServer that implements the Python WSGI protocol"""
          application = None
      
          def server_bind(self):
              """Override server_bind to store the server name."""
              HTTPServer.server_bind(self)
              self.setup_environ()
      
          #서버가 사용할 환경변수를 dict형태로 초기화시킴
          def setup_environ(self):
              # Set up base environment
              env = self.base_environ = {}
              env['SERVER_NAME'] = self.server_name
              env['GATEWAY_INTERFACE'] = 'CGI/1.1'
              env['SERVER_PORT'] = str(self.server_port)
              env['REMOTE_HOST']=''
              env['CONTENT_LENGTH']=''
              env['SCRIPT_NAME'] = ''
      
          def get_app(self):
              return self.application
      
          #입력받은 콜백을 attr에 저장시킴
          def set_app(self,application):
              self.application = application
      #https://github.com/python/cpython/blob/main/Lib/http/server.py#L134
      class HTTPServer(socketserver.TCPServer):
          allow_reuse_address = 1    # Seems to make sense in testing environment
      
          def server_bind(self):
              """Override server_bind to store the server name."""
              #socketserver.TCPServer.server_bind는 static method가 아니기 때문에
              #사용하려면 아래처럼 self를 별도로 넣어주어야됨
              socketserver.TCPServer.server_bind(self)
              host, port = self.server_address[:2]
              self.server_name = socket.getfqdn(host)
              self.server_port = port

      image.png

      WSGI 서버는 HTTPServer클래스를 상속받고 HTTPServer는 python의 low level인 socketserver.TCPServer를 상속받는 것을 볼 수 있습니다.

      벌써부터 socket API를 사용해서 HTTP Msg를 주고받는것을 예상할 수 있습니다.

      조금더 들어가볼까요?

      #https://github.com/python/cpython/blob/main/Lib/socketserver.py#L392
      class TCPServer(BaseServer):
          address_family = socket.AF_INET
          socket_type = socket.SOCK_STREAM
          request_queue_size = 5
          allow_reuse_address = False
          allow_reuse_port = False
      
          def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
              """Constructor.  May be extended, do not override."""
              BaseServer.__init__(self, server_address, RequestHandlerClass)
              self.socket = socket.socket(self.address_family,
                                          self.socket_type)
              if bind_and_activate:
                  try:
                      self.server_bind()
                      self.server_activate()
                  except:
                      self.server_close()
                      raise
      
          def server_bind(self):
              """Called by constructor to bind the socket.
              May be overridden.
              """
              if self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR"):
                  self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
              if self.allow_reuse_port and hasattr(socket, "SO_REUSEPORT"):
                  self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
              #열려있는 socket에 server_address(host, port_nm)을 bind시킴
              self.socket.bind(self.server_address)
              self.server_address = self.socket.getsockname()

      여기까지 address와 port를 이용해서 socketserver가 만들어지는 것을 알 수 있습니다.


      그러면, 이후에 server_forever()를 살펴보겠습니다.

      출처 : lib/socketserver.py

      #https://github.com/python/cpython/blob/main/Lib/socketserver.py#L155
      class BaseServer:
          ...
          #https://github.com/python/cpython/blob/main/Lib/socketserver.py#L218
          def serve_forever(self, poll_interval=0.5):
              """Handle one request at a time until shutdown."""
              self.__is_shut_down.clear()
              try:
                  with _ServerSelector() as selector:
                      selector.register(self, selectors.EVENT_READ)
                      
                      #shutdown이 되기 전까지 무한루프를 돌면서 server를 돌리게됨
                      while not self.__shutdown_request:
                          ready = selector.select(poll_interval)
                          if self.__shutdown_request:
                              break
                          if ready:
                              
                              self._handle_request_noblock()
          
                          #wsgiref의 경우 service_actions를 별도로 사용하지 않음
                          self.service_actions()
              finally:
                  self.__shutdown_request = False
                  self.__is_shut_down.set()
          #https://github.com/python/cpython/blob/main/Lib/socketserver.py#L305    
          def _handle_request_noblock(self):
              """Handle one request, without blocking."""
              try:
                  #자식cls에서 정의되는 get_request()를 사용
                  #socket을 통해서 HTTP Requests Msg를 가지고옴
                  request, client_address = self.get_request()
              except OSError:
                  return
              #이번 예제에서는 사용되지 않지만
              #다른 프래임워크 구현등에서 request verify용으로 overridden필요
              if self.verify_request(request, client_address):
                  try:
                      self.process_request(request, client_address)
                  except Exception:
                      self.handle_error(request, client_address)
                      self.shutdown_request(request)
                  except:
                      self.shutdown_request(request)
                      raise
              else:
                  self.shutdown_request(request)
              
          ...
      #https://github.com/python/cpython/blob/main/Lib/socketserver.py#L500
      class TCPServer(BaseServer):
          def get_request(self):
              """Get the request and client address from the socket."""
              return self.socket.accept()
          ...

      serve_forever를 통해서 무한루프가 만들어지고, 내부 selector를 사용해서 적절할 socket을 골라서 http requests를 처리하게 됩니다.

      (이때 selector가 어떻게 동작하는지는 지난번 포스팅에서 다뤘죠?


      이때 WSGI가 다른 프래임워크 개발을 위해서 overridden을 할수있게 준비가 되어있는 모습들을 볼 수 있습니다.

      여하튼, 보시면 아시겠지만 HTTP Msg가 올때까지 socket.accept()를 통해서 idle 상태로 대기하게 됩니다.

      무한루프를 돌면서 HTTP Request를 기다리는 Server의 모습이 코드로 구현된것이 보이죠?

      여기까지 WSGI Server가 어떻게 requests를 받는지 알아 보았습니다.


      다음 시간에는 이렇게 받은 Requests를 WSGI Server가 어떻게 처리하여 Response를 보내는지 알아보겠습니다.


      감사합니다!

      댓글 0

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

      Teus 님의 최신 블로그

      더보기