PHP로 HTTP 서버 구현하기 - 05 - 아주 간단한 HTTP 서버로 첫 요청과 응답
과연 스트리밍이 필요한가?
스트리밍 처리를 위해 이리저리 알아보다가 괜찮은 글이 있어 소개한다.
https://gist.github.com/CMCDragonkai/6bfade6431e9ffb7fe88
- 스트리밍을 구현할 때 chunk 데이터를 어떻게 보내고, 이를 어떻게 처리해야 하는지
- HTTP 트랜잭션 안의 각 요소(component) 간 buffer는 어떻게 처리되는지
- 등등
잘 나와있다.
예전에 댓글로 아웃 버퍼링에 관한 글도 소개했었는데,
같은 맥락으로 읽어보면 좋다.
내가 만들 서버는 아주 가벼운 HTML 파일과 이미지 정도를 보내주기 때문에 굳이 (chunk로 나눠 보내주는) 스트리밍은 필요없다.
마찬가지로 클라이언트에서도 multi-part chunk 데이터가 들어올 것을 고려할 필요가 없다.
그럼 이제 HTTP 요청을 받아서 HTTP 응답을 반환해주는 최소한의 구성으로 서버를 하나 만들어보겠다.
아주 간단한 HTTP 서버
이전 시간에 만들었던 서버 소스를 조금 수정해서, 클라이언트가 보낸 내용을 읽고 HTTP 응답 메시지로 리턴한다.
아래는 전체 소스다.
|
클라이언트와 연결되면 해당 스트림에서 1024 바이트만큼 읽어온다.
더 많이 보낼 수 있지 않냐고? 물론이다. 그 문제는 그 때 생각하자.
그 다음 HTTP 메시지를 만들어 준다.
- 말했지만, HTTP 메시지는 시작줄, 헤더, 공백, 본문(message body)으로 이뤄졌다.
요청할 때는 시작줄에 GET / HTTP/1.1
와 같이 메소드, URL, HTTP 버전 순으로 보냈지만
응답할 때의 시작줄에는 HTTP/1.0 200 OK
와 같이 HTTP 버전과 상태 코드 그리고 상태 텍스트를 넣어준다.
여기에 헤더를 추가해줘야 하니 Content-Type을 text/html으로 우선 하나 넣어주고
메시지 본문과의 구분을 위해 CRLF를 두 개 추가한다.
첫 HTTP 요청과 응답
테스트 용으로 만들었던 클라이언트 코드는 이제 버리고,
진짜 HTTP 클라이언트를 사용해보자.
브라우저에서 접속해도 괜찮지만 아직은 HTML 파일을 내려보낼 것이 아니므로
Postman같은 클라이언트가 테스트하기 편할 것이다.
GET
메소드로 127.0.0.1:1337
에 접근해보자.
(만약 서버에서 적절한 시작줄을 안 보내주면 Postman은 Could not get any response
라며 응답이 오지 않은 것으로 간주한다.)
Postman에서 받은 응답 메시지 본문은 아래와 같다.
you sent : |
서버에서 응답 본문 앞에 붙여둔 you sent :
한 주를 제외하면 나머지는 HTTP 클라이언트(여기서는 Postman)가 요청 시 붙여 보낸 것이다.
크롬에서 보내면?
you sent : |
다시 서버 소스를 돌아보자.
지금은 무조건 HTTP/1.0 200 OK
라고 응답하게 되어있다.
클라이언트는 HTTP/1.1을 지원하기 때문에 이 프로토콜 버전을 기준으로 헤더를 붙여 요청했지만
우리 서버는 ‘난 HTTP/1.0까지만 지원하는 서버라서 1.0 기준의 헤더를 보낼 거야’라는 의미로 응답에 HTTP/1.0 200 OK
를 리턴한다.
브라우저에서 index.html 확인하기
저기 저 아주 간단한 서버 소스에선 메시지 본문을 클라이언트가 보낸 데이터를 그대로 돌려줬는데,
$response .= 'you sent :' . PHP_EOL . $request . PHP_EOL;
이제 예시로 만들어 두었던 index.html 파일을 읽어 클라이언트로 보내기로 한다.
드디어 브라우저에서 확인해볼 차례다.
Request를 그대로 돌려주는 서버에서 무조건 index.html을 떨궈주는 서버로 진화해보자.
// 생략 |
todo가 먼저 눈에 띌 것 같은데,
우리의 아주 간단한 서버가 조금씩 복잡해지기 시작했다(냄새가 난다, 냄새가).
크게 보면 응답을 받아서 뭔가 하는 역할과 응답을 만들어주는 역할이다.
리팩토링은 나중에 하기로 하고, 맥주를 한잔 따르고, 성공을 축하하자.
크롬으로 접속!
만약 없는 경로의 파일을 찾으려고 하면 404로 응답해주면 된다.
그 유명한 404 not found
다.
그런데 소스를 보면 HTTP/1.0 404 파일 없음
이라고 되어있는데 잘못 쓴 게 아니다.
클라이언트에게는 404라는 응답코드가 중요하지 메시지는 중요하지 않다.
200 OK
나 200 NOT OK
나 모두 성공을 의미한다.
(물론 서비스 운영 시에는 이런 짓은 하지 않는다)
404 처리도 완벽하게(!?) 됐다.
이미지 서빙
첫 요청으로 index.html을 서빙하는 것 까지는 성공했는데, 중간에 이미지가 빠져있는 게 보일 것이다.
이미지를 노출하려면 몇 가지 더 수정을 해야한다.
첫째, 두 가지의 요청을 처리할 수 있어야 한다.
즉, HTML을 요청하는 것과 이미지를 요청하는 것.
첫 요청으로 브라우저는 index.html이라는 HTML을 얻었다.
이 HTML 문서를 파싱하다가 img 태그를 만났고 src가 beer_server.jpg라는 상대 링크이기 때문에 우리의 서버에 다시 요청을 보낸다.
첫 요청이 GET / HTTP/1.1
라면 이어서 GET /beer_server.jpg HTTP/1.1
라는 요청이 이어질 것이다.
개발자 도구 > 네트워크
를 열어 확인해보자.
(contents_min.css 저 놈은 크롬 확장 프로그램 때문에 날아간 것이니 신경 쓰지 말자 😭)
favicon.ico는 브라우저 탭에 표시해주는 작은 이미지인데, 최신 브라우저에서는 favicon이 등록되어 있지 않은 사이트일 경우 서버에 /favicon.ico가 있는지 한번 찔러본다.
(사실 계속 찔러보기는 하는데..)
따라서 예상치 못하게 이미지 하나를 더 서비스 해야할 상황이다.
- index.html
- beer_server.png
- favicon.ico
이제 클라이언트가 보낸 요청을 분석해서 뭘 원하는 지 알아내야 할 판이다.
둘째, 이미지를 읽고 써야한다.
위 소스에서 index.html을 어떻게 찾았는지 살펴보자.
server.php가 실행된 위치에서 ..
로 상위 디렉토리로 올리가 public
이라는 디렉토리에 있는 파일을 서빙했다.
이러다 서버 프로그램 위치가 이동한달지 webroot를 변경하면 서버 코드를 수정할 판이다.
결론
냄새가 난다, 냄새가.
index.html 하나 서비스했을 뿐인데 많은 구조적인 문제가 폭발했다.
클라이언트 요청(request)를 분석하고 html을 줄지 이미지를 줄지 결정해야하고,
DOCUMENT_ROOT, WEB_ROOT 등 config 설정도 해야할 것 같다.
200과 404 응답을 만들어주는 코드 모두에 fwrite/fclose 로직이 중복되어 있기도 하다.
이 모든 걸 담기에는 이미 서버 프로그램이 너무 복잡하다.
다음 시간부터 이를 하나씩 분리해서 HTTP 완벽가이드 5장에 소개된 ‘진짜 웹 서버가 하는 일’을 구현해볼 생각이다.
- 커넥션을 맺는다
- 요청을 받는다
- 요청을 처리한다
- 리소스에 접근한다
- 응답을 만든다
- 응답을 보낸다
- 트랜잭션을 로그로 남긴다
(1번, 2번 정도는 PHP 함수로 대충 때웠으니 했다고 치자)