오늘 헷갈린 지점은
- 내 HelloServlet#doGet() 안에서는 resp만 쓰고 req는 안 썼다.
- 그런데도 GET 요청이 잘 처리된다.
- 그럼 req는 대체 어디서 쓰인 건가
- “톰캣이 만든다/전달한다” 같은 추상 얘기 말고, 코드 레벨로 어디서 쓰였는지가 궁금하다.
결론부터 박는다.
req는 드에서 안 써도, 이미 Tomcat/Servlet API 내부에서 최소 1번은 무조건 쓰인다.
그 지점이 바로 HttpServlet#service(req, resp) 안의 req.getMethod()다.
그래서 GET이면 doGet()으로 들어올 수 있고, 그 안에서 네가 req를 안 읽어도 “Hello World”만 쓰면 되니 잘 돌아간다.
“req 없이도” 동작하는 이유
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServerException, IOException {
PrintWriter out = resp.getWriter();
out.print("Hello World!");
}
}
이 코드가 하는 일은 단 하나다.
- 응답 본문에 "Hello World!"를 쓴다.
여기엔 요청 URL, 쿼리, 헤더, 파라미터가 전혀 필요 없다.
그러니까 req.getParameter() 같은 걸 “꺼낼 필요”가 없다.
하지만 “코드에서 필요 없었다”와 “Tomcat 내부에서 아예 안 쓰였다”는 다르다.
req가 정확히 어디서 쓰였나: 핵심 한 줄
req가 “직접 안 꺼내도” 쓰이는 대표 지점은 여기다.
- javax.servlet.http.HttpServlet#service(HttpServletRequest req, HttpServletResponse resp)
- 여기서 req.getMethod() 호출
- "GET"이면 doGet(req, resp) 호출
- "POST"면 doPost(req, resp) 호출
즉, doGet()까지 들어온 순간,
Tomcat/Servlet 내부가 이미 req.getMethod()를 읽어서 doGet()으로 분기한 결과다.
그래서 “req 안 읽었는데?”라고 느끼는 건 맞고,
그렇다고 req가 안 쓰인 건 아니다. 내부에서 이미 최소 1번 쓰였다.
“그럼 request는 언제 만들어지고 값은 언제 채워지나?”
(1) Tomcat이 소켓에서 HTTP 요청을 받는다
- TCP 소켓에서 바이트를 읽는다
- 요청 라인/헤더/바디를 파싱한다 (GET /hello HTTP/1.1 같은 거)
(2) 그 파싱 결과를 “요청 객체”에 채운다
- 메서드, URI, 헤더, 파라미터 등이 요청 객체에 저장된다
- 이 객체가 서블릿 입장에서 보이는 HttpServletRequest(= req)다
(실제로는 내부 구현체가 있고, 서블릿에 전달될 땐 파사드 형태로 보이는 경우가 많다.)
(3) 그 다음에 서블릿 호출로 넘어간다
- “어떤 URL이 어떤 서블릿이냐” 매핑을 찾는다
- 필터 체인을 거친다
- 최종적으로 servlet.service(req, resp)가 호출된다
즉 결론:
req는 service()보다 먼저 만들어지고, 먼저 값이 채워진다.
service()는 “이미 채워진 req”를 받아서 처리 흐름을 분기한다.
req는 톰캣이 쓰고 resp는 사용자가 쓰는 건가?
계속 꼬였던 이유는, 이게 “둘 중 하나”가 아니라 둘 다이기 때문이다.
정확히 말하면:
- req는 Tomcat 내부에서도 쓰이고, 사용자 코드에서도 쓰인다
- resp도 Tomcat 내부에서도 쓰이고, 사용자 코드에서도 쓰인다
다만 보통 체감이 이렇게 갈린다.
- 사용자 코드는 응답을 만들어야 하니까 resp는 거의 항상 쓴다
- 반면 요청값이 필요 없는 단순 응답이면 req는 안 쓸 수도 있다
그래서 나는 resp만 쓰는 서블릿을 만들었고, 그게 정상인 거다.
“내가 req를 안 썼는데도” req가 내부에서 쓰인 포인트들
1) 반드시 쓰이는 포인트: req.getMethod() (분기)
- HttpServlet#service() 안에서 실행됨
- "GET"인지 "POST"인지 판단해야 doGet/doPost를 부를 수 있으니 필수다
2) URL 매핑 단계에서도 사실상 쓰인다 (경로 정보)
- Tomcat은 “어떤 서블릿이 이 요청을 처리할지” 결정해야 한다
- 이때 요청의 URI/경로 정보를 보고 매핑한다
- 즉 req에 들어있는 경로 정보는 내부에서 “매핑 판단”에 이미 활용된다
단, 내가 직접 req.getRequestURI()를 호출하지 않았다는 건
“그 값을 네 코드가 꺼내지 않았다”는 의미일 뿐, 내부에서 참조될 수 있다.
왜 doGet()만 오버라이드하면 되나
Tomcat 내부에서 service()가 분기한다는 걸 이해하면 답이 바로 나온다.
- 나는 doGet()을 오버라이드했다
- 내부 service()가 GET 요청을 보고 doGet()을 호출한다
- 그래서 내 코드가 실행된다
반대로,
- GET 요청인데 doPost()만 오버라이드하면
- service()는 doGet()을 호출하려고 하는데
- 내가 doGet()을 안 바꿨으면 기본 구현이 실행되고 보통 405 같은 결과로 이어질 수 있다
정리
req는 “요청 정보를 담는 객체”이고, 그 정보는 네가 안 꺼내도 Tomcat/Servlet 내부가 최소한 메서드 분기(req.getMethod()) 같은 용도로 먼저 꺼내 쓴다.
그 후 doGet()에 들어오면, 그때부터는 네가 필요할 때만 req에서 값을 더 꺼내 쓰면 된다.
resp도 마찬가지로 내부/사용자 모두 쓰지만, 사용자는 보통 응답을 만들어야 해서 resp를 항상 만지는 편이다.
이해 확인용: req를 진짜 써보기
req가 “요청값을 꺼내는 통로”라는 것을 직접 이해
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String uri = req.getRequestURI();
String method = req.getMethod();
resp.setContentType("text/plain; charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("method=" + method);
out.println("uri=" + uri);
}
여기서 req.getMethod()는
- 내부 service()에서도 이미 썼던 값이고,
- 내가 여기서도 다시 꺼내 쓰는 것뿐이다.
'Backend > Servlet' 카테고리의 다른 글
| forward와 JSP 실행 흐름 (0) | 2026.02.06 |
|---|---|
| 멀티스레드 환경에서 Servlet 사용 (0) | 2026.02.06 |
| Servlet Container와 Servlet의 관계 (0) | 2026.02.05 |
| RequestMapping static Map은 괜찮은 이유 (0) | 2026.02.05 |
| 서블릿에서의 static (0) | 2026.02.05 |