5장. 웹서버
웹 서버는 HTTP 요청을 처리하고 응답을 제공한다. (중략) 모든 웹 서버는 리소스에 대한 HTTP 요청을 받아서 콘텐츠를 클라이언트에게 돌려준다. (중략) 웹 서버는 HTTP 및 그와 관련된 TCP 처리를 구현한 것이다.
그러니까 HTTP 통신을 하는 모든 애플리케이션이 웹 서버는 아니지만 (e.g: iOS 애플리케이션), 모든 웹 서버는 HTTP 요청을 처리할 수 있어야 한다.
또한 웹서버는 TCP 커넥션을 사용하기 때문에 (왜냐하면 HTTP 통신을 해야 하니까) 운영체제와 TCP 커넥션 관리에 대한 책임을 나눠 갖는다. 운영체제의 역할은 하드웨어를 관리하고 TCP/IP 네트워크 지원, 웹 리소스를 유지하기 위한 파일 시스템, 연산 활동을 제어하기 위한 프로세스 관리를 제공한다. (e.g: 운영체제 시간에 들었던 CPU 스케줄링 기법이라던가)
웹 서버의 형태는 다양하다. 책에 든 예시로는 2가지가 있다. 다목적 소프트웨어 웹서버는 일반적인 컴퓨터에서 실행되는 서버를 말한다. 웹서버 소프트웨어 역시 다양하지만 넷크래프트의 2023년 웹 서버 설문조사 에 따르면 2021년 말에는 nginx, Apache, Cloudflare 웹서버가 가장 많이 사용되었다. 아래 그래프를 보면 한동안 Apache 웹서버가 우위를 점령하다가 nginx 의 등장 이후로 점유율이 확연히 줄어드는 것을 볼 수 있다. 두번째로 든 예시는 임베디드 웹 서버다. 일반 소비자용 제품에 내장되도록 작게 만들어진 웹서버인데, 프린터나 가전제품 등에 들어간다.
모든 웹서버가 공통적으로 하는 일이 있다.
이제 각 단계를 자세히 살펴보자.
웹서버는 클라이언트의 정체를 밝혀서 커넥션을 맺을지 말지를 결정할 수 있다. 클라이언트의 IP 주소나 호스트명이 인가되지 않았거나 악의적이라고 판단된 경우 커넥션을 닫는다.
이 때 클라이언트의 호스트명을 알아내기 위해서는 reverse DNS 를 사용한다. 생각해보면 TCP 통신은 IP 주소 기반으로 이루어지는데, 서버 로그에는 요청을 보낸 서버의 IP뿐만 아니라 호스트명도 남아있었다. 요청을 받은 서버가 직접 hostname lookup을 수행해서 알아낸 것이다.
안전한 클라이언트로부터 들어온 요청이란게 판별되었다면, 이제 요청을 받아들일 차례다. 책에서는 클라이언트로부터 동시에 들어오는 수많은 요청을 처리하기 위한 아키텍처 4가지를 소개한다.
부분은 이렇게 말하고 넘어가버렸다.
우리는 요청 처리에 대해서는 이야기하지 않을 것이다. 왜냐하면 그건 이 책 나머지 대부분의 주제이기 때문이다!
웹서버의 역할은 요청을 받아 리소스를 돌려주는 것이다. 정적, 동적 컨텐츠를 모두 돌려줄 수 있어야 한다. 클라이언트가 원하는 정적 컨텐츠를 식별하는 가장 쉬운 방법은 해당 리소스의 경로를 URI로 넘겨받는 것이다. 물론 동적 리소스에도 매핑할 수 있다. 요청에 맞게 컨텐츠를 생성하는 프로그램에 URI를 매핑하면 된다. nginx 가 요청을 받아서 우리가 만든 Spring Boot 애플리케이션의 컨트롤러에 URI 를 매핑하는 식이다.
접근 제어 역시 웹 서버가 담당한다. 웹 서버는 클라이언트의 IP 주소에 따라 리소스 접근을 제한하거나 패스워드를 물어볼 수 있다. 이 내용은 12장에서 다룰 예정이다.
응답 메세지에 들어가는 응답 상태 코드, 헤더, 본문을 넣는 과정이다. 본문이 있다면 MIME 타입과 본문의 길이를 알려주는 Content-Type
, Content-Length
헤더가 각각 필요하다. 보통 MIME 타입을 결정하기 위해서는 그 리소스의 확장자를 사용한다.
한편, 성공 메시지 대신 리다이렉션 응답을 반환해야 할 때도 있다. Location
응답 헤더에 리다이렉션의 목적지 주소를 넣어서 보내면 된다. 리소스가 임시 혹은 영구적으로 옮겨졌거나 이름이 변경된 경우, URL 확장을 위해, 과부화된 서버의 로드를 줄이기 위해 등등 생각보다 다양한 이유로 리다이렉션을 수행한다. 한가지 재밌는 점은 www.google.com
이라고만 요청을 보냈을 때 마지막에 자동으로 /
를 넣어서 상대경로가 정상적으로 동작하게 하는 것도 리다이렉션이라고 한다. (하지만 이 동작은 현재 크롬에서 재현되지는 않고 있다. 분명 어디서 본 것 같은데......🥲)
이제 거의 다 왔다! 응답을 보내기만 하면 끝이라고 생각할 수도 있겠지만 응답을 보낼 때에도 고려할 점이 꽤 있다. 만약 HTTP 지속 커넥션이라면 고려할게 더 많아진다. 서버가 Content-Length 를 바르게 계산하기 위해 특별한 주의를 해야 하거나, 클라이언트가 응답이 언제 끝나는지 알 수 없는 경우 커넥션을 계속 열린 상태로 유지해야 한다. 그렇지 않은 비지속 커넥션이라면 서버는 모든 메세지를 전송했을 때 자신의 커넥션을 닫을 것이다.
트랜잭션이 완료되었을 때 웹 서버는 트랜잭션의 수행에 관한 로그를 로그 파일에 기록한다. 좀 더 자세한 내용은 21장에서 다룰 것이다.
이번 장에서는 당연하게 여겼던 HTTP 요청 받아들이기와 응답 돌려주기에 대해 좀 더 자세히 들여다보았다. 다음 장부터 본격적으로 웹을 구성하는 컴포넌트 - 프락시, 게이트웨이, 캐시 등등 - 를 배우게 될 것 같다.