1. 도입부 (Introduction)
서블릿 컨테이너(톰캣)가 웹 브라우저의 HTTP 요청을 처리할 때 내부적으로 가장 먼저 수행하는 작업은 물리적 네트워크 스트림을 개발자가 다루기 쉬운 자바 객체로 포장하는 것이다. 이때 탄생하는 양대 산맥이 바로 HttpServletRequest와 HttpServletResponse다.
대부분의 현대 개발자들은 스프링 MVC가 지원하는 @RequestParam, @RequestBody, ResponseEntity 등의 편리한 애노테이션 뒤에 숨겨진 서블릿 객체를 잊고 산다. 하지만 이 서블릿 객체들의 생명주기와 세부 API 동작 방식을 완벽히 이해하지 못하면, 멀티파트 파일 업로드 처리, 글로벌 필터 단에서의 인증 예외 제어, 혹은 응답 버퍼 관리와 같은 고급 문제를 해결할 때 한계에 부딪힌다.
이번 글에서는 HTTP 요청과 응답 메시지가 이 두 객체 내부에서 어떻게 표현되고 데이터화되는지 코드 수준까지 꼼꼼히 파헤쳐 보겠다.
[Spring] Web Server, WAS, Servlet 그리고 Tomcat 핵심 원리 파악
웹 개발을 하다 보면 가장 먼저 마주치는 개념들이 있다. "웹 서버(Web Server)와 웹 애플리케이션 서버(WAS)의 차이는 무엇일까?", "톰캣(Tomcat)은 정확히 어떤 일을 할까?", "서블릿(Servlet)은 왜 필요할
myblog01150.tistory.com
2. HttpServletRequest의 구조와 핵심 기능
HttpServletRequest는 클라이언트가 서버로 전송한 HTTP 요청 메시지를 서블릿 컨테이너가 파싱하여 제공하는 자바 객체다. 단순히 메시지 텍스트를 파싱하는 것을 넘어, 임시 저장소 역할과 세션 관리 기능까지 갖춘 다재다능한 객체다.
📌 HTTP 요청 메시지 구성 요소 매핑
HTTP 요청 메시지는 크게 Start Line, Header, Body로 이루어져 있다. HttpServletRequest 객체는 이 각각의 구성 요소를 편리하게 조회할 수 있는 메서드를 제공한다.

