HTTP란? 클라이언트-서버 구조부터 Stateless, 비연결성, HTTP 메시지까지

대현·2026년 9월 9일

네트워크

목록 보기
3/6
post-thumbnail

HTTP란? 클라이언트-서버 구조부터 Stateless, 비연결성, HTTP 메시지까지

현재 우리가 사용하는 대부분의 웹 서비스는 HTTP를 기반으로 통신한다.

웹 브라우저가 서버에 HTML을 요청할 때도 HTTP를 사용하고, 애플리케이션이 API를 통해 JSON을 주고받을 때도 HTTP를 사용한다. 이미지, 영상, 음성, 파일도 마찬가지다.

처음에는 단순하게 생각했다.

“HTTP는 클라이언트가 서버에 요청을 보내고 응답을 받는 규칙 아닌가?”

물론 맞는 말이다.

그런데 HTTP를 조금 더 공부하다 보니 또 다른 질문들이 생겼다.

“서버가 클라이언트의 상태를 저장하지 않는다는 것은 무슨 의미일까?”

“HTTP가 연결을 유지하지 않는다면 이미지와 CSS를 받을 때마다 다시 연결해야 하는 걸까?”

“우리가 보는 HTTP 메시지는 어떤 구조로 되어 있을까?”

이번 글에서는 HTTP의 전체 기능을 한 번에 다루기보다, HTTP를 이해하기 위한 기본 뼈대를 먼저 정리해보려고 한다.


HTTP란?

HTTP는 HyperText Transfer Protocol의 약자다.

이름만 보면 HyperText, 즉 HTML 문서를 전송하기 위한 프로토콜처럼 보인다. 실제로 초기 HTTP는 웹 문서를 전달하기 위한 단순한 형태에서 시작했다.

하지만 지금은 HTML만 전송하지 않는다.

  • HTML, Text
  • Image, Video, Audio
  • JSON, XML
  • 각종 파일
  • 그 밖에 바이트로 표현할 수 있는 데이터

HTTP 메시지는 다양한 형태의 데이터를 담아 전송할 수 있다. 그래서 웹 브라우저와 웹 서버의 통신뿐 아니라 모바일 앱, 백엔드 API, 서버 간 통신 등에서도 널리 사용된다.

물론 세상의 모든 통신이 HTTP를 사용하는 것은 아니다.

그럼에도 현대 웹 서비스에서 데이터를 주고받는 가장 기본적인 통신 방식 중 하나가 HTTP라는 점은 분명하다.

HTTP의 역사와 전송 프로토콜

HTTP는 한 번에 지금의 모습이 된 것이 아니다.

버전시기특징
HTTP/0.91991년GET만 지원한 매우 단순한 형태, HTTP Header 없음
HTTP/1.01996년Method와 Header 등이 추가됨
HTTP/1.11997년확장성, 안정성, 지속 연결 등이 개선되며 오랫동안 웹의 기본 토대가 됨
HTTP/22015년하나의 연결에서 여러 요청을 효율적으로 처리하는 Multiplexing과 Header Compression 도입
HTTP/32022년 표준화TCP 대신 UDP 기반의 QUIC을 사용해 여러 Stream을 처리

HTTP/1.1은 1997년 RFC 2068로 처음 표준화된 뒤 여러 차례 개정되었다. 현재 HTTP의 공통 의미는 RFC 9110, HTTP/1.1 메시지 문법은 RFC 9112에서 정의한다.

그런데 여기서 한 가지 주의할 점이 있다.

“HTTP/3는 그냥 UDP 위에서 바로 동작하는 걸까?”

조금 더 정확히 말하면 HTTP/3는 UDP 위에서 동작하는 QUIC을 전송 프로토콜로 사용한다.

HTTP/1.1          HTTP/2             HTTP/3
    ↓                ↓                  ↓
   TCP              TCP                QUIC
    ↓                ↓                  ↓
    IP               IP                UDP
                                         ↓
                                         IP

HTTP/1.1과 HTTP/2가 사용하는 TCP 연결에서는 연결을 맺기 위해 TCP 3-way Handshake가 필요하다.

반면 HTTP/3는 QUIC을 사용한다. QUIC은 UDP를 기반으로 신뢰성, 보안, 다중 Stream 처리 등을 제공한다.

즉 HTTP/1.1·HTTP/2 = TCP, HTTP/3 = QUIC over UDP로 이해할 수 있다.


HTTP의 특징

