Spring/MVC

[Spring MVC] 02. 서블릿

lumana 2024. 11. 19. 21:04

 

02. 서블릿

#Spring/MVC



스프링 부트 서블릿 환경 구성
스프링 부트는 서블릿을 직접 등록해서 사용할 수 있도록 @ServletComponentScan 을 지원ㅇ한다.


서블릿 등록
인텔리제이에서 ctrl + o로 오버라이딩 할 수 있는데, 자물쇠 잠겨있는 service(protected)를 오버라이딩해주자. 서블릿이 호출되면, 서비스 메서드가 호출된다.


  • @WebServlet 서블릿 애노테이션
    • name: 서블릿 이름
    • urlPatterns: URL 매핑

HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 다음 메서드를 실행한다.
protected void service(HttpServletRequest request, HttpServletResponse response)


HttpServletRequest, HttpServletResponse 얘내들은 인터페이스다. Tomcat, Jetty, Undertow 등등 WAS 서버들이 이 서플릿 표준 스펙을 구현하는 거다. 콘솔에 찍힌 것들이 구현체다.


쿼리 파라미터 값을 request.getParameter("파라미터이름")을 통해 꺼내 사용할 수 있다.



response.setContentType("text/plain");
Http 헤더 컨텐츠 타입을 정해준다.


response.setCharacterEncoding("utf-8");
Http 헤더에 인코딩 정보를 알려준다


response.getWriter().write("heelo" + username);
Http 메시지 바디에 데이터가 들어간다.


Content-Length 같은 나머지 정보는 톰켓 서버가 자동으로 만들어준다.


HTTP 요청 메시지 로그 확인

  • 설정 파일 application.properties에 아래 내용을 추가:
logging.level.org.apache.coyote.http11=trace

이 기능은 실제 운영서버에 추가할 시 성능 저하가 발생할 수 있으므로 개발 단계에서만 사용하자.



스프링 부트를 실행하면 부트가 내장 톰켓 서버를 띄운다. 톰캣 서버 내부에는 서블릿 컨테이너 기능이 있다.
서블릿 컨테이너에서 서블릿을 다 생성을 해주고, 우리가 만들었던 HelloServlet도 생성이 된다.


서블릿 컨테이너 동작 방식


클라이언트가 HTTP 메시지를 만들어서 던져주면, 서버는 request, response 객체를 만들어서 싱글톤 객체인 helloServlet을 호출해준다. 서비스 메서드를 호출하면서 request, response 를 넘겨주는 것이다.
필요한 작업을 하고(ex. response에 contenttype 지정), 서블릿이 종료된다. 서블릿이 종료되고 나가면서 WAS 서버가 이 response 정보를 가지고 HTTP 응답 메시지를 만들어서 반환해준다. 부가정보는 WAS가 자동으로 생성해준다.


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


  • START LINE
    • HTTP 메소드
    • URL
    • 쿼리 스트링
    • 스키마, 프로토콜
  • 헤더
    • 헤더 조회
  • 바디
    • form 파라미터 형식 조회
    • message body 데이터 직접 조회

다 파싱해준다.


임시 저장소 기능
해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능

  • 저장:request.setAttribute(name, value)
  • 조회: request.getAttribute(name)

세션 관리 기능request.getSession(create: true)


Start-Line 조회

System.out.println("request.getMethod() = " + request.getMethod());
        System.out.println("request.getRequestURL() = " + request.getRequestURL());
        System.out.println("request.getRequestURI() = " + request.getRequestURI());
        System.out.println("request.getQueryString() = " + request.getQueryString());

이런식으로 get메서드를 통해 조회할 수 있다.


헤더 조회

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

이렇게 하면 원하는 것만 뽑아서 못본다.
request.getServerName()
request.getServerPort()
를 통해서 호스트를 편하게 조회할 수 있고,


Accept-Language를 조회하기 위해선 아래와 같이 조회할 수 있다.

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

request.getLocale(): 클라이언트의 가중치가 가장 높은 Accept-language를 선택


쿠키를 편하게 조회하기 위해서는
request.getContentType()
request.getContentLength()
request.getCharacterEncoding() 를 사용할 수 있다.


바디에 값이 없으면 -1이나 null 값이 된다.


이외에도

System.out.println("request.getRemoteHost() = " +
                request.getRemoteHost()); //
        System.out.println("request.getRemoteAddr() = " +
                request.getRemoteAddr()); //
        System.out.println("request.getRemotePort() = " +
                request.getRemotePort()); //
        System.out.println();
        System.out.println("[Local 정보]");
        System.out.println("request.getLocalName() = " + request.getLocalName()); //
        System.out.println("request.getLocalAddr() = " + request.getLocalAddr()); //
        System.out.println("request.getLocalPort() = " + request.getLocalPort()); //

요런 기능들을 제공한다.


