1. 파싱을 하기 전, HTTP의 본질 알아보기
앞선 과정에서 우리는 멀티스레드를 활용해 서버와 클라이언트가 안정적으로 연결되는 순수 TCP 소켓 망을 구축했습니다. 이제 이 도로 위로 '의미 있는 데이터'를 주고받아야 하는데, 이때 사용하는 전 세계적인 약속이 바로 HTTP(HyperText Transfer Protocol)입니다.
메시지를 주고받는 두 주체(클라이언트와 서버) 사이에서 "우리 데이터를 이런 형식과 순서로 교환하자!"라고 미리 정해둔 규칙이자 약속을 의미합니다.
초기에는 HTML 문서만 주고받았지만, 현재는 이미지, 영상, 파일, JSON 형태의 데이터까지 모든 것을 HTTP 메시지에 담아 전송합니다. 거창해 보이지만, HTTP 메시지의 본질은 결국 '정해진 규칙대로 작성된 단순한 텍스트 문자열'입니다. 따라서 WAS를 직접 만든다는 것은, 클라이언트가 보낸 이 긴 텍스트 문자열을 규칙에 맞게 잘라내고 해석(Parsing)하는 과정이라고 볼 수 있습니다.
2. HTTP 메시지의 전체 구조 (CRLF의 중요성)

