Skip to content

01. 웹과 HTTP ​

서버가 무엇을 받고 무엇을 돌려주는지, 그 형식을 정리합니다.

인터넷 기초 ​

요청이 서버에 닿기까지 거치는 것들입니다.

요소역할
IP네트워크 상의 주소
DNS도메인 이름을 IP로 변환
포트한 장비 안에서 어떤 프로그램인지 구분
TCP끊김 없이 순서대로 전달

서버는 특정 포트를 듣고 있다가 연결이 오면 처리합니다.

bash
lsof -i :3000        # 3000번 포트를 쓰는 프로세스
netstat -an | grep 3000

localhost는 자기 자신(127.0.0.1)입니다. 0.0.0.0으로 바인딩하면 외부에서도 접근할 수 있습니다. 컨테이너 안에서 127.0.0.1로 띄우면 밖에서 닿지 않는 것이 이 때문입니다.

HTTP 요청과 응답 ​

http
POST /api/todos HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJ...

{"title":"공부"}
http
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/todos/1

{"id":1,"title":"공부","done":false}

요청은 메서드·경로·헤더·본문, 응답은 상태 코드·헤더·본문으로 이루어집니다.

메서드 ​

메서드용도안전멱등
GET조회예예
POST생성아니오아니오
PUT전체 교체아니오예
PATCH부분 수정아니오아니오
DELETE삭제아니오예

안전은 서버 상태를 바꾸지 않는다는 뜻이고, 멱등은 여러 번 호출해도 결과가 같다는 뜻입니다.

이 성질이 중요한 이유는 재시도 때문입니다. 네트워크가 끊겨 응답을 못 받았을 때, 멱등한 요청은 그냥 다시 보내면 됩니다. POST는 그럴 수 없어서 중복 생성이 생깁니다.

GET으로 데이터를 바꾸면 안 됩니다. 브라우저와 프록시가 마음대로 미리 호출하거나 캐시합니다.

상태 코드 ​

코드의미
200성공
201생성됨 — Location 헤더로 위치를 알려 줍니다
204성공했지만 본문 없음 (삭제 등)
400요청 형식이 잘못됨
401인증되지 않음 (누구인지 모름)
403인가되지 않음 (누구인지는 알지만 권한 없음)
404없음
409현재 상태와 충돌 (중복 등)
422형식은 맞지만 값이 유효하지 않음
429요청이 너무 많음
500서버 오류
503일시적으로 처리 불가

401과 403을 섞어 쓰는 경우가 많습니다. 토큰이 없거나 만료면 401, 토큰은 유효한데 권한이 없으면 403입니다.

500은 서버가 잘못한 것입니다. 사용자가 이상한 값을 보내서 난 오류를 500으로 돌려주고 있다면 400대로 바꿔야 합니다. 모니터링에서 진짜 장애와 구분되지 않습니다.

헤더 ​

http
Content-Type: application/json      요청·응답 본문의 형식
Accept: application/json            원하는 응답 형식
Authorization: Bearer <token>       인증 정보
User-Agent: ...                     클라이언트 정보
X-Request-Id: abc-123               요청 추적용

프록시를 거치면 원래 클라이언트 정보가 바뀝니다. 실제 IP는 별도 헤더로 전달됩니다.

http
X-Forwarded-For: 203.0.113.1
X-Forwarded-Proto: https

이 헤더는 클라이언트가 위조할 수 있습니다. 신뢰할 수 있는 프록시가 덧붙인 값만 믿어야 합니다.

연결 재사용과 HTTP 버전 ​

HTTP/1.1은 한 연결에서 요청을 하나씩 처리합니다. 앞이 느리면 뒤가 막힙니다.

HTTP/2는 하나의 연결에서 여러 요청을 동시에 주고받고, 헤더를 압축합니다. 보통 앞단의 리버스 프록시나 로드밸런서가 처리하고, 뒤쪽 애플리케이션 서버와는 HTTP/1.1로 통신하는 구성이 흔합니다.

서버 쪽에서 신경 쓸 것은 연결 재사용입니다. 외부 API를 부를 때 매번 새 연결을 맺으면 TLS 핸드셰이크 비용이 반복됩니다. HTTP 클라이언트를 한 번 만들어 재사용합니다.

js
// 매 요청마다 새로 만들지 않습니다
const agent = new https.Agent({ keepAlive: true });

캐시 헤더와 콘텐츠 협상 ​

서버가 "이 응답을 얼마나 써도 되는지" 알려 줍니다.

http
Cache-Control: public, max-age=3600
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: private, max-age=0
값의미
no-store저장하지 말 것
no-cache저장하되 매번 서버에 확인할 것
private브라우저만 저장, 공용 프록시는 금지
max-age초 단위 유효 기간

개인 정보가 담긴 응답에 public을 주면 중간 프록시에 캐시되어 다른 사용자에게 노출될 수 있습니다.

조건부 요청으로 변경 여부만 확인할 수 있습니다.

http
# 첫 응답
ETag: "abc123"

# 다음 요청
If-None-Match: "abc123"

# 바뀌지 않았으면
HTTP/1.1 304 Not Modified

본문이 비어 있어 전송량이 거의 없습니다.

콘텐츠 협상은 클라이언트가 원하는 형식을 요청하는 방식입니다.

http
Accept: application/json
Accept-Language: ko-KR
Accept-Encoding: gzip, br

압축은 대부분 앞단에서 처리하지만, 직접 설정해야 하는 경우도 있습니다. JSON 응답은 압축 효과가 큽니다.

HTTPS와 TLS, JSON ​

HTTPS는 TLS로 감싼 HTTP입니다. 세 가지를 보장합니다.

  • 내용을 가운데서 볼 수 없음
  • 내용을 가운데서 바꿀 수 없음
  • 상대가 그 도메인의 주인이 맞음

인증서는 보통 Let's Encrypt로 무료 발급받고 자동 갱신합니다. 실무에서는 로드밸런서나 리버스 프록시에서 TLS를 끝내고(termination) 내부는 평문으로 통신하는 구성이 많습니다.

JSON은 형식이 단순한 대신 몇 가지 주의가 필요합니다.

json
{
  "id": 9007199254740993,
  "amount": 0.1,
  "createdAt": "2026-10-26T09:00:00Z"
}
  • 큰 정수는 JavaScript에서 정밀도를 잃습니다. ID가 크면 문자열로 보냅니다
  • 소수는 금액에 쓰지 않습니다. 최소 단위 정수로 보내거나 문자열을 씁니다
  • 날짜 타입이 없습니다. ISO 8601 문자열에 시간대를 포함해 보냅니다

요청 직접 확인하기 ​

curl로 서버를 직접 두드려 보는 것이 가장 빠릅니다.

bash
curl -i http://localhost:3000/api/todos

curl -X POST http://localhost:3000/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"공부"}'

curl -v https://api.example.com/todos    # 핸드셰이크와 헤더까지
옵션역할
-i응답 헤더 포함
-v요청·응답 전부
-X메서드 지정
-H헤더 추가
-d본문

브라우저 개발자 도구의 Network 탭에서 요청을 우클릭하면 Copy as cURL로 그대로 복사할 수 있습니다. 재현할 때 유용합니다.