![[웹 기반기술 01] HTTP - 웹이 대화하는 방식](https://img.thenullpage.com/posts/8221/8221_1_d5818b.webp)
HTTP라는 프로토콜
HTTP는 HyperText Transfer Protocol의 줄임말로, 클라이언트가 서버에 요청을 보내고 서버가 응답하는 절차를 정해놓은 통신 규약이다. 이름 때문에 HTML 문서만 실어 나른다고 오해하기 쉽지만, 실제로는 JSON, 이미지, 압축 파일, API가 주고받는 순수 데이터까지 형식만 지키면 어떤 바이트열이든 실어 나르는 범용 운반 수단에 가깝다.
1997년 표준이 된 1.1 버전은 지금도 널리 쓰이는 기본형이고, 2015년의 2 버전은 성능을 끌어올렸으며, 2022년의 3 버전은 전송 계층을 QUIC로 바꿨다. 버전이 바뀌어도 요청과 응답이라는 골격, 메서드와 상태코드라는 어휘 체계는 그대로 유지된다.
요청과 응답의 뼈대
요청은 메서드와 경로, 프로토콜 버전이 놓이는 시작줄, 부가 정보를 담는 헤더 몇 줄, 헤더와 본문을 가르는 빈 줄, 필요하면 본문으로 구성된다. 응답은 시작줄 자리에 상태줄이 오고, 그 아래 헤더와 빈 줄, 본문이 이어진다. 상태줄에는 프로토콜 버전과 상태코드, 그 코드를 풀어쓴 짧은 문구가 순서대로 놓인다.
GET /articles/42 HTTP/1.1
Host: example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 81
{"id":42,"title":"HTTP 개요"}
GET 요청에 조회 조건을 본문으로 실어 보내려는 시도는 흔한 실수다. 중간 프록시나 서버 상당수가 GET의 본문을 무시하거나 거부하므로, 조회 조건은 경로 뒤 쿼리스트링으로 전달하는 편이 안전하다.
메서드가 담는 의도
메서드는 서버에 어떤 동작을 시키고 싶은지 알리는 동사다. GET은 조회, POST는 생성, PUT은 전체 교체, PATCH는 일부 수정, DELETE는 삭제를 뜻한다. HEAD는 본문 없이 헤더만, OPTIONS는 허용된 메서드를 확인할 때 쓴다.
PUT /articles/42
리소스 전체를 본문으로 교체
PATCH /articles/42
title 필드 하나만 수정
DELETE /articles/42
삭제, 두 번 호출해도 결과는 동일
조회용 링크에 삭제 동작을 얹어 GET으로 처리하면 위험하다. 크롤러나 브라우저 프리페치가 그 링크를 미리 불러들이면서 의도치 않게 자원이 지워지는 사고로 이어진다. 상태를 바꾸는 동작은 POST, PUT, PATCH, DELETE 중 하나로 처리해야 한다. 경로 이름에 동사를 넣는 대신 자원은 명사로, 동작은 메서드로 표현하는 편이 경로를 짧고 일관되게 유지한다.
안전성과 멱등성이라는 기준
안전한 메서드는 서버 상태를 바꾸지 않는 메서드로 GET, HEAD, OPTIONS가 해당한다. 멱등한 메서드는 반복 호출해도 결과가 처음과 같은 메서드로 GET, PUT, DELETE가 해당하고, POST는 호출마다 자원이 늘어나므로 멱등하지 않다. 네트워크 오류로 응답을 못 받았을 때 멱등한 메서드는 안심하고 재전송할 수 있지만, POST는 그대로 재전송하면 중복 생성 위험이 있어 요청마다 고유 식별값을 실어 보내 서버가 중복을 걸러내게 한다. 안전은 상태를 바꾸지 않는다는 성질이고 멱등은 여러 번 실행해도 결과가 같다는 성질이라 둘은 별개다. PUT은 안전하지 않지만 멱등하다.
상태코드라는 신호 체계
상태코드 첫 자리로 범주가 갈린다. 1은 정보성, 2는 성공, 3은 리다이렉트, 4는 클라이언트 오류, 5는 서버 오류를 뜻한다. 200은 성공, 201은 생성됨, 204는 본문 없는 성공, 301은 영구 이동, 302나 307은 일시 이동, 304는 캐시와 동일해 본문을 다시 안 보낸다는 뜻이다.
401과 403은 자주 헷갈린다. 401은 누구인지 아직 확인하지 못했다는 뜻이고, 403은 누구인지는 확인했지만 권한이 없다는 뜻이다. 400은 요청 형식 오류, 404는 자원 없음, 409는 상태 충돌, 422는 형식은 맞지만 값이 규칙에 어긋남, 429는 요청이 너무 잦다는 뜻이다.
201 Created
Location: /articles/43
429 Too Many Requests
Retry-After: 60
500은 서버 내부 오류, 502는 중계 서버가 뒤쪽 서버의 정상 응답을 못 받았다는 뜻, 503은 서버가 일시적으로 요청을 처리할 여력이 없다는 뜻이다. 500번대를 받으면 자신의 요청보다 서버 쪽 사정을 먼저 의심하는 것이 순서에 맞다.
헤더에 실리는 정보
요청 쪽에는 원하는 응답 형식을 알리는 Accept, 인증 정보를 담는 Authorization, 로그인 상태를 유지시키는 Cookie가 흔히 붙는다. 응답 쪽에는 본문 형식을 알리는 Content-Type, 쿠키를 심으라고 지시하는 Set-Cookie, 캐시 방침을 정하는 Cache-Control이 붙는다. Content-Type을 빠뜨리면 서버가 본문 형식을 판단하지 못해 파싱에 실패하는 경우가 흔하다.
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
헤더 이름은 대소문자를 구분하지 않는다. 표준에 없는 값은 흔히 X- 접두사를 붙인 커스텀 헤더로 주고받지만, 표준 헤더로 표현할 수 있다면 굳이 새로 만들기보다 표준을 우선 살펴보는 편이 다른 시스템과 연동할 때 유리하다.
무상태라는 태생적 성격
HTTP는 무상태 프로토콜이다. 이번 요청과 다음 요청 사이에 서버가 아무 것도 기억해두지 않는다는 뜻이다. 방금 로그인한 사용자의 다음 요청도 서버 입장에서는 그저 새로운 요청 하나일 뿐이다. 그래서 로그인 성공 시 서버가 식별값을 발급해 Set-Cookie로 심어두면, 브라우저는 이후 요청마다 그 값을 Cookie 헤더에 실어 보내고 서버는 그 값으로 요청의 주인을 확인한다. 무상태를 없애는 것이 아니라 그 제약을 인정한 채로 상태를 흉내 내는 방식이다.
이 성질은 확장성 면에서는 오히려 이점이다. 서버가 요청 사이의 문맥을 기억할 필요가 없으므로, 같은 서비스를 여러 대의 서버로 나누어 운영해도 어느 서버든 다음 요청을 받아 처리할 수 있다. 메서드가 의도를 밝히고 상태코드가 결과를 되돌리며 헤더가 세부 정보를 채우고 무상태가 이 모든 것을 매 요청마다 새로 성립시킨다는 점에서, HTTP는 단순한 왕복처럼 보여도 그 안에 서버 여러 대를 안정적으로 운영하기 위한 규약을 담고 있다.