본문 바로가기
공식문서

Servlet

by sangyunpark99 2025. 3. 7.
공식문서로 Servlet을 배워볼까요?

 

 

이번 글은 Servlet 공식 문서로 공부한 내용을 정리했습니다.

version - Java 17 

 

공식 문서 링크

https://docs.oracle.com/javaee/7/tutorial/servlets.htm

 

17 Java Servlet Technology (Release 7)

 

docs.oracle.com

 

서블릿이 무엇일까요?

 

Servlet

서블릿은 Java 클래스로, 요청 - 응답 프로그래밍 모델을 사용하는 서버의 기능을 위해 만들어진 기술입니다.

이론적으로 어떤 종류의 요청에도 응답이 가능하지만, 보통 웹 서버에서 웹 어플리케이션을 확장하는 용도로 많이 사용합니다.

 

javax.servlet과 javax.servlet.http 패키지는 서블릿을 작성할 때 사용하는 interface와 class를 제공합니다.

모든 서블릿은 반드시 Servlet 인터페이스를 구현해야 하고 이 인터페이스는 서블릿의 라이프사이클 메서드를 정의하고 있습니다.

 

만약, HTTP에 종속되지 않는 일반적인 서비스를 만들고 싶은 경우, 서블릿에서 제공하는 GenericServlet 클래스를 직접 사용할 수 있습니다. 반면, HTTP 요청/응답 처리를 하고 싶은 경우, HttpServlet을 상속해서 doGet이나 doPost와 같은 HTTP 전용 메서드를 구현하는 방식으로 개발이 가능합니다.

 

서블릿의 라이프 사이클은 어떻게 될까요?

 

 

Servlet LifeCycle

서블릿의 라이프사이클은 서블릿이 배포된 컨테이너에 의해서 제어가 됩니다.

클라이언트의 요청에 따라 특정 서블릿에 매핑이 되는경우, 컨테이너는 다음과 같은 LifeCycle을 갖게 됩니다.

크게 서블릿 인스턴스가 존재하지 않는 경우와 존재하는 경우 2가지로 나뉘게 됩니다.

 

1. 서블릿 인스턴스가 존재하지 않는 경우

  • 웹 컨테이너가 servlet 클래스를 로드합니다.
  • servlet 클래스의 인스턴스를 생성합니다.
  • init() 메서드를 호출해서 servlet 인스턴스를 초기화합니다.

2. 서블릿 인스턴스가 존재하는 경우

  • 컨테이너가 서블릿 인스턴스에 요청과 응답 객체를 넘겨주면서, 서블릿의 service 메서드를 호출합니다.
servlet 인스턴스 제거는 어떻게 하나요?

 

컨테이너는 서블릿의 destory() 메서드를 호출해서 서블릿 인스턴스를 정리 합니다.

 

servlet 라이프 사이클에서 발생하는 이벤트를 감지할 수 있나요?

 

Servlet Lifecycle Events

servlet의 라이프사이클에서 발생하는 이벤트를 감지하고 반응할 수 있습니다.

이를 위해서 리스너 객체를 정의하고, 해당 리스너 객체가 라이프 사이클 이벤트가 발생할 때 실행할 메서드를 구현해야 합니다.

 

리스너 객체를 어떻게 정의 할 수 있나요?

 

Defining the Listener Class

리스너 클래스는 리스너 인터페이스를 구현함으로 정의할 수 있습니다.

감지할 수 있는 이벤트와 이벤트를 감지하기 위해 구현해야 하는 인터페이스는 다음과 같습니다.

Servlet Lifecycle Events

 

리스너 메서드가 호출되는 경우, 해당 이벤트에 맞는 이벤트 객체가 매개 변수로 전달이 됩니다.

에시로, HttpSessionListener의 메서드들은 HttpSessionEvent 객체를 전달받습니다.  이 HttpSessionEvent에는 내부적으로 HttpSession 정보를 담고 있습니다.

 

웹 애플리케이션 컨텍스트에서 발생하는 이벤트를 받는 리스너를 정의하기 위해서, @WebListener 어노테이션을 사용합니다.

@WebListener가 붙은 클래스는 반드시 다음 중 하나의 인터페이스를 구현해야 합니다.

javax.servlet.ServletContextListener
javax.servlet.ServletContextAttributeListener
javax.servlet.ServletRequestListener
javax.servlet.ServletRequestAttributeListener
javax.servlet..http.HttpSessionListener
javax.servlet..http.HttpSessionAttributeListener

 

아래와 같이 두 개의 리스너 인터페이스를 동시에 구현한 리스너를 정의할 수 있습니다.

