컴퓨터

Python WSGI vs. ASGI

고래처럼 2023. 5. 9. 11:43

WSGI의 탄생

 

PEP 3333 – Python Web Server Gateway Interface v1.0.1 | peps.python.org

 

파이썬 초창기에 다양한 웹 프레임워크가 등장했다. (Zope, Webware, SkunkWeb, ...)

지금의 상황과 다르지 않게 파이썬 프로그래머들은 웹 어플리케이션을 작성하기 위해

웹 프레임워크를 선택해야 했는데, 문제는 웹 프레임워크와 웹 서버 사이에 표준이 존재하지 않았다.

결국 선택한 웹 프레임워크가 사용하는 웹 서버만 사용할 수 있는 문제가 발생했다.

(반대로도, 웹 서버를 선택하면 선택할 수 있는 웹 프레임워크의 폭이 크게 줄었다.)

 

이 문제를 해결하기 위해 파이썬 커뮤니티는 웹 서버와 웹 프레임워크 사이에

간단하면서도 모두를 아우를 수 있는 인터페이스를 제안하였는데 이것이 WSGI다.

파이썬 커뮤니티는 WSGI를 제안하며 두가지 목표를 제안했다.

  • WSGI는 단순해야한다. 그렇지 않으면 현존하는 웹 프레임워크들과 웹 서버들이 받아들이지 못할 것이다.
  • Request, Response processors, 미들웨어 컴포넌트를 만들기 쉬워야한다.

이렇게 파이썬의 Web Server Gateway Inferace가 탄생하였다.

 

WSGI는 서버 측과 어플리케이션 측, 미들웨어로 구성된다.

 

WSGI의 구조

  1. WSGI는 서버(게이트웨이) 측과 어플리케이션(프레임워크) 측으로 구성된다.
  2. 서버 측은 HTTP 클라이언트로부터 받은 Request마다 어플리케이션 측의 callable 개체를 호출한다.
  3. 미들웨어를 둘 수 있다.

 

미들웨어란?

서버 측과 어플리케이션 측 모두 미들웨어의 존재를 몰라도 상관없다.

(The presence of middleware in general is transparent to both the server and application sides of the interface.)

 

어플리케이션 측이 미들웨어를 추가하고 싶다면, 서버 측에 callable 개체를 제공할 때

미들웨어를 먼저 호출하고, 미들웨어가 어플리케이션 callable 개체를 호출하도록 미들웨어를 작성하면 된다.

따라서 서버 측에게는 제공된 미들웨어가 어플리케이션 측이 되는 것이고 (호출하니까)

어플리케이션 측에게는 서버 측이 되는 것이다. (호출당하니까)

 

어플리케이션 측

어플리케이션 측의 callable 개체는 두 인자(positional)를 전달받는다.

첫번째 인자는 environ이고, 두번째 인자는 start_response이다.

result = application(environ, start_response)

environ은 파이썬 딕셔너리다.

HTTP 메소드, URL Path, Query String, Content Type, Content Length 등

HTTP Request를 표현하기 위한 값들이 담겨있다.

 

start_response는 두 인자(positional)를 받는 callable 개체다.

이 인자를 status, response_header라 하자. 어플리케이션 측은 start_response를 반드시 호출해야 한다.

start_response(status, response_headers)

 

HTTP의 진보, WSGI의 문제점

http://books.gigatux.nl/mirror/beaweblogic8.1/0672324873_ch06lev1sec2.html

 

HTTP는 Request-Response 형식의 통신을 가정해 디자인되었다.

그러나 HTTP가 사실상 인터넷의 표준 어플리케이션 프로토콜이 되면서

Request-Response 형식 이외의 통신이 필요해졌다.

 

인터넷 뉴스와 같은 웹 서비스들은 Request-Reponse 형식으로 효과적으로 구현이 가능하지만,

실시간 채팅, 위치공유 등의 웹 서비스들은 이러한 방법으로 불가능하다.

Request에 대해 Response로 끝내는 것이 아니라, 지속적으로 데이터를 주고받는 방법이 필요해진 것이다.

지속적인 통신의 대표적인 방식이 바로 그 유명한 웹소켓(WebSocket)이다.

 

