[Network] 1. 웹 브라우저가 요청 메시지를 만드는 방법

0. 도입

당분간 [Network] 포스팅에서는 브라우저에 URL을 입력한 후 웹 페이지가 표시되기까지의 원리를 알아보고자 한다.

첫번째 챕터는 웹 브라우저가 요청 메시지를 만드는 방법이다.

 

브라우저의 URL 입력 상자에 입력한 URL을 해독하는 것부터 시작해

그것을 바탕으로 HTTP 요청 메시지를 만들고

만든 메시지를 송신하도록 OS에 의뢰하는 것까지 알아보자.

 

1. HTTP 요청 메시지를 작성한다

1.1. 탐험 여행은 URL 입력부터 시작한다

URL은 여러 종류가 있다.

URL은 http:// 뿐만 아니라 ftp:, file: 등으로 시작할 수도 있다.

브라우저는 웹 서버에 접근하는 클라이언트 기능뿐만 아니라 파일을 다운로드, 업로드하는 FTP의 클라이언트 기능이나 메일의 클라이언트 기능도 가지고 있다.

몇 개의 기능 중 어느 것을 사용하여 데이터에 접근하면 좋을지 판단하기 위해 여러 종류의 URL이 존재한다.

 

즉, 대부분의 URL 앞에는 HTTP, FTP 등 프로토콜의 종류가 붙는다. 접근 대상이 웹 서버인지, FTP 서버인지에 따라.

(file:의 경우 네트워크를 사용하지 않으므로 반드시 프로토콜을 나타내는 것은 아님)

 

1.2. 브라우저는 먼저 URL을 해독한다

브라우저가 처음 하는 일은 웹 서버에 보내는 요청 메시지를 작성하기 위해 URL을 해독하는 것이다.

URL의 형식은 프로토콜에 따라 다르므로 웹 서버에 접근하는 경우를 살펴보도록 하자.

URL 부분만 살펴보자면

http는 프로토콜,

host는 서버의 이름

port는 80일 경우 생략 가능

path는 파일의 경로

 

최종적으로 global.php 라는 파일에 접근한다는 의미이다.

 

1.3. 파일명을 생략한 경우

http://www.mydomain.com/javascript/ 

 

대부분의 서버가 'index.html'이나 'default.html'이라는 파일명을 설정해둔다.

그러므로 위와 같이 파일명을 생략한 경우, index.html 또는 default.html 파일에 접근한다.

 

1.4. HTTP의 기본 개념

URL을 해독하면 어디에 접근해야 하는지가 판명된다.

그러고 나면 브라우저는 HTTP 프로토콜을 사용해 웹 서버에 접근하는데, 그 동작을 살펴보기 전에 HTTP 프로토콜이 무엇인지 알아보자.

 

HTTP 프로토콜은 클라이언트와 서버가 주고받는 메시지의 내용이나 순서를 정한 것이다.

먼저 클라이언트에서 서버를 향해 요청 메시지를 보내고,

요청 메시지 안에는 '무엇을', '어떻게 해서' 하겠다는 내용이 쓰여져있다.

 

URI: '무엇을'에 해당하는 것, 접근 대상을 통칭하는 말

메서드: '어떻게 해서'에 해당하는 것, 웹 서버에 어떤 동작을 하고 싶은지 전달함

 

요청 메시지가 웹 서버에 도착하면 웹 서버는 그 속에 쓰여있는 내용을 해독한다.

URI와 메시지를 조사해 무엇을 어떻게 하는지 판단한 후, 요구에 따라 동작하고 결과 데이터를 응답 메시지에 저장한다.

응답 메시지의 맨 앞부분에는 실행 결과를 나타내는 상태 코드가 있다.

그 응답 메시지를 클라이언트에 반송하고, 클라이언트에 도착하여 브라우저가 메시지에서 데이터를 추출해 화면에 표시하면서 HTTP의 동작은 끝난다.

 

1.5. HTTP 요청 메시지를 만든다

HTTP 메시지를 작성하는 방법, 즉 포맷이 결정되어 있으므로 브라우저는 이 포맷에 맞게 요청 메시지를 만든다.

HTTP 요청 메시지의 포맷

Request Message Header와 Request Message Body로 나뉜다.

헤더에는

첫 행에 메서드, URL 부분에는 일반적으로 경로명, HTTP 버전 번호를 작성한다.

이후 두번째 행부터는 메시지 헤더가 이어진다.