import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener()
public class SimpleServletListener implements ServletContextListener,
        ServletContextAttributeListener {
    ...

 

코드는 ServletContextListener와 ServletContextAttributesListener를 동시에 구현하고 있습니다.

 

Servlet은 에러를 어떻게 처리할까요?

 

Handling Servlet Errors

Servlet이 실행되는 동안 여러 종류의 예외가 발생할 수 있습니다. 예외가 발생할때, 웹 컨테이너는 기본 오류 페이지를 생성하고, 이 페이지는 다음과 같은 메시지가 표시됩니다.

A Servlet Exception Has Occured

 

특정 예외가 발생했을 때, 컨테이너가 반환할 에러 페이지를 직접 지정하는 것도 가능합니다.

 

Servlet 객체는 어떻게 정보를 공유하나요?

 

Sharing Information

웹 컴포넌트는 다른 객체들과 협력해서 작업을 처리하는 경우가 많이 존재합니다. 아래와 같은 방법으로 다른 객체들과 정보를 공유할 수 있습니다.

 

정보를 공유하는 방법

  • private 헬퍼 객체 사용 : Java Benas와 같은 보조 객체를 생성해서 사용하는 방식
  • 공용 범위에 속성으로 객체 저장 : 웹 애플리케이션 전체에서 공유하고 싶은 객체를 공용 영역에 저장하고 꺼내 쓰는 방식
  • 데이터 베이스 사용 : DB에 저장된 데이터를 읽고 쓰는 방식
  • 다른 웹 리소스 호출

 

그럼, 어떤 방법으로 객체의 정보를 공유하나요?

 

Using Scope Objects

웹 컴포넌트들은 4가지 스코프 객체의 속성을 통해 정보를 제공할 수 있습니다.

이 속성들은 각 스코프를 대표하는 클래스가 제공하는 getAttribute()와 setAttribute() 메서드를 이용해 접근할 수 있습니다.

 

공식 문서에 정의된 4가지 스코프 객체는 다음과 같습니다.

 

Web context : 같은 웹 애플리케이션의 모든 웹 컴포넌트에서 접근이 가능합니다. (서버 시작부터 서버 종료까지)

Session : 같은 세션 내에서 처리되는 모든 요청을 다루는 웹 컴포넌트에서 접근 가능합니다. (로그인 유저 1명 기준 공유)

Request : 같은 요청을 처리하는 웹 컴포넌트에서 접근 가능합니다.(요청 시작부터 응답 완료 까지)

Page : 해당 JSP 페이지 내에서만 접근이 가능합니다.(JSP 페이지 처리 중에만)

 

Servlet은 공유 자원의 동시 접근은 어떻게 처리할까요?

 

Controlling Concurrent Access to Shared Resource

멀티 스레드 환경에서, 여러 스레드가 동시에 공유 자원에 접근할 수 있습니다.

공유 자원에는 스코프 객체의 속성 뿐만 아니라, 메모리 내 데이터(인스턴스나 클래스 변수)와 외부 객체(파일, 데이터 베이스 연결, 네트워크 연결)가 포함됩니다.

 

Servlet에서 동시 접근이 발생하는 상황은 다음과 같습니다.

  • 여러 웹 컴포넌트가 웹 컨텍스트에 저장된 객체에 동시에 접근하는 경우 
  • 여러 웹 컴포넌트가 같은 세션에 저장된 객체에 동시에 접근하는 경우
  • 하나의 웹 컴포넌트에서 여러 스레드가 인스턴스 변수에 동시 접근하는 경우

서블릿은 기본적으로 싱글턴이기 때문에, 하나의 서블릿 클래스에 대해 하나의 인스턴스만 생성합니다.

이 하나의 인스턴스를 여러 스레드(요청)가 동시에 공유 하기 때문에 서블릿의 인스턴스 변수가 동시성 문제에 노출되게 됩니다.

 

하나의 웹 컴포넌트에서 여러 스레드가 인스턴스 변수에 동시 접근하는 경우 어떻게 해결할 수 있을까요?

 

웹 컨테이너는 일반적으로 각 요청을 처리할 스레드를 생성합니다. 서블릿 인스턴스가 한 번에 하나의 요청만 처리하도록 보장하기 위해선

서블릿은 SingleThreadModel 인터페이스를 구현하면 됩니다. 이 인터페이스 구현시 두 개의 스레드가 동시에 해당 서블릿의 service 메서드를 실행하는 일은 발생하지 않습니다. 

 

웹 컨테이너는 이를 수행하기 위해 2가지 방법을 제공합니다.

  • 하나의 서블릿 인터페이스에 대한 접근을 동기화 합니다.
  • 웹 컴포넌트 인스턴스를 여러 개 풀로 유지하고, 각 요청을 처리할 때마다 사용 가능한 인스턴스를 하나씩 할당합니다.

요청마다 독립된 서블릿 인스턴스를 제공하면, 각 요청은 자기만의 인스턴스 변수 공간을 갖게 되기 때문에, 동시성 문제를 예방할 수 있습니다.

단, 이 인터페이스는 static 클래스 변수나 외부 객체(파일, DB 연결 등)에 대한 동기화 문제까지는 방지하지 못합니다.

 

Servlet을 사용해 볼까요?

 

Creating and Initializing a Servlet

@WebServlet 어노테이션은 웹 애플리케이션에서 서블릿(Servlet) 컴포넌트를 정의할 때 사용됩니다. 이 어노테이션은 클래스 위에 지정되며, 선언되는 서블릿에 대한 메타데이터를 담고 있습니다.

이 어노테이션을 사용할 때는 반드시 하나 이상의 URL 패턴을 지정해야 합니다. URL 패턴은 어노테이션의 urlPatterns 또는 value 속성을 사용해서 설정할 수 있습니다.

 

다른 모든 속성은 기본값이 설정되어 있기 때문에 필수가 아닙니다. 만약 URL 패턴 이외에 다른 속성도 함께 지정한다면 urlPatterns 속성을 사용하고, URL 패턴만 지정한다면 value 속성을 사용하는 것이 권장됩니다.

 

@WebServlet 어노테이션을 붙이는 클래스는 반드시 javax.servlet.http.HttpServlet 클래스를 상속(extend)해야 합니다.
예를 들어, 다음의 코드 조각은 URL 패턴이 /report인 서블릿을 정의합니다.

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;

@WebServlet("/report")
public class MoodServlet extends HttpServlet {
    ...
 

 

웹 컨테이너는 서블릿 클래스가 로드되고 인스턴스화된 후, 클라이언트의 요청을 전달하기 전에 서블릿을 초기화합니다.

이 초기화 과정을 커스터마이징하여 서블릿이 영속적인 설정 데이터를 읽거나, 자원을 초기화하거나, 그 밖의 일회성 작업을 수행하도록 하려면, 서블릿 인터페이스의 init 메서드를 오버라이딩하거나, @WebServlet 어노테이션의 initParams 속성을 지정할 수 있습니다.

 

initParams 속성은 @WebInitParam 어노테이션을 포함합니다. 서블릿이 초기화 과정에서 작업을 완료할 수 없는 경우, 서블릿은 UnavailableException을 던지게 됩니다.

 

초기화 파라미터(initialization parameter)는 특정 서블릿에서만 필요한 데이터를 제공할 때 사용합니다. 반면, 컨텍스트 파라미터(context parameter)는 웹 애플리케이션의 모든 컴포넌트에서 공유하여 사용할 수 있는 데이터를 제공할 때 사용합니다.

 

서블릿이 어떻게 서비스 메서드를 제공할까요?

Writing Service method

서블릿이 제공하는 서비스는 다음과 같은 방법으로 구현됩니다.

  • GenericServlet 클래스의 service 메서드에서 구현하거나,
  • HttpServlet 클래스의 doMethod 메서드에서 구현합니다. (여기서 Method는 Get, Delete, Options, Post, Put, Trace 중 하나가 됩니다.)
  • 또는 Servlet 인터페이스를 구현하는 클래스에서 정의한 기타 프로토콜 관련 메서드에서 구현할 수도 있습니다.

일반적으로 클라이언트에게 서비스를 제공하는 서블릿 클래스의 모든 메서드를 서비스 메서드(service method) 라고 합니다.

서비스 메서드의 일반적인 처리 흐름은 다음과 같습니다.

  1. 클라이언트의 요청(request)에서 필요한 정보를 추출합니다.
  2. 외부 리소스(DB, 파일 등)에 접근하여 필요한 데이터를 얻습니다.
  3. 이 정보를 기반으로 클라이언트에게 보낼 응답(response)을 구성합니다.

HTTP 서블릿의 경우 응답을 구성할 때 다음 절차를 따르는 것이 바람직합니다.

  • 응답(response)으로부터 출력 스트림(Output Stream)을 가져옵니다.
  • 응답 헤더(Response Headers)를 채웁니다.
  • 본문(Body)에 포함될 데이터를 출력 스트림에 기록합니다.

이때 응답 헤더는 응답이 클라이언트에 커밋(commit)되기 전에 반드시 설정되어야 합니다.
웹 컨테이너는 이미 응답이 커밋된 이후에 헤더를 추가하거나 변경하는 시도를 무시합니다.

다음 두 섹션에서는 요청으로부터 정보를 가져오는 방법과 응답을 생성하는 방법을 구체적으로 다룹니다.

 

요청 객체로 부터 정보를 얻기 위해선 어떻게 해야 할까요?

Getting Information from Requests

요청(Request) 객체는 클라이언트와 서블릿 간에 전달되는 데이터를 포함합니다. 모든 요청 객체는 ServletRequest 인터페이스를 구현하며, 이 인터페이스는 다음과 같은 정보에 접근할 수 있는 메서드를 정의하고 있습니다.

  • 파라미터(Parameters)
    일반적으로 클라이언트와 서블릿 간에 전달되는 정보를 담고 있습니다.
  • 객체 값의 속성(Object-valued Attributes)
    일반적으로 웹 컨테이너와 서블릿 간, 혹은 서블릿끼리 협력하여 데이터를 전달할 때 사용됩니다.
  • 요청을 전달하는 데 사용된 프로토콜 정보 및 요청과 관련된 클라이언트와 서버 정보
  • 지역화(Localization)와 관련된 정보

또한 요청 객체에서 입력 스트림(input stream)을 가져와 데이터를 직접 파싱할 수도 있습니다.

  • 문자(character) 데이터를 읽을 때는, 요청 객체의 getReader() 메서드로 반환되는 BufferedReader 객체를 사용합니다.
  • 바이너리(binary) 데이터를 읽을 때는, 요청 객체의 getInputStream() 메서드로 반환되는 ServletInputStream 객체를 사용합니다.

HTTP 서블릿의 경우, 웹 컨테이너는 요청 URL, HTTP 헤더, 쿼리 문자열 등의 정보를 담고 있는 HttpServletRequest 객체를 서블릿에 전달합니다.

 

HTTP 요청 URL은 다음과 같은 부분으로 구성됩니다.

http://[host]:[port][request-path]?[query-string]

 

요청 경로(Request Path)는 다음과 같은 세 가지 요소로 구성됩니다.

  • 컨텍스트 경로(Context Path)
    서블릿이 속한 웹 애플리케이션의 컨텍스트 루트(context root) 앞에 슬래시(/)를 붙여 나타낸 것입니다.
  • 서블릿 경로(Servlet Path)
    현재 요청을 처리하는 서블릿을 식별하기 위한 경로입니다. 항상 슬래시(/)로 시작합니다.
  • 경로 정보(Path Info)
    요청 경로에서 컨텍스트 경로와 서블릿 경로를 제외한 나머지 부분을 의미합니다.

이 정보는 HttpServletRequest 인터페이스의 getContextPath(), getServletPath(), getPathInfo() 메서드를 사용하여 얻을 수 있습니다. URL 인코딩(encoding)의 차이를 제외하면, 요청 URI(request URI)는 항상 컨텍스트 경로 + 서블릿 경로 + 경로 정보의 형태로 구성됩니다.

한편, 쿼리 문자열(Query String)은 파라미터와 값의 집합으로 구성됩니다. 요청으로부터 개별 파라미터의 값을 얻으려면 요청 객체의 getParameter() 메서드를 사용합니다. 쿼리 문자열을 만드는 방법은 두 가지가 있습니다.

  • 쿼리 문자열을 웹 페이지 내에서 명시적으로 직접 작성하는 방법이 있습니다.
  • HTML 폼(form)을 GET 방식으로 제출(submit)할 때 URL 뒤에 자동으로 추가되는 방법이 있습니다.
응답 객체를 생성하기 위해선 어떻게 해야할까요?

Constructing Responses

응답(Response)은 서버와 클라이언트 간에 전달되는 데이터를 포함합니다. 모든 응답 객체는 ServletResponse 인터페이스를 구현합니다. 이 인터페이스는 다음 작업을 할 수 있는 메서드를 정의하고 있습니다.

  • 출력 스트림(Output Stream)을 얻어서 클라이언트에게 데이터를 보낼 수 있습니다.
    • 문자(character) 데이터를 보낼 때는 응답 객체의 getWriter() 메서드로 반환되는 PrintWriter를 사용합니다.
    • 바이너리(binary) 데이터를 MIME 형식으로 보낼 때는 getOutputStream() 메서드로 반환되는 ServletOutputStream을 사용합니다.
    • 바이너리와 문자 데이터를 섞어서 보낼 때(멀티파트 응답)는 ServletOutputStream을 사용하고, 문자 데이터를 직접 관리합니다.
  • 반환될 콘텐츠 타입(Content-Type)을 지정할 수 있습니다.
    • 예: setContentType("text/html")
    • 이 메서드는 반드시 응답이 클라이언트에 커밋(commit)되기 전에 호출해야 합니다.
    • 콘텐츠 타입의 공식적인 이름은 IANA(인터넷 할당 번호 관리기관)가 관리하며, 다음 사이트에서 확인할 수 있습니다.
    • http://www.iana.org/assignments/media-types/
  • 출력 버퍼링(Buffering)을 설정할 수 있습니다.
    • setBufferSize(int) 메서드를 이용해 출력 데이터를 즉시 클라이언트로 보내지 않고, 버퍼링한 후 한 번에 보낼 수 있습니다.
    • 기본적으로 출력 스트림에 기록되는 데이터는 즉시 클라이언트로 전송되지만, 버퍼링을 설정하면 콘텐츠를 보내기 전에 서블릿이 상태 코드나 헤더를 추가로 설정하거나, 다른 리소스로 포워딩(forwarding)할 시간을 벌 수 있습니다.
    • 이 메서드는 반드시 응답에 어떤 내용이라도 쓰기 전에, 또는 응답이 커밋되기 전에 호출해야 합니다.
  • 로케일(Locale)과 문자 인코딩(Character Encoding)과 같은 지역화(Localization) 관련 정보를 설정할 수 있습니다.
    (자세한 내용은 20장 "웹 애플리케이션의 국제화와 지역화"를 참고하세요.)

HTTP 응답 객체(javax.servlet.http.HttpServletResponse)는 다음과 같은 HTTP 헤더 정보를 관리할 수 있습니다.

  • 상태 코드(Status Codes)
    클라이언트의 요청이 실패하거나 다른 URL로 리다이렉트(redirect)된 이유를 나타낼 때 사용됩니다.
  • 쿠키(Cookies)
    클라이언트에서 애플리케이션과 관련된 정보를 저장할 때 사용됩니다.
    특히 쿠키는 사용자의 세션(session)을 유지하기 위한 식별자를 저장하는 용도로 흔히 사용됩니다. (세션 추적(Session Tracking) 참고)

 

필터링을 통해 무엇을 할 수 있을까요?

Filtering Requests and Responses

 

필터(Filter)는 요청(request)과 응답(response)의 헤더나 내용을 변형하거나, 혹은 둘 다 변형할 수 있는 객체입니다.

필터는 요청과 응응의 다음 사항을 제어할 수 있습니다:

  • 요청의 내용을 확인하고, 그에 따라 특정 동작을 수행할 수 있습니다.
  • 요청과 응답이 더 이상 진행되지 않도록 차단(block) 할 수 있습니다.
  • 요청의 헤더나 데이터를 수정할 수 있습니다. 이를 위해 맞춤형(customized) 요청 객체를 제공할 수 있습니다.
  • 응답의 헤더나 데이터를 수정할 수 있습니다. 마찬가지로 맞춤형(customized) 응답 객체를 제공하여 처리할 수 있습니다.

필터는 여러 가지 유형의 웹 리소스와 함께 조합하여 사용할 수 있어야 하기 때문에, 필터 객체는 특정 웹 리소스 하나에 의존성을 가지지 않고 다양한 종류의 웹 리소스와 조합될 수 있어야 합니다. 즉, 필터는 일반적으로 응용 프로그램의 응답을 직접 생성하지 않고, 웹 컴포넌트(Servlet이나 JSP 등)와 함께 사용되어 요청과 응답을 처리하거나 수정하는 역할을 합니다.

필터가 수행할 수 있는 일반적인 작업은 다음과 같습니다.

  • 요청 내용을 확인하고, 그에 따라 특정 작업을 수행
  • 요청과 응답의 진행을 중지시키는 작업 (예: 인증 실패 시 차단)
  • 요청의 헤더와 데이터를 수정하여, 이후 컴포넌트로 전달
  • 응답의 헤더와 데이터를 변형하거나 추가하여 응답을 클라이언트로 보냄

웹 애플리케이션에서는 필터를 여러 개 연결(chain)해서 특정한 순서로 실행하도록 구성할 수도 있습니다.
이러한 필터 체인은 웹 애플리케이션이 배포(deploy)될 때 설정되며, 웹 컨테이너가 웹 컴포넌트를 로딩할 때 필터 체인도 함께 인스턴스화되어 요청·응답 처리 시 사용됩니다.

필터는 여러 타입의 웹 컴포넌트와 결합되어 사용할 수 있도록 설계되어 있으며, 대표적인 용도로는 인증, 권한 검사, 로깅(logging), 요청이나 응답 압축(compression), 인코딩 설정, 보안 검사 등 다양한 처리가 있습니다.

 

필터링은 어떻게 구현할까요?

Programming Filters

필터와 관련된 API는 javax.servlet 패키지의 Filter, FilterChain, FilterConfig 인터페이스로 정의됩니다.

필터를 정의하려면 반드시 Filter 인터페이스를 구현해야 하며, 구현한 클래스 위에 @WebFilter 어노테이션을 사용하여 설정합니다.

  • @WebFilter 어노테이션을 사용할 때는 반드시 하나 이상의 URL 패턴을 지정해야 합니다.
  • URL 패턴은 urlPatterns 또는 value 속성으로 지정할 수 있습니다.
  • URL 패턴 하나만 지정할 때는 value 속성을 사용하고, URL 패턴과 함께 다른 속성도 설정해야 할 경우에는 urlPatterns 속성을 사용합니다.
  • 다른 속성들은 기본값이 있어서 생략 가능하며, 필요할 때만 지정하면 됩니다.

추가적인 설정 정보를 필터에 전달하고자 할 경우, @WebFilter 어노테이션의 initParams 속성을 사용하면 됩니다.
이 속성은 다시 @WebInitParam 어노테이션을 통해 설정 데이터를 포함합니다.

@WebFilter 어노테이션을 사용한 클래스는 반드시 javax.servlet.Filter 인터페이스를 구현해야 합니다.

import javax.servlet.Filter;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;

@WebFilter(filterName = "TimeOfDayFilter",
urlPatterns = {"/*"},
initParams = {
    @WebInitParam(name = "mood", value = "awake")})
public class TimeOfDayFilter implements Filter {
    ...

 

필터 인터페이스(Filter)에서 가장 중요한 메서드는 doFilter입니다.

doFilter 메서드는 요청과 응답 객체를 처리하는 핵심 메서드이며, 다음과 같은 작업을 수행할 수 있습니다.

  • 요청의 내용을 검사하여 필요한 작업을 수행합니다.
  • 요청과 응답이 더 이상 처리되지 않도록 차단할 수 있습니다.
  • 요청의 헤더나 데이터를 수정하려면, 이를 위한 맞춤형(customized) 요청 객체를 만들어서 전달할 수 있습니다.
  • 응답의 헤더나 데이터를 수정하려면, 맞춤형 응답 객체를 만들어 전달할 수 있습니다.
  • 현재 필터 다음으로 구성된 다른 필터를 호출할 수 있습니다.
    • 만약 현재 필터가 체인에서 마지막 필터라면, 필터 체인의 끝에 있는 웹 컴포넌트(Servlet, JSP 등) 또는 정적 리소스가 다음 호출 대상이 됩니다.
    • 그렇지 않으면, WAR에 설정된 다음 필터가 호출됩니다.
    • 다음 필터나 리소스를 호출하려면, chain.doFilter(request, response) 메서드를 사용합니다.
  • 다음 필터를 호출한 후, 응답이 돌아올 때 응답 헤더나 데이터를 추가로 검사하거나 변경할 수 있습니다.
  • 처리 과정에서 문제가 발생하면 예외(Exception)를 던질 수 있습니다.

doFilter 메서드 외에도, 반드시 init 및 destroy 메서드를 구현해야 합니다.

  • init 메서드는 필터 객체가 생성되고 웹 컨테이너에 의해 초기화될 때 호출됩니다.
    초기화 파라미터를 필터에 전달하려면 init 메서드 내에서 이를 조회하여 처리할 수 있습니다.
  • destroy 메서드는 필터가 웹 컨테이너에서 제거될 때 호출되며, 필터가 사용한 리소스를 해제하는 작업에 활용됩니다.

 

요청과 응답 방식은 어떻게 수정할 수 있을까요?

Programming Customized Requests and Responses

필터(Filter)는 요청(request)이나 응답(response)을 다양한 방식으로 수정할 수 있습니다. 예를 들어, 필터는 요청에 속성(attribute)을 추가하거나 응답 데이터에 내용을 추가할 수도 있습니다.

특히 응답(response)을 수정할 때는, 원본 응답 객체에 바로 접근하는 대신, 중간에 대체 스트림(stand-in stream) 을 끼워 넣어야 합니다.

 

그 이유는, 웹 컨테이너가 직접 생성한 원본 응답 객체는 바로 클라이언트로 전송되기 때문에, 이미 응답이 전달(commit)된 후에는 수정이 불가능하기 때문입니다.

이 문제를 해결하기 위해 필터는 다음과 같은 방법을 사용합니다.

  1. 필터가 대체 스트림(stand-in stream) 을 만듭니다.
    이 스트림은 실제 응답이 나가기 전에 임시로 내용을 저장(capture)하기 위한 역할입니다.
  2. 필터가 응답 래퍼(response wrapper) 라는 것을 생성합니다.
    이 래퍼(wrapper)는 응답 객체의 getWriter() 또는 getOutputStream() 메서드를 오버라이드하여 방금 만든 대체 스트림을 반환합니다.
  3. 필터는 이 래퍼(wrapper) 객체 를 필터 체인의 doFilter 메서드에 전달합니다.
    따라서 실제 서블릿은 원본 응답 객체가 아니라, 래퍼를 통해 이 임시 스트림에 응답 데이터를 기록하게 됩니다.

이렇게 하면 서블릿의 응답을 최종적으로 클라이언트로 보내기 전에 필터가 내용을 수정하거나 추가할 수 있게 됩니다.

 

필터 매핑은 어떤식으로 할까요?

Specifying Filter Mappings

웹 컨테이너는 필터 매핑(filter mapping)을 사용하여 웹 리소스에 어떤 필터를 적용할지를 결정합니다.

필터 매핑은 필터웹 컴포넌트 이름(서블릿 이름 등) 또는 URL 패턴을 기준으로 웹 리소스와 연결시켜줍니다. 필터들은 설정된 필터 매핑 목록의 순서대로 호출됩니다.

필터를 웹 애플리케이션의 모든 요청에 적용하려면 URL 패턴을 /*로 설정하면 됩니다. 예를 들어, 필터 매핑을 /*로 설정하면 해당 필터는 모든 요청에 적용됩니다.

필터는 하나 이상의 웹 리소스에 매핑할 수 있고, 하나의 웹 리소스에도 여러 개의 필터를 매핑할 수 있습니다.

예를 들어 다음과 같은 상황이 가능합니다.

  • 필터 F1을 서블릿 S1, S2, S3에 모두 매핑할 수 있습니다.
  • 필터 F2를 서블릿 S2에만 매핑할 수도 있습니다.
  • 특정 서블릿 하나(예: S1)에 필터 F1, F2, F3 등 여러 필터를 순서대로 연결하여 매핑할 수도 있습니다.

웹 컨테이너는 요청이 들어왔을 때 배포(desploy) 시 설정한 필터 매핑을 보고, 각 요청마다 적용해야 할 필터를 순서대로 실행하여 요청을 처리합니다.

모든 요청을 하나의 필터가 처리하도록 하려면 필터의 URL 패턴을 /* 로 지정하면 됩니다. (예: 모든 요청에 대한 로깅이나 인증 처리를 하고 싶은 경우 등)

 

이미지 출처 : Servlet 공식문서

필터 체인(Filter Chain)은 필터의 doFilter 메서드에 전달되는 객체 중 하나입니다.

이 체인은 필터의 doFilter 메서드에 전달된 chain 객체를 통해 간접적으로 형성됩니다.

 

필터 체인의 실행 순서는 웹 애플리케이션의 배포 서술자(deployment descriptor)에 설정된 필터 매핑의 순서와 일치합니다.

예를 들어, 서블릿 S1의 필터 체인이 필터 F1과 필터 F3으로 구성되어 있다고 가정해봅시다.

 

서블릿 S1에 요청이 오면, 웹 컨테이너는 먼저 필터 F1의 doFilter 메서드를 호출합니다.

이때 필터 체인의 다음 필터를 호출하는 방식은, 각 필터의 doFilter 메서드 안에서 다시 chain.doFilter() 메서드를 호출하여 진행됩니다.

 

서블릿 S1의 필터 체인이 필터 F1과 필터 F3으로 구성된 상황이라면, 필터 F1이 호출한 chain.doFilter 메서드는 필터 F3의 doFilter 메서드를 실행합니다.

 

즉, F1의 doFilter 메서드 안에서 호출된 chain.doFilter 메서드는 필터 F3의 doFilter 메서드를 실행하게 되며, 필터 F3의 doFilter 메서드 실행이 완료되면 다시 제어권이 필터 F1의 doFilter 메서드로 돌아오게 됩니다.

 

웹 애플리케이션에서 필터 매핑은 어떻게 할까요?

To Specify Filter Mappings Using NetBeans IDE

다음은 웹 애플리케이션 프로젝트에서 필터를 설정하는 방법입니다.

애플리케이션의 프로젝트 노드를 확장하여, 필터를 추가하려면 다음과 같이 진행하세요.

  1. 애플리케이션 프로젝트의 노드를 확장(expand)합니다.
  2. 서블릿 필터 추가(Add Servlet Filter) 다이얼로그 박스를 엽니다.
  3. 필터 이름(Filter Name) 필드에 필터의 이름을 입력합니다.
  4. 필터를 적용할 서블릿 클래스를 찾기 위해 찾아보기(Browse)를 클릭하여 선택합니다.
    • 여러 개의 서블릿에 필터를 적용하고 싶다면 와일드카드(*) 문자를 사용하여 패턴을 지정할 수 있습니다.
  5. 확인(OK)을 클릭하여 적용합니다.

필터가 요청에 적용되는 방식을 제한하려면, 추가로 다음과 같은 단계를 수행합니다.

  1. 필터 적용 방식 제한을 위해 다음 옵션 중 하나를 선택할 수 있습니다.
    • REQUEST: 클라이언트로부터 직접 전달된 요청일 때만 필터 적용
    • FORWARD: 다른 컴포넌트로 요청이 포워딩(forward)될 때만 필터 적용
      (다른 웹 컴포넌트로 제어권 전달 시)
    • INCLUDE: 다른 리소스를 응답에 포함(include)할 때만 필터 적용
      (응답에 다른 리소스 포함 시)
    • ERROR: 에러 및 오류 페이지 처리 메커니즘이 동작할 때만 필터 적용
      (에러 페이지 메커니즘 작동 시)

이러한 제약 조건들은 필터가 요청의 특정 상황에서만 호출되도록 제한할 수 있게 합니다. 요청 처리 과정에서 원하는 조건을 정확히 설정할 수 있습니다.

 

 

 Invoking Other Web Resources

웹 컴포넌트는 다른 웹 리소스를 간접적 또는 직접적으로 호출할 수 있습니다.

  • 간접 호출
    웹 컴포넌트가 클라이언트에게 전달하는 콘텐츠(예: HTML 페이지)에 다른 리소스의 URL을 넣어 클라이언트가 해당 리소스를 호출하도록 하는 방식입니다.
  • 직접적 호출은 웹 컴포넌트가 실행되는 중에 이루어지며, 다음 두 가지 방법이 있습니다.
    • 다른 리소스의 콘텐츠를 현재 응답에 포함(include)하는 방법
    • 요청 자체를 다른 리소스로 전달(forward)하여 처리를 넘기는 방법

웹 컴포넌트를 실행 중인 서버에 있는 리소스를 직접 호출하려면, 먼저 RequestDispatcher 객체를 얻어야 합니다.
이 객체는 요청 객체의 getRequestDispatcher("URL") 메서드를 호출하여 얻을 수 있습니다.

이때 URL 경로를 지정할 때의 주의사항이 있습니다.

  • 상대 경로로 URL을 지정할 때는 현재 요청을 기준으로 상대적인 경로를 설정할 수 있습니다.
  • 절대 경로로 지정할 때는 반드시 슬래시(/)로 시작하는 절대 경로를 지정해야 합니다.

만약 지정한 리소스가 존재하지 않거나 서버가 요청한 리소스에 대한 RequestDispatcher 객체를 지원하지 않는다면,
getRequestDispatcher 메서드는 null을 반환합니다.

따라서 서블릿을 작성할 때는 이러한 상황(리소스가 없거나, 디스패처가 null인 상황)을 적절히 처리할 수 있도록 대비해야 합니다.

 

Including Other Resources in the Response

웹 컴포넌트가 다른 웹 리소스의 결과를 가져와서 사용하는 방법으로는 include 방식이 있습니다. 이 방식은 다른 서블릿이나 JSP 등의 웹 리소스를 호출하고, 그 결과를 현재 서블릿의 응답(response)에 포함하는 방식입니다.

이렇게 하려면, 먼저 웹 리소스를 호출할 수 있는 RequestDispatcher 객체를 얻어서 사용합니다.
호출할 리소스를 지정하여 이 객체를 얻고, 다음과 같이 사용합니다.

include(request, response);
 

 

이때 호출된 리소스(포함된 리소스)는 요청(request)에 대해서는 자유롭게 접근할 수 있지만, 응답(response)에 대해서는 제약이 있습니다.

포함된 리소스는 다음과 같은 작업을 할 수 있습니다.

  • 본문(body)에 데이터를 작성하여 응답 내용을 채울 수 있습니다.

하지만 다음과 같은 작업은 할 수 없습니다.

  • 응답의 헤더를 변경하거나 추가하는 작업은 불가능합니다.
  • 쿠키(setCookie)나 상태 코드 등의 헤더에 영향을 주는 작업 역시 할 수 없습니다.

즉, 포함되는 리소스는 오직 본문 내용만 작성할 수 있고, 응답 헤더 설정에는 관여할 수 없습니다.
헤더 설정은 반드시 포함을 호출한 원래의 서블릿에서만 수행할 수 있습니다.

 

Transferring Control to Another Web Component

어떤 애플리케이션에서는 하나의 웹 컴포넌트가 요청(request)에 대한 사전 처리를 하고, 또 다른 컴포넌트가 실제 응답(response)을 생성하도록 하고 싶을 수 있습니다. 예를 들어, 특정 요청에 대해 일부분만 처리한 뒤, 요청의 성격에 따라 처리를 다른 컴포넌트에 넘길 수도 있습니다.

 

이렇게 다른 웹 컴포넌트에 처리를 넘기려면, RequestDispatcher 객체의 forward 메서드를 호출하면 됩니다.

요청이 다른 컴포넌트로 전달(forward)될 때, 요청 URL은 포워딩된 페이지(다음 컴포넌트)의 경로로 설정됩니다. 이때 원래의 요청 URL과 그 구성 요소는 다음과 같은 요청 속성(request attributes)으로 저장됩니다.

javax.servlet.forward.request_uri
javax.servlet.forward.context_path
javax.servlet.forward.servlet_path
javax.servlet.forward.path_info
javax.servlet.forward.query_string

 

이 forward 메서드는 다른 리소스가 클라이언트에 대한 응답을 처리하게끔 제어권을 완전히 넘길 때 사용해야 합니다.

단, 서블릿 내에서 이미 ServletOutputStream 또는 PrintWriter 객체를 사용하여 데이터를 출력한 상태라면, 이 메서드를 사용할 수 없습니다. 만약 이미 응답을 작성한 상태에서 이 메서드를 호출하게 되면, IllegalStateException 이 발생합니다.

 

 

Accessing the Web Context

웹 애플리케이션에서 공통적으로 사용할 수 있는 정보를 저장하거나 접근하기 위한 웹 컨텍스트(Web Context)의 역할을 설명하고 있습니다.

웹 컨테이너는 웹 애플리케이션 전체에 대해 하나의 웹 컨텍스트(web context)를 생성합니다.
이 웹 컨텍스트는 ServletContext라는 인터페이스로 나타나며, 다음과 같은 작업을 수행할 수 있습니다.

  • 초기화 파라미터를 읽을 수 있습니다.
  • 웹 컨텍스트에 연결된 각종 리소스(파일, 이미지 등)에 접근할 수 있습니다.
  • 웹 애플리케이션 전체에서 공유 가능한 속성(Attribute)을 저장하고 읽을 수 있습니다.
  • 로그(log)를 기록하는 기능을 제공합니다.

또한, 이 컨텍스트에 저장된 객체는 동시에 여러 서블릿이 접근할 수 있기 때문에, 병행 처리 시 문제가 없도록 반드시 동기화(synchronized)를 해야 합니다.

 

예를 들어, 웹 애플리케이션에서 방문자 수를 기록하는 카운터(counter)가 있다면, 이 카운터 객체에 접근하는 메서드는 병행 처리를 위해 synchronized(동기화) 되어야 합니다.

 

필터(Filter)는 이 카운터 객체를 컨텍스트의 getAttribute 메서드를 사용하여 가져올 수 있고, 필터가 카운터 값을 증가시킨 후, 이를 로그(log)에 기록할 수 있습니다.

 

즉, ServletContext를 사용하면 웹 애플리케이션 전체에서 데이터를 공유하거나 리소스에 접근할 수 있으며, 이때 동시 접근에 대한 동기화 처리가 중요합니다.

 
 

Maintaining Client State

많은 애플리케이션은 클라이언트에서 온 일련의 요청들을 서로 연결해서 처리해야 합니다. 이러한 요청들의 연결을 관리하는 것을 세션(session) 이라고 부릅니다.

예를 들어, 온라인 쇼핑몰에서는 한 번 로그인하면 사용자의 상태(예: 로그인 여부, 장바구니에 담긴 물품 등)를 여러 요청에 걸쳐서 기억해야 합니다. 이런 작업에 세션이 사용됩니다.

 

세션을 사용하는 주요 목적은 하나의 클라이언트가 보내는 여러 요청 사이에 데이터를 유지하고 공유하기 위해서입니다.

웹 컨테이너는 Servlet API를 통해 다양한 방식으로 세션을 구현할 수 있도록 지원합니다. 대표적으로는 쿠키나 URL 리라이팅(URL rewriting) 등을 활용하여 세션을 관리합니다.

 

즉, 세션을 사용하면 애플리케이션이 사용자의 여러 요청을 묶어 관리할 수 있게 됩니다. HTTP는 기본적으로 상태가 없기 때문에 이러한 방법을 통해 사용자를 추적하고 데이터를 유지할 수 있도록 합니다.

 

Accessing a Session

세션(Session)은 HttpSession 객체를 통해 표현됩니다.
클라이언트의 요청과 연결된 세션에 접근하려면 request.getSession() 메서드를 호출하면 됩니다.

 

Associating Objects with a Session

세션에는 이름-값 형식(name-value pair)의 속성(attributes) 을 저장할 수 있습니다.
이렇게 저장된 속성은 같은 웹 컨텍스트(web context) 내의 모든 웹 컴포넌트(Servlet, JSP 등) 에서 접근할 수 있습니다.

 웹 애플리케이션은 세션의 생명주기(Session Lifecycle)에 대한 이벤트를 감지하여 특정 작업을 수행할 수 있습니다.

 

세션에 객체가 추가되거나 제거될 때 감지하는 리스너

  • javax.servlet.http.HttpSessionBindingListener 인터페이스를 구현하면, 세션에 객체가 추가되거나 삭제될 때 자동으로 감지할 수 있습니다.

세션이 저장되거나 다시 활성화 될 때 감지하는 리스너

  • 세션이 서버 간 이동(VM 간 이동) 또는 영구 저장(Persistent Storage) 되는 경우, HttpSessionActivationListener를 사용하여 감지할 수 있습니다.

 

Session Management

 

클라이언트가 명시적으로 세션을 종료하지 않으면, 서버는 세션이 일정 시간 동안 사용되지 않을 경우 자동으로 종료합니다.

  • getMaxInactiveInterval() : 세션의 최대 비활성화 시간(초 단위) 을 가져옵니다.
  • setMaxInactiveInterval(int seconds) : 세션의 비활성화 타임아웃을 설정합니다.

 

To Set the Timeout Period Using NetBeans IDE

NetBeans에서 web.xml을 사용하여 세션 타임아웃을 설정하려면 다음과 같이 진행합니다.

  1. 프로젝트를 열고, Projects 탭에서 프로젝트 노드를 확장합니다.
  2. Web Pages > WEB-INF 폴더를 확장합니다.
  3. web.xml 파일을 더블 클릭하여 엽니다.
  4. General 탭을 클릭합니다.
  5. Session Timeout 필드에 타임아웃 시간을 입력합니다. (분 단위)
  6. 설정한 시간이 지나면 세션이 자동으로 만료됩니다.

 

Session Tracking

세션을 특정 사용자와 연결하기 위해 웹 컨테이너는 클라이언트와 서버 간에 세션 ID를 전달하는 방법을 사용합니다.
세션 ID를 유지하는 방법에는 다음 두 가지가 있습니다.

  1. 쿠키(Cookie)를 사용하여 세션 ID 저장
    • 기본적으로, 웹 컨테이너는 쿠키(JSESSIONID)를 사용하여 세션을 유지합니다.
  2. URL에 세션 ID 포함 (URL Rewriting)
    • 만약 클라이언트가 쿠키를 비활성화한 경우, 서블릿은 세션 ID를 URL에 포함시켜야 합니다.
    • 이를 위해 encodeURL() 메서드를 사용하여 세션 ID가 자동으로 포함된 URL을 생성합니다.

 

Finalizing a Servlet

 

웹 컨테이너는 특정 서블릿을 서비스에서 제거해야 한다고 판단할 수 있습니다.
예를 들어, 컨테이너가 메모리 리소스를 회수해야 하거나 컨테이너 자체가 종료될 경우가 있습니다.

이러한 경우, 컨테이너는 Servlet 인터페이스의 destroy 메서드를 호출합니다.

 

이 메서드에서 서블릿이 사용 중인 모든 리소스를 해제하고, 필요한 영속적인 상태(persistent state)를 저장해야 합니다.

예를 들어, init 메서드에서 생성한 데이터베이스 객체는 destroy 메서드에서 해제해야 합니다.

 

서블릿이 제거될 때, 실행 중인 서비스 메서드들은 모두 완료된 상태여야 합니다.
서버는 이를 보장하기 위해 모든 서비스 요청이 반환된 후 또는 서버가 지정한 유예 기간(grace period)이 끝난 후에만 destroy 메서드를 호출하려고 시도합니다.

그러나, 서버의 유예 기간보다 더 오래 실행되는 작업이 있을 경우, destroy 메서드가 호출될 때도 해당 작업이 계속 실행 중일 수 있습니다.
따라서, 클라이언트 요청을 처리하는 모든 스레드가 완료되었는지 확인해야 한다.

 

Tracking Service Requests

서블릿이 실행 중인 서비스 요청(service requests) 을 추적하려면, 서블릿 클래스 내에 현재 실행 중인 service 메서드의 개수를 카운트하는 필드를 추가해야 합니다. 이 필드는 동기화된(synchronized) 접근 메서드를 통해 값을 증가, 감소 및 조회할 수 있어야 합니다.

public class ShutdownExample extends HttpServlet {
    private int serviceCounter = 0;
    ...
    // Access methods for serviceCounter
    protected synchronized void enteringServiceMethod() {
        serviceCounter++;
    }
    protected synchronized void leavingServiceMethod() {
        serviceCounter--;
    }
    protected synchronized int numServices() {
        return serviceCounter;
    }
}

 

서비스 요청 개수를 정확히 추적하려면, service 메서드를 오버라이드(override)하여 요청이 들어올 때마다 카운터를 증가시키고, 요청이 끝날 때 감소시켜야 합니다.

이것은 HttpServlet을 상속받은 서블릿에서 service() 메서드를 직접 오버라이드해야 하는 몇 안 되는 경우 중 하나입니다.
이때, 원래의 service 메서드 기능을 유지하기 위해 super.service(request, response) 를 호출해야 합니다.

 

Notifying Methods to Shut Down

서블릿이 안전하게 종료되려면,

  1. 실행 중인 모든 서비스 요청이 완료될 때까지 기다려야 합니다.
    → 이를 위해 요청 개수를 추적하는 카운터(service counter)를 확인해야 합니다.
  2. 오래 실행되는 작업(long-running tasks)에게 종료 신호를 보내야 합니다.
    → 이를 위해 "서블릿이 종료 중인지" 여부를 저장하는 필드를 추가해야 합니다.
public class ShutdownExample extends HttpServlet {
    private boolean shuttingDown;
    ...
    //Access methods for shuttingDown
    protected synchronized void setShuttingDown(boolean flag) {
        shuttingDown = flag;
    }
    protected synchronized boolean isShuttingDown() {
        return shuttingDown;
    }
}​

 

아래는 서블릿이 안전하게 종료될 수 있도록 구현된 destroy() 메서드의 예제입니다.
이 메서드는 실행 중인 서비스 요청이 모두 완료될 때까지 대기하고, 리소스를 정리하는 역할을 합니다.

public void destroy() {
    /* Check to see whether there are still service methods /*
    /* running, and if there are, tell them to stop. */
    if (numServices()> 0) {
        setShuttingDown(true);
    }

    /* Wait for the service methods to stop. */
    while (numServices()> 0) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e) {
        }
    }
}

 

