본문 바로가기
Programming/JSP&Servlet

서블릿 컨테이너의 이해

by TinKerBellBass 2017. 7. 31.
728x90
반응형

1. Servlet Class Diagram


서블릿 컨테이너에서 사용되는 클래스들의 관계를 나타낸 클래스 다이어그램

큰 그림으로 이해하면 Servlet 인터페이스를 추상 클래스인 GenericServlet 이 구현,

그리고 이 GenericServlet 을 HttpServlet 이 상속하고 있다.


GenericServlet 의 메소드 Service()는 ServletRequest, ServletResponse 객체를 인자로 받고 있고,

HttpServlet 의 메소드 Service()는 HttpServletRequest, HttpServletResponse 객체를 인자로 받고 있다.

HttpServletRequest 는 ServletRequest 를 상속,

HttpServletResponse 는 ServletResponse 를 상속.


이것이 무엇을 의미하는가 생각해 볼 필요가 있다.

우리가 지금 쓰고 있는 브라우저 앞에는 http 또는 https 가 붙어 있는데,

이것은 통신규약(프로토콜) 중 하나를 의미한다.

http는 응용계층의 프로토콜로 사용자(클라이어트)의 요청을 서버에서 적절히 처리하여 응답한다.

클라이언트가 http에 요청을 하면 요청 정보는 HttpServletRequest, HttpServletRespose 객체를 가지고 

서블릿 컨테이너로 들어가서 HttpServlet 클래스의 메소드들을 이용하여 처리된다.


그럼 httpServlet 클래스만 작성해 두면 될 것을, 왜 GenericServlet 추상 클래스를 만들어 두었을까?

여기서부터는 나만의 상상으로 적는 글이므로 사실이 아닐 수도 있겠지만.. 적어 보도록 하겠다.

만약 http 보다 뛰어난 프로토콜이 등장한다고 가정해 보자.

그 프로토콜의 이름을 superHttp라고 하면, 

SuperHttpServletRequest, SuperHttpServletResponse 객체를 가지고

SuprerHttpServlet 클래스의 메소드들을 가지고 요청을 처리해야 할 것이다.


객체지향 개념의 다형성을 생각해 보자.

프로토콜이 바뀌어도 GenericServlet 추상클래스를 상속하는 클래스로 만들면

서블릿 컨테이너 세상을 움직이고 있는  코드의 수정이 거의 일어나지 않을 것이다. 

그래서 아마 자바 세상에서 이렇게 클래스 다이어그램을 만들어 두지 않았을까 조심스럽게 생각해 본다.


2. 클라이언트 요청을 처리하는 HttpServlet 클래스의 메소드들

클라이언트에서 요청이 들어오면 서블릿 컨테이너는 http 프로토콜의 header 와 body에서 정보를 가져와
HttpServletRequest, HttpServletResponse 객체에 담는다.
그리고 서블릿 컨테이너는 무조건 HttpServlet 클래스의 service(ServletRequest req, ServletResponse res) 메소드를 실행한다.
눈여겨 볼 것은 메소드의 인자가 ServletRequest, ServletResponse 라는 것이다.
ServletRequest 는 HttpServetRequest, ServletResponse 는 HttpServletResponse 의
super 클래스 이므로 서블릿 컨테이너에서 생성된 HttpServletRequest, HttpServeltResponse 객체가 들어 갈 수 있다.
이런 다형성을 이용해서 나중에 새로운 프로토콜이 나와도 확장을 용이하게 할 수 있을 것이다.

실제로 코드는 이렇게 작성되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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("non-HTTP request or response");
    }
    service(request, response);
    }
}
cs

이 메소드는  ServletRequest, ServletRespnse 객체를 HttpServletRequest, HttpServletResponse 로 형변환 시켜,
service(HttpServletReuest req, HttpServletResponse res)를 호출하고 있다.

서블릿 컨테이너에서 제일 먼저 무조건 실행되는 service(ServletRequest req, ServletResponse res) 메소드 다음에는
http 프로토콜을 처리하기 위한 service(HttpServletReuest req, HttpServletResponse res)가 실행되는 것이다.
(참고로 GenericServlet 클래스의 메소드들은 일반적인 통신 및 네트워크를 처리하기 위한 메소드들이 정의되어 있고,
HttpServlet 클래스의 메소드들은 http 통신 및 네트워크를 처리하기 위한 메소드들이 정의되어 있다.)


이번에는 service(HttpServletReuest req, HttpServletResponse resp) 코드를 살펴보자
 
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
54
55
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
 
    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
        // servlet doesn't support if-modified-since, no reason
        // to go through further expensive logic
        doGet(req, resp);
        } else {
        long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
            // If the servlet mod time is later, call doGet()
            // Round down to the nearest second for a proper compare
            // A ifModifiedSince of -1 will always be less
            maybeSetLastModified(resp, lastModified);
            doGet(req, resp);
            } else {
            resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
 
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);    
        
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
        
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
        
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
        
    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //
 
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0= method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}
cs

코드가 길어졌지만 하나씩 뜯어보면,
먼저 String method = req.getMethod(); 을 통해 
http header에 기록되어 있는 정보(HttpServletRequest 객체에 담겨 있다)를 얻어온다.
그 정보는 http 메소드들 중 하나일 것이다.
대표적으로 우리가 잘 아는 
정보를 얻어와라 GET,
정보를 줄게 POST,
정보를 업데이트 시켜라 PUT,
정보를 지워라 DELETE 가 있다.

이렇게 요청 받은 http의 메소드 형식에 따라, 
HttpServlet 클래스에 있는 적절한 메소드를 호출한다.
METHOD_GET -> doGet(req, resp)
METHOD_POST -> doPost(req, resp)
우리가 서블릿에서 제일 많이 사용하는 두 메소드 이다.

이렇게 클라이언트에서 들어오는 요청을 서블릿 컨테이너는 처리한다.

3. 정리

(1) 클라이언트 요청 
(2) 서블릿 컨테이너에서 http header 및 body 에 담겨있는 정보를 담아서 
    HttpServletRequest, HttpServletResponse 객체 생성
(3) HttpServlet 클래스의 service(ServletRequest req, ServletResponse res) 메소드 실행
(4) service(ServletRequest req, ServletResponse res) 메소드가 
    service(HttpServletRequest req, ServletResponse resp) 메소드 호출
(5) service(HttpServletRequest req, ServletResponse resp) 메소드가
    http 가 요청한 method 방식에 따라 적절한 메소드를 호출하여 처리
    (대표적으로 GET -> doGet(req, resp) / POST -> doPost(req, resp) 호출)



728x90
반응형

댓글