TCP상에서 HTTP 뜯어보기: HTTP Message

2025. 10. 22. 14:35·클라우드컴퓨팅·네트워크

TCP/IP 4계층의 관점에서 HTTP는 TCP의 인터페이스를 이용하는 상위 프로토콜입니다. 이 글은 TCP 통신에서 직접 HTTP 규격에 맞게 HTTP Message를 작성해 전송해보면서 프로토콜의 구성에 대해 연구한 기록을 다룹니다.

 

 

TCP에서 직접 HTTP Message를 쏴봅시다

TCP/IP Protocol Layer
TCP/IP 4계층

 

진행중인 프로젝트에 필요한 소켓 통신을 더 잘 구현하기 위해 TCP 통신에 대해 연구하다 보니, 문득 TCP레벨에서 통신하는 서버에서 HTTP 요청을 성공적으로 처리해보고싶어졌습니다.

 

냅다 보내보기

서버가 TCP로 listen하고 있는 포트를 향해 브라우저에서 GET 요청을 보내보았습니다.

서버가 받은 데이터는 아래와 같습니다.

GET / HTTP/1.1
Host: localhost:52536
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Whale";v="3", " Not;A Brand";v="99", "Chromium";v="106"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.114 Whale/3.17.145.12 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7

 

이렇게 평문으로 구성된 HTTP Message를 확인했습니다. 메서드, 프로토콜, 호스트 등등 헤더도 잘 포함돼있습니다.

HTTP 메시지는 Chrome devtools, Postman, curl 등에서 자주 접하기 때문에 익숙하지만, 날 것 그대로의 HTTP 메시지를 TCP통신을 통해 확인한 경험은 조금 감회가 새로웠습니다.

 

비연결성, 무상태성을 확인

그리고 이 때 서버의 콘솔에서 눈길을 끈 점이 하나 있습니다. 브라우저의 요청을 받음으로써 서버가 열었던 소켓은, 서버가 응답 전송을 마침과 동시에 끊겼습니다. 이것이 HTTP의 특징인 비연결성(Connectionless), 무상태성(Stateless)입니다.

  1. 연결을 초기화(3-way-handshake)
  2. 요청이 담긴 세그먼트를 서버로 송신
  3. 응답이 담긴 세그먼트를 서버로부터 수신
  4. 연결 종료(4-way-handshake)

의 과정을 거친 것을 직접 확인하였습니다.

 

HTTP Body를 작성

HTTP의 `GET` 요청에는 Header만 있고 Body는 없습니다. 바디도 확인해보기 위해 `POST` 요청도 한 번 보내보았습니다.

POST 요청 전송

Postman을 사용해서 요청을 보냈더니 이전의 GET 요청에서 보이던 브라우저정보에 대한 헤더나 다른 여러 헤더들이 사라진 것이 보입니다.

POST / HTTP/1.1
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Postman-Token: 85d4ac69-2a4d-456b-81d7-dda594b2cd6c
Host: localhost:52536
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 17

network-test=test

 

Content-Type이라는 반가운 헤더가 보이네요. 그 아래에 있는 Content-Length라는 헤더는 payload의 길이를 정의하고있습니다.

MDN(mozilla developer network)의 문서에 정의된 HTTP 메시지 규격과 비교해보기 위해 인용을 첨부합니다.

HTTP 요청과 응답의 구조는 서로 닮았으며, 그 구조는 다음과 같습니다.

1. 시작 줄('start-line')에는 실행되어야 할 요청, 또은 요청 수행에 대한 성공 또는 실패가 기록되어 있습니다. 이 줄은 항상 한 줄로 끝납니다.
2. 옵션으로 'HTTP 헤더' 세트가 들어갑니다. 여기에는 요청에 대한 설명, 혹은 메시지 본문에 대한 설명이 들어갑니다.
3. 요청에 대한 모든 메타 정보가 전송되었음을 알리는 빈 줄('blank line')이 삽입됩니다.
4. 요청과 관련된 내용(HTML 폼 콘텐츠 등)이 옵션으로 들어가거나, 응답과 관련된 문서가 들어갑니다. 본문의 존재 유무 및 크기는 첫 줄과 HTTP 헤더에 명시됩니다.

HTTP 메시지의 시작 줄과 HTTP 헤더를 묶어서 '요청 헤드(head)' 라고 부르며, 이와 반대로 HTTP 메시지의 페이로드는 '본문(body)'이라고 합니다.

 

위에서 받은 데이터와 정의된 메시지 규격이 (당연히) 맞아떨어지는 것을 확인할 수 있습니다.

 

HTTP 메시지 규격 맞춰 응답 전송하기

이번에는 HTTP 응답에 대한 인용을 첨부합니다. 상세한 내용은 중략하였습니다.

HTTP 응답의 시작 줄은 '상태 줄(status line)'' 이라고 불리며, 일반적으로 HTTP/1.1 404 Not Found. 같이 표현됩니다.
응답에 들어가는 HTTP 헤더는 다른 헤더와 동일한 구조를 따릅니다.
본문은 응답의 마지막 부분에 들어갑니다. 넓게 보면 본문은 세가지 종류로 나뉩니다.
...
3. Content-Type와 Content-Length라는 두 개의 헤더로 정의하는 길이가 알려진 하나의 파일로 구성된 단일-리소스 본문(Single-resource bodies).

 