Creating Polite Long-Running Methods

서블릿이 종료될 때, 오래 실행되는 작업(long-running methods)이 서버 종료를 감지하고 안전하게 종료하도록 만들어야 합니다.

public void doPost(...) {
    ...
    for(i = 0; ((i < lotsOfStuffToDo) &&
         !isShuttingDown()); i++) {
        try {
            partOfLongRunningOperation(i);
        } catch (InterruptedException e) {
            ...
        }
    }
}

 

Uploading Files with Java Servlet Technology

파일 업로드 지원은 많은 웹 애플리케이션에서 매우 기본적이고 일반적인 요구사항입니다.
이전 Servlet 사양 버전에서는 파일 업로드를 구현하려면 외부 라이브러리를 사용하거나 복잡한 입력 처리가 필요했습니다.
이제 Java Servlet 사양은 이 문제에 대한 일반적이고 이식 가능한(generic and portable) 해결책을 제공할 수 있도록 개선되었습니다.

 

현재 Java Servlet 기술은 기본적으로 파일 업로드를 지원하므로, 사양을 구현한 어떤 웹 컨테이너에서도 multipart 요청을 자동으로 해석하고, 업로드된 파일(MIME 첨부 파일)을 HttpServletRequest 객체를 통해 접근할 수 있습니다.

 