- Start Line 정보 조회
- HTTP 메소드: request.getMethod() (GET, POST, PUT, DELETE 등)
- URL 및 URI: request.getRequestURL(), request.getRequestURI()
- 쿼리 스트링: request.getQueryString()
- 프로토콜 및 스키마: request.getProtocol(), request.getScheme()
- 헤더(Header) 정보 조회
- 특정 헤더값 가져오기: request.getHeader("Header-Name")
- 모든 헤더 이름 목록 가져오기: request.getHeaderNames()
- 바디(Body) 정보 조회
- 메시지 바디 데이터를 읽기 위한 원시 입력 스트림 반환: request.getInputStream()
📌 부가 기능 1: 임시 저장소 기능 (Request Attributes)
해당 HTTP 요청이 시작되어 응답이 끝날 때까지(Request의 생명주기 동안) 유지되는 임시 저장소 역할을 수행한다. 하나의 요청 흐름 안에서 필터(Filter), 인터셉터(Interceptor), 컨트롤러(Servlet) 간에 데이터를 전달할 때 매우 유용하다.
- 데이터 저장: request.setAttribute(name, value)
- 데이터 조회: request.getAttribute(name)
📌 부가 기능 2: 세션 관리 기능 (Session Management)
HTTP는 무상태(Stateless) 프로토콜이므로 클라이언트의 이전 상태를 유지하지 못한다. 이를 극복하기 위해 클라이언트와 서버 간의 연결 상태를 유지하는 '세션(Session)'을 다루는 기능을 제공한다.
- 세션 조회 및 생성: request.getSession(true) (기존 세션이 없으면 새로 만들어 반환한다.)
3. HTTP 요청 데이터 전달 방식 3가지와 파싱 기법
클라이언트가 서버로 데이터를 전송하는 방식은 크게 세 가지로 나뉜다. 서블릿은 이 각각의 방식을 처리하기 위한 명확한 수단을 가지고 있다.
① GET - 쿼리 파라미터 (Query Parameter)
- 특징: 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함시켜 전송한다. 주로 검색, 필터링, 페이징에 쓰인다.
- 예시: /url?username=hello&age=20
서블릿은 다음과 같은 파라미터 조회 메서드를 제공한다.
// 단일 파라미터 조회 (동일한 키가 여러 개일 경우 첫 번째 값을 반환)
String username = request.getParameter("username");
// 파라미터 이름들 전체 조회
Enumeration<String> parameterNames = request.getParameterNames();
// 모든 파라미터를 Map 구조로 조회
Map<String, String[]> parameterMap = request.getParameterMap();
// 동일한 키로 전송된 복수 파라미터 조회
String[] usernames = request.getParameterValues("username");
② POST - HTML Form 전송
- 특징: 메시지 바디에 쿼리 파라미터 형식으로 데이터를 동봉해 보낸다.
- Content-Type: application/x-www-form-urlencoded
- 바디 데이터 예시: username=hello&age=20
<form action="/request-param" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
💡 GET 쿼리 스트링과 POST HTML Form의 파라미터 조회 방식이 같은 이유
서블릿 컨테이너(톰캣)는 application/x-www-form-urlencoded 형식으로 들어오는 바디 데이터를 내부적으로 파싱하여 쿼리 스트링과 동일한 구조로 메모리에 보관한다. 따라서 개발자는 GET 방식이든 POST Form 방식이든 상관없이 똑같이 request.getParameter() 메서드로 데이터를 편리하게 조회할 수 있다.
⚠️ Content-Type 지식 체크
GET 방식은 메시지 바디를 사용하지 않으므로 전송 데이터 형식을 나타내는 Content-Type 헤더가 존재하지 않는다.
반면 POST HTML Form 전송 방식은 메시지 바디에 데이터를 포함하므로 반드시 Content-Type: application/x-www-form-urlencoded 헤더가 명시되어야 서버가 올바르게 바디를 읽어 파라미터로 변환할 수 있다.
③ HTTP Message Body 직접 요청 (API 방식 - TEXT & JSON)
현대 백엔드 개발(HTTP API)에서 가장 많이 사용하는 방식으로, POST, PUT, PATCH 메서드와 함께 JSON, XML, TEXT 형태의 데이터를 직접 메시지 바디에 실어 보낸다. 주로 JSON을 표준으로 채택한다.
A. 단순 텍스트(TEXT) 읽기
바디에 실린 데이터를 원시 바이트 코드로 직접 꺼내와 문자열로 변환한다. 자바에서 바이트 코드를 문자로 변환할 때는 반드시 명시적인 문자표(Charset)를 지정해야 올바르게 인코딩된다.
// 원시 바이트 스트림 획득
ServletInputStream inputStream = request.getInputStream();
// 스프링 프레임워크가 제공하는 StreamUtils를 활용해 UTF-8 기준으로 문자열 변환
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
B. JSON 데이터 읽기 및 객체 매핑
단순 문자열로 변환된 JSON 데이터를 자바 객체(DTO)로 다루기 위해 Jackson 라이브러리의 ObjectMapper를 활용하여 파싱한다.
// JSON 데이터를 매핑받을 DTO 정의
public class HelloData {
private String username;
private int age;
// Getter & Setter 생략
}
// 서블릿 내부 처리 로직
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1. InputStream에서 문자열 꺼내기
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
// 2. ObjectMapper를 사용해 JSON 문자열을 자바 객체로 즉시 변환
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
System.out.println("helloData.username = " + helloData.getUsername());
System.out.println("helloData.age = " + helloData.getAge());
}
4. HttpServletResponse의 역할과 핵심 기능
HttpServletResponse 객체는 서블릿 컨테이너가 클라이언트에게 보낼 HTTP 응답 메시지를 생성하는 전권을 가진다. 개발자는 이 객체를 제어하여 HTTP 응답 코드(Status Code), 응답 헤더, 응답 바디를 직접 채워 넣는다.

