본문 바로가기

스프링 부트

📋 Spring MVC - 2. 서블릿

2021-05-03글

예제

스프링을 사용하지는 않지만 스프링 부트 프로젝트를 만든다.
서블릿은 톰캣 같은 WAS를 직접 설치하고, 그 위에 서블릿 코드를 클래스 파일로 빌드해서 올린 다음 톰캣 서버를 실행해야 했는데,
스프링 부트는 톰캣 서버를 내장하고 있으니, 이로 진행한다.

보통은 Jar를 선택하지만, JSP를 돌리기 위해 War를 선택한다.

HelloServlet

// 서블릿이 호출되면 이 service가 호출된다.
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
        System.out.println("HelloServlet");
        System.out.println("Request : " + req);
        System.out.println("Response : " + resp);

        String name = req.getParameter("name");
        System.out.println(name);
        resp.setCharacterEncoding("utf-8");
        resp.getWriter().write("hello" + name);
    }
}
HelloServlet
Request : org.apache.catalina.connector.RequestFacade@de1a8e6
Response : org.apache.catalina.connector.ResponseFacade@2b3735f6
amazzi
  • @WebServlet : 서블릿 어노테이션
  • @ServletComponentScan : 스프링이 자동으로 서블릿을 찾아 등록해준다.
  • HTTP 요청이 오면 서블릿 컨테이너인 WAS가 HTTP 요청, 응답 객체를 서블릿에 던져준다.
  • req.getParameter() : 요청에서 해당 Parameter를 가져온다.
  • resp.setCharacterEncoding("utf-8"); : 헤더에 CharacterEncoding을 지정한다.
  • resp.getWriter().write(); : write() 에 바디에 담을 데이터를 넣어준다.

🤔 RequestFacade?

HttpServletRequest는 인터페이스이다.
여러가지 WAS 서버들이 이 인터페이스의 구현체를 구현하고 있어 다양한 WAS를 사용할 수 있는 것이다.

HTTP 요청 메시지 로그로 확인하기

application-properties에 다음을 추가한다.

logging.level.org.apache.coyote.http11=debug

웹 애플리케이션 서버의 요청 응답 구조


HttpServletRequest

서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다.
그리고 결과를 HttpServletRequest 객체에 담아서 제공한다.

  • START LINE
    • HTTP 메소드
    • URL
    • 쿼리 스트링
    • 스키마, 프로토콜
  • HEADER
    • 헤더 조회
  • BODY
    • form 파라미터의 형식 조회
    • message body 데이터 직접 조회
  • 부가 기능
    • 임시 저장소 기능
      • 해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능
      • 저장: request.setAttribute(name, value)
      • 조회: request.getAttribute(name)
    • 세션 관리 기능
      • request.getSession(create: true)

기본 사용

Start Line

request.getMethod() = GET
request.getProtocal() = HTTP/1.1
request.getScheme() = http
request.getRequestURL() = http://localhost:8080/request-header
request.getRequestURI() = /request-header
request.getQueryString() = username=hello
request.isSecure() = false

헤더 정보

request.getHeaderNames().asIterator()
        .forEachRemaining(headerName -> System.out.println(headerName + ": " +
                request.getHeader(headerName)));
System.out.println("--- Headers - end ---");
System.out.println();

이 외에도 Header 정보를 추출하는 메서드들이 있다.

  • request.getServerName()
  • request.getServerPort()
  • request.getLocale()
  • request.getCookies()
  • request.getContentType()
  • request.getCharacterEncoding()

HTTP 요청 데이터 - GET 쿼리 파라미터

메시지 바디 없이, URL의 쿼리 파라미터를 사용해서 데이터를 전달하는 방법.
예) 검색, 필터, 페이징등에서 많이 사용

http://localhost:8080/request-param?username=hello&age=20

쿼리파라미터는URL에다음과같이 ?를시작으로보낼수있다.추가파라미터는 &로구분하면된다.

RequestParamServlet

/**
 * 파라미터 전송 기능
 * http://localhost:8080/request-param?username=hello&age=20
 */

@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("1. 전체 파라미터 조회");
        req.getParameterNames()
                .asIterator()
                .forEachRemaining(paramName ->
                        System.out.println(paramName + "=" + req.getParameter(paramName)));

        System.out.println("2. 단일 파라미터 조회 (더 많이 쓰는 방식)");
        String username = req.getParameter("username");
        System.out.println(username);

        System.out.println("3. 파라미터 이름이 같은 여러개 값이 있을 경우");
        String[] usernames = req.getParameterValues("username");
    }
}

HTTP 요청 데이터 - POST HTML Form

메시지 바디에 데이터가 들어가기 때문에 content-type이 있다.
content-type : application/x-www-form-urlencoded
바디에 쿼리 파리미터 형식으로 데이터를 전달한다. username=hello&age=20

