포스트

Day68 Java프로그래밍 기초(Servlet)

Servlet

Web Application과 CGI

CGI(Common Gateway Interface)

  • 웹 서버가 외부 프로그램을 실행하고 그 결과를 클라이언트(브라우저)에게 전달하는 방법을 정의하는 표준 프로토콜이다.
  • CGI 스크립트는 주로 웹 서버와 동적 콘텐츠를 생성하는 프로그램 간의 인터페이스 역할을 한다.
  • CGI를 통해 웹 서버는 데이터베이스 쿼리, 파일 처리, 폼 데이터 처리와 같은 작업을 수행할 수 있다.

  • 특징:
    • CGI 스크립트는 서버에서 실행되는 프로그램으로, 다양한 언어(Perl, Python, C 등)로 작성될 수 있다.
    • 각 요청마다 별도의 프로세스가 생성되므로, 많은 트래픽이 발생할 경우 성능 문제가 생길 수 있다.
    • 웹 서버와 프로그램 간의 통신에 주로 환경 변수와 표준 입출력 방식을 사용한다.
  • 장점:
    • 언어에 구애받지 않으며, 다양한 플랫폼에서 동작할 수 있다.
    • 비교적 구현이 간단하여 초기 웹 개발에서 많이 사용되었다.
  • 단점:
    • 요청마다 프로세스를 생성하기 때문에 서버 자원 소모가 많다.
    • 성능과 확장성 측면에서 현대적인 웹 기술에 비해 떨어진다.

Web Application의 발전

  • 대용량의 트레픽을 처리하기 위해 Java와 같은 OOP(객체지향프로그래밍)이 등장하였다.

image

Servlet의 구동과정

  • Servlet 컨테이너는 Java Servlet을 실행하기 위해 웹 서버와 애플리케이션 간의 중간 역할을 하는 컴포넌트이다.
  • Servlet 컨테이너가 Servlet을 호출하는 과정은 다음과 같은 단계로 이루어진다.
  1. 클라이언트 요청 수신
    • 사용자가 브라우저를 통해 웹 애플리케이션에 대한 요청을 보낸다. 이 요청은 HTTP 프로토콜을 통해 전달된다.
  2. Servlet 컨테이너가 요청을 처리
    • 요청이 웹 서버에 도착하면, 웹 서버는 이 요청을 Servlet 컨테이너로 전달한다.
    • Servlet 컨테이너는 요청된 URL에 따라 어떤 Servlet이 이 요청을 처리할지 결정한다.
    • 이는 웹 애플리케이션의 web.xml 파일이나 애노테이션(@WebServlet)을 통해 매핑되어 있다.
  3. Servlet 인스턴스 생성 또는 재사용
    • 컨테이너는 요청을 처리할 Servlet 인스턴스를 생성하거나, 이미 생성된 인스턴스를 재사용한다.
    • 최초 요청: 해당 Servlet이 처음 요청되었다면, 컨테이너는 해당 Servlet 클래스를 로드하고, 인스턴스를 생성한다.
    • 이미 생성된 경우: 컨테이너는 기존의 Servlet 인스턴스를 재사용한다.
  4. Servlet 초기화 (init 메서드 호출)
    • 만약 Servlet 인스턴스가 새로 생성되었을 경우, 컨테이너는 init() 메서드를 호출하여 초기화 작업을 수행한다.
    • init() 메서드는 Servlet이 한 번만 호출되며, 이후 동일한 Servlet 인스턴스가 여러 요청을 처리할 수 있게 된다.
  5. 요청 처리 (service 메서드 호출)
    • Servlet 인스턴스가 준비되면, 컨테이너는 service() 메서드를 호출하여 클라이언트의 요청을 처리한다.
    • service() 메서드는 클라이언트 요청에 따라 적절한 메서드를 호출하여 요청을 처리한다.
    • ServletRequest 객체와 ServletResponse 객체가 service() 메서드에 전달되어 요청 정보를 처리하고 응답을 생성하는 데 사용된다.
  6. 응답 전송
    • Servlet이 요청을 처리한 후, 생성된 응답은 ServletResponse 객체를 통해 클라이언트에게 전송된다.
    • 클라이언트는 웹 브라우저에서 이 응답을 받아 화면에 표시한다.
  7. Servlet 종료 (destroy 메서드 호출)

    • Servlet 컨테이너가 종료되거나 해당 Servlet이 더 이상 필요하지 않게 되면, 컨테이너는 destroy() 메서드를 호출하여 Servlet을 종료하고, 자원을 해제한다.

