포워드 프록시는 요청 줄과 헤더를 읽어 전달한 뒤 select에서 POST 본문을 기다린다. 클라이언트는 이미 보냈다. 양쪽이 서로를 기다리는 것처럼 보인다.

Python의 BaseHTTPRequestHandler에서는 헤더 읽기가 버퍼링된 입력 스트림인 rfile을 거친다. 그 스트림은 미리 읽어 본문에 속한 바이트를 보관할 수 있다. 기반 소켓을 기다리는 것은 파일 디스크립터가 준비되었는지만 묻고, Python 스트림에 이미 쓸 수 있는 데이터가 있는지는 묻지 않는다.

헤더를 읽은 버퍼를 따라가라

본문도 self.rfile로 읽어라. 버퍼링된 스트림으로 헤더를 파싱한 뒤 self.connection.recv로 전환하지 마라. 그렇지 않으면 코드가 그 스트림이 이미 보관한 바이트를 건너뛰고 다시는 전송되지 않을 데이터를 기다릴 수 있다.

rfile 자체를 select에 넘긴다고 해서 select가 그 Python 버퍼를 검사하지는 않는다. 준비 상태 API는 객체의 파일 디스크립터를 통해 동작한다.

유효한 Content-Length가 하나 있고 Transfer-Encoding이 없는 요청에서는 선언된 바이트 수를 정확히 소비하고, 입력의 조기 종료와 크기 및 시간 제한을 처리하라. EOF까지 읽지 마라. 지속 연결을 쓰는 클라이언트는 요청을 마친 뒤에도 연결을 열어 둘 수 있다.

프레이밍을 프록시의 명시적 일부로 만들어라

청크된 요청 본문에는 청크된 프레이밍 지원이 필요하다. 없는 Content-Length를 찾아 읽을 수는 없다. 모호한 길이 메타데이터에는 편리해 보이는 헤더를 고르는 대신 정의된 거부 경로가 필요하다.

블로킹 핸들러는 제한된 읽기로 스트림 인터페이스를 계속 쓸 수 있다. 이벤트 기반 프록시에는 파서의 미리 읽기를 준비 상태 처리와 통합하는 버퍼 모델이 필요하다. 타임아웃을 바꾼다고 같은 소켓의 두 독립적 소비자가 조화를 이루지는 않는다.

이 진단은 들어오는 바이트가 어디에 보관되는지에 관한 것이다. 프록시가 올바른 본문을 소비한 뒤에도 그 본문을 일관된 HTTP 프레이밍으로 전달해야 한다. 표준 http.server 핸들러는 메커니즘을 이해하기에 유용한 곳이지만 프로덕션 포워드 프록시 구현은 아니다.

관련 글