HTTP의 대표적인 특징은 다음과 같다.

  • 클라이언트-서버 구조
  • Stateless Protocol
  • 비연결성(Connectionless)
  • 단순한 메시지 구조
  • 확장 가능성

하나씩 살펴보자.

클라이언트-서버 구조

HTTP는 Request와 Response를 기준으로 역할을 분리한다.

클라이언트                              서버
    │                                    │
    │ ──────── HTTP Request ───────────> │
    │                                    │ 요청 처리
    │ <──────── HTTP Response ────────── │
    │                                    │

클라이언트는 서버에 요청을 보내고 응답을 기다린다.

서버는 요청을 받아 필요한 작업을 처리한 뒤 그 결과를 응답으로 보낸다.

예를 들어 사용자가 상품 페이지를 열었다고 해보자.

클라이언트: GET /products/1 요청
서버: 1번 상품 조회
서버: 상품 정보를 HTTP Response로 반환

이렇게 역할을 분리하면 클라이언트는 화면과 사용자 경험에 집중하고, 서버는 비즈니스 로직과 데이터 처리에 집중할 수 있다. 양쪽이 독립적으로 발전할 수 있다는 것도 장점이다.


Stateless: 서버가 상태를 보존하지 않는다

HTTP는 Stateless Protocol이다.

말 그대로 해석하면 서버가 클라이언트의 상태를 보존하지 않는다는 뜻이다.

그런데 이 설명만으로는 조금 추상적이다.

그래서 강의 자료에 나온 노트북 판매 예시로 Stateful과 Stateless의 차이를 정리해봤다.

Stateful: 이전 대화의 상태를 기억한다

먼저 상태를 유지하는 경우다.

고객: 이 노트북은 얼마인가요?
점원: 100만 원입니다.

고객: 2개 구매하겠습니다.
점원: 200만 원입니다. 신용카드와 현금 중 무엇으로 구매하시겠어요?

고객: 신용카드로 구매하겠습니다.
점원: 200만 원 결제 완료되었습니다.

이 대화에서 고객은 두 번째 요청부터 모든 정보를 다시 말하지 않는다.

2개 구매하겠습니다라는 말이 어떤 상품을 의미하는지, 신용카드로 구매하겠습니다라는 말이 몇 개의 상품에 대한 것인지 점원이 기억하고 있기 때문이다.

즉 점원이 이전 대화의 상태를 보존한다.

상태 1: 고객이 선택한 상품 = 노트북
상태 2: 구매 수량 = 2개
상태 3: 결제 수단 = 신용카드

그런데 중간에 점원이 바뀌면 어떻게 될까?

고객: 이 노트북은 얼마인가요?
점원 A: 100만 원입니다.

고객: 2개 구매하겠습니다.
점원 B: 무엇을 2개 구매하시겠어요?

고객: 신용카드로 구매하겠습니다.
점원 C: 어떤 상품을 몇 개 구매하시겠어요?

점원 B와 점원 C는 이전 대화를 알지 못한다.

상태를 유지하려면 같은 점원이 계속 응대하거나, 점원이 바뀔 때 이전 상태를 새 점원에게 전달해야 한다.

서버도 마찬가지다.

특정 서버가 클라이언트의 이전 요청 상태를 가지고 있다면 이후 요청도 같은 서버가 처리해야 한다. 해당 서버에 장애가 발생하면 상태를 복구하거나 다른 서버로 전달하는 과정도 필요하다.

Stateless: 요청 자체에 필요한 정보를 담는다

이번에는 상태를 유지하지 않는 경우다.

고객: 이 노트북은 얼마인가요?
점원 A: 100만 원입니다.

고객: 이 노트북 2개를 구매하겠습니다.
점원 B: 노트북 2개는 200만 원입니다. 신용카드와 현금 중 무엇으로 구매하시겠어요?

고객: 이 노트북 2개를 신용카드로 구매하겠습니다.
점원 C: 200만 원 결제 완료되었습니다.

이번에는 점원이 계속 바뀌어도 문제가 없다.

각 요청에 작업을 처리하기 위해 필요한 정보가 모두 들어 있기 때문이다.

Stateful에서는 이전 상태를 가진 서버가 필요하다. Stateless에서는 요청 자체에 필요한 정보가 있으므로 여러 서버 중 하나가 처리할 수 있다.

서버 1에 장애가 발생해도 요청에 필요한 정보가 포함되어 있다면 서버 2나 서버 3이 대신 응답할 수 있다.

새로운 서버를 추가하기도 쉽다.

그래서 Stateless 설계는 서버를 수평 확장하는 Scale-out에 유리하다.

