PHP로 HTTP 서버 구현하기 - 03 - CGIStream class 둘러보기

연재글 전체 보기


지난번에 이어 CGIStream class(HTTPServer 참고)를 살펴보는 시간.

말했지만, 좀 옛스러운 코드니 결벽증이 있는 분은 다음 장으로…

왜 요즘 버전의 소스를 분석하지 않냐는 질문도 할 법 하다.

못 구했다.

일단 이건 정말 공부를 위한 뻘짓이기 때문에 굳이 구현하고 싶은 사람이 없었을 것으로 보인다.

게다가 PHP5.4부터 내장 서버를 지원하는데 뭐하러 굳이…

다시 집중!

CGIStream class

주석을 보면 CGIStream class는 CGI 프로세스가 완료될 때까지 버퍼 기능도 하고, 헤더를 조작하는 것도 가능하다고 한다.

외부에서 이 클래스를 어떻게 사용하는 지 찾아보면 눈에 익은 문법이 보일 것이다.

stream_wrapper_register("cgi", "CGIStream");

HTTPServer class(httpserver.php)에서 서버를 띄우기 전에 등록하는 코드다.

코드에서 보듯이 CGIStream class는 Stream wrapper class라고 할 수 있고,

Stream wrapper class의 메소드 중 stream_cast, stream_open, stream_read, stream_eof, stream_close만 구현했다.

이 중에 이해하기 쉬운 애들은 빼고 두가지 메소드만 설명해보겠다.

stream_read

stream_read는 BUFFERING/BUFFERED/EOF 상태에 따라 해야할 일을 구현해놓았다.

BUFFERING 단계에선 버퍼에 request 메시지를 담고, server 쪽 클래스를 통해 request 구문을 분석하고, response를 위한 문자열을 만들고, 이를 data type의 스트림에 쌓아둔다. 그리고 현재 상태를 BUFFERED로 업데이트 한다.

BUFFERED 단계에선 앞서 data 스트림에 쌓아두었던 값을 리턴한다. 그리고 현재 상태를 EOF로.

EOF 단계에선 return false;.

내가 상상했던 코드는 이 스트림쪽에선 버퍼링 정도만 처리하고 나머지는 server 쪽 코드에 위임하는 것이었는데,

\r\n\r\n(HTTP 메시지의 header와 body 구분자)로 메시지를 검사한다거나 HTTPServer::parse_headers 결과를 분석한다거나 하는 일도 한다.

이런 부분은 역할 분리가 덜 된 것 같다는 생각이 든다.

stream_open

stream_open 메소드가 호출되는 시점은 php.net에서 이렇게 설명한다.

This method is called immediately after the wrapper is initialized

wrapper가 초기화 될 때, 그러니까

fopen("cgi://PHP파일"...

이런 식이다.

stream_open에서 cgi로 php파일을 열고, stream_read에서 그 실행한 결과를 스트림으로 받아 처리하는 것이다.

이 예제에선 stream_context_get_options로 현재 설정된 stdin, env, server, response 등의 context를 얻어오고

이를 활용해 proc_open으로 스트림을 연다.

CGIStream는 여기서 마무리

이제 실제 서버를 띄우는 HTTPServer class에서 이 Stream wrapper(CGIStream class)를 등록하고 활용하는 부분을 보면 이 동네는 대략 이해가 될 것이다.

여기까지 분석해보니 결국 CGI용 Stream wrapper는 PHP 스크립트를 실행하기 위한 것이었다.

http://app.dev/test.php 같은 요청을 받는 일 말이다.

즉, (일단은) HTML과 이미지 정도의 static한 응답만 처리할 나에게는 php-cgi binary 같은 건 필요없다!! (오예)

HTTPServer class

이왕 본 김에 이 CGI 스트림 wrapper를 어떻게 등록하는 지만 살펴보고 (쓸 데 없었던) 2, 3장을 마무리 해야겠다.

HTTPServer class는 PHP 요청과 이미지 같은 static 리소스를 별도로 라우팅한다.

우선 서버를 실행하면

stream_wrapper_register("cgi", "CGIStream");

//중략

$sock = @stream_socket_server("tcp://$addr_port", $errno, $errstr);

CGI용 스트림 wrapper를 등록하고 tcp 소켓을 하나 열어둔다.

Client가 요청한 리소스가 PHP 스크립트일 경우, HTTPServer의 get_php_response 메소드로 라우팅된다.

앞서 CGIStream의 stream_open을 설명하면서 stream_context_get_options으로 컨텍스트 정보를 가져온다고 했는데,

바로 여기에서 컨텍스트 정보를 만들어 주는 것이다.

$context = stream_context_create(array(
'cgi' => array(
'env' => array_merge($_ENV, $this->cgi_env, $cgi_env),
'stdin' => $request->content_stream,
'server' => $this,
'response' => $response,
)
));
$cgi_stream = fopen("cgi://{$this->php_cgi}", 'rb', false, $context);

결론

예상대로(?) php-cgi binary 같은 건 필요없다는 결론을 얻기 위해 긴 시간을 허비했다.

이 시리즈의 다음 글부터는 최소한의 구성으로 서버를 띄우고, 기능을 하나씩 입혀 나갈 예정이다.

그러니까…

예정이다.