새로운 어노테이션인 javax.servlet.annotation.MultipartConfig는 해당 서블릿이 multipart/form-data MIME 타입의 요청을 받을 것임을 나타내는 데 사용됩니다. @MultipartConfig가 선언된 서블릿은 request.getPart(String name) 또는 request.getParts() 메서드를 호출하여 multipart/form-data 요청의 파일 데이터를 Part 객체로 가져올 수 있습니다.

 

The @MultipartConfig Annotation

@MultipartConfig 어노테이션은 다음과 같은 선택적 속성을 지원합니다.

  1. location (파일 저장 위치)
    • 절대 경로로 지정된 파일 시스템상의 디렉터리 경로를 나타냅니다.
    • 애플리케이션 컨텍스트에 대한 상대 경로는 지원되지 않습니다.
    • 이 위치는 파일을 처리하는 동안 임시 저장소로 사용되거나,
      업로드된 파일 크기가 fileSizeThreshold를 초과할 경우 사용됩니다.
    • 기본값: "" (빈 문자열, 즉 기본 임시 디렉토리 사용).
  2. fileSizeThreshold (파일이 디스크에 저장되기 전까지의 임계 크기)
    • 파일 크기가 이 값(바이트 단위)을 초과하면, 메모리가 아닌 디스크에 임시 저장됩니다.
    • 기본값: 0 바이트 (즉, 기본적으로 모든 파일이 즉시 디스크에 저장됩니다.)
  3. maxFileSize (업로드 가능한 개별 파일의 최대 크기)
    • 업로드 가능한 각각의 파일 크기 제한 (바이트 단위)
    • 업로드된 파일이 이 크기를 초과하면, 웹 컨테이너가 IllegalStateException 예외를 발생시킵니다.
    • 기본값: 무제한 (unlimited)
  4. maxRequestSize (전체 요청 크기 제한)
    • multipart/form-data 요청 전체 크기의 최대 값 (바이트 단위).
    • 모든 업로드된 파일의 총 크기가 이 값을 초과하면, 웹 컨테이너가 예외를 발생시킵니다.
    • 기본값: 무제한 (unlimited)
