• 클라이언트의 요청을 처리하고 그 결과를 반환하는 Servlet 클래스의 규칙을 지킨 자바 웹 프로그래밍 기술
  • 즉, 자바를 사용하여 웹을 만들기 위해 사용하는 기술

특징

  1. 클라이언트 요청에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트
  2. JAVA Thread로 동작
  3. tomcat의 HttpServlet을 구현

서블릿 컨테이너

  • 서블릿을 관리하는 컨테이너
  • ex) tomcat

◼︎ 특징

  1. 클라이언트 요청에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트
  2. JAVA Thread로 동작
  3. tomcat의 HttpServlet을 구현

서블릿 라이프사이클

  1. 서블릿 생성 이후 초기화 init()
  2. 클라이언트가 보낸 요청 처리 service()
  3. 서블릿 사용 종료 후 제거 destory()

스프링 서블릿의 상속관계

imageHttpServlet까지는 WAS인 톰캣에서 구현되어 있고 그 아래부터는 스프링에 구현되어 있다

1. Servlet (javax.servlet)

public interface Servlet {
    public void init(ServletConfig config) throws ServletException;

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    public void destroy();
}
  • 서블릿 라이프사이클에 따라 메서드를 정의하고 있다

2. HttpServlet (javax.servlet.http)

public abstract class HttpServlet extends GenericServlet {
    private static final long serialVersionUID = 1L;

    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";

    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
    private static final String HEADER_LASTMOD = "Last-Modified";

    ...

     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_get_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_post_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_put_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }

    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_delete_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ...
        if (method.equals(METHOD_GET)) {
            ...
            doGet(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);
        }
        ...
    }

    @Override
    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"));
        }
        service(request, response);
    }
}

→ HTTP 메서드에 따라 실행될 service() 부분이 구현되어 있다

3. DispatcherServlet (org.springframework.web.servlet)

  • HTTP 요청 핸들러(=컨트롤러)의 중심이 되는 프론트 컨트롤러이다
  • doDispatch() : 적절한 핸들러를 찾아 맵핑해주고 뷰까지 찾아 렌더링하는 핵심로직
public class DispatcherServlet extends FrameworkServlet {
    ...
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

        ModelAndView mv = null;
        Exception dispatchException = null;

        processedRequest = checkMultipart(request);
        multipartRequestParsed = (processedRequest != request);

        // 1. 핸들러 조회(맵핑)
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }

        // 2. HandlerAdapter 조회
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        String method = request.getMethod();
        boolean isGet = HttpMethod.GET.matches(method);
        if (isGet || HttpMethod.HEAD.matches(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                return;
            }
        }

        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        // 3. HandlerAdapter 실행
        // 4. 핸들러 어댑터를 통해 핸들러 실행
        // 5. 실행결과로 ModelAndView 반환
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        if (asyncManager.isConcurrentHandlingStarted()) {
            return;
        }

        applyDefaultViewName(processedRequest, mv);
        mappedHandler.applyPostHandle(processedRequest, response, mv);

        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
        if (exception != null) {
            ...
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            // EXCEPTION 처리
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
            ...
        }
        render(mv, request, response);
    }

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        View view;
        String viewName = mv.getViewName();

        // 6. ViewResolver를 통해 뷰 조회
        // 7. View 반환
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request)

        // 8. 뷰 렌더링
        view.render(mv.getModelInternal(), request, response);
    }

    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
            Locale locale, HttpServletRequest request) throws Exception {

        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }

    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, 
            @Nullable Object handler, Exception ex) throws Exception {

        ModelAndView exMv = null;
        if (this.handlerExceptionResolvers != null) {
            for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
                // ExceptionResolver 탐색
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }

        if (exMv != null) {
            ...
            if (!exMv.hasView()) {
                String defaultViewName = getDefaultViewName(request);
                if (defaultViewName != null) {
                    exMv.setViewName(defaultViewName);
                }
            }
        }
        return exMv;
    }
}

서블릿 객체

서블릿에서 HTTP Request와 Response를 다루는 객체이다
HTTP(링크) 선행학습이 필요하다

1. HttpServletRequest

HTTP Request 메시지를 편리하게 사용할 수 있도록 해주는 객체

GET /request-test HTTP/1.1
Host: localhost:8080
Content-Type: text/plain

@WebServlet('/request-test')
public class RequestHeaderServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        printStartLine(request);
        printHeader(request);
        printHeaderUtils(request);
        printEtc(request);
        
        response.getWriter().write("ok");
    }

    private void printStartLine(HttpServletRequest request) {
        System.out.println(request.getMethod()); //GET
        System.out.println(request.getProtocol()); //HTTP/1.1
        System.out.println(request.getScheme()); // http 
        System.out.println(request.getRequestURL()); // http://localhost:8080/request-test
        System.out.println(request.getRequestURI());  // /request-test
        System.out.println(request.getQueryString());  // username=hi
        System.out.println(request.isSecure()); //https사용 유무
    }

    private void printHeader(HttpServletRequest request) {
        request.getHeaderNames().asIterator()
                .forEachRemaining(headerName -> System.out.println(headerName + ":" + request.getHeader(headerName)));
    }

    private void printHeaderUtils(HttpServletRequest request) {
        // Host 편의
        System.out.println(request.getServerName());//Host 헤더
        System.out.println(request.getServerPort());//Host 포트

        // Accept-Language
        request.getLocales().asIterator()
                .forEachRemaining(locale -> System.out.println("locale = "+ locale));

        // Cookie
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                System.out.println(cookie.getName() + ": "+cookie.getValue());
            }
        }

        // Content 편의
        System.out.println(request.getContentType());
        System.out.println(request.getContentLength());
        System.out.println(request.getCharacterEncoding());
    }

    private void printEtc(HttpServletRequest request) {
        // Remote 정보
        System.out.println(request.getRemoteHost());
        System.out.println(request.getRemoteAddr());
        System.out.println(request.getRemotePort());

        // Local 정보
        System.out.println(request.getLocalName());
        System.out.println(request.getLocalAddr());
        System.out.println(request.getLocalPort());
    }
}

2. HttpServletResponse

HTTP Response 메시지를 편리하게 사용할 수 있도록 해주는 객체

HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 1523

<html>
    <body>...</body>
</html>
@WebServlet('/request-test')
public class ResponseHeaderServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Status-Line
        response.setStatus(HttpServletResponse.SC_OK)

        // Header
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("my-header", "hello");

        // Header 편의
        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");

        // Cookie
        Cookie cookie = new Cookie("myCookie", "good");
        cookie.setMaxAge(600); //600초
        response.addCookie(cookie);

        // Redirect
        response.sendRedirect("/basic/hello-form.html");

        // Body
        PrintWriter writer = response.getWriter();
        writer.println("성공")
    }
}