Stateless의 단점은 없을까?

물론 단점도 있다.

서버가 이전 상태를 기억하지 않으므로 클라이언트는 각 요청에 필요한 정보를 함께 보내야 한다. Stateful 방식에서는 2개 주세요라고 보낼 수 있었지만, Stateless 방식에서는 이 노트북 2개를 구매하겠습니다처럼 더 많은 정보를 보내야 한다.

그리고 모든 기능을 완전한 Stateless로 설계할 수도 없다.

  • 단순한 서비스 소개 화면: 서버가 사용자 상태를 거의 관리하지 않아도 됨
  • 로그인한 사용자: 로그인 상태를 어딘가에서 확인해야 함
  • 장바구니와 결제: 사용자의 진행 상태를 관리해야 할 수 있음

로그인은 보통 Cookie와 Session 또는 Token 등을 이용해 처리한다. HTTP 자체는 Stateless지만 애플리케이션은 필요한 상태를 별도의 방식으로 관리할 수 있다.

중요한 것은 상태를 무조건 없애는 것이 아니다.

상태 유지는 꼭 필요한 곳에서만 사용하고, 나머지는 가능한 한 Stateless하게 설계한다.


비연결성: 연결을 계속 유지해야 할까?

이번에는 연결 관점에서 생각해보자.

만약 클라이언트와 서버가 한 번 연결된 뒤 계속 연결을 유지한다면 어떻게 될까?

클라이언트 1 ───────────── 서버
클라이언트 2 ───────────── 서버
클라이언트 3 ───────────── 서버

요청하지 않는 시간에도 연결 유지

클라이언트 2와 클라이언트 3이 아무 작업도 하지 않는 동안에도 서버는 연결을 유지해야 한다. 연결이 많아질수록 서버가 관리해야 할 자원도 늘어난다.

반대로 요청과 응답이 끝난 뒤 연결을 종료하면 필요한 순간에만 자원을 사용할 수 있다.

TCP 연결
   ↓
HTTP 요청
   ↓
HTTP 응답
   ↓
연결 종료

HTTP의 비연결성은 서버가 클라이언트와 영구적인 연결을 계속 유지하지 않아도 된다는 특성을 말한다.

웹 서비스는 보통 사용자가 계속 요청을 보내는 것이 아니라 화면을 보고 생각하는 시간이 더 길다. 1시간 동안 수천 명이 서비스를 이용하더라도 특정 순간에 서버가 실제로 처리하는 요청 수는 전체 사용자 수보다 훨씬 적을 수 있다.

필요할 때만 요청을 처리한다면 서버 자원을 효율적으로 사용할 수 있다.

그런데 매번 TCP 연결을 다시 맺으면 비효율적이지 않을까?

맞다.

TCP 연결을 새로 만들 때는 3-way Handshake가 필요하다.

그리고 브라우저가 웹 페이지 하나를 보여주기 위해 필요한 것은 HTML 하나가 아니다.

HTML
CSS
JavaScript
Image
Font
그 밖의 여러 자원

자원을 하나 받을 때마다 TCP 연결을 만들고 종료한다면 연결 비용이 반복된다.

초기 HTTP의 연결 방식을 단순화하면 다음과 같다.

연결 → HTML 요청/응답 → 종료
연결 → CSS 요청/응답  → 종료
연결 → 이미지 요청/응답 → 종료

그래서 HTTP/1.1은 Persistent Connection, 즉 지속 연결을 기본으로 사용한다.

지속 연결에서는 한 번 만든 TCP 연결로 HTML, CSS, JavaScript, Image 등의 요청과 응답을 연속해서 처리하고 필요할 때 연결을 종료한다.

하나의 TCP 연결을 여러 HTTP 요청과 응답에 재사용하므로 연결을 반복해서 만드는 비용을 줄일 수 있다.

HTTP/2는 하나의 TCP 연결 안에서 여러 요청과 응답을 동시에 처리할 수 있도록 Multiplexing을 지원한다. HTTP/3는 QUIC의 Stream을 이용해 이를 다시 개선했다.

여기서 처음의 질문으로 돌아가보자.

“HTTP는 비연결성이라면서 왜 연결을 유지하는 거지?”

비연결성은 모든 요청마다 반드시 TCP 연결을 끊어야 한다는 뜻이 아니다.

HTTP/1.1은 전송 효율을 위해 연결을 일정 시간 재사용할 수 있다. 하지만 클라이언트와 서버가 애플리케이션 사용 내내 하나의 연결에 영구적으로 묶여 있어야 하는 것은 아니다.