@MultipartConfig(location="/tmp", fileSizeThreshold=1024*1024,
    maxFileSize=1024*1024*5, maxRequestSize=1024*1024*5*5)

 

파일 업로드 서블릿에서 @MultipartConfig 어노테이션을 사용하여 설정을 직접 지정하는 대신, web.xml에 서블릿 설정 요소의 하위 요소(child element) 로 추가할 수도 있습니다.

<multipart-config>
    <location>/tmp</location>
    <max-file-size>20848820</max-file-size>
    <max-request-size>418018841</max-request-size>
    <file-size-threshold>1048576</file-size-threshold>
</multipart-config>

 

The getParts and getPart Methods

Servlet 사양은 두 개의 추가적인 HttpServletRequest 메서드를 지원합니다.

 
  • Collection<Part> getParts();
  • Part getPart(String name);

request.getParts()  메서드는 모든 Part 객체의 컬렉션을 반환합니다.
만약 여러 개의 파일 입력(input)이 있을 경우, 여러 개의 Part 객체가 반환됩니다.
Part 객체는 이름이 지정되어 있으므로, getPart(String name) 메서드를 사용하여 특정한 Part에 접근할 수 있습니다.
또한, Iterable<Part>을 반환하는 getParts() 메서드를 사용하면, 모든 Part 객체에 대한 Iterator를 가져올 수 있습니다.

 