특정 메서드를 지정하지 않고 원하는 헤더 정보를 꺼내려면(e.g host)
request.getHeader(“host”) 요런 식으로 원하는 헤더 이름을 적어주면 그 값을 꺼낼 수 있다.


HTTP 요청 데이터

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법에는 크게 3가지가 있다.


  1. GET - 쿼리 파라미터
    • URL 형식: /url?username=hello&age=20
    • 쿼리 파라미터는 URL에 다음과 같이 ?를 시작으로 보낼 수 있다. 추가 파라미터는 &로 구분한다.

HttpServletRequest의 다음 메서드를 통해 쿼리 파라미터를 조회할 수 있다.

String username = request.getParameter("username"); // 단일 파라미터 조회
Enumeration<String> parameterNames = request.getParameterNames(); // 파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); // 파라미터를 Map으로 조회
String[] usernames = request.getParameterValues("username"); // 복수 파라미터 조회

http://localhost:8080/request-param?username=hello&age=20&username=hello2
이런식으로 이름이 같은 복수 파라미터도 가능하다.


request.getParameter()하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용해야 한다. 지금처럼 중복일 때는request.getParameterValues() 를 사용해야 한다.


  1. POST - HTML Form
    • content-type: application/x-www-form-urlencoded
      • 메시지 바디에 쿼리 파리미터 형식으로 전달
    • application/x-www-form-urlencoded 형식은 GET에서 살펴본 쿼리 파라미터 형식과 같다.

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


  1. HTTP message body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용, JSON, XML, TEXT
    • JSON, XML… 도 결국에는 문자열이다.

단순 문자열: HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있다.

ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

JSON 형식
JSON 형식 데이터를 자바 객체로 변환하여 받는다.
스프링 MVC가 제공하는 ObjectMapper를 통해 Json 문법의 InputStream을 객체로 변환할 수 있다.

private ObjectMapper objectMapper = new ObjectMapper();

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

HTTP 응답 데이터

크게 3가지 내용을 담아서 전달한다.

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

HttpServletResponse는 이를 편리하게 지정할 수 있도록 메서드를 제공한다.
Status-Line

response.setStatus(HttpServletResponse.SC_OK); // 200

기본적인 헤더 설정 방법

response.setHeader("Content-Type", "text/plain;charset=utf-8");

기본적으로는 헤더 종류, 값을 일일히 지정해야 한다. 하지만 편의 메서드를 제공한다.


참고

response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");

캐시를 완전히 무효화하겠다.


response.setHeader("Pragma", "no-cache");

과거 버전까지 캐시를 없앤다.


Content 편의 메서드

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

특정 헤더를 설정할 수 있는 메서드가 존재해서 그걸 사용하면 된다.
기본으로 보낼 때는 Content-Length가 필요하다. 생략하면 content-body에 있는 걸 자동으로 계산해준다.


쿠키 편의 메서드

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

redirect 편의 메서드

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

원래는 상태코드를 지정하고, Location 헤더에 url을 적어줘야 하지만,
sendRedirect 메서드로 url만 적어주면 상태코드까지 설정된다.



메시지 바디

// [message body]
PrintWriter writer = response.getWriter();
writer.println("ok");

response.getWriter나 response.getInputStream으로 가져와서 값을 넣어주면 값이 메시지 바디에 넣어지게 된다.


HTTP 응답 데이터 종류


HTTP 응답 메시지는 주로 다음 내용을 담아서 전달한다.

  1. 단순 텍스트 응답
    • 앞에서 살펴봄 ( writer.println("ok"); )
  2. HTML 응답
    • 웹 브라우저에 HTML을 반환
 response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        PrintWriter writer = response.getWriter();
        writer.println("<html>");
        writer.println("<body>");
        writer.println("  <div>안녕?</div>");
        writer.println("</body>");
        writer.println("</html>");

서블릿으로 html을 렌더링할 때는 이렇게 직접 작성해야 한다. 동적으로 html을 생성할 수도 있다.


  1. HTTP API - MessageBody JSON 응답
    • JSON 데이터 반환

response.setHeader("content-type", "application/json");
        response.setCharacterEncoding("utf-8"); // 사실은 필요 없다.

        HelloData data = new HelloData();
        data.setUsername("kim");
        data.setAge(20);

        // {"username":"kim","age":20}
        String result = objectMapper.writeValueAsString(data);
        response.getWriter().write(result);

json도 문자열이기 때문에 objectMapper를 통해 json 문법 문자열로 변환한다.


application/json은 스펙상 UTF-8 형식을 사용하도록 정의되어 있다.
따라서 application/json;charset=utf-8이라고 전달하는 것은 의미 없는 파라미터를 추가한 것이다. response.getWriter()를 사용하면 추가 파라미터가 자동으로 추가될 수 있다.


Ref) 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의 | 김영한 - 인프런