부가적인 정보인 날짜, 클라이언트 측이 취급하는 데이터의 종류, 언어, 압축 형식 등을 작성한다.

 

공백 행을 한 줄 넣고, 메시지 바디가 이어진다.

메시지의 실제 내용으로, GET 메서드인 경우 바디에 쓰는 데이터가 없다.

POST 메서드인 경우 폼에 입력한 데이터 등을 작성한다.

 

1.6. 요청 메시지를 보내면 응답이 되돌아온다

자세한 내용은 이후 포스팅에서 다룰 예정이니 간단히 살펴보자.

HTTP 응답 메시지의 포맷

 

응답 메시지의 포맷도 기본적인 개념은 요청 메시지와 같지만, 첫 번째 행이 다르다.

첫 행에 정상 종료했는지, 오류가 발생했는지 상태 코드와 응답 문구를 작성한다.

상태 코드는 숫자로 나타내어 주로 프로그램 등에 실행 결과를 알리는 것,

응답 문구는 문장으로 작성해 사람에게 실행 결과를 알리는 것이 목적이다.

 

클라이언트가 응답 메시지를 받으면, 그때부터 브라우저는 데이터를 추출해 화면에 표시한다.

페이지가 문장으로만 되어 있으면 이것으로 끝이지만, 영상 등이 포함되어 있는 경우에는 추가 작업을 해야 한다.

 

<img src="image1.jpg alt="첫번째 이미지"> 등의 태그를 만나면, 그곳에 공백을 비워두고 alt 값의 텍스트를 표시한다.

이후 다시 한 번 요청 메시지를 만들어 웹 서버에 접근하여 태그에 쓰여있는 영상 파일을 읽어와서 방금 전 비워 둔 공백에 표시한다. 

 

만약 처음 요청한 파일에 3개의 영상이 포함되어 있다면, 총 4회의 요청 메시지를 웹 서버에 보낸다.

이와 같이 필요한 파일을 판단하고 이것을 읽은 후 레이아웃을 정하여 화면에 표시하는 상태로 전체의 동작을 조정하는 것도 브라우저의 역할이다.

 

2. 웹 서버의 IP 주소를 DNS 서버에 조회한다

2.1. IP 주소의 기본

HTTP 메시지를 만들면, 이것을 OS에 의뢰하여 접근 대상의 웹 서버에게 송신한다.

브라우저는 URL을 해독하거나 HTTP 메시지를 만들지만, 메시지를 네트워크에 송출하는 기능은 없으므로 OS에 의뢰하여 송신한다.

이때 URL 안에 쓰여있는 서버의 도메인명에서 IP 주소를 조사해야 한다.

OS에 송신을 의뢰할 때는 도메인명이 아니라 IP 주소로 메시지를 받을 상대를 지정해야 하기 때문이다.

그러므로 HTTP 메시지를 만든 다음, 도메인명에서 IP 주소를 조사하는 과정이 이루어진다.

이를 알아보기 전에 IP 주소에 대해 간단히 살펴보자.

 

인터넷과 사내 LAN은 TCP/IP의 개념에 기초하여 만들어졌으므로 TCP/IP의 기본이 되는 개념부터 알아보자.

TCP/IP 프로토콜을 기반으로 한 네트워크(인터넷)는 서브넷이라는 작은 네트워크를 라우터로 연결하여 전체 네트워크가 만들어진 것이라고 할 수 있다. 서브넷이란, 허브에 몇 대의 PC가 접속된 것이라고 생각하자.

여기에 서울시 강남구 역삼동 소영아파트 101동 202호 (네트워크 주소 호스트 주소) 라는 형태로 IP 주소를 할당한다.

 

접근 대상 서버까지 메시지를 운반할 때는, 이 IP 주소에 따라 접근 대상이 어디에 있는지 판단하고 운반한다.

송신측이 메시지를 보내면 서브넷 안에 있는 허브가 운반하고 (1번)

라우터가 메시지를 보낸 상대를 확인해 다음 라우터를 판단하고, 거기에 보내도록 지시해 송신 동작을 실행한 후 다시 서브넷의 허브가 라우터까지 메시지를 보낸다. (2번)

이런 동작을 반복하면 최종적으로 상대의 데이터가 도착한다는 원리인데, 이것이 TCP/IP와 IP 주소의 기본적인 개념이다.

 

2.2. 도메인명과 IP 주소를 구분하여 사용하는 이유

IP 주소를 기억하고 도메인명 대신 IP 주소를 브라우저에 입력해도 된다. 하지만 기억하기 어렵다.