javax.servlet.http.Part 인터페이스는 각 Part를 조사할 수 있는 간단한 인터페이스를 제공합니다.
이 인터페이스는 다음과 같은 기능을 수행하는 메서드를 포함합니다.

  • Part의 이름, 크기, 콘텐츠 타입을 가져옵니다.
  • Part에 제출된 헤더를 조회합니다.
  • Part를 삭제합니다.
  • Part를 디스크에 기록합니다.

예를 들어, Part 인터페이스는 write(String filename) 메서드를 제공하여, 지정된 이름의 파일을 기록할 수 있습니다.
그 후, 파일은 @MultipartConfig 어노테이션의 location 속성에 지정된 디렉터리에 저장되거나, 파일 업로드 예제의 경우, 폼에서 Destination 필드에 지정된 위치에 저장될 수 있습니다.

 

Asynchronous Processing

일반적으로 애플리케이션 서버의 웹 컨테이너는 하나의 클라이언트 요청당 하나의 서버 스레드를 할당합니다.
그러나 트래픽이 많아질 경우, 웹 컨테이너는 요청을 처리하기 위해 많은 스레드를 필요로 하게 됩니다.
확장성(Scalability) 문제는 다음과 같은 경우 발생할 수 있습니다.

  • 메모리 부족 (Out of Memory)
    → 너무 많은 스레드를 생성하면 서버가 메모리를 초과할 수 있습니다.
  • 컨테이너의 스레드 풀 고갈 (Thread Pool Exhaustion)
    → 모든 스레드가 사용 중이면 새로운 요청을 처리할 수 없습니다.

