서블릿(Servlet) 이란?
- 클라이언트의 요청을 처리하고 그 결과를 반환하는 Servlet 클래스의 규칙을 지킨 자바 웹 프로그래밍 기술
- 즉, 자바를 사용하여 웹을 만들기 위해 사용하는 기술
특징
- 클라이언트 요청에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트
- JAVA Thread로 동작
- tomcat의 HttpServlet을 구현
서블릿 컨테이너
- 서블릿을 관리하는 컨테이너
- ex) tomcat
◼︎ 특징
- 클라이언트 요청에 대해 동적으로 작동하는 웹 애플리케이션 컴포넌트
- JAVA Thread로 동작
- tomcat의 HttpServlet을 구현
서블릿 라이프사이클
- 서블릿 생성 이후 초기화
init()
- 클라이언트가 보낸 요청 처리
service()
- 서블릿 사용 종료 후 제거
destory()
스프링 서블릿의 상속관계
→ HttpServlet
까지는 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("성공")
}
}