웹 개발을 하다 보면 매일 HTTP 통신을 사용하지만, 정작 그 내부 동작 원리를 제대로 이해하는 경우는 드뭅니다. “GET으로 데이터를 가져오고, POST로 데이터를 보낸다” 정도만 알고 넘어가는 경우가 많죠.
하지만 HTTP를 깊이 있게 이해하면 API 설계, 성능 최적화, 보안 강화에서 훨씬 더 나은 결정을 내릴 수 있습니다. 이 글에서는 HTTP의 탄생 배경부터 실무에서 꼭 알아야 할 핵심 개념까지 체계적으로 정리해보겠습니다.
HTTP는 왜 탄생했을까?
문서 링크의 혁명
1989년, CERN(유럽 입자물리학 연구소)의 Tim Berners-Lee는 한 가지 문제에 직면했습니다. 과학자들이 작성한 논문과 연구 자료를 공유할 때, 참고 문헌이나 관련 문서를 찾아가는 것이 너무 불편했던 것입니다.
그는 하이퍼텍스트(Hypertext) 개념을 떠올렸습니다. 문서 안에 다른 문서로 연결되는 링크를 넣으면 어떨까? 이것이 바로 우리가 지금 사용하는 <a> 태그의 시작입니다.
이 아이디어를 실현하기 위해 필요했던 것이 바로:
- HTTP (HyperText Transfer Protocol) - 문서를 전송하는 규약
- HTML (HyperText Markup Language) - 링크가 포함된 문서 형식
- URL (Uniform Resource Locator) - 문서의 위치를 나타내는 주소
HTTP의 진화
초기 HTTP는 단순히 HTML 문서 하나를 요청하고 받는 수준이었습니다. 하지만 웹이 발전하면서 HTTP도 함께 진화했습니다:
HTTP/0.9 (1991) → 단순 문서 전송
HTTP/1.0 (1996) → 헤더 추가, 상태 코드 도입
HTTP/1.1 (1997) → 지속 연결, 파이프라이닝
HTTP/2 (2015) → 멀티플렉싱, 헤더 압축
HTTP/3 (2022) → QUIC 프로토콜, UDP 기반
현재의 HTTP는 단순한 문서 전송을 넘어 실시간 통신, 영상 스트리밍, REST API 등 다양한 용도로 활용됩니다.
HTTP 기본 개념과 작동 원리
TCP 위에서 동작하는 HTTP
HTTP는 홀로 동작하지 않습니다. 네트워크 계층 구조에서 TCP 프로토콜 위에 올라가 동작합니다.
HTTP/HTTPS] -->|사용| B[전송 계층
TCP] B -->|사용| C[인터넷 계층
IP] C -->|사용| D[네트워크 인터페이스 계층
Ethernet/WiFi] style A fill:#2d4a5e,stroke:#4a90a4,stroke-width:2px,color:#e8f4f8 style B fill:#2d3e50,stroke:#4a7c8a,stroke-width:2px,color:#e8f4f8 style C fill:#2d3545,stroke:#4a6870,stroke-width:2px,color:#e8f4f8 style D fill:#2d2f3a,stroke:#4a5460,stroke-width:2px,color:#e8f4f8
HTTP 통신이 시작되기 전에 TCP 3-Way 핸드셰이크가 먼저 발생합니다:
이 과정을 통해 클라이언트와 서버는 “통신 준비 완료”를 서로 확인합니다.
참고: MDN - HTTP 개요
HTTP는 무상태(Stateless) 프로토콜
HTTP의 가장 중요한 특징 중 하나는 무상태성입니다. 서버는 이전 요청을 기억하지 않습니다.
[상태를 유지하는 경우]
1번째 요청: "안녕, 나는 홍길동이야"
서버: "아, 홍길동이구나. 기억할게!" ✅
2번째 요청: "내 정보 보여줘"
서버: "너는 홍길동이니까 네 정보를 보여줄게" ✅
---
[무상태인 경우 - HTTP]
1번째 요청: "안녕, 나는 홍길동이야"
서버: "알겠어" → (응답 후 홍길동을 잊음) ❌
2번째 요청: "내 정보 보여줘"
서버: "너 누구야? 다시 말해봐" ❌
매번 “너 누구니?”라고 물어야 하는 번거로움이 생깁니다. 이 문제를 해결하는 것이 바로 세션과 쿠키입니다.
HTTP 메서드의 모든 것
주요 HTTP 메서드
HTTP는 클라이언트가 어떤 작업을 하려는지 알려주기 위해 메서드를 사용합니다.
| 메서드 | 용도 | CRUD 매핑 |
|---|---|---|
| GET | 리소스 조회 | Read |
| POST | 리소스 생성, 데이터 처리 | Create |
| PUT | 리소스 전체 수정 | Update |
| PATCH | 리소스 부분 수정 | Update |
| DELETE | 리소스 삭제 | Delete |
| OPTIONS | 지원하는 메서드 조회 | - |
| HEAD | GET과 동일하지만 바디 없이 헤더만 | - |
POST vs PUT - 멱등성의 차이
실무에서 가장 혼란스러운 부분이 POST와 PUT의 차이입니다. 핵심은 멱등성(Idempotent)입니다.
멱등성이란? 같은 요청을 여러 번 보내도 결과가 동일한 성질
POST (멱등성 ❌)
- 같은 요청을 3번 보내면 리소스가 3개 생성됨
예시: 계좌이체 1만원을 3번 요청
결과: 3만원 이체 ❌
---
PUT (멱등성 ✅)
- 같은 요청을 3번 보내도 최종 상태는 동일
예시: "잔액을 70만원으로 변경"을 3번 요청
결과: 잔액은 70만원 (동일) ✅
실무 적용:
- 결제, 이체 같은 중요한 작업 → 멱등성 필수 (PUT, PATCH 고려 또는 중복 방지 로직 추가)
- 댓글 작성, SNS 팔로우 → 멱등성 고려 (중복 클릭 방지)
- 단순 조회 → 멱등성 자동 보장 (GET)
HTTP 헤더와 바디
헤더는 메타데이터, 바디는 실제 데이터
HTTP 메시지는 크게 헤더와 바디 두 부분으로 구성됩니다.
[개념]
헤더 = 편지 봉투에 적힌 정보 (보내는 사람, 받는 사람, 우편번호)
바디 = 편지 안에 담긴 실제 내용
자주 사용하는 HTTP 헤더
1. Content-Type - 바디의 데이터 형식 지정
Content-Type: application/json
Content-Type: text/html
Content-Type: image/png
Content-Type: multipart/form-data
2. Authorization - 인증 정보 전달
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
3. Cookie - 클라이언트가 저장한 쿠키 전송
Cookie: sessionId=abc123; userId=12345
multipart/form-data의 비밀
파일 업로드 시 왜 application/json이 아니라 multipart/form-data를 사용할까요?
JSON (텍스트 기반)
이미지 바이너리 → Base64 인코딩 → 용량 33% 증가 ❌
→ 1MB 파일이 1.33MB로 증가
→ 인코딩/디코딩 시간 추가 소요
multipart/form-data (바이너리 그대로)
이미지 바이너리 → 그대로 전송 → 용량 최소화 ✅
→ 1MB 파일은 1MB 그대로
→ 추가 처리 없음
결론: 바이너리 데이터는 multipart/form-data로 전송하는 것이 효율적입니다.
HTTP 요청/응답 구조
HTTP 요청 메시지
GET /api/users/123 HTTP/1.1 ← 요청 라인
Host: example.com ← 헤더 시작
Content-Type: application/json
Authorization: Bearer abc123
Cookie: sessionId=xyz789
← 빈 줄 (구분자)
{"name": "홍길동", "age": 30} ← 바디
HTTP 응답 메시지
HTTP/1.1 200 OK ← 상태 라인
Content-Type: application/json ← 헤더 시작
Set-Cookie: sessionId=xyz789
Content-Length: 45
← 빈 줄 (구분자)
{"id": 123, "name": "홍길동"} ← 바디
핵심: 헤더와 바디는 빈 줄로 구분됩니다.
응답: HTTP/1.1 200 OK] C --> G[Host: example.com
Content-Type: application/json
Authorization: Bearer token] D --> H[헤더와 바디 구분자] E --> I[실제 데이터
JSON, HTML, 이미지 등] style A fill:#2d4a5e,stroke:#4a90a4,stroke-width:2px,color:#e8f4f8 style B fill:#2d3e50,stroke:#4a7c8a,stroke-width:2px,color:#e8f4f8 style C fill:#2d3e50,stroke:#4a7c8a,stroke-width:2px,color:#e8f4f8 style D fill:#4a3a2a,stroke:#8a6a4a,stroke-width:2px,color:#e8f4f8 style E fill:#2d3e50,stroke:#4a7c8a,stroke-width:2px,color:#e8f4f8
HTTP 상태 코드 완벽 정리
상태 코드 분류
HTTP 응답은 상태 코드로 결과를 알려줍니다.
| 범위 | 의미 | 예시 |
|---|---|---|
| 1xx | 정보 (Informational) | 100 Continue |
| 2xx | 성공 (Success) | 200 OK, 201 Created |
| 3xx | 리다이렉션 (Redirection) | 301 Moved Permanently |
| 4xx | 클라이언트 오류 (Client Error) | 400 Bad Request, 404 Not Found |
| 5xx | 서버 오류 (Server Error) | 500 Internal Server Error |
참고: MDN - HTTP 상태 코드
자주 보는 상태 코드
| 상태 코드 | 의미 | 설명 |
|---|---|---|
| 2xx - 성공 | ||
200 OK |
요청 성공 | 요청이 정상적으로 처리됨 |
201 Created |
리소스 생성 성공 | 새로운 리소스가 생성됨 |
204 No Content |
성공, 반환 내용 없음 | 요청은 성공했지만 응답 바디가 없음 |
| 3xx - 리다이렉션 | ||
301 Moved Permanently |
영구 이동 | 리소스가 영구적으로 이동됨 (SEO에 영향) |
302 Found |
임시 이동 | 리소스가 일시적으로 다른 위치에 있음 |
304 Not Modified |
수정되지 않음 | 캐시된 리소스를 그대로 사용 가능 |
| 4xx - 클라이언트 오류 | ||
400 Bad Request |
잘못된 요청 | 요청 구문이 잘못됨 |
401 Unauthorized |
인증 필요 | 인증되지 않은 사용자 |
403 Forbidden |
권한 없음 | 접근 권한이 없음 |
404 Not Found |
리소스 없음 | 요청한 리소스를 찾을 수 없음 |
| 5xx - 서버 오류 | ||
500 Internal Server Error |
서버 내부 오류 | 서버에서 예상치 못한 오류 발생 |
502 Bad Gateway |
게이트웨이 오류 | 게이트웨이나 프록시 서버가 잘못된 응답 수신 |
503 Service Unavailable |
서비스 이용 불가 | 서버가 일시적으로 요청을 처리할 수 없음 |
401 vs 403 - 헷갈리는 차이
많은 개발자가 헷갈려 하는 두 상태 코드의 차이:
401 Unauthorized = "너 누구야? 로그인 먼저 해"
└─ 인증(Authentication) 필요
└─ 예시: 로그인 안 한 상태에서 마이페이지 접근
403 Forbidden = "너 누군지 아는데, 권한이 없어"
└─ 인가(Authorization) 실패
└─ 예시: 일반 사용자가 관리자 페이지 접근
쿠키와 세션으로 상태 유지하기
쿠키 vs 세션
HTTP는 무상태 프로토콜이지만, 쿠키와 세션으로 상태를 유지할 수 있습니다.
쿠키 (Cookie)
└─ 클라이언트의 브라우저에 저장
└─ 클라이언트가 직접 접근 가능
└─ 보안에 취약할 수 있음
세션 (Session)
└─ 서버의 메모리/DB에 저장
└─ 클라이언트는 세션ID만 보유
└─ 상대적으로 안전
참고: MDN - HTTP 쿠키
로그인 세션 동작 방식
(브라우저) participant S as 서버 C->>S: ① POST /login
(아이디, 비밀번호) S->>S: ② 인증 성공
세션 생성 (ID: abc123) S->>C: ③ Set-Cookie: sessionId=abc123 C->>C: ④ 쿠키 저장 Note over C,S: 다음 요청부터 자동으로 쿠키 전송 C->>S: ⑤ GET /mypage
Cookie: sessionId=abc123 S->>S: ⑥ 세션 확인
(abc123 = 홍길동) S->>C: ⑦ 200 OK
(홍길동의 데이터)
세션 하이재킹 대응 방법
만약 해커가 세션ID를 탈취하면 그 사용자인 척 할 수 있습니다. 이를 방지하는 방법:
1. HTTPS 사용 ✅
HTTP (평문) → 세션ID가 그대로 노출 ❌
HTTPS (암호화) → 세션ID가 암호화되어 전송 ✅
2. HttpOnly 쿠키 속성 ✅
// JavaScript로 쿠키 접근 차단
Set-Cookie: sessionId=abc123; HttpOnly
→ XSS 공격으로 쿠키를 탈취할 수 없음
3. Secure 쿠키 속성 ✅
Set-Cookie: sessionId=abc123; Secure
→ HTTPS 연결에서만 쿠키 전송
4. 세션 타임아웃 ✅
일정 시간(예: 30분) 동안 활동이 없으면 세션 만료
→ 탈취된 세션도 곧 무효화됨
HTTPS - 안전한 HTTP 통신
HTTP vs HTTPS
HTTP (HyperText Transfer Protocol)
└─ 평문 전송
└─ 데이터가 그대로 노출 ❌
└─ 중간에서 가로채기 가능
HTTPS (HTTP Secure)
└─ 암호화 전송 (TLS/SSL)
└─ 데이터 보호 ✅
└─ 서버 신원 확인 ✅
참고: MDN - HTTPS
HTTPS의 두 가지 역할
1. 암호화 - 데이터를 안전하게 전송
평문: "비밀번호는 1234입니다"
암호화: "xK9#mP2@qL7$nR4..."
2. 신원 확인 - 이 서버가 진짜인지 확인
가짜 피싱 사이트를 걸러내는 역할
인증서 체인의 비밀
HTTPS는 인증서 체인(Certificate Chain)으로 서버의 신원을 보증합니다.
최상위 인증 기관] -->|서명| B[Intermediate CA
중간 인증 기관] B -->|서명| C[서버 인증서
example.com] D[브라우저] -.신뢰.-> A D -->|검증| C C -->|체인 확인| B B -->|체인 확인| A E[결과: 안전한 사이트 🔒] style A fill:#2d4a3a,stroke:#4a8a6a,stroke-width:3px,color:#e8f4f8 style B fill:#2d3e4a,stroke:#4a7c8a,stroke-width:2px,color:#e8f4f8 style C fill:#2d3545,stroke:#4a6870,stroke-width:2px,color:#e8f4f8 style D fill:#3d3a4a,stroke:#6a6a8a,stroke-width:2px,color:#e8f4f8 style E fill:#2a4a3a,stroke:#4aaa6a,stroke-width:2px,color:#e8f4f8
검증 과정:
1. 사용자가 https://example.com 접속
2. 서버가 인증서 전송
"나는 example.com이고, 이 인증서로 증명합니다"
3. 브라우저 검증
① 서버 인증서의 서명을 Intermediate CA로 검증
② Intermediate CA의 서명을 Root CA로 검증
③ Root CA는 브라우저가 이미 신뢰함 ✅
4. 검증 성공 → 자물쇠 아이콘 표시 🔒
가짜 인증서는 왜 통하지 않을까?
해커가 가짜 인증서를 만들어도 Root CA의 디지털 서명을 위조할 수 없습니다.
디지털 서명 원리:
- Root CA는 비밀키로 서명
- 브라우저는 공개키로 검증
- 해커는 Root CA의 비밀키를 모름
- 따라서 정상적인 서명을 만들 수 없음
결과:
브라우저가 "이 사이트는 안전하지 않습니다" 경고 표시 🚨
실무 적용 팁
1. API 설계 시 멱등성 고려하기
// ❌ 나쁜 예: POST로 수정
POST /api/users/123/update
{
"name": "홍길동"
}
// ✅ 좋은 예: PUT 또는 PATCH 사용
PUT /api/users/123
{
"name": "홍길동"
}
2. 적절한 상태 코드 사용하기
// ❌ 나쁜 예: 모든 응답에 200 사용
res.status(200).json({ error: "사용자를 찾을 수 없음" });
// ✅ 좋은 예: 의미 있는 상태 코드
res.status(404).json({ error: "사용자를 찾을 수 없음" });
3. 보안 헤더 설정하기
// Express.js 예시
app.use((req, res, next) => {
// HTTPS만 허용
res.setHeader('Strict-Transport-Security', 'max-age=31536000');
// XSS 방지
res.setHeader('X-Content-Type-Options', 'nosniff');
// 클릭재킹 방지
res.setHeader('X-Frame-Options', 'DENY');
next();
});
4. 쿠키 보안 강화하기
// ✅ 안전한 쿠키 설정
res.cookie('sessionId', 'abc123', {
httpOnly: true, // JavaScript 접근 차단
secure: true, // HTTPS에서만 전송
sameSite: 'strict', // CSRF 공격 방지
maxAge: 1800000 // 30분 후 만료
});
마무리
HTTP는 단순해 보이지만 그 안에는 웹의 핵심 원리가 모두 담겨 있습니다. 이 글에서 다룬 내용을 정리하면:
✅ HTTP는 하이퍼텍스트 링크를 위해 탄생했고, 계속 진화하고 있다
✅ TCP 3-Way 핸드셰이크 후 HTTP 통신이 시작된다
✅ HTTP는 무상태 프로토콜이며, 쿠키와 세션으로 상태를 유지한다
✅ POST/PUT 선택 시 멱등성을 고려해야 한다
✅ 헤더는 메타데이터, 바디는 실제 데이터다
✅ 상태 코드로 요청 결과를 명확히 전달해야 한다
✅ HTTPS는 암호화와 신원 확인 두 가지 역할을 한다
✅ 인증서 체인으로 서버의 진위를 확인한다
여러분은 HTTP 통신에서 가장 중요하다고 생각하는 개념이 무엇인가요? 댓글로 여러분의 경험과 생각을 공유해주세요!
참고 자료
자신만의 철학을 만들어가는 중입니다.
댓글남기기