image

서블릿 인스턴스와 클래스의 관계

  • 서블릿 인스턴스는 오직 클래스 마다 한 개만 생성된다.
  • 클라이언트마다 구분되어야 할 데이터는 서블릿 인스턴스 변수에 보관해서는 안된다.
  • 인스턴스는 모든 클라이언트가 공유하기 때문에 config와 같은 공유하는 데이터만 필드에 사용해야한다.

image

Servlet의 기본개념

Servlet 등록하기

  • Servlet을 사용하기위해서는 Servlet Container가 Servlet을 호출해야한다.
  • Servlet을 호출하는 방법은 web.xml 파일을 사용하는 방법과 애노테이션(@WebServlet)을 사용하는 방법이 있다.

image

web.xml 파일을 사용하는 방법

  • web.xml은 서블릿의 배포 설명자(deployment descriptor) 파일로, 서블릿 매핑을 설정하는 데 사용되며, 이 파일은 WEB-INF 폴더 내에 위치한다.
  • DDF의 형식은 등록할 class파일의 별명과 패키지명을 등록하고, 해당 별명에 대한 URL Path를 설정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0"
  metadata-complete="false">
    <!--리스너 등록 -->
    <listener>
        <listener-class>com.eomcs.web.ex02.Listener01</listener-class>
    </listener>
    
    <!--서블릿 등록 -->
    <servlet>
        <servlet-name>s01</servlet-name>
        <servlet-class>com.eomcs.web.ex01.Servlet01</servlet-class>
    </servlet>
    
    <!-- 서블릿 URL Path -->
    <servlet-mapping>
        <servlet-name>s01</servlet-name>
        <url-pattern>/ex01/first</url-pattern>
    </servlet-mapping>
    
    
    <!--필터 등록 -->
   <filter>
        <filter-name>f01</filter-name>
        <filter-class>com.eomcs.web.ex02.Filter01</filter-class>
    </filter>
    
    <!-- 필터 URL Path -->
    <filter-mapping>
        <filter-name>f01</filter-name>
        <url-pattern>/ex02/*</url-pattern>
    </filter-mapping>
    
</web-app>


에노테이션을 사용하는 방법

  • @WebServlet(“URL”)을 사용하여 Servlet Container가 request에 대한 호출을 수행 할 수 있게 한다.
  • 다음은 사용가능한 에노테이션 모음이다.
1
2
3
4
5
6
7
8
9
10
// 하나의 클래스 파일이 여러개의 url을 가질때 사용한다. 
@WebServlet(urlPatterns = {"/ex01/s2", "/ex01/ss2", "/ex01/sss2"})

// 에노테이션의 기본형에 urlPatterns도 사용 가능하다.
@WebServlet(urlPatterns={"/ex01/s2"})
@WebServlet(urlPatterns="/ex01/s2")

// 어노테이션의 기본형도 사용가능하다.
@WebServlet(value="/ex01/s2")
@WebServlet("/ex01/s2")


Servlet 만들기

Servlet 구현체로 만들기

  • 기본적으로 Servlet 구현체의 구조는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Servlet01 implements Servlet {

  ServletConfig config;

  public Servlet01() {
    System.out.println("Servlet01()");
  }

  @Override
  public void init(ServletConfig config) throws ServletException {
    // 서블릿 객체를 생성한 후 생성자 다음에 이 메서드가 호출된다.
    // => 서블릿을 실행할 때 사용할 자원을 이 메서드에서 준비한다.
    // => 파라미터로 받은 ServletConfig 객체는 인스턴스 변수에 보관해 두었다가 필요할 때 사용한다.
    // => 각각의 서블릿 클래스마다 객체는 한 개만 생성된다.
    // => 따라서 각 서블릿에 대해 init()는 한 번만 호출된다.
    this.config = config;
    System.out.println("Servlet01.init(ServletConfig)");
  }

  @Override
  public void service(ServletRequest req, ServletResponse res)
      throws ServletException, IOException {
    HttpServletRequest req2 = (HttpServletRequest) req;
    HttpServletResponse res2 = (HttpServletResponse) res;
    // 클라이언트가 이 서블릿의 실행을 요청할 때마다 호출된다.
    // 클라이언트가 요청한 작업을 수행한다.
    System.out.println("Servlet01.service(ServletRequest,ServletResponse)");
  }

  @Override
  public void destroy() {
    // 웹 애플리케이션을 종료할 때(서버 종료 포함) 호출된다.
    // => 이 서블릿이 만든 자원을 해제하는 코드를 이 메서드에 둔다.
    System.out.println("Servlet01.destroy()");
  }

  @Override
  public ServletConfig getServletConfig() {
    // 서블릿에서 작업을 수행하는 중에 서블릿 관련 설정 정보를 꺼낼 때 이 메서드를 호출한다.
    // 이 메서드가 리턴하는 값이 ServletConfig 객체인데
    // 이 객체를 이용하면 서블릿 설정 정보를 알 수 있다.
    // 보통 init()의 파라미터 값을 리턴한다.
    System.out.println("Servlet01.getServletConfig()");
    return this.config;
  }

  @Override
  public String getServletInfo() {
    // 서블릿 컨테이너가 관리자 화면을 출력할 때 이 메서드를 호출한다.
    // 이 메서드의 리턴 값은 서블릿을 소개하는 간단한 문자열이다.
    System.out.println("Servlet01.getServletInfo()");
    return "Servlet01";
  }


GenericServlet 상속 받기

  • javax.servlet.Servlet 인터페이스를 구현하였다.
  • service() 메서드만 남겨두고 나머지 메서드들은 모두 구현하였다.
  • 이 클래스를 상속 받는 서브 클래스는 service() 만 구현하면 된다.
  • 초기설정이 필요하면 init()메서드를 오버라이딩하면된다.
  • Servlet이 호출시에 init(req,res)의 메서드가 inint()를 호출한다.
1
2
3
4
public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}

image

HttpServlet 상속받기

  • javax.servlet.GenericServlet 추상 클래스를 상속 받았다.
  • service(ServletRequest req, ServletResponse res) 메서드도 구현했다.
  • service(HttpServletRequest req, HttpServletResponse resp) 메서드도 구현했다.

service(HttpServletRequest req, HttpServletResponse resp) 호출하기

  • Servlet 인터페이스의 service(ServletRequest, ServletResponse)를 오버라이딩 하는 대신에 HttpServlet 클래스가 추가한 service(HttpServletRequest, HttpServletResponse)를 오버라이딩을 해야 한다.
  • HTTP 프로토콜을 다루려면 GenericServlet을 상속 받지 말고 HttpServlet을 상속 받아 서블릿 클래스를 만드는 것이 유지보수에 용이하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

    HttpServletRequest request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }
    //HttpServletRequest, HttpServletResponse의 매개변수로도 service 호출이 가능하다.
    service(request, response);
}


doGet(), doPost() 호출하기

  • HttpServlet은 특정 프로토콜에 대한 명령을 처리할 수 있다.
  • 대표적으로 GET, POST요청이 있다.

GET 요청

  • 목적: GET 요청은 주로 서버에서 데이터를 조회할 때 사용한다. 예를 들어, 웹 페이지를 열거나, 검색 쿼리를 전달할 때 주로 사용된다.
  • 데이터 전송 방식: GET 요청은 데이터를 URL의 쿼리 스트링(query string)으로 전달한다. 예를 들어, https://example.com/search?query=chatgpt에서 query=chatgpt 부분이 쿼리 스트링이다.
  • 특징:
    • URL에 데이터가 포함되기 때문에 데이터가 브라우저 히스토리, 즐겨찾기, 서버 로그 등에 저장될 수 있다.
    • 데이터 크기에 제한이 있다. URL의 길이가 너무 길면 일부 서버나 브라우저에서 요청이 실패할 수 있다.
    • 캐싱이 가능하다. 브라우저나 프록시 서버는 동일한 GET 요청에 대해 응답을 캐싱할 수 있다.
    • 멱등성: 동일한 GET 요청을 여러 번 보내도 결과가 동일해야 한다. 즉, 서버의 상태를 변경하지 않는다.

image

POST 요청

  • 목적: POST 요청은 서버에 데이터를 제출하거나 서버의 상태를 변경할 때 사용한다. 예를 들어, 폼 데이터를 제출하거나, 파일을 업로드하거나, 데이터베이스에 새로운 레코드를 추가할 때 사용된다.
  • 데이터 전송 방식: POST 요청은 데이터를 HTTP 요청의 본문(body)으로 전달한다. URL에는 데이터가 포함되지 않고, 본문에 숨겨져 있기 때문에 보안 측면에서 더 안전할 수 있다(하지만 전송 중에 데이터가 암호화되지 않은 경우, 여전히 노출될 수 있다).
  • 특징:
    • 데이터 크기에 제한이 거의 없다. 대량의 데이터를 전송할 수 있다.
    • URL에 데이터가 포함되지 않기 때문에 브라우저 히스토리나 즐겨찾기에 노출되지 않는다.
    • 캐싱되지 않는다. 일반적으로 동일한 POST 요청을 여러 번 보내면 서버의 상태가 변경될 수 있다.
    • 멱등성이 없다. 동일한 POST 요청을 여러 번 보내면 서버에서 다른 결과를 초래할 수 있다(예: 동일한 데이터를 여러 번 추가).

image

Filer 만들기

  • 서블릿을 실행하기 전후에 필요한 작업을 수행
  • 서블릿 실행 전
    • 웹브라우저가 보낸 암호화된 파라미터 값을 서블릿으로 전달하기 전에 암호 해제하기
    • 웹브라우저가 보낸 압축된 데이터를 서블릿으로 전달하기 전에 압축 해제하기
    • 서블릿의 실행을 요청할 권한이 있는지 검사하기
    • 로그인 사용자인지 검사하기
    • 로그 남기기
  • 서블릿 실행 후
    • 클라이언트로 보낼 데이터를 압축하기
    • 클라이언트로 보낼 데이터를 암호화시키기

image

Filter의 구조

  • init(FilterConfig config)
    • init() 메서드는 Filter가 초기화될 때 호출되며, Filter가 사용할 초기 설정을 구성하는 데 사용된다.
    • FilterConfig 객체를 통해 초기화 매개변수를 얻을 수 있으며, 필터가 처음 생성될 때 한 번만 호출된다.
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    • doFilter() 메서드는 클라이언트의 요청이 들어올 때마다 호출되며, 실제 필터링 작업을 수행한다.
    • 이 메서드는 ServletRequestServletResponse 객체를 통해 요청과 응답을 조작할 수 있다.
    • FilterChain 객체를 사용해 요청을 다음 필터 또는 최종 대상(Servlet 또는 JSP)으로 전달할 수 있다.
    • 주로 요청의 로깅, 인증 및 인가, 데이터 압축, 응답 조작 등의 작업을 수행한다.
  • destroy()
    • destroy() 메서드는 Filter가 더 이상 필요하지 않거나, 컨테이너가 종료될 때 호출된다.
    • 이 메서드를 통해 자원을 해제하거나, 종료 작업을 수행할 수 있다.
    • destroy() 메서드는 필터의 생명주기에서 마지막으로 호출되는 메서드라 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@WebFilter("/ex02/*")
public class Filter01 implements Filter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 웹 애플리케이션을 시작할 때 필터 객체를 생성한다.
    // 이 메서드는 필터 객체를 생성한 후 자동으로 호출된다.
    // 필터가 사용할 자원을 이 메서드에서 준비한다.
    // => 웹 애플리케이션을 시작할 때 필터는 자동 생성된다.
    System.out.println("Filter01.init()");
  }

  @Override
  public void destroy() {
    // 웹 애플리케이션을 종료할 때 호출된다.
    // init()에서 준비한 자원을 해제한다.
    System.out.println("Filter01.destroy()");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    // 요청이 들어 올 때 마다 호출된다.
    // => 단 필터를 설정할 때 지정된 URL의 요청에만 호출된다.
    // => 서블릿이 실행되기 전에 필터가 먼저 실행된다.
    // => 서블릿을 실행한 후 다시 필터로 리턴한다.
    System.out.println("Filter01.doFilter() : 시작");

    // 다음 필터를 실행한다.
    // 만약 다음 필터가 없으면,
    // 요청한 서블릿의 service() 메서드를 호출한다.
    // service() 메서드 호출이 끝나면 리턴된다.
    chain.doFilter(request, response);

    // 체인에 연결된 필터나 서블릿이 모두 실행된 다음에
    // 다시 이 필터로 리턴될 것이다.
    System.out.println("Filter01.doFilter() : 종료");
  }


doFilter

  • FilterChain을 호출하여, 다음 Filter가 있는지 확인하고, 없으면 Servlet의 Service를 실행한다.
  • Servlet 컨테이너는 요청에 적용될 모든 필터를 순서대로 연결하여 하나의 FilterChain 객체를 생성한다. 이 객체는 각 필터가 요청을 처리한 후, 다음 필터로 요청을 전달할 수 있도록 한다.

image

  • Filter의 맵핑에 따라 필터 적용 유무가 결정된다.

image

Listener 만들기

  • 서블릿 컨테이너 또는 서블릿, 세션 등의 객체 상태가 변경되었을 때 보고 받는 옵저버패턴이다.

image

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.