[CS 스터디] 7주차 HTTP & DNS
OSI 5 ~ 7계층
이전 글에서는 OSI 1 ~ 3계층과 TCP/UDP에 대해 알아봤다.
이번에는 그 위에 있는 5 ~ 7계층과, 웹 요청에서 중요한 DNS와 HTTP에 대해 알아보려고 한다.
OSI 5 ~ 7계층은 다음과 같다.
5계층: 세션 계층
6계층: 표현 계층
7계층: 응용 계층
5계층: 세션 계층 (Session Layer)
세션 계층은 통신하는 두 장치 사이의 연결 상태를 관리하는 계층이다.
여기서 말하는 세션은 클라이언트와 서버가 데이터를 주고받기 위해 유지하는 논리적인 연결을 뜻한다.
세션 계층은 연결을 생성하고, 유지하고, 종료하는 역할을 한다.
예를 들어, 로그인 상태 유지나 화상 회의 연결처럼 일정 시간 동안 통신 상태를 유지해야 하는 경우 세션 개념이 사용된다.
6계층: 표현 계층 (Presentation Layer)
표현 계층은 데이터를 어떤 형식으로 표현할지 담당하는 계층이다.
응용 계층에서 사용하는 데이터를 전송 가능한 형태로 변환하거나, 반대로 받은 데이터를 응용 계층이 이해할 수 있는 형태로 변환한다.
대표적인 역할은 다음과 같다.
- 데이터 인코딩
- 데이터 압축
- 데이터 암호화/복호화
예를 들어, 문자열을 UTF-8로 인코딩하거나, HTTPS에서 TLS를 통해 데이터를 암호화하는 과정이 표현 계층의 역할과 관련이 있다.
7계층: 응용 계층 (Application Layer)
응용 계층은 사용자가 실제로 사용하는 서비스와 가장 가까운 계층이다.
브라우저, 이메일 클라이언트, 메신저 같은 애플리케이션이 네트워크를 사용할 수 있도록 프로토콜을 제공한다.
대표적인 프로토콜은 다음과 같다.
- HTTP
- DNS
- FTP
- SMTP
- SSH
즉, 사용자가 웹 브라우저에 URL을 입력해서 웹 페이지를 보는 과정은 응용 계층의 HTTP, DNS 같은 프로토콜을 통해 이루어진다.
DNS (Domain Name System)
DNS는 Domain Name System의 약자로, 도메인 이름을 IP 주소로 변환해주는 시스템이다.
사용자는 google.com 같은 도메인 이름을 사용하지만, 실제 네트워크 통신에서는 IP 주소가 필요하다.
따라서 브라우저가 서버에 요청을 보내기 전에, 먼저 도메인에 해당하는 IP 주소를 알아내야 한다.
이 과정을 DNS 조회라고 한다.
DNS 동작 과정
사용자가 브라우저에 www.example.com을 입력했다고 가정해보자.
DNS 조회는 보통 다음과 같은 순서로 이루어진다.
- 브라우저 캐시 확인
- 운영체제 캐시 확인
- 로컬 DNS 서버에 질의
- Root DNS 서버에 질의
- TLD DNS 서버에 질의
- Authoritative DNS 서버에 질의
- IP 주소 반환
재귀 질의 (Recursive Query)
재귀 질의는 클라이언트가 DNS 서버에게 최종 결과를 대신 찾아달라고 요청하는 방식이다.
즉, 클라이언트는 로컬 DNS 서버에게 www.example.com의 IP 주소를 물어보고, 로컬 DNS 서버는 여러 DNS 서버에 대신 질의해서 최종 IP 주소를 찾아온다.
Client Local DNS
| --- 질의 -------> |
| | --- Root DNS 질의 --->
| | --- TLD DNS 질의 ---->
| | --- Authoritative DNS 질의 --->
| <--- IP 반환 ---- |
클라이언트 입장에서는 로컬 DNS 서버에게 한 번만 요청하면 되기 때문에 간단하다.
반복 질의 (Iterative Query)
반복 질의는 DNS 서버가 최종 결과를 직접 찾아주는 것이 아니라, 다음에 물어볼 DNS 서버의 주소를 알려주는 방식이다.
예를 들어 Root DNS 서버는 www.example.com의 IP 주소를 직접 알려주지 않고, .com을 담당하는 TLD DNS 서버를 알려준다.
그 다음 TLD DNS 서버는 example.com을 담당하는 Authoritative DNS 서버를 알려준다.
Local DNS Root DNS
| --- 질의 --------> |
| <--- TLD 주소 ---- |
Local DNS TLD DNS
| --- 질의 --------> |
| <--- Authoritative DNS 주소 --- |
Local DNS Authoritative DNS
| --- 질의 --------> |
| <--- IP 주소 ----- |
즉, 로컬 DNS 서버는 여러 DNS 서버를 순서대로 따라가며 최종 IP 주소를 찾아낸다.
DNS 캐싱
DNS 조회는 여러 단계를 거치기 때문에 매번 새로 조회하면 시간이 오래 걸린다.
이를 줄이기 위해 DNS 결과는 여러 곳에 캐싱된다.
- 브라우저 캐시
- 운영체제 캐시
- 로컬 DNS 서버 캐시
- ISP DNS 캐시
DNS 캐시는 TTL(Time To Live) 값을 가진다.
TTL은 캐시된 DNS 정보를 얼마나 오래 사용할 수 있는지를 나타내는 값이다.
TTL이 지나면 캐시는 만료되고, 다시 DNS 조회를 수행한다.
DNS 캐싱을 사용하면 같은 도메인에 다시 접속할 때 더 빠르게 IP 주소를 얻을 수 있다.
HTTP (HyperText Transfer Protocol)
HTTP는 클라이언트와 서버가 웹에서 데이터를 주고받기 위해 사용하는 프로토콜이다.
브라우저는 HTTP 요청을 보내고, 서버는 HTTP 응답을 반환한다.
Client Server
| ---- HTTP Request --> |
| <-- HTTP Response --- |
HTTP는 기본적으로 요청과 응답 구조를 가진다.
클라이언트가 먼저 요청을 보내야 서버가 응답할 수 있다.
HTTP/0.9
HTTP/0.9는 가장 초기의 HTTP 버전이다.
구조가 매우 단순했고, 요청은 한 줄로 이루어졌다.
GET /index.html
HTTP 헤더도 없고, 상태 코드도 없고, 메서드도 GET만 지원했다.
응답도 HTML 문서만 반환하는 단순한 방식이었다.
HTTP/1.1
HTTP/1.1은 현재까지도 많이 사용되는 HTTP 버전이다.
HTTP/1.1에서는 다음과 같은 기능이 추가되었다.
- Host 헤더 필수
- Keep-Alive 기본 지원
- 파이프라이닝 지원
- 캐시 제어 개선
- 청크 전송 지원
HTTP/1.1의 중요한 특징 중 하나는 하나의 TCP 연결을 재사용할 수 있다는 점이다.
기존에는 요청 하나마다 TCP 연결을 새로 만들고 끊어야 했지만, HTTP/1.1에서는 Keep-Alive를 통해 하나의 연결로 여러 요청과 응답을 처리할 수 있다.
HTTP/2
HTTP/2는 HTTP/1.1의 성능 문제를 개선하기 위해 등장했다.
HTTP/1.1에서는 하나의 TCP 연결에서 요청과 응답을 순차적으로 처리하기 때문에, 앞 요청이 지연되면 뒤 요청도 영향을 받는 문제가 있었다.
이를 Head Of Line Blocking이라고 한다.
HTTP/2는 다음과 같은 특징을 가진다.
- 바이너리 프레이밍
- 멀티플렉싱
- 헤더 압축
- 스트림 우선순위
멀티플렉싱은 하나의 TCP 연결 안에서 여러 요청과 응답을 동시에 주고받을 수 있게 해준다.
하나의 TCP 연결
├── Stream 1
├── Stream 2
└── Stream 3
이를 통해 HTTP/1.1보다 더 효율적으로 리소스를 주고받을 수 있다.
HTTP/3
HTTP/3는 TCP가 아닌 QUIC 위에서 동작한다.
QUIC은 UDP 기반의 전송 프로토콜이다.
기존 HTTP/2는 TCP 위에서 동작했기 때문에, TCP 레벨에서 패킷 손실이 발생하면 여러 스트림이 함께 영향을 받을 수 있었다.
HTTP/3는 QUIC을 사용하여 이런 문제를 줄이고, 연결 설정 속도도 개선했다.
HTTP/3의 특징은 다음과 같다.
- UDP 기반 QUIC 사용
- TLS 기본 포함
- 빠른 연결 설정
- 연결 마이그레이션 지원
- 패킷 손실 영향 감소
HTTP/3는 HTTP over QUIC 형태로 동작한다고 볼 수 있다.
HTTP 버전 비교
| 버전 | 특징 | 연결 방식 |
|---|---|---|
| HTTP/0.9 | GET만 지원, 헤더 없음 | 요청마다 연결 |
| HTTP/1.1 | Host 헤더, Keep-Alive, 캐시 제어 | TCP |
| HTTP/2 | 멀티플렉싱, 헤더 압축, 바이너리 프레임 | TCP |
| HTTP/3 | QUIC 사용, 빠른 연결, 패킷 손실 영향 감소 | UDP 기반 QUIC |
Keep-Alive
Keep-Alive는 하나의 TCP 연결을 여러 HTTP 요청에서 재사용하는 방식이다.
HTTP 요청을 보낼 때마다 TCP 연결을 새로 만들면 3-way handshaking이 계속 발생한다.
이렇게 되면 불필요한 지연이 생긴다.
Keep-Alive를 사용하면 한 번 만든 TCP 연결을 일정 시간 동안 유지하고, 같은 서버에 대한 여러 요청을 같은 연결에서 처리할 수 있다.
Client Server
| ---- TCP 연결 ------ |
| ---- Request 1 ----> |
| <--- Response 1 ---- |
| ---- Request 2 ----> |
| <--- Response 2 ---- |
| ---- 연결 종료 ----- |
이를 통해 연결 생성 비용을 줄일 수 있다.
HTTP/1.1에서는 Persistent Connection이 기본으로 사용된다.
Connection 관리
HTTP에서 Connection 관리는 성능과 관련이 깊다.
웹 페이지 하나를 로딩할 때 HTML만 받는 것이 아니라, CSS, JavaScript, 이미지, 폰트 같은 여러 리소스를 함께 받아야 한다.
만약 리소스마다 TCP 연결을 새로 만든다면 비효율적이다.
그래서 HTTP/1.1에서는 Keep-Alive를 사용하고, HTTP/2에서는 하나의 연결에서 여러 스트림을 동시에 처리한다.
HTTP/3에서는 QUIC을 통해 연결 설정과 패킷 손실 문제를 개선했다.
즉, HTTP 버전이 올라갈수록 연결을 더 효율적으로 사용하도록 발전했다고 볼 수 있다.
웹 요청 흐름
사용자가 브라우저에 URL을 입력하면 다음과 같은 과정이 진행된다.
1. URL 입력
사용자가 브라우저 주소창에 URL을 입력한다.
https://www.example.com/index.html
브라우저는 URL을 해석해서 프로토콜, 도메인, 경로를 분리한다.
- 프로토콜: HTTPS
- 도메인: www.example.com
- 경로: /index.html
2. DNS 조회
브라우저는 도메인 이름만으로 서버에 접속할 수 없기 때문에 IP 주소를 알아내야 한다.
먼저 브라우저 캐시와 운영체제 캐시를 확인한다.
캐시에 없다면 로컬 DNS 서버에 질의하고, 로컬 DNS 서버는 Root DNS, TLD DNS, Authoritative DNS 서버를 거쳐 IP 주소를 찾아온다.
3. TCP 연결
IP 주소를 알아냈다면 서버와 연결을 맺는다.
HTTP/1.1이나 HTTP/2는 TCP 위에서 동작하기 때문에 3-way handshaking을 수행한다.
Client Server
| -------- SYN ------------> |
| <------ SYN + ACK -------- |
| -------- ACK ------------> |
HTTPS라면 TCP 연결 이후 TLS Handshake도 수행한다.
4. HTTP 요청
연결이 완료되면 브라우저는 서버에 HTTP 요청을 보낸다.
GET /index.html HTTP/1.1
Host: www.example.com
요청에는 메서드, 경로, HTTP 버전, 헤더 등이 포함된다.
5. 서버 처리
서버는 요청을 받고 필요한 작업을 수행한다.
정적 파일이라면 HTML, CSS, 이미지 같은 파일을 반환한다.
동적 요청이라면 애플리케이션 서버가 비즈니스 로직을 처리하고, 필요하다면 데이터베이스에 접근한다.
6. HTTP 응답
서버는 처리 결과를 HTTP 응답으로 반환한다.
HTTP/1.1 200 OK
Content-Type: text/html
<html>
...
</html>
응답에는 상태 코드, 헤더, 바디가 포함된다.
7. 브라우저 렌더링
브라우저는 HTML을 파싱해서 DOM Tree를 만든다.
CSS를 파싱해서 CSSOM Tree를 만든다.
그 다음 DOM Tree와 CSSOM Tree를 합쳐 Render Tree를 만들고, Layout과 Paint 과정을 거쳐 화면에 표시한다.
JavaScript가 있다면 실행 과정에서 DOM이나 CSSOM이 변경될 수 있다.
정리하면 다음과 같다.
- URL 입력
- DNS 조회
- TCP 연결
- TLS Handshake
- HTTP 요청
- 서버 처리
- HTTP 응답
- HTML 파싱
- CSS 파싱
- JavaScript 실행
- 렌더링
Stateless
Stateless는 서버가 클라이언트의 상태를 저장하지 않는 방식을 뜻한다.
HTTP는 기본적으로 Stateless한 프로토콜이다.
즉, 서버는 이전 요청을 기억하지 않는다.
예를 들어 클라이언트가 로그인을 한 뒤 다시 요청을 보내더라도, 서버는 그 요청만 보고는 사용자가 로그인했는지 알 수 없다.
Request 1: 로그인
Request 2: 마이페이지 요청
HTTP 자체만 보면 Request 1과 Request 2는 서로 독립적인 요청이다.
Stateless의 장점은 서버 확장이 쉽다는 점이다.
서버가 클라이언트 상태를 저장하지 않기 때문에 요청을 여러 서버가 나누어 처리하기 쉽다.
하지만 로그인 상태처럼 상태 유지가 필요한 기능은 별도의 방법이 필요하다.
그래서 쿠키, 세션, 토큰 등을 사용한다.
Stateful
Stateful은 서버가 클라이언트의 상태를 저장하는 방식을 뜻한다.
예를 들어 서버가 사용자의 로그인 상태를 세션에 저장하면, 이후 요청에서 같은 사용자인지 확인할 수 있다.
Client ---- 로그인 요청 ----> Server
Server ---- 세션 저장 ----> Session Storage
Client ---- 세션 ID 포함 요청 ----> Server
Stateful 방식은 상태를 서버에서 관리하기 때문에 구현이 직관적이다.
하지만 서버가 상태를 가지고 있기 때문에 서버가 여러 대로 늘어났을 때 세션 공유 문제가 발생할 수 있다.
이를 해결하기 위해 Sticky Session, Session Clustering, Redis 같은 외부 저장소를 사용할 수 있다.
Stateless vs Stateful
| 구분 | Stateless | Stateful |
|---|---|---|
| 상태 저장 | 저장하지 않음 | 저장함 |
| 요청 관계 | 각 요청이 독립적 | 이전 요청의 상태를 활용 |
| 확장성 | 좋음 | 상대적으로 어려움 |
| 예시 | HTTP, REST API | 세션 기반 로그인, TCP 연결 |
| 장점 | 서버 확장 쉬움 | 상태 관리가 편함 |
| 단점 | 매 요청마다 필요한 정보를 포함해야 함 | 서버 확장 시 상태 공유 필요 |