문제는 WSGI가 Request-Response 형식을 파이썬으로 표현한 것이라는 점이다.

어플리케이션 측이 callable 개체를 제공하면 서버 측이 Request를 받을 때마다

callable 개체를 호출해 Response 개체를 반환받고 이를 클라이언트에게 돌려준다.

 

당연한 결과로 WSGI만으로는 더이상 웹 서비스를 작성하기 힘들어졌다.

WSGI 기반의 Django를 웹소켓를 지원하도록 확장하기 위한 프로젝트인 Django Channels는

WSGI가 아닌 ASGI를 기반으로 작성되었다.

 

ASGI

Introduction — ASGI 3.0 documentation

 

ASGI는 Asynchronous Server Gateway Interface의 약자로

위에서 다룬 WSGI의 한계를 해결하기 위해 등장했다.

 

ASGI는 WSGI와 비슷하게 프로토콜 서버와 어플리케이션으로 구성된다.

  • 프로토콜 서버: 클라이언트로부터 요청을 받아 이것을 connection으로 변환해 어플리케이션으로 전달한다.
  • 어플리케이션: connection을 처리한다. 서버와 이벤트 메시지를 주고 받는다. 서버는 이벤트 메시지의 데이터를 클라이언트에게 전송한다.

또한, 어플리케이션으로 호출하는 것으로 끝나는 WSGI와는 달리 ASGI의 connection은 두 가지의 구성요소가 있다.

  • Connection Scope: 프로토콜에 따른 유저와의 연결을 표현하며, 연결이 끊길 때까지 유지된다.
  • Events: 어플리케이션이 받거나, 돌려주는 메시지이다.

Connection Scope

서버는 유저의 요청을 모두 connection으로 변환해 어플리케이션 callable을 호출한다.

이 때 connection이 얼마나 오래 유지되는지를 Connection Scope이라 한다.

 

어플리케이션 callable의 가장 첫번째 인자로 전달되는 것이 scope 딕셔너리인데

connection을 설명하기 위한 정보가 담겨있다.

 

예를 들어 HTTP라면 한 Request에 대해서만 connection이 유지될 것이고,

WebSocket이라면 socket이 연결되어 있는 한, connection이 유지되어야 한다.

 

scope 딕셔너리의 구조를 살펴보자.

 

{
  "type": "http",
  "http_version": "2",
  "method": "GET",
  "path": "/example",
  "headers": ...
}

어플리케이션 callable은 이 구조를 보고 요청을 어떻게 처리할 지 판단할 수 있다.

 

 

Events

ASGI는 프로토콜을 이벤트의 흐름으로 본다.

어플리케이션은 이벤트를 받아 처리하고, 다시 이벤트를 서버에 돌려준다.

 

HTTP의 예를 들어보자.

어플리케이션은 HTTP Request, HTTP Disconnect를 순서대로 받고

HTTP Response 이벤트 메시지를 서버에 돌려준다.

WebSocket의 경우라면

WebSocket Connect, WebSocket Send, WebSocket Receive, WebSocket Disconnect와 같은

이벤트 흐름을 생각해 볼 수 있다.

 

모든 이벤트는 딕셔너리로 표현되며,

type에 매핑되어 있는 유니코드 문자열을 통해 어떤 종류의 메시지인지 구분할 수 있다. (HTTP Request, WebSocket Send, ...)

 

HTTP Response의 이벤트 메시지 구조를 살펴보자.

{
  "type": "http.response.start",
  "status": 200,
  "headers": ...
 }

어플리케이션은 이러한 딕셔너리를 아래서 볼 send (awaitable callable)를 이용해 서버에 보낼 수 있다.

Applications

ASGI 어플리케이션은 async callable이다.

coroutine application(scope, receive, send)

scope은 위에서 다뤘던 connection scope이고,

receive와 send는 awaitable callable이며 event 딕셔너리를 주고 받을 때 사용한다.

application 코루틴은 connection이 끝날 때까지 유지되기 때문에

WebSocket과 같이 지속적인 연결이 필요한 통신 프로토콜을 처리하는 데 사용할 수 있다.