23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
안녕하세요 Teus 입니다.
오랜만에 RN 스터디가 아닌 글로 찾아왔습니다.
이번시간에 다뤄볼 내용은 WGSI입니다.
WSGI에 앞서, 모든 프로그래밍언어에서 사용되는 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};
}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()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를 받고, 이를 처리하는 로직이 있을 것이란것 예상해볼 수 있습니다.
위 예제에서는 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 = portWSGI 서버는 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를 보내는지 알아보겠습니다.
감사합니다!
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.