따라서 웹 애플리케이션의 확장성을 높이려면, 요청과 관련된 스레드가 불필요하게 대기하는 일이 없도록 해야 합니다.
즉, 스레드는 즉시 컨테이너로 반환되어 새로운 요청을 처리할 수 있도록 해야 합니다.

 

스레드가 유휴 상태(Idle)로 대기하는 두 가지 주요 시나리오는 다음과 같습니다.

  1. 리소스를 기다리는 경우
    • 응답을 생성하기 전에 데이터베이스 조회, 원격 웹 서비스 호출 등의 작업이 필요할 수 있습니다.
    • 이러한 경우, 스레드는 리소스가 준비될 때까지 대기하며 아무 작업도 수행하지 않습니다.
  2. 이벤트를 기다리는 경우
    • 응답을 생성하기 전에 특정 이벤트(JMS 메시지, 다른 클라이언트의 데이터, 대기열의 새로운 데이터 등)를 기다려야 하는 경우 
    • 이때도 스레드는 이벤트가 발생할 때까지 유휴 상태로 유지됩니다.

위의 문제들은 모두 스레드가 블로킹(blocking) 상태에 놓여 컨테이너의 확장성을 제한하는 것이 원인입니다.
이를 해결하기 위해 비동기 처리(Asynchronous Processing) 를 사용할 수 있습니다. 