또한 숫자 대신 이름으로 상대를 지정해 통신한다면 IP 주소는 32비트로 고정되어 있지만 이름은 일정한 길이가 아니고 랜덤의 문자를 취급해야 하기에 라우터가 부하되어 네트워크 속도가 느려질 수 있다.

 

사람은 도메인명을, 라우터는 IP 주소를 사용하는 방법이 정착되어 있다.

도메인명과 IP 주소를 대응시키는 역할을 하는 것이 DNS이다.

 

2.3. Socket 라이브러리가 IP 주소를 찾는 기능을 제공한다

IP 주소를 조사하는 방법은 가장 가까운 DNS 서버에게 'www.ssoyeong.tistory.com'이라는 서버의 IP 주소를 묻는 것이다. 그럼 DNS 서버가 IP 주소를 알려준다.

그렇다면 브라우저는 어떻게 DNS 서버를 조회할 수 있을까? 잠시 이 부분을 알아보자.

 

DNS 서버에 조회한다는 것은 DNS 서버에 조회 메시지를 보내고, 거기에서 반송되는 응답 메시지를 받는다는 것이다.

DNS 클라이언트(브라우저) <-> DNS 리졸버 <-> DNS 서버

브라우저는 도메인명으로 DNS 쿼리를 보내고

DNS 리졸버가 중간에서 DNS 쿼리를 받아 DNS 서버에 전달하고, 응답을 받아 다시 DNS 클라이언트에 반환한다.

 

DNS의 원리를 사용하요 IP 주소를 조사하는 것을 네임 리졸루션(name resolution)이라고 하는데,

이 리졸루션을 실행하는 것이 DNS 리졸버이다.

리졸버는 DNS 쿼리를 캐싱해 동일한 요청에 빠르게 응답할 수 있도록 도와준다.

 

리졸버의 실체는 Socket 라이브러리에 들어있는 부품화한 프로그램이다.

Socket 라이브러리는 네트워크의 기능을 호출하기 위한 프로그램의 부품집으로, 대부분의 주요 운영체제에는 네트워크 프로그래밍을 위한 Socket 라이브러리가 포함되어 있다.

 

2.4. 리졸버를 이용하여 DNS 서버를 조회한다

Socket 라이브러리에 들어있는 프로그램은 부품화되어 있고 애플리케이션에서 간단히 호출하여 이용할 수 있다.

즉, 브라우저 등의 애플리케이션을 만들 때 리졸버의 프로그램명(gethostbyname)과 웹 서버의 이름(www.ssoyeong.tistory.com)을 쓰기만 하면 리졸버를 호출할 수 있다.

 

리졸버를 호출하면 리졸버가 DNS 서버에 조회 메시지를 보내고, DNS 서버에서 응답 메시지가 돌아온다.

이 응답 메시지 속에 IP 주소가 포함되어 있으므로 리졸버는 이것을 추출하여 브라우저에서 지정한 메모리 영역에 넣는다.

그럼 브라우저가 웹 서버에 메시지를 보낼 때는 이 메모리 영역에서 IP 주소를 추출해 HTTP 요청 메시지와 함께 OS에 건네주어 송신을 의뢰한다.

 

요약: 도메인명에서 IP 주소를 조사할 때 브라우저는 Socket 라이브러리의 리졸버를 이용한다.

 

2.5. 리졸버 내부의 작동

네트워크 애플리케이션(브라우저)이 리졸버를 호출하면 제어가 리졸버로 넘어간다.

브라우저가 HTTP 요청 메시지를 만드는 것처럼 DNS 서버에 문의하기 위한 메시지를 만든다.

메시지 송신 동작은 리졸버가 스스로 실행하는 것이 아니라, OS 내부에 포함된 프로토콜 스택을 호출해 실행을 의뢰한다.

리졸버도 브라우저와 같이 네트워크에 대해 데이터를 송수신하는 기능이 없기 때문이다.

 

프로토콜 스택은 OS 내부에 내장된 네트워크 제어용 소프트웨어로, OSI 모델, TCP/IP 모델 등이 있다. (우리가 아는 그 OSI 7계층!)

리졸버가 프로토콜 스택을 호출하면, 메시지를 보내는 동작을 실행하여 LAN 어댑터를 통해 메시지가 DNS 서버를 향해 송신된다 (4번, 5번)

그러면 조회 메시지가 DNS 서버에 도착하고, DNS 서버는 메시지에 쓰여있는 조회 내용을 조사하여 답을 찾는다.

 

