이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
기초개념 › 네트워크
HTTP 응답
HTTP 응답은 어떻게 생긴 글인가
서버가 요청에 답해 돌려주는 HTTP 응답도 정해진 모양의 글자 몇 줄입니다. 첫 줄(상태 줄)에는 버전과 상태 코드(200, 404 같은 세 자리 숫자)와 짧은 설명을 적습니다. 그 아래에는 "이름: 값" 꼴의 줄인 헤더를 적고, 아무 글자도 없는 빈 줄 뒤에 본문(실제 내용)을 붙입니다. 클라이언트(브라우저)는 상태 코드로 요청이 어떻게 됐는지를 알고, Content-Type 헤더로 본문이 어떤 종류의 내용인지를 알고, Content-Length 헤더로 본문이 몇 바이트인지를 압니다. 이 실습은 상태 줄, Content-Type 줄, Content-Length 줄을 하나씩 바꾸거나 지워 가며 그 글자를 그대로 클라이언트에 보내, 클라이언트가 무엇을 읽어 내는지를 봅니다. 코드 칸의 (빈 줄)은 아무 글자도 없는 줄을 눈에 보이게 적은 것입니다.
실행으로 대조 · Node.js 24.18.0의 net 서버가 글자를 그대로 보내고 http 클라이언트가 읽는 열여덟 가지 조합을 실행해 대조
이 실습에 나오는 개념
- 상태 줄(status line)
- 상태 줄은 HTTP 응답의 맨 첫 줄로, HTTP 버전, 상태 코드, 짧은 설명을 공백으로 나누어 차례로 적는다. 예를 들어 HTTP/1.1 200 OK는 HTTP/1.1 규칙으로 쓴 글이고, 요청의 결과가 200이며, 사람이 읽는 설명이 OK라는 뜻이다. 상태 코드는 요청이 어떻게 됐는지를 알리는 세 자리 숫자이고, 설명은 사람을 위한 글일 뿐이다(RFC 9112 4절). 클라이언트는 이 줄부터 읽기 때문에, 응답이 상태 줄로 시작하지 않으면 그 글을 HTTP 응답으로 읽지 못한다.
- HTTP 상태 코드
- 서버가 요청을 처리한 결과를 나타내는 세 자리 숫자다. 2xx는 성공, 3xx는 다른 위치로의 이동(리다이렉션), 4xx는 클라이언트의 요청에 문제가 있음, 5xx는 서버 쪽 오류를 뜻한다. 200(성공), 201(생성됨), 400(잘못된 요청), 401(인증 필요), 403(권한 없음), 404(없음), 500(서버 오류)이 자주 쓰인다.
- Content-Type 헤더
- Content-Type은 응답 본문이 어떤 종류의 내용인지를 알리는 헤더로, text/plain; charset=utf-8처럼 적는다. 앞의 text/plain은 꾸밈없는 글자라는 종류(미디어 타입)이고, charset=utf-8은 그 글자를 적은 문자 인코딩이다. 클라이언트는 이 값을 보고 본문을 글로 보여 줄지, 그림이나 다른 형식으로 다룰지를 정한다. RFC 9110 8.3절은 이 헤더가 없으면 받는 쪽이 종류를 application/octet-stream으로 가정하거나 데이터를 살펴 정할 수 있다고 한다.
- Content-Length 헤더
- Content-Length는 응답 본문의 크기를 바이트 수로 알리는 헤더다. 클라이언트는 이 숫자로 헤더 다음에 오는 글자 가운데 어디까지가 본문인지를 정한다(RFC 9112 6.2절). 글자 수가 아니라 바이트 수라서, UTF-8에서 한 글자가 3바이트인 한글은 글자 수의 세 배가 된다. Content-Length도 없고 다른 길이 표시도 없는 응답은, 서버가 연결을 닫기 전까지 받은 바이트를 본문으로 본다(RFC 9112 6.3절). 그래서 이 경우 연결을 닫지 않는 서버는 본문의 끝을 알릴 수 없다.
이렇게 바꿔 보세요
- 처음 설정으로 — 모양이 맞는 응답. 클라이언트가 상태, 본문의 종류, 본문을 모두 읽어 냅니다.
- 상태 코드를 404로 바꾸면 — 상태만 404로 읽히고, 본문은 그대로 읽힙니다.
- Content-Type 줄을 지우면 — 본문은 읽히지만 본문의 종류를 알려 주는 헤더가 없습니다.
- Content-Length를 본문보다 짧게 적으면 — 적힌 6바이트만 본문으로 읽혀 글이 잘립니다.
- Content-Length 줄을 지우면 — 길이 정보가 없어, 연결이 닫힐 때까지 받은 글자 전부가 본문이 됩니다.
- 첫 줄(상태 줄)을 지우면 — 응답으로 읽히지 않습니다. 헤더와 본문이 맞아도 소용이 없습니다.
처음 설정으로 실행하면
- 클라이언트가 서버에 GET /hello 요청을 보내자, 서버가 요청에 답해 아래와 같은 글자를 돌려보냅니다. 이 글자가 HTTP 응답입니다. 이 실습의 서버는 코드 칸의 글자를 그대로 보내고, 다 보내면 연결을 닫습니다. 응답을 읽는 클라이언트는 Node.js의 http 모듈입니다.
- 클라이언트가 첫 줄을 읽습니다. 읽은 글은 "HTTP/1.1 200 OK"입니다. 첫 줄(상태 줄)에는 세 가지가 차례로 적힙니다. HTTP/1.1은 어떤 규칙으로 쓴 글인지(버전), 200 부분은 요청이 어떻게 됐는지를 알리는 세 자리 숫자(상태 코드), OK는 그 코드를 사람이 읽도록 풀어 쓴 짧은 설명입니다. 200은 요청이 잘 처리됐다는 뜻입니다. 클라이언트가 읽은 상태는 "200 OK"입니다.
- 클라이언트가 다음 줄을 읽습니다. 읽은 글은 "Content-Type: text/plain; charset=utf-8"입니다. "이름: 값" 꼴의 줄을 헤더라고 합니다. Content-Type 헤더는 본문이 어떤 종류의 내용인지를 알려 줍니다. text/plain은 꾸밈없는 글자라는 뜻이고, charset=utf-8은 그 글자를 UTF-8 방식으로 적었다는 뜻입니다.
- 클라이언트가 다음 줄을 읽습니다. 읽은 글은 "Content-Length: 15"입니다. Content-Length 헤더는 본문이 몇 바이트인지를 알려 주고, 클라이언트는 이 숫자로 본문이 어디까지인지를 정합니다. 바이트는 데이터의 크기를 세는 단위(글자 수와는 다를 수 있음)이고, UTF-8에서 한글 한 글자는 3바이트라서 "안녕하세요" 다섯 글자는 15바이트입니다.
- 클라이언트가 다음 줄을 읽었더니 아무 글자도 없는 빈 줄입니다(화면의 (빈 줄)). 빈 줄은 헤더가 끝났다는 표시이고, 다음 글자부터는 본문입니다.
- 클라이언트가 Content-Length에 적힌 15바이트만큼을 본문으로 읽습니다. 읽은 글은 "안녕하세요"이고, 본문이 정확히 여기서 끝납니다. 본문이 어디서 끝나는지를 이 숫자로 압니다.
- 클라이언트가 응답을 다 읽었습니다. 읽어 낸 것은 상태 "200 OK", 본문의 종류 "text/plain; charset=utf-8", 본문 "안녕하세요"입니다.
실행 결과
- HTTP 응답은 서버가 돌려주는 글자 몇 줄입니다. 첫 줄(상태 줄)에 버전, 상태 코드, 짧은 설명을 적고, 그 아래에 "이름: 값" 꼴의 헤더를 적고, 빈 줄 뒤에 본문을 붙입니다.
- 클라이언트는 상태 코드 200으로 요청이 잘 처리됐음을, Content-Type으로 본문이 text/plain 글자임을, Content-Length로 본문이 15바이트임을 알고 본문을 읽었습니다. 읽은 본문은 "안녕하세요"입니다.
코드
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 15
(빈 줄)
안녕하세요
이 모형의 전제
서버는 Node.js 24.18.0의 net 모듈로 띄운 것으로, 연결이 오면 요청을 빈 줄까지 읽은 뒤 코드 칸의 글자를 줄 끝 CRLF로 그대로 써 보내고 연결을 닫습니다(http 모듈로 응답을 만들면 틀린 모양을 보낼 수 없어 쓰지 않았습니다). 본문 "안녕하세요" 뒤에는 아무것도 붙이지 않았고, 이 글자는 UTF-8로 15바이트, 앞의 "안녕"은 6바이트로 재서 확인했습니다. 클라이언트는 Node.js의 http 모듈(실제 HTTP 해석기)로 GET /hello 요청을 보내 응답을 읽었습니다. 열여덟 가지 조합을 모두 실행해, 클라이언트가 읽은 상태(상태 코드와 설명), 받은 content-type 헤더 값, 실제로 받은 본문과 그 바이트 수를 확인해 모형과 대조했고, 화면의 코드가 서버가 보낸 글과 같은 글인지도 조합마다 확인했습니다. 이 클라이언트는 Content-Length가 6이면 앞의 6바이트("안녕")만 본문으로 받았고, 그때 남은 9바이트를 읽다가 해석 오류(HPE_INVALID_CONSTANT)를 하나 냈습니다. 상태 줄이 없는 여섯 조합은 헤더와 본문이 어떻든 모두 같은 해석 오류(HPE_INVALID_CONSTANT, 메시지는 Expected HTTP/, RTSP/ or ICE/)로 응답을 읽지 못했습니다. 이것들은 이 Node.js 버전에서 관찰한 동작입니다. Content-Length가 없을 때 이 서버는 보낸 뒤 연결을 닫기 때문에 클라이언트가 연결이 닫힐 때까지를 본문으로 읽었습니다. 연결을 닫지 않는 서버는 화면에서 다루지 않았지만, 확인용으로 따로 같은 글을 연결을 닫지 않는 서버에 보내 보았더니 클라이언트는 본문 15바이트를 받고도 1.5초가 지나도록 응답이 끝나지 않았습니다(Content-Length가 있을 때는 연결을 닫지 않아도 바로 끝났습니다). 그 이상 오래 기다렸을 때나 다른 클라이언트에서의 동작은 보지 않았습니다. 브라우저가 Content-Type이 없는 본문을 어떻게 추측해 보여 주는지는 이 실습에서 확인하지 않았습니다. 문서로 확인한 것은 RFC 9112의 2.1절(메시지 형식: 시작 줄, 헤더, 빈 줄, 본문), 4절(상태 줄: 버전, 세 자리 상태 코드, 설명), 6.2절과 6.3절(Content-Length가 본문의 바이트 수를 정하고, 길이 정보가 없는 응답은 서버가 연결을 닫기 전에 받은 바이트 수로 본문 길이가 정해진다는 규칙)과 RFC 9110의 8.3절(Content-Type은 본문의 미디어 타입을 알리고, 없으면 받는 쪽이 application/octet-stream으로 가정하거나 데이터를 살펴 종류를 정할 수 있다), 8.6절(Content-Length), 15절(상태 코드는 세 자리 숫자), 15.3.1절(200), 15.5.5절(404)입니다. 화면의 응답은 헤더를 두 개만 남긴 최소한의 모양이고, 본문이 글자인 GET 응답만 다뤘습니다. 서버가 보낸 글에 줄 끝을 CRLF로 쓴 것, 클라이언트로 Node.js를 쓴 것은 이 실습의 선택이며 브라우저는 쓰지 않았습니다. 같은 주제의 세부 동작(요청과 응답의 모양, 상태 코드 전체)은 "세부 동작 실습: HTTP 메시지와 상태 코드"에서 다룹니다.
앞 실습: HTTP 메서드 · 다음 실습: 쿠키