서블릿 컨테이너는 클라이언트로부터 요청이 들어오면, 해당 요청 정보를 담은 HttpServletRequest 객체를 생성하여 서블릿의 service() 또는 doGet(), doPost() 메서드에 전달한다.
이 객체는 HTTP 요청에 포함된 파라미터, 헤더, URI 등 다양한 정보를 제공하며, 개발자는 이를 통해 요청 데이터를 추출하거나 클라이언트 정보를 파악할 수 있다.
HttpServletRequest는 인터페이스이므로, 실제 구현은 서블릿 컨테이너가 제공한다. Tomcat에서는 RequestFacade 클래스를 통해 구현되어 있으며, 내부적으로 Request 객체와 연결되어 있다.
public class RequestFacade implements HttpServletRequest {
protected Request request;
public String getParameter(String name) {
checkFacade();
return request.getParameter(name);
}
}
Tomcat에서는 RequestFacade가 HttpServletRequest 인터페이스를 구현하며, 내부적으로 Request 객체를 감싸는 구조를 가진다.
이처럼 내부의 복잡한 동작을 감추고 외부에는 단순한 인터페이스만을 제공하는 구조를 퍼사드(Facade) 패턴이라 한다. 해당 내용에 대해서는 좀 더 자세히 공부한 이후에 다루어 보겠다.
개발자는 내부 구조나 흐름을 모두 알지 않아도,
getParameter()와 같은 메서드를 통해 필요한 기능만 사용할 수 있다.
서블릿을 사용할 때 web.xml 파일 대신 어노테이션을 통해 URL 매핑을 선언할 수 있게 해주는 것이 @WebServlet이다.
어노테이션이란, 코드에 메타데이터(정보)를 추가하여 시스템이나 프레임워크가 이를 해석하고 동작할 수 있도록 돕는 문법 요소이다.
이로 인해 개발자는 별도의 설정 파일 없이, 클래스 위에 간단한 선언만으로 서블릿 등록이 가능하다.
@WebServlet(name = "HelloServlet", urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {
...
}
해당 어노테이션은 서블릿을 실행 가능한 상태로 등록하는 역할을 한다. Tomcat의 소스코드에 따르면 다음과 같은 작성 규칙이 명시되어 있다.
HttpServlet을 상속한 클래스에서만 사용할 수 있다.value는 간단한 URL 매핑을 선언할 때 사용하며, 다른 속성이 함께 필요한 경우urlPatterns를 사용하는 것이 좋다.value또는urlPatterns중 하나는 반드시 명시되어야 하며, 둘 다 동시에 사용해서는 안 된다.name을 생략하면 해당 클래스의 전체 이름이 서블릿 이름으로 사용된다.
@WebServlet("/hello")
→ /hello 경로로 요청이 들어오면 이 서블릿이 실행된다.
예: 브라우저에서 http://localhost:8080/hello로 접근
@WebServlet(urlPatterns = {"/user", "/admin"})
→ 하나의 서블릿 클래스가 /user와 /admin 경로 모두 처리한다.
예: 로그인 사용자와 관리자를 동일한 서블릿에서 처리할 수 있다.
@WebServlet(name = "userServlet", urlPatterns = {"/user"})
→ 서블릿에 이름을 지정하면, 디버깅이나 로깅에서 이름을 식별자로 사용할 수 있다.
@WebServlet(loadOnStartup = 1)
클라이언트가 서버로 문자열 데이터를 전송할 때, 해당 문자열이 어떤 문자셋으로 인코딩되었는지를 서버가 정확히 알아야만 올바르게 파싱할 수 있다.
서블릿에서는 이를 위해 setCharacterEncoding() 메서드를 사용한다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
}
GET 방식은 URL 쿼리 스트링을 통해 데이터를 전달한다. 이 쿼리 문자열은 HTTP 헤더나 바디가 아닌 URL 자체에 포함된 정보이므로,
서버는 이를 디코딩할 때 톰캣 설정 파일에 명시된 인코딩 방식(예: URIEncoding)을 기반으로 처리한다.
<Connector URIEncoding="UTF-8" ... />
이 설정이 없다면, 톰캣은 기본적으로 ISO-8859-1을 사용하여 URL을 디코딩하며, 이로 인해 문자 해석이 올바르지 않을 수 있다.
즉, GET 방식에서는 코드로 직접 인코딩 방식을 지정할 수 없기 때문에 서버 설정의 영향을 받는다.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("name");
}
POST 방식은 요청 데이터를 HTTP 메시지 본문(body)에 담아 전송한다.
서버는 이 본문을 파싱할 때 setCharacterEncoding()에 지정된 문자셋을 사용한다.
단, 이 메서드는 getParameter()를 호출하기 이전에 반드시 설정되어야 한다.
파라미터가 이미 파싱된 이후에는 인코딩 설정을 변경해도 적용되지 않기 때문이다.
따라서 POST 요청을 다룰 때는 항상
setCharacterEncoding()을 가장 먼저 호출하는 습관을 들이는 것이 안전하다.
좋습니다. 질문하신 "주석 해석 위주" 스타일은 다음과 같은 방식입니다:
앞서 setCharacterEncoding()은 getParameter()보다 먼저 호출되어야 한다고 설명했다.
이번에는 그 이유를 톰캣 내부 소스코드의 주석을 중심으로 살펴보자.
getParameter()는 클라이언트로부터 전달된 요청 데이터를 추출하는 대표적인 메서드이다.
톰캣의 Request 클래스 내부에서는 다음과 같이 정의되어 있다.
@Override
public String getParameter(String name) {
parseParameters();
return coyoteRequest.getParameters().getParameter(name);
}
요청 파라미터를 반환하기 전
parseParameters()를 호출하여 파싱을 수행한다.
protected void parseParameters() {
doParseParameters();
if (parametersParseException != null) {
throw parametersParseException;
}
}
요청 파라미터를 파싱하며, 파싱 중 오류가 발생한 경우 예외를 던진다.
실제 파싱 로직은doParseParameters()메서드에 위임되어 있다.
protected boolean parametersParsed = false;
protected void doParseParameters() {
if (parametersParsed) {
return;
}
parametersParsed = true;
...
}
요청 파라미터는 최초 1회만 파싱되며, 이후에는 파싱 작업이 수행되지 않는다.
내부적으로parametersParsed플래그를 활용해 중복 파싱을 방지하고 있다.
이 구조로 인해, getParameter()가 먼저 호출되어 파라미터가 파싱된 이후에는
setCharacterEncoding()으로 인코딩을 설정하더라도 이미 파싱된 파라미터에는 적용되지 않는다.
따라서 POST 방식 요청에서 한글이나 특수문자가 깨지지 않도록 하려면
request.getParameter()를 호출하기 전에setCharacterEncoding()을 반드시 호출해야 한다.
이번에는 getParameter()의 정확한 역할과, 서버 내부에서 데이터를 공유할 때 사용하는 getAttribute()와의 차이를 살펴본다.
서블릿에서는 클라이언트가 보낸 데이터를 추출하거나, 서버 내부에서 데이터를 공유하는 방식으로 getParameter()와 getAttribute() 메서드를 사용한다.
// 클라이언트가 전송한 값 추출
String name = request.getParameter("name");
// 서버 내부에서 설정한 값을 공유
request.setAttribute("user", userDto);
User user = (User) request.getAttribute("user");
getParameter()는 클라이언트가 HTTP 요청 시 전송한 파라미터를 문자열(String) 형태로 추출하는 메서드이다.
내부적으로는 요청 본문 또는 쿼리 스트링을 파싱하여 값을 반환하며, 쉽게 말해 클라이언트가 전송한 데이터를 분석하는 과정이라 볼 수 있다.
모든 값은 문자열로 반환되므로, 숫자나 날짜처럼 다른 자료형으로 사용하려면 별도의 파싱 작업이 필요하다.
getAttribute()는 setAttribute()로 설정한 값을 서버 내부 로직 간에 전달할 때 사용하는 메서드이다.
이때 전달되는 값은 문자열이 아닌 객체(Object)이며, 문자열, 숫자, 컬렉션, 또는 사용자가 정의한 클래스 인스턴스 등 다양한 형태의 데이터를 포함할 수 있다.
getParameter()와getAttribute()는 사용 목적과 적용 범위가 전혀 다르다는 점을 명확히 이해해야 한다.
getMethod()
getRequestURI()
getContextPath()
getRemoteAddr()
getHeader(String name)
getSession()
getSession(false)를 사용하면 세션이 없을 때 새로 생성하지 않고 null을 반환한다.이번 글에서는 클라이언트의 요청 정보를 서블릿에서 어떻게 다루는지를 중심으로, HttpServletRequest 객체의 구조와 동작 방식에 대해 정리하였다.
요청 객체는 서블릿 컨테이너가 요청 시점에 생성하며, doGet()이나 doPost() 등의 메서드에 전달된다.
이 객체는 인터페이스로 정의되어 있으며, 실제 구현은 Tomcat의 RequestFacade 클래스가 담당한다.
RequestFacade는 내부의 Request 객체를 감싸는 형태로 설계되어 있으며, 이는 내부 구현을 감추고 표준화된 인터페이스만을 제공하는 퍼사드(Facade) 패턴의 예이다.
서블릿 매핑은 @WebServlet 어노테이션을 통해 선언적으로 설정할 수 있으며, URL 경로, 이름 지정, 서버 시작 시 초기화 등의 다양한 속성 사용법을 정리하였다.
요청 방식에 따라 문자 인코딩 처리 방식이 달라질 수 있으며,
특히 POST 방식에서는 request.setCharacterEncoding()을 반드시 getParameter() 호출 전에 실행해야 한다는 점이 중요하다.
마지막으로, 클라이언트가 전달한 파라미터는 getParameter()를 통해 문자열(String) 형태로 추출되며,
서버 내부에서 데이터를 공유할 경우에는 getAttribute()를 통해 객체(Object) 형태로 전달된다는 차이를 확인하였다.
두 메서드는 사용 목적과 적용 범위가 전혀 다르므로, 개념적으로 구분하여 이해하는 것이 중요하다.
유용한 내용 잘 보고 갑니다~
행복한 하루 보내세요~ %^-^%