본문 바로가기
CS

[Network] 브라우저는 어떻게 웹 서버로 요청을 보내는걸까?

by doodoom 2023. 5. 17.

0. 이 글을 쓰게 된 이유

아주 예전부터 기술 부채에 "요청 버튼을 누르면 구체적으로 어떤 방식으로 요청이 서버에 닿을까"가 있었다. 만약 면접에서 이 질문이 들어오면 대답할 자신이 없었기 때문에 언젠간 해야지 하다가 드디어 브라우저부터 찔끔 정리해보려고 한다. 1%의 네트워크 원리를 토대로 정리를 하는 것이라서 거의 비슷한 순서로 글을 작성한다.
브라우저는 여러 개의 클라이언트 기능을 제공하는 복합적인 클라이언트 소프트웨어이다. 브라우저가 어떤 기능을 제공하고, 어떤 과정을 통해 웹서버로 요청을 보내는지 알아보자.

1. HTTP Request 메시지 작성

일단 요청을 보내려면 요청 메시지를 작성해야한다. HTTP 말고도 다양한 프로토콜이 있지만 주제가 웹 서버로 요청을 하는 것이니 HTTP를 기준으로 설명한다.

1.1 URL 해석

URL(Uniform Resource Locator)은 인터넷에서 웹 페이지, 이미지, 비디오 등 리소스의 위치를 가리키는 문자열이다. 단순 문자열이기 때문에 해독하는 방법이 여러가지 있을 수 있는데, 위 사진이 URL을 해석하는 규칙이다. 이를 통해 브라우저는 여러가지 정보를 얻는다.
우리는 우리가 원하는 자원의 URL을 만들어서 입력하고 ENTER 버튼을 누른다. 이게 요청의 시작점이다. 그럼 브라우저는 정해진 규칙대로 URL을 해석한다. 이를 통해 웹 서버의 도메인 주소와 파일명을 판단한다.

1.2 HTTP Request 메시지 생성

웹 서버의 도메인 주소와 파일명을 알았으니 이를 토대로 HTTP Request 메시지를 생성한다. HTTP 프로토콜을 통해 메시지를 작성하는 포맷이 정해져있다. 브라우저는 이 포맷에 맞게 리퀘스트 메시지를 만든다.

위 사진은 HTTP Request 메시지 규약을 간단하게 나타낸 것이다. 자세하게 알아보고 싶으면 HTTP 규약을 찾아보는 것을 추천한다.
HTTP Method에 따라서 요청하는 방법이 달라질 수 있다. 이것도 HTTP Method를 검색해보는것이..
정리하자면 브라우저는 HTTP 프로토콜에 맞게 메시지를 작성한다.

2. 웹 서버의 IP주소를 DNS 서버를 통해 조회

브라우저는 HTTP Message를 만든 후 도메인 주소를 통해 IP주소를 찾는다. 하지만 브라우저는 따로 네트워크 송출 기능이 없기 때문에 이를 사용자의 OS에 요청한다. OS는 DNS 서버를 통해 도메인 주소를 통해 IP주소를 찾아서 메모리에 저장한다.
브라우저가 OS에게 요청을 할 때, Socket 라이브러리를 이용하는데, 내부에 DNS 클라이언트에 해당하는 리졸버를 이용해서 얻어온다. DNS에 대해서는 다음에 자세하게 다루도록 하자.
Request 메시지를 생성했고 IP 주소를 알아냄으로써 웹서버에 요청을 보낼 준비를 마치었다.

3. 메시지 송신

이제 웹 서버로 메시지를 송신할 차례이다. 하지만 위에서 얘기했듯이 브라우저는 따로 네트워크 송출 기능이 없다. 이것도 Socket 라이브러리를 통해 OS에게 요청을 하는데 구체적으로는 OS 내부에 있는 프로토콜 스택에게 요청을 한다.
즉, 브라우저는 프로토콜 스택에게 요청하여 웹 서버와 통신을 하는데, 이 방식을 Socket 통신이라고한다. Socket 통신에 대해서 좀 더 자세하게 알아보자.

3.1 Socket 통신

