HTTP 요청
고친 사람 github-actions[bot]
HTTP 요청은 클라이언트가 서버에게 할 일을 부탁하려고 보내는 메시지입니다. 무슨 일을 어느 대상에 해 달라는지 첫 줄에 적어 보냅니다. 서버는 요청 하나를 받으면 응답 하나를 돌려줍니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 클라이언트가 서버에 보내는 부탁 쪽지입니다. 게시글 42번을 보여 달라는 요청은 첫 줄에 GET /posts/42 라고 적습니다. GET 은 「읽어서 돌려 달라」는 뜻입니다.
왜 이렇게 하나 — 서버 하나가 수많은 클라이언트의 부탁을 받습니다. 부탁마다 적는 순서가 다르면 서버가 읽을 수 없습니다. 그래서 할 일과 대상, 안내, 보낼 데이터를 늘 같은 순서로 적습니다.
어떻게 도나
- 첫 줄에 할 일과 대상을 적습니다
- 그 아래 줄마다 받을 서버의 이름이나 데이터 형식 같은 안내를 적습니다
- 빈 줄 하나를 두고 보낼 데이터를 붙입니다
대가 — 서버는 앞서 받은 요청을 기억하지 않습니다. 그래서 로그인 정보 같은 안내를 요청마다 다시 실어 보냅니다. 요청이 그만큼 무거워집니다.
상세
이 절은 요청이 무엇으로 이루어지는지를 봅니다. 클라이언트가 보낸 요청 하나를 펼쳐 놓고 첫 줄부터 차례로 읽습니다. 끝에서는 서버가 요청을 받아 개발자의 코드에 넘기는 과정과, 요청마다 같은 안내를 다시 싣는 까닭을 봅니다.
관공서 창구에 내는 신청서를 떠올리면 됩니다. 맨 위 칸에는 「주민등록등본 발급」처럼 무슨 일을 해 달라는지 적습니다. 그 아래 칸에 신청인 이름과 연락처를 채웁니다. 필요하면 맨 뒤에 첨부 서류를 붙입니다.
요청 하나에 응답 하나
HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)는 웹에서 프로그램끼리 데이터를 주고받는 약속입니다. 주고받기는 언제나 클라이언트가 엽니다. 클라이언트가 요청을 보내면 서버가 그 요청에 답하는 HTTP 응답을 돌려줍니다.
클라이언트는 요청을 보내는 쪽 프로그램입니다. 브라우저나 휴대폰 앱이 흔한 클라이언트입니다. 백엔드 서버도 다른 서버의 데이터가 필요하면 요청을 보냅니다. 그때는 그 백엔드 서버가 클라이언트입니다.
요청 하나에는 최종 응답 하나가 짝을 이룹니다. 이렇게 묻고 답하는 한 쌍으로 대화가 굴러가는 방식을 요청-응답 모델이라고 부릅니다. 「최종」이라고 한 까닭은 서버가 최종 응답 앞에 짧은 중간 응답을 먼저 보낼 때가 있어서입니다. 중간 응답은 요청을 받았으니 계속 보내라는 식으로 처리 중임을 알리는 응답입니다.
요청을 이루는 세 부분
아래는 클라이언트가 보낸 요청 하나입니다. 새 게시글을 하나 올려 달라는 요청입니다. HTTP 의 1.1 버전인 HTTP/1.1 은 요청을 이렇게 사람이 읽을 수 있는 글자로 적습니다.
POST /posts HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 40
{"title": "hello", "body": "first post"}
방금 본 요청은 세 부분으로 나뉩니다. 첫 줄이 요청줄입니다. 그 아래 빈 줄 전까지가 헤더입니다. 빈 줄 다음이 요청 본문입니다.
이 본문은 JSON(JavaScript Object Notation, 자바스크립트 객체 표기법)으로 적혀 있습니다. JSON 은 중괄호 안에 「이름: 값」 쌍을 늘어놓는 글자 형식입니다.
| 부분 | 무엇을 적나 | 이 요청에서는 |
|---|---|---|
| 요청줄 | 할 일 · 대상 · HTTP 버전 | POST /posts HTTP/1.1 |
| 헤더 | 요청을 어떻게 다룰지 알리는 「이름: 값」 줄 | 받을 서버는 api.example.com 이다. 본문은 40바이트짜리 JSON 이다 |
| 본문 | 서버에 넘길 데이터 | 새 게시글의 제목과 내용 |
앞의 신청서에 견주면 맨 위 칸이 요청줄입니다. 신청인 칸이 헤더입니다. 첨부 서류가 본문입니다.
헤더와 본문 사이의 빈 줄은 경계 표시입니다. 서버는 빈 줄을 만나야 헤더가 끝났다는 것을 압니다. 그 뒤로 오는 바이트는 본문으로 읽습니다.
세 부분을 늘 같은 순서로 적는 까닭은 서버가 요청을 한 가지 방법으로 읽기 위해서입니다. 서버 하나에 브라우저와 휴대폰 앱과 다른 서버가 함께 요청을 보냅니다. 보내는 쪽마다 적는 순서가 다르면 서버는 보낸 쪽마다 읽는 코드를 따로 짜야 합니다.
요청 메서드
요청줄의 첫 낱말이 요청 메서드입니다. 서버에게 무슨 일을 할지 알립니다. 같은 대상에 보내도 메서드가 다르면 서버가 하는 일이 달라집니다. 백엔드 코드가 흔히 받는 메서드는 아래 여섯입니다.
| 메서드 | 서버에게 부탁하는 일 | 요청 본문 |
|---|---|---|
| GET | 대상을 읽어서 돌려 달라 | 대개 없음 |
| HEAD | GET 과 같지만 응답 본문은 빼고 헤더만 달라 | 없음 |
| POST | 보낸 데이터를 처리해 달라. 새로 만들 때 흔히 쓴다 | 있음 |
| PUT | 대상을 보낸 데이터로 통째로 바꿔 달라 | 있음 |
| PATCH | 대상의 일부만 고쳐 달라 | 있음 |
| DELETE | 대상을 지워 달라 | 대개 없음 |
메서드에는 서버가 지킬 약속이 붙어 있습니다. GET 과 HEAD 는 서버의 데이터를 바꾸지 않겠다는 약속입니다. 이런 메서드를 안전한 메서드라고 부릅니다. 링크를 따라가는 일은 GET 요청을 보내는 일이라, 검색 엔진의 수집 프로그램은 이 약속 덕분에 링크를 마음 놓고 따라갑니다.
또 하나의 약속은 같은 요청을 여러 번 보내도 결과가 같다는 것입니다. 이 성질을 멱등성이라고 부릅니다. 같은 글을 두 번 지워도 글이 없어진 결과는 같습니다. 그래서 DELETE 는 멱등입니다.
PUT 도 멱등입니다. 안전한 메서드는 데이터를 바꾸지 않으니 모두 멱등입니다. POST 는 멱등이 아닙니다. 두 번 보내면 글이 두 개 생길 수 있습니다.
멱등성은 응답을 못 받았을 때 쓸모가 있습니다. 요청을 보낸 뒤 연결이 끊기면 클라이언트는 서버가 처리했는지 모릅니다. 멱등인 요청은 다시 보내도 탈이 없습니다. POST 를 다시 보낼 때는 두 번 처리되지 않게 서버가 따로 막아야 합니다.
요청 대상
메서드 다음 낱말이 요청 대상입니다. 서버의 무엇에 그 일을 할지 가리킵니다. 대개 URL(Uniform Resource Locator, 자원 위치 표시자) 가운데 경로와 그 뒤에 붙는 조건만 적습니다.
GET /posts?page=2&size=20 HTTP/1.1
이 요청에서 /posts 가 경로입니다. 서버의 무엇을 다룰지 정합니다.
? 뒤의 page=2&size=20 은 쿼리 문자열입니다. 「이름=값」 쌍을 & 로 이어 조건을 붙입니다. 서버는 흔히 이 요청을 게시글 목록을 20개씩 나눈 둘째 묶음을 달라는 뜻으로 읽습니다.
요청 대상에는 서버의 이름이 없습니다. api.example.com 같은 이름은 헤더의 Host 줄이 따로 싣습니다.
Host 를 따로 싣는 까닭은 서버 컴퓨터 한 대가 여러 도메인 이름의 사이트를 함께 맡는 일이 흔해서입니다. 그 서버는 Host 를 보고 어느 사이트로 온 요청인지 가립니다. 그래서 HTTP/1.1 요청에는 Host 헤더가 꼭 있어야 합니다.
요청 헤더
헤더는 요청을 어떻게 다룰지 알리는 안내 줄입니다. 한 줄에 「이름: 값」 한 쌍을 적습니다. 백엔드 코드가 자주 읽는 요청 헤더는 아래 일곱입니다.
| 헤더 | 알리는 것 |
|---|---|
| Host | 요청을 받을 서버의 이름 |
| Content-Type | 요청 본문의 형식 |
| Content-Length | 요청 본문의 바이트 수 |
| Authorization | 보내는 쪽이 누구인지 증명하는 값 |
| Cookie | 서버가 전에 맡겨 둔 값 |
| Accept | 응답 본문으로 받고 싶은 형식 |
| User-Agent | 요청을 보낸 프로그램의 이름과 버전 |
표의 Cookie 헤더가 싣는 값을 쿠키라고 부릅니다. 서버가 응답에 실어 맡긴 값을 클라이언트가 저장해 두었다가 다음 요청마다 되돌려 보냅니다. 로그인한 사용자를 알아보는 값은 흔히 쿠키나 Authorization 헤더에 실립니다.
서버는 모르는 이름의 헤더를 만나면 그 줄을 건너뜁니다. 그래서 클라이언트는 새 헤더를 더해도 옛 서버를 깨뜨리지 않습니다. 팀끼리 이름을 정해 쓰는 헤더도 이 덕분에 섞어 보낼 수 있습니다.
요청 본문
본문은 서버에 넘길 데이터입니다. 새 글의 내용이나 올릴 파일이 여기에 실립니다. 본문은 무엇이 들었는지 스스로 말하지 않습니다. Content-Type 헤더가 알려 줍니다.
| Content-Type 값 | 본문 모양 | 흔히 쓰는 곳 |
|---|---|---|
application/json |
JSON 한 덩이 | 앱과 서버 사이 |
application/x-www-form-urlencoded |
이름=값&이름=값 |
웹 페이지의 입력 양식 제출 |
multipart/form-data |
경계 문자열로 나눈 여러 조각 | 파일 올리기 |
서버는 본문이 어디서 끝나는지 알아야 합니다. 클라이언트가 길이를 미리 알면 Content-Length 에 적습니다.
미리 모르면 HTTP/1.1 은 본문을 조각으로 나눠 보냅니다. 조각마다 앞에 그 조각의 길이를 붙입니다. 마지막에 길이가 0 인 조각을 보내 끝을 알립니다. 이 방식이 청크 전송 인코딩입니다.
GET 요청에 본문을 실으면 그 뜻이 정해져 있지 않습니다. 본문을 무시하는 서버가 있습니다. 클라이언트와 서버 사이에서 요청을 받아 넘기는 프록시 같은 장치 가운데는 본문이 실린 GET 을 아예 거절하는 것도 있습니다. 그래서 GET 으로 조건을 보낼 때는 쿼리 문자열에 담습니다.
버전마다 달라지는 겉모양
HTTP 에는 여러 버전이 있습니다. 앞에서 본 글자 모양은 HTTP/1.1 의 것입니다. 뒤를 이은 HTTP/2 와 HTTP/3 은 같은 요청을 이진 형식으로 바꿔 보냅니다. 이진 형식은 사람이 읽는 글자 대신 기계가 바로 읽는 바이트 배열로 적는 방식입니다.
HTTP/2·HTTP/3 요청에는 요청줄이 따로 없습니다. 메서드는 :method, 요청 대상은 :path 라는 특별한 헤더에 실립니다. 이 헤더들은 이름이 콜론으로 시작해서 보통 헤더와 구별됩니다. 서버 이름은 Host 대신 :authority 에 실립니다.
그래도 뜻은 바뀌지 않습니다. 어느 버전이든 요청은 메서드와 대상, 헤더, 본문으로 이루어집니다. 그래서 백엔드 코드는 요청이 어느 버전으로 왔는지 몰라도 같은 값을 꺼내 씁니다.
요청이 핸들러에 닿기까지
이 절은 서버에 도착한 요청이 개발자의 코드에 닿기까지를 봅니다. 그 사이에서 요청을 받아 넘겨 주는 프로그램이 하는 일을 순서대로 따라갑니다.
백엔드 코드는 요청을 바이트로 직접 읽는 일이 드뭅니다. 웹 프레임워크가 대신 읽습니다. 웹 프레임워크는 요청을 받아 개발자가 짠 함수에 넘겨 주는 라이브러리입니다.
개발자가 짠 이 함수를 핸들러라고 부릅니다. 요청 하나를 받아 할 일을 하고 돌려줄 값을 내놓는 함수입니다. 게시글 42번을 읽어 돌려주는 함수가 그 예입니다.
아래 그림은 요청 바이트가 프레임워크를 거쳐 핸들러에 닿고, 핸들러가 돌려준 값이 응답으로 나가는 순서를 보여 줍니다.
sequenceDiagram
participant 클라이언트
participant 프레임워크
participant 핸들러
클라이언트->>프레임워크: 요청 바이트
Note over 프레임워크: 요청줄과 헤더를 읽어 값으로 나눈다
Note over 프레임워크: 메서드와 경로로 핸들러를 고른다
프레임워크->>핸들러: 요청 객체
핸들러-->>프레임워크: 돌려줄 값
프레임워크-->>클라이언트: 응답
요청은 클라이언트와 서버 사이에 열린 연결을 타고 바이트로 흘러옵니다. 프레임워크는 먼저 요청줄과 헤더를 읽어 메서드, 대상, 헤더 값으로 나눕니다. 글자 덩어리를 이렇게 뜻 있는 값으로 나누는 일을 파싱이라고 부릅니다.
다음으로 메서드와 경로를 보고 어느 핸들러를 부를지 고릅니다. 이 일을 라우팅이라고 부릅니다. GET /posts/42 와 DELETE /posts/42 는 경로가 같아도 다른 핸들러로 갑니다.
마지막으로 프레임워크는 나눈 값들을 객체 하나에 담아 핸들러에 넘깁니다. 그림의 요청 객체가 이것입니다. 핸들러는 이 객체에서 메서드나 헤더 값, 경로 속 42 같은 값을 꺼내 씁니다.
인증 확인이나 로그 남기기처럼 모든 요청에 똑같이 할 일도 있습니다. 이런 일은 핸들러 앞에 끼운 단계가 맡습니다. 이 단계를 미들웨어라고 부릅니다.
요청마다 다시 싣는 안내
HTTP 서버는 앞서 받은 요청을 기억하지 않습니다. 요청 하나하나가 그것만으로 뜻이 통해야 합니다. 이런 성질을 무상태라고 부릅니다.
그래서 로그인 정보도 요청마다 다시 실어 보냅니다. 로그인할 때 서버가 내준 값을 클라이언트가 저장해 둡니다. 그 뒤로는 요청마다 Cookie 나 Authorization 헤더에 그 값을 실어 보냅니다. 서버는 그 값으로 누가 보낸 요청인지 알아봅니다.
기억하지 않는 것은 HTTP 가 요청을 처리하는 방식이고, 서버 프로그램은 사용자 정보를 따로 저장소에 넣어 둘 수 있습니다. 이렇게 저장해 둔 정보를 세션이라고 부릅니다. 서버는 요청이 싣고 온 열쇠로 그 정보를 찾습니다. 그래서 세션을 써도 열쇠는 요청마다 실려 와야 합니다.
무상태로 얻는 것은 어느 서버가 받아도 같은 처리를 할 수 있다는 점입니다. 그래서 서버를 여러 대 두기 쉽습니다.
서버를 여러 대 두면 그 앞에 들어온 요청을 여러 서버에 나눠 보내는 로드 밸런서를 세웁니다. 요청마다 필요한 정보가 다 실려 오니 로드 밸런서는 요청을 아무 서버에나 보내도 됩니다.
대가는 같은 헤더가 요청마다 되풀이된다는 점입니다. 요청이 그만큼 무거워집니다. HTTP/2 와 HTTP/3 이 헤더를 압축하는 까닭이 여기 있습니다.
클라이언트만 여는 대화
서버는 요청 없이 먼저 말을 걸 수 없습니다. 채팅방에 새 메시지가 와도 서버는 클라이언트에게 바로 알릴 방법이 없습니다. HTTP 요청만 쓰는 클라이언트는 새 메시지를 보려면 먼저 물어야 합니다.
가장 쉬운 방법은 몇 초마다 요청을 보내 새 소식이 있는지 묻는 것입니다. 이 방식을 폴링이라고 부릅니다. 대가가 둘입니다. 새 소식이 없어도 요청이 오갑니다. 소식은 묻는 간격만큼 늦게 닿습니다.
서버가 먼저 보내야 하는 일이 많으면 다른 방식을 고릅니다. 하나는 웹소켓입니다. 연결을 열어 둔 채 양쪽이 아무 때나 메시지를 보냅니다.
다른 하나는 서버 전송 이벤트입니다. 서버가 응답을 끝내지 않고 열어 둔 채, 새 소식이 생길 때마다 그 응답에 이어 붙여 보냅니다. 소식은 서버에서 클라이언트 쪽으로만 흐릅니다.
믿을 수 없는 입력
요청의 모든 부분은 클라이언트가 마음대로 적을 수 있습니다. 브라우저가 아닌 프로그램으로 헤더와 본문을 아무 값으로 채워 보내면 됩니다. User-Agent 를 지어낼 수 있습니다. Content-Length 를 본문의 진짜 길이와 다르게 적을 수도 있습니다.
그래서 서버는 요청에서 꺼낸 값을 쓰기 전에 검사합니다. 본문이 정해진 모양인지, 값이 허용하는 범위 안인지 확인합니다. 이 검사를 입력 검증이라고 부릅니다. 정해 둔 크기를 넘는 요청은 끝까지 읽기 전에 거절합니다.
관련 항목
HTTP 요청을 이루는 구성 요소
요청줄 · 요청 메서드 · 요청 대상 · 헤더 · 요청 본문 · 쿼리 문자열 · 경로 매개변수 · 트레일러
HTTP 요청 첫 줄에 오는 메서드
GET · HEAD · POST · PUT · PATCH · DELETE · OPTIONS · CONNECT · TRACE
HTTP 요청에 실리는 헤더 필드
Host · Content-Type · Content-Length · Authorization · 쿠키 · Accept · Accept-Encoding · User-Agent · If-None-Match · Origin · Referer
HTTP 요청 메서드가 지키는 성질
HTTP 요청 본문을 적는 형식
JSON · HTML 폼 · multipart/form-data · 퍼센트 인코딩 · 청크 전송 인코딩 · 직렬화 · 콘텐츠 협상
HTTP 요청을 받아 처리하는 서버 쪽 단계
웹 서버 · 리버스 프록시 · 로드 밸런서 · 웹 프레임워크 · 파싱 · 라우팅 · 라우터 · 미들웨어 · 핸들러 · 입력 검증 · 세션
HTTP 요청을 보내는 프로그램
클라이언트 · 서버 · 브라우저 · HTTP 클라이언트 · curl · fetch · 백엔드
HTTP 요청이 돌려받는 응답
HTTP 응답 · 상태 코드 · 100 Continue · 400 Bad Request · HTTP 429
HTTP 요청 형식을 정한 버전과 표준
HTTP/1.1 · HTTP/2 · HTTP/3 · RFC 9110 · RFC 9112 · URL · URI
HTTP 요청이 지나가는 연결
TCP · TLS · HTTPS · QUIC · 지속 연결 · HTTP 파이프라이닝 · 멀티플렉싱
요청-응답 모델을 넘어서는 통신 방식
폴링 · 롱 폴링 · 웹소켓 · Server-Sent Events
HTTP 요청을 재는 지표
초당 요청 수 · 응답 시간 · 지연 시간 · 처리량 · 오류율
HTTP 요청을 꾸며 노리는 공격과 그 방어
크로스 사이트 요청 위조 · 서버 사이드 요청 위조 · HTTP 요청 스머글링 · 헤더 인젝션 · CORS · 프리플라이트 요청
HTTP 요청이 속하는 상위 분류
다른 이름: HTTP request · HTTP 요청 메시지 · 요청 메시지