WSGI, WS

newhyork·2022년 10월 2일
0

본문을 시작하기 앞서, 초창기 Web 설계 구조와 CGI에 대해 먼저 알아보도록 한다.


초창기 web 설계 구조


Web Server(이하 WS)는 client로부터 request를 받아,
정적 컨텐츠만을 response하는 것을 바탕으로 하였다.

즉, Server의 disk에 저장되어 있는 것 그대로 response할 뿐이었다.

이러한 구조로 설계되었기 때문에,
form 따위로 사용자로부터 입력을 받아
그에 따른 로직을 server에서 처리하고 response하는 것은 불가능 했다. (동적 컨텐츠)


이를 가능하게 하기 위해
그러한 request들에 대해서, WS는 외부 스크립트를 호출하는 방식을 사용하였다.
(예를 들어, 외부 스크립트로써 Python file을 호출하는 격이다.)

그리하여 그 스크립트가 실행되고서 return 된 것을 response하는 것이다.


WS는, fork를 통해 외부 스크립트를 실행하였다.

외부 스크립트를 실행하기 위해 process를 fork할 때,
WS로부터 환경 변수를 상속받는다.

WS는, request를 parsing한 후에
해당 요청을 나타내는 환경 변수를 만들고,
그러고 나서 외부 스크립트를 실행한다.

그러면 해당 스크립트는 환경 변수로부터 request에 대한 정보를 알 수 있게 된다.


CGI


Common Gateway Interface

하지만 그 당시 초기에, WS의 종류가 워낙 다양했고
이에 따라 환경 변수 형태도 제각각 달랐기 때문에,
외부 스크립트가 모든 WS에 대응하여 실행될 수 있기 위한 표준이 필요했다.

그렇게 탄생한 것이 CGI이다.

즉, CGI는 WS와 외부 script에 있어서 환경 변수의 이름과 목적에 대한 표준이다.


WSGI


Web Server Gateway Interface

Python community에서는 CGI 개념을 더 확장하였다. (WSGI)

단순히 환경 변수를 표준화하는 것에서 더 나아가,
스크립트가 호출되는 방식을 표준화하였다.

WS에서 호출되는 스크립트는 함수를 가져야 한다는 것으로 말이다.
(참고로, 함수가 아니라 callable이 더 정확하다.
__call__메서드를 갖는 객체여야 한다는 말이다.)


이런 방식을 통해, WS측에서 Python script를 호출하는 것은
그리 까다로운 일이 아니게 된다.

왜냐면 이 당시 모든 WS가 CGI script로 동작하고 있었고,
다른 한편으로 개발자들은 WSGI의 간단한 규칙만을 준수하면서도
django와 같은 복잡한 framework를 호출할 수 있었기 때문이다.


WSGI의 목적은 웹 서버에서 외부 스크립트가 실행되는 방식을 표준화하는 것이다.

웹 서버에서 쉽게 호출하거나 모든 웹 서버와 쉽게 통합되도록 설계되었으며
모든 웹 프레임워크를 실행할 수 있을 만큼 유연하다.


  • 요약하면, WSGI는, WS가 Python script를 실행할 수 있게 해주는 것이다.
  • Python에서는, CGI가 WSGI에 대응된다.

WSGI server


gunicorn이나 uWSGI와 같은 application server(WSGI server)에서
어떻게 이런 것(serve WSGI)들을 해주는 지에 앞서,
CGI에 대해 한 가지 더 짚고 넘어가야 할 부분이 있다.

CGI sript는 외부 스크립트를 load하는 데에 너무 많은 시간이 걸린다는 문제가 있다.

즉, fork를 하는 것 자체가 병목이라는 것이다.


이에 대한 해결책으로써 pre-fork 방식이 있다.

WS가 idle(유휴 상태)일 때
fork 혹은 외부 스크립트 인터프리터와 dependency들을 시작하는 것이다.

심지어 이 때에는 여러 번 fork 할 수도 있다.
예를 들어, Python 인터프리터를 3번 fork 하면 3개의 WS worker가 생성된다.

이러한 worker는, Python 인터프리터와 이를 실행하는 데 필요한 dependency들의
in-memory 인스턴스이다.

이런 식으로 하면, request가 왔을 때,
WS에서의 처리 시간(request를 parse하고 환경 변수를 생성하는 작업)과
외부 스크립트가 처리되는 시간(request에 따라)만 걸릴 뿐이다.


pre-fork는 WS에서 사용하는 것으로,
WSGI application(application written in python)을 pre-fork 할 수 있다.
다른 말로, WS가 python application을 실행할 수 있다는 말이다.

