스프링 MVC - 서블릿
서블릿은 비지니스 로직에 집중할 수 있도록 도와주는 도구이다. HTTP 통신을 위해 요청/응답에 대한 메시지 객체을 만들고, 컨탠츠 타입에 맞게 파싱하고, 클라이언트와 서버간의 세션을 생성하는 등 비지니스 로직 이외의 모든 이벤트를 처리해준다.
스프링 프레임워크에서는 스프링 전용 서블릿이 있으며, 자바의 서블릿 -> 스프링의 서블릿의 발전 과정을 확인해보면서 알아가도록 한다.
https://start.spring.io/ 으로 프로젝트를 생성할 때 Packaging은 JSP를 사용하기 위해서 WAR를 선택해야 한다.
Packaging - 애플리케이션을 쉽게 배포하고 동작할 수 있도록 필요한 파일을 모아두는 것이며, 스프링에서는 JAR/WAR 두가지를 지원한다. 기본적으로 JAR 포맷을 선택해서 사용하며, 스프링이 제공하는 이외의 별도 웹 서버(ex. JSP)가 필요하다면 WAR를 선택한다. 이번 실습에서는 WAR를 선택했다.
- JAR(Java ARchive)
- Java 애플리케이션의 클래스와 관려된 리소스 및 메타데이터를 하나로 모은 파일 포맷이다.
- 실제로 zip 파일 포맷으로 이우어진 압축 파일이며, zip 해제 소프트웨어를 사용하거나 jar -xf [jar 파일]을 통해 압축을 해제 할 수 있다.
- WAR(Webp application ARchive)
- JAR의 파일과 자바 서버 페이지, 자바 서블릿, 자바 클래스, XML, 파일, 태그 라이브러리 등 웹 애플리케이션을 함께 이루는 자원을 모아 배포하는데 사용되는 JAR 파일
서블릿 등록 및 사용
스프링 부트는 서블릿을 직접 등록해서 사용할 수 있도록 @ServletComponentScan을 지원한다.
서블릿을 등록할 때는 @WebServlet 에너테이션을 추가하며, 필드로 들어가는 name과 urlPatterns은 유일해야한다.
urlPatterns과 매핑되는 요청이 호출되면 service(return type - protected) 메서드가 실행된다.
@WebServlet(name ="servletName", urlPatterns = "/servlet/path")
public class ServletTest extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
...
}
Tip. 로그 확인하기
application.properties 에 logging.loevel.org.apache.coyote.http11=debug 를 추가하면 콘솔에서 Debug 레벌의 로그 확인이 가능하다.
운영 서버에 모든 HTTP 통신에 대한 로그를 남기면 성능저하가 발생할 수 있기 때문에 개발 단계에서만 debug 레벨로 로깅하는 것을 권장한다.
HTTP 요청 데이터
HTTP 요청은 등록된 서블릿의 urlPatterns에 매칭되는 값을 호출할 수 있다.
호출 시 HTTP 메소드 / Content-type 별로 분류 할 수 있다.
- GET 쿼리 파라미터
- url과 함께 HTTP 헤더에 같이 key=value 형태로 전달되며 검색, 필터, 페이징 등에서 많이 사용한다.
- getParameter 메소드(HttpServletResposne 객체, reutrn - string) - key에 해당되는 단일 파라미터에 대한 값을 얻기 위해 사용한다. (request.getParameter([key]))
- getParameterValues 메소드(HttpServletResposne 객체, return - string[]) - key에 해당되는 파라미터에 대한 값들을 얻기 위해 사용한다. (request.getParameter([key]))
- POST의 HTML <form> 태그
- <form>태그를 사용하는 경우 웹 브라우저는 content-type: application/x-www-form-urlencoded, message body: username-hello&age=20 으로 메시지를 만든다.
- application/x-www-form-urlencoded 형식은 앞서 GET에서 살펴본 쿼리 파라미터 형식과 같으며, 쿼리 파라미터 조회 메서드(request.getParameter())를 그대로 사용하면 된다.
→ request.getParameter() 는 GET URL 쿼리 파라미터 형식도 지원하고, POST HTML Form 형식도 둘 다 지원한다.
- API 메시지 바디
- HTTP API에서 주로 사용하며 데이터 형식은 json을 사용한다.
- POST, PUT, PATCH 메서드에서 많이 사용
- message body가 문자열인 경우
- getInputstream 메서드를을 사용하여 내용을 확인 할 수 있다.
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 문자열의 인코딩은 대부분 UTF-8을 사용
- getInputstream 메서드를을 사용하여 내용을 확인 할 수 있다.
- message body가 json인 경우
- 스프링은 json을 사용하기 위해 jackson 라이브러리의 ObjectMapper 를 사용한다.
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
- 스프링은 json을 사용하기 위해 jackson 라이브러리의 ObjectMapper 를 사용한다.
- message body가 문자열인 경우
HTTP 응답
HttpServletResponse 객체에 헤더를 설정하여 클라이언트에 전달하며, 응답메시지를 직접 작성할 수 있다.
헤더 설정의 경우 SetHeader(헤더 필드명, 값) 으로 지정하는 방법과 헤더 필드와 관련된 Set 메서드를 직접 호출하는 방법이 있다.
@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// [status-line] 으로 약속된 enum을 통해 상태 코드를 지정할 수 있음
response.setStatus(HttpServletResponse.SC_OK);
// [response-headers]
// response.setContentType("text/plain");
// response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "text/plain;charset=utf-8");
// 캐시를 사용하지 않기 위해 지정해야되는 헤더
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
// 커스텀 헤더
response.setHeader("my-header","hello");
// 응답 메시지 작성
response.getWriter().write("안녕하세요");
cookie(response);
redirect(response);
}
private void cookie(HttpServletResponse response) {
// Set-Cookie: myCookie=good; Max-Age=600;
// response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600);
response.addCookie(cookie);
}
private void redirect(HttpServletResponse response) throws IOException{
// Status Code 302
// Location: /basic/hello-form.html
response.setStatus(HttpServletResponse.SC_FOUND);
response.setHeader("Location", "/basic/hello-form.html");
}
}
HTML을 service 메서드에 직접 넣는다면 굉장히 힘들고, 기능 구분(비지니스 로직 vs 뷰 랜더링)이 되지 않아 유지보수에 문제가 될 수 있다. 이를 해결하기 위해 템플릿 엔진(JSP, Thymleaf)을 사용한다.