데이터를 송신하는 과정은 서버와 클라이언트에 파이프라인을 만들어놓고, 한쪽에서 데이터를 밀어넣는 것과 비슷하게 작동한다. 클라이언트에서 데이터를 밀어넣으면 서버가 받고, 서버가 밀어넣으면 클라이언트가 받게된다. 이 파이프라인과 같은 과정을 만들기 위해서는 다음과 같은 4단계가 필요하다.

  1. 소켓 생성(소켓 작성 단계).
  2. 서버 측의 소켓에 파이프 연결(접속 단계)
  3. 데이터 송수신(송수신 단계)
  4. 파이프 해제(연결 끊기)
    브라우저 입장에서 좀 더 자세하게 살펴보자.

3.1.1 소켓 생성(소켓 작성 단계)

소켓 생성은 생각보다 간단하다. Socket 라이브러리에 있는 socket함수를 통해서 생성할 수 있다. 여기에 몇가지 설정 사항을 뜻하는 인자를 넘겨주면 알아서 소켓을 생성해준다. 실제로 소켓 생성은 굉장히 복잡하지만 라이브러리에서 추상화 되어있다고 보면 된다. 소켓을 생성하면 디스크립터라는 것이 반환 값으로 나온다. 브라우저는 이것을 받아 메모리에 기록해둔다. 디스크립터는 소켓을 식별하기 위해 사용되는 것이다. 컴퓨터 내부에서는 동시에 여러개의 데이터 송수신 동작이 진행될 수 있다. 이럴 경우 복수의 소켓이 한 대의 컴퓨터에 있으니 이를 식별할 식별자가 필요한데, 이 식별자가 디스크립터이다.

3.1.2 서버 측의 소켓과 파이프 연결(접속 단계)

여기서도 Socket 라이브러리를 이용하고, connect 함수를 통해서 가능하다. connect 함수의 인자로 디스크립터, 서버의 ip주소, 포트 번호 세가지를 넘긴다. 디스크립터를 프로토콜 스택에게 주면 알아서 소켓을 찾아온다. ip주소는 웹서버의 ip주소이므로 이또한 프로토콜 스택에게 전달해줘야한다. 하지만 여기서 문제가 발생한다. 요청을 보낼 웹서버에 여러가지 소켓이 있는데, 이 중 어떤 소켓과 연결해야할지 식별하는 작업이 필요하다. 이 역할을 하는 것이 포트번호이다. 일반적으로 웹서버는 80번 포트에 요청을 기다리는 socket을 미리 만들어 놓고 기다린다. 80번 포트에 요청이 오면 해당 socket으로 요청을 일단 받고 동적으로 새로운 socket을 만들어 이와 요청 socket을 연결한다. 이를 통해 socket을 통한 파이프라인이 연결됐다.

3.1.3 데이터 송수신

소켓이 서버와 연결되면 그 다음은 간단하다. 소켓을 통해 데이터를 쏟아부으면 서버에 데이터가 도착한다. 이는 Socket 라이브러리의 write 메소드를 통해 가능하다. 브라우저는 송신 데이터(HTTP Request 메시지)를 준비한다. 그리고 write 함수에 디스크립터, 송신 데이터, 여러가지 메타데이터를 전달한다. 이미 소켓이 연결되어있으므로 소켓을 통해 메시지가 전달된다. 이 메시지를 수신한 웹서버는 응답 메시지를 역으로 보내게되는데, 이는 read 메소드를 통해 동작한다. 수신한 응답 메시지를 위한 메모리 영역이 지정되는데, 이를 수신 버퍼라고 부른다.

3.1.4 파이프 해제(연결 분리)

브라우저가 데이터 송수신을 완료하면 Socket의 close를 통해 연결 끊기 단계로 들어간다. 보통 웹 서버에서 응답 메시지를 송신을 완료할때 먼저 소켓 연결을 끊는다. 이후 브라우저가 read를 했는데 연결이 없으면 끊긴 사실을 알고 브라우저도 close를 호출한다.

여기까지가 HTTP의 동작이다. 원래는 HTML 문서에 있는 다른 데이터들(다른 html, 영상, 사진 등)을 얻어올 때, 이를 각각 다른 연결을 통해서 가져왔었다. 하지만 요즘에는 하나의 연결을 통해서 가져온다고 한다(HTTP1.1).