단, 이는 Apache HTTP Server에 해당되는 것이다. Nginx는 다르다.


Nginx는 WSGI application을 pre-fork 할 수 없다.

대신, WSGI를 application을 pre-fork 할 수 있는 다른 WS로
request를 전달해줄 수 있다.

바로 그 WS 격으로는, gunicorn 혹은 uWSGI와 같은 WSGI server가 해당된다.

이는 HTTP를 이해할 수 있으며, pre-fork 방식으로 WSGI app을 호출할 수 있다.

(일반적으로는 WSGI server와 WS를 따로 구분하지만,
uWSGI의 경우에는 static file을 serve 할 수 있다는 점 등에서
이를 비슷하게 취급해볼 수 있는 여지가 있다.
물론 세부적인 역할과 기능은 크게 다를 수 있지만 말이다.

https://uwsgi-docs.readthedocs.io/en/latest/StaticFiles.html
https://lincolnloop.com/blog/serving-static-files-uwsgi/)


따라서, python application(Python Web framework 포함)을 serve하기 위한
WSGI에 호환되는 WS(내지는 WSGI Server)로는,
gunicorn, uWSGI, Apache HTTP Server, gevent 등이 있다.

WSGI이라는 표준에 의해, 양측에 어떤 것을 사용하더라도 서로 호환 가능하다.

(WSGI를, 일종의 protocol 같은 것이라고 이해해도 좋다.)


  • 요약하면, WSGI server는 WSGI를 serve하는 middleware이다.
  • Python에서는, WAS(Web Application Server)가 WSGI server에 대응된다.

WS


Web Server

  • WAS(WSGI server) 앞에 위치하여, 클라이언트(브라우저)의 HTTP 요청을 받아들인다.
    • 정적 데이터: 웹 페이지의 HTML, CSS, JS, 이미지, 동영상 파일 등에 대한 요청으로,WS가 이에 대해 스스로 응답할 수 있으므로 서버와의 별도의 connection이 필요없다.
    • 동적 데이터: 서버의 비즈니스 로직을 거쳐야 하는 경우 등에 대한 요청으로,
      서버(WAS 등)와 connection을 형성하고, 응답을 전달한다.
    • 모듈을 통해 WS에서도 동적 데이터를 취급할 수는 있다.
      (하지만 이젠 잘 쓰이지 않는다.)
  • WAS의 앞에 두어, reverse proxy 역할을 하는 등에 의의가 있다.
    • 모듈을 통해 WAS의 health check 등도 할 수 있다.
  • WAS에도 기본 내장된 WS이 있으나, 보통은 이와 별개의 WS를 일컫는다.
  • 종류는 Nginx, Apache HTTP Server 등이 있다.

about Flask


  • Flask 객체를 뜯어보면 다음과 같은 것을 알 수 있다.
    Flask 객체 → run() 메서드 → werkzeug의 run_simple() → BaseWSGIServer가 기본값이다.
    즉, developement server에서는 werkzeug의 WSGI Server를 사용하게 되는 것이다.
    이게 바로 Flask의 기본 built-in WSGI Server이다.
    • Flask는 기본적으로 별도의 WS, WSGI server 없이도
      static, dynamic contents를 처리할 수는 있다.
    • werkzeug의 base WSGI Server 는 local development 환경에서 사용될 목적이기 때문에, 하나의 client로부터 하나의 request-response 씩 만 처리할 수 있다.
      • (debugger로 breakpoint를 걸거나 time.sleep()을 둔 채로
        여러 웹 페이지(client)를 열어서 연속적으로 request를 해봄으로써 몸소 알 수 있다.)
      • gunicorn이나 uWSGI와 같은 production WSGI server를 사용하면,
        여러 worker를 둘 수 있기 때문에 동시에 여럿을 처리할 수 있다.
        - (WS 뒷단에 WAS를 여러 개 둔다고 표현하는 것은
        Python에서는, WSGI server에서 worker를 여러 개 둔다는 것에 상응한다.)


구조 비교


  • 일반적으로, 운영 환경과 개발 환경은 주로 다음과 같은 구조로 구성된다.
    • Production
      • Client < - - - > WS < - - - > WSGI < - - - > Flask(Python APP)
      • WS, WSGI server를 따로 설치하여 사용한다.
      • 정적 컨텐츠: WS (ex. Nginx)
        동적 컨텐츠: WSGI server (ex. uWSGI, gunicorn)
    • Development
      • Client < - - - > Flask
      • Flask에 내장된 WSGI server 및 WS를 사용한다.
      • 정적 컨텐츠: base WSGI server의 내장 WS.
        동적 컨텐츠: Flask의 내장 base WSGI server.

0개의 댓글