HTTP: 요청과 응답으로 웹 자원을 표현하고 전달하는 애플리케이션 프로토콜
HTTP(Hypertext Transfer Protocol)는 클라이언트가 자원을 요청하고 서버가 응답하는 애플리케이션 계층 프로토콜이다. 메서드, 대상 경로, 헤더, 상태 코드, 본문을 사용해 요청 의도와 처리 결과를 표현한다. HTTP/1.1, HTTP/2, HTTP/3은 같은 의미 체계를 유지하면서 연결과 전송 방식에서 차이를 보인다.
By 송민성
1. 개념
HTTP(Hypertext Transfer Protocol)는 웹에서 자원의 표현(Representation)을 주고받기 위한 요청·응답(Request-Response) 프로토콜이다.
클라이언트는 HTTP 요청(Request)을 보내고 서버는 HTTP 응답(Response)을 반환한다.
클라이언트 서버
│ │
│ GET /articles/42 HTTP/1.1 │
│──────────────────────────────>│
│ │
│ HTTP/1.1 200 OK │
│ Content-Type: application/json
│ {"id":42,"title":"HTTP"} │
│<──────────────────────────────│HTTP 메시지는 다음 요소로 구성된다.
- 시작 줄(Start Line): 메서드와 요청 대상 또는 상태 코드
- 헤더(Header): 메시지와 자원에 관한 부가 정보
- 빈 줄: 헤더와 본문의 경계
- 본문(Body): 전송할 실제 데이터
HTTP 자체는 무상태(Stateless) 프로토콜이다. 서버는 각 요청을 독립적으로 처리하며, 이전 요청의 상태를 자동으로 기억하지 않는다. 로그인 상태처럼 여러 요청에 걸친 정보는 쿠키(Cookie), 세션(Session), 토큰(Token) 등으로 별도 관리한다.
HTTP는 애플리케이션 계층(Application Layer) 프로토콜이다. 하위 전송 방식은 버전에 따라 다르다.
- HTTP/1.1: TCP 사용
- HTTP/2: 하나의 TCP 연결에서 여러 스트림 다중화
- HTTP/3: UDP 기반 QUIC 사용
HTTPS는 별도 의미 체계가 아니라 TLS(Transport Layer Security)로 HTTP 통신을 암호화한 방식이다.
2. 왜 사용하는가
HTTP는 클라이언트와 서버가 구현 언어나 운영체제에 관계없이 공통 형식으로 통신하게 한다.
HTTP가 제공하는 핵심 기능은 다음과 같다.
- 자원 조회, 생성, 수정, 삭제 의도 표현
- 처리 결과를 상태 코드로 전달
- 데이터 형식을 미디어 타입(Media Type)으로 명시
- 캐시를 이용한 네트워크 요청 감소
- 인증과 권한 정보 전달
- 프록시, 게이트웨이, 콘텐츠 전송 네트워크(CDN) 연동
- 콘텐츠 협상(Content Negotiation)을 통한 표현 선택
HTTP는 자원과 자원의 표현을 구분한다. /articles/42는 자원을 식별하는 주소이고, JSON이나 HTML은 해당 자원의 표현이다.
자원: /articles/42
가능한 표현:
- application/json
- text/html
- application/xml클라이언트는 Accept 헤더로 원하는 형식을 전달할 수 있고, 서버는 Content-Type 헤더로 실제 응답 형식을 알린다.
3. 동작 원리
요청 메시지
HTTP 요청의 시작 줄에는 메서드(Method), 요청 대상(Request Target), HTTP 버전이 들어간다.
POST /articles HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 36
{"title":"HTTP","published":true}주요 메서드는 다음과 같다.
| 메서드 | 의미 | 안전성 | 멱등성 |
|---|---|---|---|
| GET | 자원 조회 | 안전 | 멱등 |
| HEAD | 본문 없이 응답 헤더 조회 | 안전 | 멱등 |
| POST | 데이터 처리 또는 자원 생성 | 안전하지 않음 | 멱등하지 않음 |
| PUT | 대상 자원 전체 생성 또는 교체 | 안전하지 않음 | 멱등 |
| PATCH | 대상 자원 일부 수정 | 안전하지 않음 | 보장되지 않음 |
| DELETE | 대상 자원 삭제 | 안전하지 않음 | 멱등 |
| OPTIONS | 지원 기능과 통신 옵션 조회 | 안전 | 멱등 |
안전(Safe)은 요청이 서버 상태 변경을 목적으로 하지 않는다는 뜻이다. 로깅이나 통계 기록 같은 부수 효과까지 금지한다는 의미는 아니다.
멱등성(Idempotency)은 동일한 요청을 한 번 보내거나 여러 번 보내도 서버의 의도된 최종 상태가 같다는 성질이다. 응답 상태 코드나 부수 효과까지 항상 같다는 뜻은 아니다.
응답 메시지
응답 시작 줄에는 HTTP 버전, 상태 코드(Status Code), 이유 문구(Reason Phrase)가 들어간다. 이유 문구는 HTTP/2와 HTTP/3에 존재하지 않는다.
HTTP/1.1 201 Created
Content-Type: application/json
Location: /articles/42
Content-Length: 37
{"id":42,"title":"HTTP","published":true}상태 코드는 처리 결과를 다섯 범주로 표현한다.
1xx: 요청 처리 중인 정보2xx: 요청 성공3xx: 리다이렉션 또는 캐시 재검증4xx: 클라이언트 요청 문제5xx: 서버 처리 문제
자주 사용하는 상태 코드는 다음과 같다.
| 상태 코드 | 의미 |
|---|---|
| 200 OK | 요청 성공 |
| 201 Created | 자원 생성 성공 |
| 204 No Content | 본문 없는 성공 |
| 301 Moved Permanently | 영구 이동 |
| 304 Not Modified | 캐시된 표현 재사용 가능 |
| 400 Bad Request | 요청 형식이나 값이 잘못됨 |
| 401 Unauthorized | 인증 정보가 없거나 유효하지 않음 |
| 403 Forbidden | 인증됐지만 접근 권한이 없음 |
| 404 Not Found | 대상 자원을 찾을 수 없음 |
| 409 Conflict | 현재 자원 상태와 요청이 충돌함 |
| 429 Too Many Requests | 요청 횟수 제한 초과 |
| 500 Internal Server Error | 서버 내부 오류 |
| 502 Bad Gateway | 상위 서버에서 잘못된 응답을 받음 |
| 503 Service Unavailable | 서버가 일시적으로 요청을 처리할 수 없음 |
헤더
HTTP 헤더는 요청과 응답의 조건, 본문 형식, 캐시 정책 같은 메타데이터를 전달한다.
Content-Type: 본문의 미디어 타입
Content-Length: 본문 크기
Accept: 클라이언트가 처리할 수 있는 미디어 타입
Authorization: 인증 정보
Cache-Control: 캐시 정책
Cookie: 클라이언트가 저장한 쿠키
Set-Cookie: 서버가 저장하도록 지시하는 쿠키
Location: 생성된 자원이나 이동할 주소
ETag: 특정 표현 버전의 식별자
Vary: 응답 선택에 영향을 준 요청 헤더Content-Type은 보내는 본문의 형식을 나타내고, Accept는 받고 싶은 응답 형식을 나타낸다.
연결과 전송
HTTP/1.1은 지속 연결(Persistent Connection)을 기본으로 사용한다. 여러 요청이 하나의 TCP 연결을 재사용할 수 있지만, 일반적인 요청 처리는 순서 제약을 받는다. 브라우저는 이를 완화하려고 출처마다 여러 TCP 연결을 만들 수 있다.
HTTP/2는 하나의 TCP 연결 안에 여러 논리 스트림(Stream)을 만들고 요청과 응답을 다중화(Multiplexing)한다. 헤더는 HPACK 방식으로 압축한다. 하나의 TCP 패킷이 손실되면 같은 연결의 모든 스트림 전달이 잠시 지연되는 TCP 계층의 헤드 오브 라인 블로킹(Head-of-Line Blocking)은 남는다.
HTTP/3은 QUIC 연결 안에서 스트림별로 데이터를 전달한다. 한 스트림의 패킷 손실이 다른 스트림의 데이터 전달을 직접 막지 않는다. 헤더 압축에는 QPACK을 사용한다.
HTTP/1.1
TCP 연결 1: 요청 A → 응답 A → 요청 B → 응답 B
HTTP/2
TCP 연결 1: 스트림 A ─┐
스트림 B ─┼─ 다중화
스트림 C ─┘
HTTP/3
QUIC 연결 1: 독립적인 여러 스트림캐시 재검증
서버는 응답 표현에 엔터티 태그(Entity Tag, ETag)를 붙일 수 있다.
HTTP/1.1 200 OK
ETag: "article-42-v3"
Cache-Control: max-age=60
Content-Type: application/json캐시가 만료되면 클라이언트는 저장된 ETag를 If-None-Match 헤더로 보낸다.
GET /articles/42 HTTP/1.1
If-None-Match: "article-42-v3"표현이 바뀌지 않았다면 서버는 본문 없이 304 Not Modified를 반환한다. 클라이언트는 기존 본문을 재사용한다.
4. 코드 예제
다음 서버는 자원 조회, JSON 생성, 메서드 제한, 조건부 요청을 처리한다. Node.js 18 이상과 tsx가 필요하다.
import { createServer } from "node:http";
import { createHash } from "node:crypto";
type Article = {
id: number;
title: string;
};
const articles: Article[] = [
{ id: 1, title: "HTTP 요청과 응답" },
];
function sendJson(
response: import("node:http").ServerResponse,
statusCode: number,
body: unknown,
extraHeaders: Record<string, string> = {},
): void {
const payload = JSON.stringify(body);
response.writeHead(statusCode, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": Buffer.byteLength(payload).toString(),
...extraHeaders,
});
response.end(payload);
}
const server = createServer((request, response) => {
const url = new URL(request.url ?? "/", "http://localhost:3000");
if (url.pathname === "/articles" && request.method === "GET") {
const payload = JSON.stringify(articles);
const etag = `"${createHash("sha256").update(payload).digest("hex")}"`;
if (request.headers["if-none-match"] === etag) {
response.writeHead(304, {
ETag: etag,
"Cache-Control": "public, max-age=30",
});
response.end();
return;
}
sendJson(response, 200, articles, {
ETag: etag,
"Cache-Control": "public, max-age=30",
});
return;
}
if (url.pathname === "/articles" && request.method === "POST") {
let rawBody = "";
request.setEncoding("utf8");
request.on("data", (chunk: string) => {
rawBody += chunk;
if (Buffer.byteLength(rawBody) > 10_000) {
request.destroy();
}
});
request.on("end", () => {
try {
const input: unknown = JSON.parse(rawBody);
if (
typeof input !== "object" ||
input === null ||
!("title" in input) ||
typeof input.title !== "string" ||
input.title.trim() === ""
) {
sendJson(response, 400, {
error: "title은 비어 있지 않은 문자열이어야 합니다.",
});
return;
}
const article: Article = {
id: articles.length + 1,
title: input.title.trim(),
};
articles.push(article);
sendJson(response, 201, article, {
Location: `/articles/${article.id}`,
});
} catch {
sendJson(response, 400, {
error: "올바른 JSON 본문이 필요합니다.",
});
}
});
return;
}
if (url.pathname === "/articles") {
response.writeHead(405, {
Allow: "GET, POST",
"Content-Length": "0",
});
response.end();
return;
}
sendJson(response, 404, {
error: "자원을 찾을 수 없습니다.",
});
});
server.listen(3000, () => {
console.log("서버 실행: http://localhost:3000");
});서버를 실행한다.
npx tsx server.ts다른 터미널에서 요청을 보낸다.
curl -i http://localhost:3000/articlescurl -i \
-X POST \
-H 'Content-Type: application/json' \
-d '{"title":"HTTP 캐시"}' \
http://localhost:3000/articles조건부 요청은 첫 응답의 ETag 값을 사용한다.
curl -i \
-H 'If-None-Match: "첫 번째 응답에서 받은 ETag 값"' \
http://localhost:3000/articles본문이 변경되지 않았다면 서버는 304 Not Modified를 반환한다.
5. 시간 복잡도 또는 성능 특성
HTTP 처리 시간은 알고리즘의 시간 복잡도보다 네트워크 왕복 시간(Round-Trip Time, RTT), 연결 수립, 본문 크기, 서버 처리 시간의 영향을 크게 받는다.
새로운 HTTPS 연결에서 첫 요청을 보내려면 전송 계층과 보안 연결을 먼저 준비해야 한다.
대략적인 응답 지연
= 연결 수립 시간
+ TLS 협상 시간
+ 요청 전송 시간
+ 서버 처리 시간
+ 응답 전송 시간정확한 왕복 횟수는 HTTP 버전, TLS 버전, QUIC 설정, 세션 재개 여부에 따라 달라진다. 연결을 재사용하면 연결 수립과 TLS 협상 비용을 후속 요청에서 줄일 수 있다.
주요 성능 특성은 다음과 같다.
- 지속 연결: 연결 수립과 TLS 협상 반복 감소
- 다중화: 하나의 연결에서 여러 요청을 동시에 처리
- 캐시: 서버 요청과 응답 본문 전송 자체를 줄임
- 압축: 전송 바이트 감소와 압축·해제 CPU 비용 사이의 교환
- 스트리밍: 전체 본문 생성 완료 전부터 데이터 전달 가능
- 콘텐츠 전송 네트워크(CDN): 사용자와 가까운 위치에서 캐시 응답 제공
압축 여부는 데이터 종류에 따라 결정해야 한다. JSON, HTML, CSS, JavaScript 같은 텍스트는 압축 효과가 크다. JPEG, PNG, MP4처럼 이미 압축된 형식은 추가 압축 효과가 작고 CPU 비용만 늘 수 있다.
HTTP/2 다중화가 애플리케이션 요청 수를 줄여 주지는 않는다. 요청마다 헤더 처리, 권한 확인, 데이터베이스 조회 같은 서버 비용은 그대로 발생한다.
6. 실무 사용 사례
REST 형식 API
자원을 URL로 식별하고 HTTP 메서드로 작업 의도를 표현한다.
GET /articles 목록 조회
GET /articles/42 단일 자원 조회
POST /articles 자원 생성
PUT /articles/42 자원 전체 교체
PATCH /articles/42 자원 일부 수정
DELETE /articles/42 자원 삭제상태 코드는 클라이언트가 처리 결과를 일관되게 판단하도록 선택한다. 모든 결과를 200 OK로 반환하고 본문 필드로 성공 여부를 구분하면 프록시, 모니터링 도구, HTTP 클라이언트가 표준 의미를 활용하기 어렵다.
정적 자원 캐싱
파일 이름에 콘텐츠 해시를 포함하면 긴 캐시 기간을 안전하게 적용할 수 있다.
/app.a84f21c.js
/styles.23bd941.cssCache-Control: public, max-age=31536000, immutable파일 내용이 바뀌면 URL도 바뀌므로 이전 캐시와 충돌하지 않는다. HTML 문서에는 더 짧은 캐시 기간이나 재검증 정책을 적용해 최신 해시가 포함된 자원 주소를 받게 한다.
인증
브라우저 세션은 쿠키를 사용할 수 있다.
Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=LaxAPI 클라이언트는 Authorization 헤더로 자격 증명을 전달할 수 있다.
Authorization: Bearer eyJhbGciOi...401 Unauthorized는 인증이 필요하거나 실패한 경우에 사용한다. 인증은 성공했지만 권한이 부족하면 403 Forbidden을 사용한다.
리버스 프록시와 게이트웨이
리버스 프록시(Reverse Proxy)는 클라이언트 요청을 받아 내부 서버로 전달한다. TLS 종료, 압축, 캐시, 접근 로그, 요청 제한, 부하 분산을 담당할 수 있다.
원래 요청 정보를 전달할 때는 Forwarded 또는 X-Forwarded-* 계열 헤더가 사용된다. 애플리케이션은 신뢰하도록 설정한 프록시가 추가한 값만 사용해야 한다.
서버 전송 이벤트
서버 전송 이벤트(Server-Sent Events, SSE)는 하나의 HTTP 응답을 유지하며 서버가 텍스트 이벤트를 연속 전송하는 방식이다.
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive알림, 작업 진행률, 실시간 로그처럼 서버에서 클라이언트로 단방향 갱신을 보낼 때 사용할 수 있다.
7. 주의할 점
GET 요청으로 상태를 변경하지 않는다
GET은 안전한 메서드로 취급된다. 브라우저 사전 로드, 검색 엔진 크롤러, 캐시가 사용자의 명시적 실행 없이 요청할 수 있다.
잘못된 예:
GET /users/42/delete
권장 예:
DELETE /users/42멱등성과 재시도를 함께 설계한다
네트워크 연결이 끊기면 클라이언트는 서버가 요청을 처리했는지 알 수 없을 수 있다. POST 결제 요청을 그대로 재시도하면 중복 결제가 발생할 수 있다.
클라이언트가 멱등성 키(Idempotency Key)를 보내고 서버가 처리 결과를 저장하면 동일 작업의 중복 실행을 막을 수 있다.
Idempotency-Key: payment-20260923-8f21요청 본문 크기를 제한한다
Content-Length만 신뢰해서는 안 된다. 청크 전송(Chunked Transfer)이나 잘못된 헤더가 사용될 수 있으므로 실제 읽은 바이트 수도 제한해야 한다. 대용량 업로드는 스트리밍하고 시간 제한을 적용한다.
캐시 키를 정확히 구성한다
인증된 사용자별 응답을 공용 캐시에 저장하면 다른 사용자에게 정보가 노출될 수 있다.
Cache-Control: private, no-store응답이 Accept-Encoding, Accept-Language 같은 요청 헤더에 따라 달라지면 Vary 헤더로 캐시 키에 반영해야 한다.
Vary: Accept-Encoding, Accept-LanguageContent-Type을 검증한다
JSON API는 요청의 Content-Type을 확인하고 지원하지 않는 형식에는 415 Unsupported Media Type을 반환할 수 있다. 응답에도 정확한 Content-Type과 문자 인코딩을 지정한다.
타임아웃을 설정한다
연결, 응답 대기, 본문 읽기에 제한이 없으면 느리거나 응답하지 않는 상대가 서버 자원을 오래 점유할 수 있다. 클라이언트와 서버 양쪽에 목적별 타임아웃을 둔다.
헤더 이름의 대소문자에 의존하지 않는다
HTTP 헤더 필드 이름은 대소문자를 구분하지 않는다.
Content-Type
content-type
CONTENT-TYPE애플리케이션은 특정 표기만 가정하지 않아야 한다. HTTP/2와 HTTP/3에서는 헤더 필드 이름을 소문자로 전송한다.
HTTP와 HTTPS를 혼동하지 않는다
HTTP의 메서드와 상태 코드는 통신 의미를 정의한다. TLS는 기밀성, 무결성, 서버 인증을 제공한다. 민감한 정보가 오가는 운영 환경에서는 HTTPS를 사용하고 인증서 검증을 비활성화하지 않는다.
8. 핵심 정리
- HTTP는 클라이언트 요청과 서버 응답으로 자원의 표현을 전달하는 무상태 애플리케이션 계층 프로토콜이다.
- 요청은 메서드, 대상, 헤더, 본문으로 구성되고 응답은 상태 코드, 헤더, 본문으로 구성된다.
- 안전성과 멱등성은 캐시, 자동 재시도, 장애 복구 방식을 결정하는 핵심 속성이다.
- HTTP/1.1, HTTP/2, HTTP/3은 의미 체계를 공유하지만 연결, 다중화, 헤더 압축, 손실 처리 방식이 다르다.
- 지속 연결, 캐시, 압축, 스트리밍, 콘텐츠 전송 네트워크를 사용하면 지연과 전송량을 줄일 수 있다.
- 상태 코드, 캐시 정책, 본문 크기 제한, 타임아웃, 인증 정보 처리를 프로토콜 의미에 맞게 설계해야 한다.