그리고 이것은 Stateless와도 다른 개념이다.

구분질문
Stateless서버가 이전 요청의 애플리케이션 상태를 기억해야 하는가?
Persistent Connection여러 HTTP 메시지를 같은 전송 연결로 주고받을 것인가?

같은 TCP 연결을 재사용해도 HTTP 요청의 의미는 각각 독립적으로 해석될 수 있다.


갑자기 몰리는 트래픽과 Stateless

서버 개발에서 특히 어려운 상황 중 하나는 많은 요청이 정확히 같은 시각에 몰리는 경우다.

  • 선착순 이벤트
  • 명절 기차표 예매
  • 콘서트 티켓 예매
  • 수강 신청

평소에는 충분했던 서버도 특정 시각에 요청이 폭발하면 버티지 못할 수 있다.

Stateless하게 설계하면 요청을 여러 서버에 분산하기 쉬워지고, 필요한 서버를 추가하는 Scale-out에도 유리하다.

                 ┌──────── 서버 1
클라이언트 요청 ── 로드 밸런서 ── 서버 2
                 └──────── 서버 3

다만 Stateless 설계만으로 대용량 트래픽 문제가 모두 해결되는 것은 아니다. 데이터베이스 병목, 동시성 제어, Cache, Queue, Rate Limiting 등도 함께 고려해야 한다.

그래도 여러 서버가 같은 요청을 처리할 수 있게 만드는 Stateless 설계는 확장의 중요한 출발점이다.


HTTP 메시지

지금까지 HTTP의 동작 특성을 살펴봤다.

그렇다면 실제 요청과 응답은 어떤 모습으로 전달될까?

HTTP 메시지는 요청 메시지와 응답 메시지로 나뉜다.

아래 구조는 사람이 읽을 수 있는 텍스트 형태를 사용하는 HTTP/1.1 메시지 문법을 기준으로 한다. HTTP/2와 HTTP/3는 실제 전송 과정에서 Binary Frame과 Pseudo-header를 사용하므로 전송 형태가 완전히 같지는 않다.

HTTP 요청 메시지

GET /search?q=hello&hl=ko HTTP/1.1
Host: www.google.com

HTTP 응답 메시지

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 3423

<html>
  <body>...</body>
</html>

두 메시지는 다음과 같은 공통 구조를 가진다.

요청과 응답은 Start Line의 형태가 다르지만, Header와 Empty Line, 선택적인 Message Body가 뒤따르는 기본 구조는 같다.

HTTP/1.1 메시지는 크게 다음 순서로 구성된다.

Start Line
Header
Empty Line
Message Body

Header와 Message Body 사이에는 반드시 빈 줄이 들어간다. 이 빈 줄은 Header가 끝났다는 것을 나타낸다.

Start Line

Start Line은 요청 메시지인지 응답 메시지인지에 따라 구조가 달라진다.

start-line = request-line / status-line
  • 요청 메시지의 시작 줄: Request Line
  • 응답 메시지의 시작 줄: Status Line

Request Line

GET /search?q=hello&hl=ko HTTP/1.1

Request Line은 세 부분으로 구성된다.

method SP request-target SP HTTP-version

SP는 Space, 즉 공백 한 칸을 의미한다.

위 요청을 나누면 다음과 같다.

구성 요소값의미
MethodGET서버가 수행해야 할 동작
Request Target/search?q=hello&hl=ko요청할 대상과 Query
HTTP VersionHTTP/1.1사용할 HTTP 버전

HTTP Method

HTTP Method는 서버가 요청 대상에 어떤 동작을 수행해야 하는지 알려준다.

Method대표적인 의미
GETResource 조회
POST요청 데이터 처리, 주로 등록 등에 사용
PUTResource를 대체하거나 생성
DELETEResource 삭제

Method는 단순한 문자열이 아니다. 각 Method에는 안전성, 멱등성 등의 의미가 정의되어 있다. 이 내용은 별도의 글에서 더 자세히 정리할 수 있다.

Request Target

일반적으로 Origin Server에 직접 요청할 때 Request Target에는 절대 경로와 선택적인 Query가 들어간다.

absolute-path [? query]

예:

/search?q=hello&hl=ko
  • Path: /search
  • Query: q=hello&hl=ko

경로가 비어 있다면 /를 사용한다.

참고로 Request Target에는 상황에 따라 전체 URI나 * 등이 들어가는 다른 형식도 있다. 다만 일반적인 웹 요청에서는 위와 같은 경로 형태를 가장 자주 보게 된다.