접근 대상의 웹 서버가 DNS 서버에 등록되어 있으면 답이 발견되므로, 답을 응답 메시지에 써서 클라이언트에게 보내진다.

역순으로 프로토콜 스택, 리졸버, 그리고 브라우저까지 전달된다.

 

3. 전 세계의 DNS 서버가 연대한다

3.1. DNS 서버의 기본 동작

3.2. 도메인의 계층

3.3. 담당 DNS 서버를 찾아 IP 주소를 가져온다

3.4. DNS 서버는 캐시 기능으로 빠르게 회답할 수 있다

 

 

4. 프로토콜 스택에 메시지 송신을 의뢰한다

4.1. 데이터 송수신 동작의 개요

IP 주소를 조사했으면 대상 웹 서버에게 메시지를 송신하도록 OS의 내부에 있는 프로토콜 스택에 의뢰한다. HTTP 메시지와 같은 디지털 데이터를 송수신하는 동작은 브라우저뿐만 아니라 네트워크를 이용하는 애플리케이션 전체에 공통이므로, 이 동작은 웹에 한정되지 않고 모든 네트워크 애플리케이션에 해당된다. 이 동작을 추적해보자.

 

DNS 서버에 IP 주소를 조회할 때와 같이, OS 내부의 프로토콜 스택에 메시지 송신 동작을 의뢰할 때는 Socket 라이브러리에 들어있는 프로그램 부품을 결정된 순번대로 호출한다.

 

Socket 라이브러를 이용한 데이터 송수신 동작의 이미지를 그림으로 나타내면 위와 같다. 즉 데이터를 송수신하는 컴퓨터 사이에 파이프와 같은 통로가 있고, 한쪽 끝에서 파이프에 데이터를 쏟아부으면 반대쪽 끝까지 도착하고 거기에서 데이터를 추출할 수 있다. 데이터는 양방향으로 흘릴 수 있다.

 

이때 송신자와 수신자 사이를 파이프로 연결하는 동작이 필요하다. 파이프의 양끝에 있는 데이터 출입구가 소켓인 것이다.

서버측에서 소켓을 만들고 소켓에 클라이언트가 파이프를 연결하기를 기다린다. 클라이언트측에도 소켓을 만들고, 소켓에서 파이프를 늘려 서버측에 소켓을 연결하는 것이다.

 

이후 소켓에서 데이터를 쏟아붓듯이 데이터 송수신 동작을 실행한다.

 

송수신 동작이 끝나면 연결했던 파이프는 분리된다. 파이프를 연결할 때는 클라이언트측에서 서버측을 향하여 연결했지만, 파이프를 분리할 때는 어느 쪽에서 분리해도 상관없다. (실제로 분리되는 순서는 애플리케이션의 규칙으로 정해둔다)

 

데이터 송수신 동작을 정리하자면,

(1) 소켓을 만든다 (소켓 작성 단계)

(2) 서버측의 소켓에 파이프를 연결한다 (접속 단계)

(3) 데이터를 송수신한다 (송수신 단계)

(4) 파이프를 분리하고 소켓을 말소한다 (연결 끊기 단계)

 

이 단계에 따라 Socket 라이브러리 안의 프로그램 부품을 호출하여 데이터의 송수신 동작을 실행한다.

실제 이 네 가지 동작을 실행하는 것은 프로그램 부품이 아닌 OS 내부의 프로토콜 스택이다. 프로그램 부품은 애플리케이션에서 의뢰받은 내용을 그대로 프로토콜 스택에 전달하는 중개역을 수행할 뿐 실질적인 작업은 하지 않는다.

 

4.2. 소켓의 작성 단계

Socket 라이브러리의 socket이라는 프로그램 부품을 호출하면 끝이다.

소켓이 생기면 소켓을 식별하기 위한 '디스크립터'라는 번호표를 반환하고, 애플리케이션은 이것을 받아 메모리에 기록해 둔다.

컴퓨터 내부에서는 복수의 데이터 송수신 동작이 동시에 진행될 수 있기에, 여러 소켓을 구분하기 위함이다.

 

4.3. 파이프를 연결하는 접속 단계

다음으로 만든 소켓을 서버측의 소켓에 접속하도록 프로토콜 스택에 의뢰한다.

애플리케이션은 Socket 라이브러리의 connect라는 프로그램 부품을 호출하여 이 의뢰 동작을 실행한다. connect를 호출할 때 파라미터로 디스크립터, 서버의 IP 주소, 포트 번호 세 가지 값을 넘긴다.

 