비동기 처리는 이러한 블로킹 작업을 새로운 스레드에서 실행하고, 요청을 처리한 스레드는 즉시 컨테이너로 반환하는 방식을 의미합니다.
즉, 기존 요청과 관련된 스레드를 비워 두어 컨테이너가 새로운 요청을 처리할 수 있도록 합니다.

 

 

Asynchronous Processing in Servlets

Java EE에서는 서블릿과 필터에서 비동기 처리를 지원합니다.
즉, 요청을 처리하는 중에 블로킹(blocking) 작업이 발생하면, 이를 별도의 비동기 실행 컨텍스트에서 처리할 수 있습니다.
이렇게 하면 요청과 연결된 스레드는 즉시 컨테이너로 반환되며, 새로운 요청을 처리하는 데 사용할 수 있습니다.

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet { ... }

 

javax.servlet.AsyncContext 클래스는 서블릿의 service 메서드 내에서 비동기 처리를 수행할 수 있도록 기능을 제공합니다.
비동기 실행을 시작하려면, 요청 객체(request)의 startAsync() 메서드를 호출하여 AsyncContext 인스턴스를 얻어야 합니다.

public void doGet(HttpServletRequest req, HttpServletResponse resp) {
   ...
   AsyncContext acontext = req.startAsync();
   ...
}
 
 

startAsync() 호출의 효과는 다음과 같습니다.

  1. 비동기 모드 활성화
    • request.startAsync()를 호출하면 요청이 비동기 모드(Asynchronous Mode) 로 전환됩니다.
    • 서비스 메서드(service(), doGet(), doPost())가 종료되더라도 응답이 즉시 커밋되지 않습니다.
    • 즉, 응답을 지연시킬 수 있으며, 별도의 스레드에서 응답을 처리할 수 있습니다.
  2. 응답을 직접 생성하거나, 다른 서블릿으로 디스패치 가능합니다.
    • 블로킹 작업(예: 데이터베이스 조회, 원격 API 호출)이 완료된 후,
      비동기 컨텍스트(AsyncContext) 내에서 응답을 생성해야 합니다.
    • 또는 요청을 다른 서블릿으로 전달(dispatch)하여 응답을 생성할 수도 있습니다.

AsyncContext class가 제공하는 기본 기능들은 다음과 같습니다.

이미지 출저 : Servlet 공식 문서

 

Waiting for a Resource

이 섹션에서는 AsyncContext 클래스가 제공하는 기능을 사용하는 방법을 다음 사용 사례를 통해 설명합니다.

  1. 서블릿은 GET 요청에서 전달된 파라미터를 받습니다.
  2. 서블릿은 데이터베이스나 웹 서비스 같은 리소스를 사용하여, 해당 파라미터 값을 기반으로 정보를 조회합니다.
    • 이 리소스는 때때로 느릴 수 있으므로, 이 과정에서 블로킹 작업이 발생할 수 있습니다.
  3. 서블릿은 조회된 결과를 사용하여 응답을 생성합니다.

아래 코드는 비동기 처리를 사용하지 않는 기본적인 서블릿을 보여줍니다.

@WebServlet(urlPatterns={"/syncservlet"})
public class SyncServlet extends HttpServlet {
   private MyRemoteResource resource;
   @Override
   public void init(ServletConfig config) {
      resource = MyRemoteResource.create("config1=x,config2=y");
   }

   @Override
   public void doGet(HttpServletRequest request, 
                     HttpServletResponse response) {
      response.setContentType("text/html;charset=UTF-8");
      String param = request.getParameter("param");
      String result = resource.process(param);
      /* ... print to the response ... */
   }
}
 
 
아래 코드는 비동기 처리를 사용하는 같은 서블릿을 보여줍니다.
@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet {
   /* ... Same variables and init method as in SyncServlet ... */

   @Override
   public void doGet(HttpServletRequest request, 
                     HttpServletResponse response) {
      response.setContentType("text/html;charset=UTF-8");
      final AsyncContext acontext = request.startAsync();
      acontext.start(new Runnable() {
         public void run() {
            String param = acontext.getRequest().getParameter("param");
            String result = resource.process(param);
            HttpServletResponse response = acontext.getResponse();
            /* ... print to the response ... */
            acontext.complete();
   }
}

 

AsyncServlet은 @WebServlet 어노테이션에 asyncSupported=true를 추가합니다.
나머지 차이점은 service 메서드 내부에서 발생합니다.

  • request.startAsync()를 호출하면 요청이 비동기적으로 처리되며, service 메서드가 끝날 때 응답이 클라이언트로 전송되지 않습니다.
  • acontext.start(new Runnable() {...})을 호출하면 컨테이너에서 새로운 스레드를 가져옵니다.
  • 내부 클래스의 run() 메서드 안에 있는 코드는 새로운 스레드에서 실행됩니다.
    • 이 내부 클래스는 비동기 컨텍스트(AsyncContext)에 접근하여 요청의 파라미터를 읽고 응답을 작성할 수 있습니다.
    • complete() 메서드를 호출하면 응답이 커밋되며 클라이언트로 전송됩니다.
  • AsyncServlet의 service 메서드는 즉시 반환되며, 요청 처리는 비동기 컨텍스트에서 계속 진행됩니다.
 

 

Nonblocking I/O

 

 

'공식문서' 카테고리의 다른 글

Garbage Collector #2  (0) 2025.03.17
[Spring Docs] DispatchServlet #1  (0) 2025.03.15
[Spring Docs] Transaction Propagation  (0) 2025.03.14
Garbage Collector #1  (0) 2025.03.09
[Spring Docs] Transaction Management #1  (0) 2025.03.07