웹 서버가 하는 일 엿보기 - HTTP 완벽 가이드 5장

Broccolism·2023년 6월 18일
2

HTTP 완벽 가이드: 웹은 어떻게 동작하는가

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 의 등장 이후로 점유율이 확연히 줄어드는 것을 볼 수 있다. 두번째로 든 예시는 임베디드 웹 서버다. 일반 소비자용 제품에 내장되도록 작게 만들어진 웹서버인데, 프린터나 가전제품 등에 들어간다.

웹서버가 하는 일

모든 웹서버가 공통적으로 하는 일이 있다.

  1. 커넥션을 맺는다: 클라이언트의 접속을 받아들이거나, 원치 않는 클라이언트라면 닫는다.
  2. 요청을 받는다: HTTP 요청 메세지를 네트워크로부터 읽어들인다.
  3. 요청을 처리한다: 요청 메시지를 해석하고 행동을 취한다.
  4. 리소스에 접근한다: 메시지에서 지정한 리소스에 접근한다.
  5. 응답을 만든다: 올바른 헤더를 포함한 HTTP 응답 메시지를 생성한다.
  6. 응답을 보낸다: 응답을 클라이언트에게 돌려준다.
  7. 트랜잭션을 로그로 남긴다: 로그 파일에 트랜잭션 완료에 대한 기록을 남긴다.

이제 각 단계를 자세히 살펴보자.

1. 클라이언트의 정체 밝히기

웹서버는 클라이언트의 정체를 밝혀서 커넥션을 맺을지 말지를 결정할 수 있다. 클라이언트의 IP 주소나 호스트명이 인가되지 않았거나 악의적이라고 판단된 경우 커넥션을 닫는다.

이 때 클라이언트의 호스트명을 알아내기 위해서는 reverse DNS 를 사용한다. 생각해보면 TCP 통신은 IP 주소 기반으로 이루어지는데, 서버 로그에는 요청을 보낸 서버의 IP뿐만 아니라 호스트명도 남아있었다. 요청을 받은 서버가 직접 hostname lookup을 수행해서 알아낸 것이다.

2. 요청 받아들이기

Concurrency 관리를 위한 모델 4가지

안전한 클라이언트로부터 들어온 요청이란게 판별되었다면, 이제 요청을 받아들일 차례다. 책에서는 클라이언트로부터 동시에 들어오는 수많은 요청을 처리하기 위한 아키텍처 4가지를 소개한다.

  • 단일 스레드 I/O 아키텍처: 하나의 스레드나 프로세스가 하나의 커넥션(요청)을 차례대로 처리한다.
  • 멀티 스레드(프로세스) I/O 아키텍처: 여러개의 스레드(프로세스)가 하나의 요청을 차례대로 처리한다.
    • 각 스레드(프로세스)는 미리 만들어져있을 수도 있고, 각 커넥션마다 새로 만들어질 수도 있다.
    • 보통 너무 많은 프로세스나 스레드를 생성하지 않도록 최대 개수를 제한하여 만든다.
  • 다중 I/O 아키텍처: TCP 지속 커넥션을 사용하여 커넥션 다중화를 하고, 각 요청은 하나의 스레드가 처리한다.
    • 요즘 말하는 ‘이벤트 루프 방식’을 사용하는 것이다.
    • 즉, 요청을 처리하는 스레드는 해당 요청을 실제로 처리해야하는 다른 스레드에게 넘겨주고 그 다음 요청을 처리한다.
    • 한번 처리된 요청은 다시 ‘열린 커넥션 목록’으로 돌아가서 자신의 차례를 기다린다.
  • 다중, 멀티 스레드 I/O 아키텍처: 다중 I/O 아키텍처를 각 CPU마다 수행하는 것이다.
    • 따라서 앞서 말한 이벤트 루프를 담당하는 스레드는 보통 CPU 개수만큼 생긴다.

3. 요청 처리

부분은 이렇게 말하고 넘어가버렸다.

우리는 요청 처리에 대해서는 이야기하지 않을 것이다. 왜냐하면 그건 이 책 나머지 대부분의 주제이기 때문이다!

4. 리소스의 매핑과 접근

웹서버의 역할은 요청을 받아 리소스를 돌려주는 것이다. 정적, 동적 컨텐츠를 모두 돌려줄 수 있어야 한다. 클라이언트가 원하는 정적 컨텐츠를 식별하는 가장 쉬운 방법은 해당 리소스의 경로를 URI로 넘겨받는 것이다. 물론 동적 리소스에도 매핑할 수 있다. 요청에 맞게 컨텐츠를 생성하는 프로그램에 URI를 매핑하면 된다. nginx 가 요청을 받아서 우리가 만든 Spring Boot 애플리케이션의 컨트롤러에 URI 를 매핑하는 식이다.

접근 제어 역시 웹 서버가 담당한다. 웹 서버는 클라이언트의 IP 주소에 따라 리소스 접근을 제한하거나 패스워드를 물어볼 수 있다. 이 내용은 12장에서 다룰 예정이다.

5. 응답 만들기

응답 메세지에 들어가는 응답 상태 코드, 헤더, 본문을 넣는 과정이다. 본문이 있다면 MIME 타입과 본문의 길이를 알려주는 Content-Type , Content-Length 헤더가 각각 필요하다. 보통 MIME 타입을 결정하기 위해서는 그 리소스의 확장자를 사용한다.

한편, 성공 메시지 대신 리다이렉션 응답을 반환해야 할 때도 있다. Location 응답 헤더에 리다이렉션의 목적지 주소를 넣어서 보내면 된다. 리소스가 임시 혹은 영구적으로 옮겨졌거나 이름이 변경된 경우, URL 확장을 위해, 과부화된 서버의 로드를 줄이기 위해 등등 생각보다 다양한 이유로 리다이렉션을 수행한다. 한가지 재밌는 점은 www.google.com 이라고만 요청을 보냈을 때 마지막에 자동으로 / 를 넣어서 상대경로가 정상적으로 동작하게 하는 것도 리다이렉션이라고 한다. (하지만 이 동작은 현재 크롬에서 재현되지는 않고 있다. 분명 어디서 본 것 같은데......🥲)

6. 응답 보내기

이제 거의 다 왔다! 응답을 보내기만 하면 끝이라고 생각할 수도 있겠지만 응답을 보낼 때에도 고려할 점이 꽤 있다. 만약 HTTP 지속 커넥션이라면 고려할게 더 많아진다. 서버가 Content-Length 를 바르게 계산하기 위해 특별한 주의를 해야 하거나, 클라이언트가 응답이 언제 끝나는지 알 수 없는 경우 커넥션을 계속 열린 상태로 유지해야 한다. 그렇지 않은 비지속 커넥션이라면 서버는 모든 메세지를 전송했을 때 자신의 커넥션을 닫을 것이다.

7. 로깅

트랜잭션이 완료되었을 때 웹 서버는 트랜잭션의 수행에 관한 로그를 로그 파일에 기록한다. 좀 더 자세한 내용은 21장에서 다룰 것이다.


이번 장에서는 당연하게 여겼던 HTTP 요청 받아들이기와 응답 돌려주기에 대해 좀 더 자세히 들여다보았다. 다음 장부터 본격적으로 웹을 구성하는 컴포넌트 - 프락시, 게이트웨이, 캐시 등등 - 를 배우게 될 것 같다.

profile
설계를 좋아합니다. 코드도 적고 그림도 그리고 글도 씁니다. 넓고 얕은 경험을 쌓고 있습니다.

0개의 댓글