(1) 디스크립터

프로토콜 스택은 전달받은 디스크립터를 보고 어느 소켓을 서버측의 소켓에 접속할지 판단하여 접속 동작을 실행함

 

(2) IP 주소

IP 주소는 네트워크의 어느 컴퓨터인가 하는 것까지 알 수 있음

접속 동작은 상대측의 소켓에 대해 이루어지므로, 소켓을 지정해야 하는데 IP 주소로는 소켓까지 지정할 수 없음

 

(3) 포트 번호

포트 번호를 통해 접속 상대의 소켓을 지정할 수 있음

디스크립터는 컴퓨터 한 대의 내부에서 소켓을 식별하기 위한 용도, 포트 번호는 접속 상대측에서 소켓을 식별하기 위한 용도임

 

그렇다면 URL에는 포트 번호를 쓰지 않았는데 어떻게 알 수 있을까?

서버측의 포트 번호는 애플리케이션의 종류에 따라 미리 결정된 값을 사용한다는 규칙이 있다.

웹은 80번, 메일은 25번 등

 

최종적으로 상대와 연결이 되면, 프로토콜 스택은 연결된 상대의 IP 주소나 포트 번호 등의 정보를 소켓에 기록한다. 이로써 송수신이 가능한 상태가 된다.

 

4.4. 메시지를 주고받는 송수신 단계

연결됐으므로 이제 소켓에서 데이터를 쏟아 부으면 상대측의 소켓에 데이터가 도착한다.

애플리케이션은 Socket 라이브러리를 통해 프로토콜 스택에 의뢰하여, write라는 프로그램 부품을 사용한다.

구체적으로는 다음과 같다.

 

애플리케이션은 송신 데이터(사용자가 입력한 URL을 바탕으로 만든 HTTP 요청 메시지)를 메모리에 준비한다.

write를 호출할 때 파라미터로 디스크립터, 송신 데이터를 넘긴다. 그러면 프로토콜 스택이 송신 데이터를 서버에게 송신한다.

그러면 서버는 수신 동작을 실행하여 받은 데이터의 내용을 조사하고 적절한 처리를 실행하여 응답 메시지를 반송한다. (이 부분은 차후 자세히 알아보자)

 

수신할 때는 Socket 라이브러이의 read라는 프로그램 부품을 통해 프로토콜 스택에 수신 동작을 의뢰한다.

이때 응답 메시지를 저장하기 위한 메모리 영역을 넘겨주는데, 이 메모리 영역을 수신 버퍼라고 부른다. 수신 버퍼에 메시지가 저장되면 애플리케이션에 건네준다.

 

4.5. 연결 끊기 단계에서 송수신이 종료된다

Socket 라이브러이의 close라는 프로그램 부품을 호출하여 연결 끊기 단계로 들어간다. 그러면 소켓 사이를 연결한 파이프와 같은 것이 분리되고 소켓도 말소된다.

구체적으로는 다음과 같다.

 

웹에서 사용하는 HTTP 프로토콜에서는 본래 응답 메시지의 송신을 완료했을 때, 웹 서버측에서 연결 끊기 동작을 실행하므로 먼저 웹 서버측에서 close를 호출하여 연결을 끊는다.

그럼 이것이 클라이언트측에 전달되어 클라이언트의 소켓은 연결 끊기 단계로 들어간다.

그리고 브라우저가 read로 수신 동작을 의뢰했을 때, read는 수신한 데이터를 건네주는 대신 송수신 동작이 완료되어 연결이 끊겼다는 사실을 브라우저에 통지한다.

 

5. 마무리

이것이 HTTP의 본래 동작이다. 원래 메시지 하나마다(HTML 문서, 영상 데이터 등) 위와 같이 접속, 송수신, 연결끊기 동작을 반복해야 하지만, 같은 서버에 복수의 데이터를 읽을 때 접속과 연결 끊기를 반복하는 것은 비효율적이다.

그래서 한 번 접속한 후 연결을 끊지 않고 여러 요청과 응답을 주고받는 방법도 나중에 마련됐다.

 

 

 

참고자료

브라우저 주소창에 URL을 치면 일어나는 일들

성공과 실패를 결정하는 1%의 네트워크 원리_1

[네트워크 원리] 성공과 실패를 결정하는 1%의 네트워크 원리 (1)

https://www3.ntu.edu.sg/home/ehchua/programming/webprogramming/http_basics.html