웹 개발을 하다 보면 매일 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 프로토콜 위에 올라가 동작합니다.

graph TB A[애플리케이션 계층
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 핸드셰이크가 먼저 발생합니다:

sequenceDiagram participant C as 클라이언트 participant S as 서버 C->>S: ① SYN (연결 요청) S->>C: ② SYN-ACK (연결 수락 + 응답) C->>S: ③ ACK (확인) Note over C,S: 이제 HTTP 통신 시작 가능! C->>S: HTTP 요청 S->>C: HTTP 응답

이 과정을 통해 클라이언트와 서버는 “통신 준비 완료”를 서로 확인합니다.

참고: 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과 동일하지만 바디 없이 헤더만 -

참고: MDN - HTTP 요청 메서드

POST vs PUT - 멱등성의 차이

실무에서 가장 혼란스러운 부분이 POST와 PUT의 차이입니다. 핵심은 멱등성(Idempotent)입니다.

멱등성이란? 같은 요청을 여러 번 보내도 결과가 동일한 성질

POST (멱등성 ❌)
- 같은 요청을 3번 보내면 리소스가 3개 생성됨

예시: 계좌이체 1만원을 3번 요청
결과: 3만원 이체 ❌

---

PUT (멱등성 ✅)
- 같은 요청을 3번 보내도 최종 상태는 동일

예시: "잔액을 70만원으로 변경"을 3번 요청
결과: 잔액은 70만원 (동일) ✅
graph LR A[네트워크 불안정] -->|POST 요청 3번 전송| B[리소스 3개 생성] A -->|PUT 요청 3번 전송| C[리소스 1개만 존재] B --> D[❌ 중복 생성 문제] C --> E[✅ 멱등성 보장] style A fill:#3d2a2e,stroke:#6a4a4e,stroke-width:2px,color:#e8f4f8 style B fill:#4a2a2a,stroke:#8a4a4a,stroke-width:2px,color:#e8f4f8 style C fill:#2a4a3a,stroke:#4a8a6a,stroke-width:2px,color:#e8f4f8 style D fill:#5a2a2a,stroke:#aa4a4a,stroke-width:2px,color:#e8f4f8 style E fill:#2a5a3a,stroke:#4aaa6a,stroke-width:2px,color:#e8f4f8

실무 적용:

  • 결제, 이체 같은 중요한 작업 → 멱등성 필수 (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": "홍길동"}             ← 바디

핵심: 헤더와 바디는 빈 줄로 구분됩니다.

graph TD A[HTTP 메시지] --> B[시작 라인] A --> C[헤더] A --> D[빈 줄] A --> E[바디] B --> F[요청: GET /api/users HTTP/1.1
응답: 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) 실패
└─ 예시: 일반 사용자가 관리자 페이지 접근
graph LR A[HTTP 요청] --> B{로그인 여부} B -->|로그인 안 됨| C[401 Unauthorized] B -->|로그인 됨| D{권한 확인} D -->|권한 없음| E[403 Forbidden] D -->|권한 있음| F[200 OK] 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:#4a2a2a,stroke:#8a4a4a,stroke-width:2px,color:#e8f4f8 style D fill:#2d3e50,stroke:#4a7c8a,stroke-width:2px,color:#e8f4f8 style E fill:#4a2a2a,stroke:#8a4a4a,stroke-width:2px,color:#e8f4f8 style F fill:#2a4a3a,stroke:#4a8a6a,stroke-width:2px,color:#e8f4f8

쿠키와 세션으로 상태 유지하기

쿠키 vs 세션

HTTP는 무상태 프로토콜이지만, 쿠키와 세션으로 상태를 유지할 수 있습니다.

쿠키 (Cookie)
└─ 클라이언트의 브라우저에 저장
└─ 클라이언트가 직접 접근 가능
└─ 보안에 취약할 수 있음

세션 (Session)
└─ 서버의 메모리/DB에 저장
└─ 클라이언트는 세션ID만 보유
└─ 상대적으로 안전

참고: MDN - HTTP 쿠키

로그인 세션 동작 방식

sequenceDiagram participant C as 클라이언트
(브라우저) 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)으로 서버의 신원을 보증합니다.

graph TD A[Root CA
최상위 인증 기관] -->|서명| 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 통신에서 가장 중요하다고 생각하는 개념이 무엇인가요? 댓글로 여러분의 경험과 생각을 공유해주세요!

참고 자료


자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기