이를 토대로 HTTP 응답 메시지를 직접 작성하고, Postman으로 보낸 POST 요청에 대한 응답으로 전송해보겠습니다.

본문은 세 번째 방식으로 작성한 HTTP Message의 전체는 아래와 같습니다.

HTTP/1.1 200 OK
Content-Length: 53
Content-Type: text/html

<html>
<body>
<h1>안녕 HTTP!!!</h1>
</body>
</html>

 

 

서버로 들어오는 소켓 연결에 대해, 이 HTTP메시지를 전송하도록 하였습니다.

 

헤더의 역할을 확인

그리고 브라우저에서 서버로 요청을 보낸 결과는,

charset이 utf-8로 설정되지 않아 깨진 한글
덈뀞 뭐요?

 

HTML이며 H1태그까지, 전송하고자 하는 바가 전송은 잘 된 것 같지만 한글 데이터가 깨져보이네요.

우리가 전송한 데이터는 네트워크 계층을 타고 타고 내려가면서 이진 데이터로 바뀌었다가 다시 TCP 또는 HTTP 계층까지 올라옵니다. 이 때, 수신자는 무결한 데이터가 도착하긴 했지만 이 데이터가 어떤 형식인지, 어떻게 읽어야할 지 모릅니다.

그러니 이걸 어떻게 읽어야할 지를 알리는 `Content-Type` 헤더를 추가해주었습니다.

 

`Content-Type: text/html; charset=UTF-8` 헤더를 추가하고 다시 한 번 시도해보았습니다.

드디어 인사를 해주네요.

 

이렇게 해서 "안녕 HTTP!!!" 라는 정적 페이지를 제공한다는 점에 한정해서, TCP 인터페이스를 이용해서 HTTP서버를 구현한 것이나 다름 없게 되었습니다.

 

헤더와 맞지 않는 데이터 보내보기

TCP는 데이터를 시작과 끝이 있는 '메시지' 정도의 개념이 아닌, 방향대로 흐르는 '스트림'으로 취급합니다. 그래서 통신 과정에서 TCP와 그 아랫단까지 다녀온 이 데이터가 응용 계층에서 다시 의도했던 의미를 가지려면 어디까지 끊어읽어야하는지에 대한 메타데이터가 필요합니다. 그 메타데이터에 해당하는 헤더가 Content-Length입니다.
이제 Content-Length에 기반해서 데이터를 처리하는 것을 직접 확인해보려고 합니다. 이전 본문을 그대로 쓰면서, 본문의 실제 길이와 다른 Content-Length를 담아서 메시지를 전송해보겠습니다.

HTTP/1.1 200 OK
Content-Length: 100
Content-Type: text/html; charset=UTF-8

<html>
<body>
<h1>안녕 HTTP!!!</h1>
</body>
</html>
  • 본문의 실제 길이: 53byte
  • 헤더에서 알려준 길이: 100byte

그리고 그 결과는 아래와 같습니다.

동그라미친 부분을 보면 페이지 로드가 완료되지 않았고, Chrome devtools의 network탭에서는 (pending) 상태로 나머지 데이터가 마저 들어오기를 기다리고 있는 것을 볼 수 있었습니다.

 

이번에는 반대로 Content-Length를 실제 본문보다 작게 해보았습니다.

</ 요?


HTML 뒷부분의 닫는 태그 이후가 수신되지 못했나봅니다. 태그로 인식되지 못하고 평문으로 표현되었습니다.

이처럼 Content-Length에 정해진 크기를 초과하는 데이터는 버려진 걸 확인할 수 있습니다.

 

이만하겠습니다. 유익한 삽질이었습니다.

'클라우드컴퓨팅·네트워크' 카테고리의 다른 글

[개인서버구축일지](3) Oracle Cloud Platform에 신세지기  (0) 2026.03.10
[Web] Redirect, Proxy와 Forward의 개념과 차이점  (0) 2026.02.06
OSI 7 계층 간략히 이해하기  (0) 2026.02.03
[개인서버구축일지](1) 클라우드와 온프레미스 사이의 선택  (1) 2026.01.23
[HTTP 보안] JWT 저장 방식별 장단점  (0) 2026.01.23
'클라우드컴퓨팅·네트워크' 카테고리의 다른 글
  • [Web] Redirect, Proxy와 Forward의 개념과 차이점
  • OSI 7 계층 간략히 이해하기
  • [개인서버구축일지](1) 클라우드와 온프레미스 사이의 선택
  • [HTTP 보안] JWT 저장 방식별 장단점
devracoon
devracoon
  • devracoon
    개발하는 너굴맨
    devracoon
  • 링크

    • 분류 전체보기 (19)
      • 개발 (9)
      • 언어·프레임워크·데이터베이스 (1)
      • 자료구조·알고리즘·컴퓨터구조 (1)
      • 클라우드컴퓨팅·네트워크 (6)
      • 티스토리 (1)
  • 인기 글

  • 태그

    docker
    short-url
    티스토리
  • hELLO· Designed By정상우.v4.10.6
devracoon
TCP상에서 HTTP 뜯어보기: HTTP Message
상단으로

티스토리툴바