Status Line

응답 메시지의 Start Line은 Status Line이라고 한다.

HTTP/1.1 200 OK

구조는 다음과 같다.

HTTP-version SP status-code SP reason-phrase
구성 요소값의미
HTTP VersionHTTP/1.1HTTP 버전
Status Code200요청 처리 결과를 나타내는 숫자 코드
Reason PhraseOK사람이 이해하기 위한 짧은 설명

대표적인 상태 코드 범주는 다음과 같다.

  • 2xx: 요청 성공
  • 4xx: 클라이언트 요청 오류
  • 5xx: 서버 내부 오류

예를 들어 200은 요청 성공, 400은 잘못된 클라이언트 요청, 500은 서버 내부 오류를 의미한다.

OK와 같은 Reason Phrase는 HTTP/1.1에서 볼 수 있는 설명이다. HTTP/2와 HTTP/3에는 이 형태의 Reason Phrase가 없다.

HTTP Header

HTTP Header에는 메시지를 해석하는 데 필요한 부가 정보가 들어간다.

HTTP/1.1의 Header Field 문법을 단순화하면 다음과 같다.

field-name ":" OWS field-value OWS

OWS는 Optional Whitespace, 즉 허용되는 선택적 공백이다.

예:

Content-Type: text/html; charset=UTF-8
Content-Length: 3423

여기서 주의할 점이 있다.

Host: www.google.com   // 올바른 형태
Host : www.google.com  // 잘못된 형태

Field Name과 : 사이에는 공백을 넣으면 안 된다.

또한 HTTP Field Name은 대소문자를 구분하지 않는다. 따라서 의미상 Content-Type과 content-type은 같은 Field Name이다. 다만 문서와 코드에서는 일관된 표기를 사용하는 것이 읽기 좋다.

Header에는 다음과 같은 정보가 들어갈 수 있다.

  • Message Body의 Content Type
  • Message Body의 길이
  • 압축 정보
  • 인증 정보
  • 요청한 Browser 정보
  • Server 정보
  • Cache 제어 정보

표준 Header Field가 많이 정의되어 있으며, 필요한 경우 새로운 Field를 확장해 사용할 수도 있다.

HTTP Message Body

Message Body에는 실제로 전송하려는 데이터가 들어간다.

<html>
  <body>...</body>
</html>

HTML뿐 아니라 JSON, 이미지, 영상, 파일처럼 바이트로 표현할 수 있는 다양한 데이터를 담을 수 있다.

모든 메시지에 Body가 필요한 것은 아니다. GET 요청처럼 Body 없이 전달되는 메시지도 있고, POST 요청처럼 처리할 데이터를 Body에 담는 경우도 많다.


HTTP는 왜 오래 사용되고 있을까?

HTTP 메시지의 기본 구조는 생각보다 단순하다.

Start Line
Header
Empty Line
Body

그런데 이 단순한 구조 안에서 Method, Status Code, Header Field, Body를 이용해 다양한 의미와 데이터를 표현할 수 있다.

단순하면서도 확장할 수 있다는 점이 HTTP의 큰 장점이다.

처음에는 HTTP를 단순히 이렇게 생각했다.

클라이언트가 요청
↓
서버가 응답

틀린 설명은 아니다.

하지만 지금은 그 안에 들어 있는 설계 의도가 조금 더 보인다.

  • 클라이언트와 서버의 역할을 분리한다.
  • 각 요청을 독립적으로 이해할 수 있게 한다.
  • 필요한 상태만 별도로 관리한다.
  • 연결은 영구적으로 유지하지 않지만 효율을 위해 재사용할 수 있다.
  • 단순한 메시지 구조를 Header와 Body로 확장한다.

특히 이번에 가장 헷갈렸던 것은 Stateless와 비연결성, 지속 연결의 관계였다.

결국 다음처럼 정리할 수 있었다.

Stateless는 이전 요청의 애플리케이션 상태를 서버가 반드시 기억해야 하는지에 대한 문제다.

지속 연결은 여러 HTTP 메시지를 하나의 전송 연결에서 재사용할지에 대한 문제다.

둘은 비슷해 보이지만 같은 개념이 아니다.

이 차이를 이해하고 나니 HTTP가 단순히 데이터를 보내는 형식이 아니라, 수많은 클라이언트와 서버가 효율적으로 통신하기 위한 설계라는 점이 조금 더 명확해졌다.


참고 자료

profile
도전을 멈추지 않는 개발자

0개의 댓글