📌 종합 응답 제어 서블릿 예제
응답 헤더를 다루는 표준적인 방법과 다양한 편의 메서드(Content 설정, 쿠키 추가, 리다이렉트 처리)의 실전 코드를 살펴보자.
@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. Status Line 제어 (HTTP 응답 코드 지정)
response.setStatus(HttpServletResponse.SC_OK); // 200 OK
// 2. Response Headers 제어
response.setHeader("Content-Type", "text/plain;charset=utf-8");
// 캐시를 완전히 무효화하여 항상 새로운 데이터를 읽도록 설정
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
// 사용자 정의 커스텀 헤더 지정
response.setHeader("my-header", "hello");
// 3. 편의 메서드를 통한 부가 헤더 설정
content(response);
cookie(response);
redirect(response);
// 4. Message Body 작성
PrintWriter writer = response.getWriter();
writer.println("ok");
}
// Content 편의 메서드
private void content(HttpServletResponse response) {
// response.setHeader("Content-Type", "text/plain;charset=utf-8")과 동일한 동작 수행
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
// response.setContentLength(2); // 생략할 경우 버퍼 크기를 기준으로 자동 계산 및 생성됨
}
// Cookie 편의 메서드
private void cookie(HttpServletResponse response) {
// response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600")를 객체지향적으로 작성
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600); // 쿠키의 유효기간 설정 (600초)
response.addCookie(cookie);
}
// Redirect 편의 메서드
private void redirect(HttpServletResponse response) throws IOException {
// 원래 수동으로 하려면 아래 두 줄을 작성해야 한다.
// response.setStatus(HttpServletResponse.SC_FOUND); // 302 Found
// response.setHeader("Location", "/basic/hello-form.html");
// sendRedirect 메서드 한 줄로 302 리다이렉션 완성
response.sendRedirect("/basic/hello-form.html");
}
}
5. HTTP 응답 데이터 종류별 구현 (TEXT, HTML, JSON)
응답 바디에 전송할 데이터 타입에 따른 서블릿 측 렌더링 구현 방식을 정리한다.
① 단순 텍스트 응답 (TEXT)
컨텐츠 타입을 일반 텍스트(text/plain)로 고정한 뒤 응답 스트림에 직접 문자를 쓴다.
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
response.getWriter().println("ok");
② HTML 응답
컨텐츠 타입을 HTML(text/html)로 정한 뒤 스트림에 직접 HTML 마크업 태그를 동적으로 빌드하여 내려보낸다.
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<body>");
writer.println(" <h1>안녕 서블릿 동적 HTML 응답이야</h1>");
writer.println("</body>");
writer.println("</html>");
③ HTTP API JSON 응답
오늘날 프론트엔드-백엔드 간 협업 구조에서 가장 중요한 응답 방식이다. 자바 객체를 JSON 텍스트로 직렬화(Serialization)하여 전달한다.
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// HTTP 응답으로 JSON을 보낼 때는 content-type을 반드시 application/json으로 명시한다.
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
// 전송할 데이터 객체 생성
HelloData data = new HelloData();
data.setUsername("kim");
data.setAge(20);
// Jackson ObjectMapper를 이용해 자바 객체를 JSON 문자열로 변환
// 결과: {"username":"kim","age":20}
String result = objectMapper.writeValueAsString(data);
// 응답 메시지 바디에 가공한 JSON 주입
response.getWriter().write(result);
}
6. 마치며 ✍️
웹 애플리케이션의 동작 원리는 본질적으로 '요청과 응답'이라는 두 개의 톱니바퀴에 의해 굴러간다.
- HttpServletRequest는 복잡하게 전달된 원시 네트워크 패킷을 말끔하게 정렬하여 파싱된 정보를 자바 객체 형식으로 전달해 주는 고마운 우편 집배원이다.
- HttpServletResponse는 개발자가 원하는 비즈니스 가공물을 프로토콜 표준에 알맞게 포장하여 클라이언트의 브라우저에게 안전하게 돌려주는 유능한 수출 전담 기사다.
스프링 프레임워크가 제공하는 화려한 추상화 기술에 안주하기보다는, 그 근간에 흐르는 이 원시 서블릿 명세들을 정확히 이해하는 것도 중요하다.
'Spring > MVC' 카테고리의 다른 글
| [Spring MVC] Bean Validation과 전송 객체(DTO) 분리: 실무형 검증 및 예외 설계 정리 (0) | 2026.05.27 |
|---|---|
| [Spring MVC] 검증(Validation) 정리 : 수동 검증부터 @Validated와 현대적 전역 API 검증까지 (0) | 2026.05.26 |
| [Spring MVC] 프론트 컨트롤러 패턴 도입부터 어댑터(V5), 로깅, 요청 매핑까지 완벽 정리 (0) | 2026.05.22 |
| [Spring MVC] 역할 분담을 위한 MVC 패턴의 구조와 설계 (0) | 2026.05.21 |
| [Spring MVC] Web Server, WAS, Servlet 그리고 Tomcat 핵심 원리 파악 (1) | 2026.05.19 |