hello-form.html 일부

<form action="/request-param" method="post">
    username: <input type="text" name="username"/> age: <input type="text" name="age"/>
    <button type="submit">전송</button>
</form>

이전에 만들었던 requestParamServlet에 요청을 보내면,

다음과 같은 결과를 얻는다!
그런데 requestParamServlet는 쿼리 파라미터만 조회하는데 ?

application/x-www-form-urlencoded 형식은 쿼리 파라미터 형식과 같다.
따라서 쿼리 파라미터 조회 메서드를 그대로 사용하면 된다.
서버 입장에서는 둘의 형식이 동일하므로, request.getParameter() 로 편리하게 구분없이 조회할 수 있다.

request.getParameter() 는 GET URL 쿼리 파라미터 형식도 지원하고, POST HTML Form 형식도 둘 다 지원한다.

💡 POSTMAN으로 테스트할 경우

Form 데이터를 입력하기 귀찮은데, 이때 Content-typeapplication/x-www-form-urlencoded 로 설정한다.


HTTP 요청 데이터 - API 메시지 바디

HTTP message body에 데이터를 직접 담아서 요청한다.
HTTP API에서 주로 JSON을 사용한다.

단순 텍스트

RequestBodyStringServlet - Raw한 String

@WebServlet(name = "requestBodyStringServlet", urlPatterns = "/request-body-string")
public class RequestBodyStringServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream(); // byte 코드를 얻음
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 스프링이 제공하는 유틸리티
    }
}

HTML form도 이렇게 조회할 수 있지만 번거로우니 쿼리 파라미터 조회를 사용하자!

JSON

  • content-type: application/json

HelloData

@Getter
@Setter
public class HelloData {
    private String userName;
    private int age;
}

RequestBodyJsonServlet

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream(); // byte 코드를 얻음
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 스프링이 제공하는 유틸리티

        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
    }
}

JSON 결과를 파싱해서 객체로 변환하려면 Jackson, Gson 같은 JSON 변환 라이브러리를 추가해서 사용해야 한다.
스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson 라이브러리( ObjectMapper)를 제공한다.


HTTPServletResponse - 기본 사용법

개발자가 직접 응답 객체를 만들기는 번거롭지 않게 서블릿이 제공해준다!

  • HTTP 응답코드 지정
  • 헤더 생성
  • 바디 생성 등등...

ResponseHeaderServlet

@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")public class ResponseHeaderServlet extends HttpServlet {    @Override    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        //[status-line] 상태코드 지정        resp.setStatus(HttpServletResponse.SC_OK); //200        //[response-headers] 응답 헤더 지정        resp.setHeader("Content-Type", "text/plain;charset=utf-8");        resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");        resp.setHeader("Pragma", "no-cache");        resp.setHeader("my-header", "hello");        //[Header 편의 메서드] content(response); cookie(response); redirect(response);        //[message body] 응답 바디 지정        PrintWriter writer = resp.getWriter();        writer.println("ok");        //Content-Type: text/plain;charset=utf-8        //Content-Length: 2        //response.setHeader("Content-Type", "text/plain;charset=utf-8");        resp.setContentType("text/plain");         resp.setCharacterEncoding("utf-8");         //response.setContentLength(2); //(생략시 자동 생성)    }}

쿠키 설정하기

//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); //600초response.addCookie(cookie);

Redirect 설정하기

//Status Code 302//Location: /basic/hello-form.htmlresponse.setStatus(HttpServletResponse.SC_FOUND); //302response.setHeader("Location", "/basic/hello-form.html");// 또는response.sendRedirect("/basic/hello-form.html");

HTTP 요청 데이터

단순 텍스트, HTML

HttpServletResponse

@WebServlet(name = "responseHtmlServlet", urlPatterns = "/response-html")
public class HttpServletResponse extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, javax.servlet.http.HttpServletResponse resp) throws ServletException, IOException {
        // Content-Type: text:html; charset=utf-8
        resp.setContentType("text:html");
        resp.setCharacterEncoding("utf-8");
        PrintWriter writer = resp.getWriter();
        writer.println("<html>");
        writer.println("<body>");
        writer.println(" <div>이건 아니지</div>");
        writer.println("</body>");
        writer.println("</html>");
    }
}

간단하지만, HTML을 일일히 작성해주어야 한다?

API JSON

ResponseJsonServlet

@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
    private final ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //Content-Type: application/json
        resp.setHeader("content-type", "application/json");
        resp.setCharacterEncoding("utf-8");
        HelloData data = new HelloData();
        data.setUserName("mazzi");
        data.setAge(100);

        // JSON으로 변환
        String result = objectMapper.writeValueAsString(data);
        resp.getWriter().write(result);
    }
}

(사실 스프링 쓰면 이렇게 길어지지도 않는다.)


✍️ 김영님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의 노트 ✍️