사진 첨부 : https://hahahoho5915.tistory.com/62#google_vignette
HTTP 메시지는 클라이언트가 보내는 요청(Request)과 서버가 보내는 응답(Response)으로 나뉩니다. 두 메시지의 구조는 거의 동일하며, 크게 세 부분으로 이루어져 있습니다.
header-field: field-value \r\n
header-field: field-value \r\n
\r\n (Empty Line - 헤더와 바디를 구분하는 매우 중요한 빈 줄!)
message-body
여기서 CRLF(Carriage Return + Line Feed, \r\n)는 줄바꿈을 의미합니다. 파서를 구현할 때 헤더가 끝났음을 어떻게 알 수 있을까요? 바로 '연속된 두 번의 CRLF(빈 줄)'이 등장하는 지점이 헤더의 끝이자 바디의 시작입니다.
3. Start Line (시작 줄)
시작 줄은 메시지의 종류에 따라 형태가 다릅니다. 클라이언트가 보내면 Request Line, 서버가 보내면 Status Line이 됩니다.
① 클라이언트의 Request Line
형식: 메서드(Method) SP 요청URI SP HTTP버전 CRLF (SP = Space, 공백 1칸)
[ HTTP 주요 메서드 ]
| 메서드 | 역할 | 특징 및 실무 디테일 |
|---|---|---|
| GET | 자원 조회 | 서버에 데이터를 요청할 때 사용합니다. 데이터를 Body가 아닌 URL(쿼리 파라미터)에 담아 보내기 때문에 데이터가 외부로 노출되며, 캐싱이 가능합니다. |
| POST | 자원 생성 및 데이터 처리 | 로그인 시 주로 POST를 사용하는 이유: 아이디나 비밀번호 같은 민감한 정보가 URL에 노출되면 안 되기 때문입니다. POST는 데이터를 URL이 아닌 Message Body 내부에 숨겨서 전달하므로 상대적으로 안전(HTTPS 적용 시 암호화됨)하며 대용량 데이터 전송에 적합합니다. |
| PUT | 자원 전체 수정 | 요청한 URI의 자원을 완전히 새로운 데이터로 덮어씌웁니다. |
| PATCH | 자원 부분 수정 | PUT과 달리 자원의 특정 부분(예: 회원의 비밀번호만)을 변경할 때 사용합니다. |
| DELETE | 자원 삭제 | 서버에 있는 특정 자원의 삭제를 요청합니다. |
[ HTTP 버전 ]
| 버전 | 특징 |
|---|---|
| HTTP/1.1 | 가장 널리 쓰이는 기본적인 버전입니다. 연결을 유지하는 Keep-Alive 기능이 기본값이며, 우리가 순수 Java로 WAS를 구현할 때 파싱의 기준이 되는 버전입니다. |
| HTTP/2 | 1.1의 성능을 개선하여 하나의 연결로 여러 메시지를 동시에 주고받는 멀티플렉싱(Multiplexing)을 지원합니다. |
| HTTP/3 | TCP 대신 속도가 빠른 UDP 기반의 QUIC 프로토콜을 사용하여 통신 속도를 비약적으로 끌어올렸습니다. |
② 서버의 Status Line
형식: HTTP버전 SP 상태코드(Status-Code) SP 상태메시지(Reason-Phrase) CRLF
| 상태 코드 대역 | 의미 | 대표 예시 |
|---|---|---|
| 1xx (Informational) | 요청이 수신되어 처리 중 | 100 Continue |
| 2xx (Successful) | 요청이 성공적으로 처리됨 | 200 OK, 201 Created |
| 3xx (Redirection) | 완료를 위해 클라이언트의 추가 작업 필요 | 301 Moved Permanently |
| 4xx (Client Error) | 클라이언트의 잘못된 요청으로 처리 불가 | 400 Bad Request, 404 Not Found |
| 5xx (Server Error) | 서버 내부의 문제로 요청 처리 실패 | 500 Internal Server Error |
4. Header (헤더)
헤더는 HTTP 전송에 필요한 부가 정보(메시지 크기, 압축, 인증, 클라이언트 정보 등)를 담고 있습니다. RFC-7230에 정의된 스펙을 따릅니다.
형식: field-name: OWS field-value OWS CRLF
1.
field-name 뒤에는 띄어쓰기 없이 곧바로 콜론(:)이 붙어야 합니다.2. 콜론과
field-value 사이에는 띄어쓰기(OWS, Optional White Space)가 있어도 되고 없어도 됩니다.3.
field-name은 대소문자를 구분하지 않지만(Case-insensitive), field-value는 대소문자를 엄격히 구분합니다.예시: Host: localhost:8080
5. Message Body (바디)
요청이나 응답의 실제 데이터(Payload)가 담기는 곳입니다. HTML 문서, 이미지 바이트, JSON 객체 등 전송할 수 있는 모든 데이터가 들어갑니다.
하지만 모든 HTTP 메시지가 바디를 가지는 것은 아닙니다. 다음과 같은 경우에는 바디가 생략됩니다.
- 클라이언트가
HEAD메서드로 요청했을 때 (GET과 같지만 상태 줄과 헤더만 반환) - 상태 코드가
1xx (정보),204 (No Content),304 (Not Modified)인 응답 메시지
바디의 크기는 보통 헤더의 Content-Length 값에 명시되어 있으며, 파서를 만들 때는 이 값을 읽어 바디의 끝을 판단하게 됩니다.
6. 다음 편 예고: 직접 HTTP 파서(Parser) 구현하기
우리가 일상적으로 쓰는 스프링의 @GetMapping이나 @RequestBody 같은 편리한 애노테이션들은 결국 톰캣(Tomcat) 같은 WAS가 이 지저분한 텍스트 문자열을 대신 읽어서 Java 객체(HttpServletRequest)로 변환해 주었기 때문에 가능한 일입니다.
이제 적(HTTP 스펙)의 생김새를 완벽하게 파악했으니, 다음 포스팅에서는 이전 편에서 만든 멀티스레드 소켓 서버에 순수 Java 코드로 HTTP 메시지를 한 줄씩 파싱하는 로직을 직접 구현해 보겠습니다.
'정리 > WAS' 카테고리의 다른 글
| 순수 Java로 WAS 구현 (6) - 프레임워크의 마법, 리플렉션(Reflection) 완벽 이해 (0) | 2026.05.15 |
|---|---|
| 순수 Java로 WAS 구현 (5) - HTTP 요청 파서(Parser) 구현과 3가지 트러블슈팅 (0) | 2026.05.15 |
| 순수 Java로 WAS 구현 (3) - 완벽한 양방향 통신 구현과 치명적인 삽질의 기록 (0) | 2026.05.13 |
| 순수 Java로 WAS 구현 (2) - 블로킹(Blocking) 해결을 위한 멀티스레드(Multi-Thread)의 이해 (0) | 2026.05.13 |
| 순수 Java로 WAS 구현 (1) - WAS의 심장, 소켓(Socket) 프로그래밍 (0) | 2026.05.12 |