본문 바로가기

MVC

[MVC] 서블릿

목차

  • HttpServletRequest
  • HTTP 요청 데이터
  • HttpServletResponse
  • HTTP 응답 데이터

 

스프링 부트 서블릿 환경 구성

✍ @ServletComponentScan

@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class ServletApplication {

    public static void main(String[] args) {
       SpringApplication.run(ServletApplication.class, args);
    }
}
  • 스프링 부트는 서블릿을 직접 등록해서 사용할 수 있도록 @ServletComponentScan 을 지원한다. 
  • 스프링이 자동으로 현재 내 패키지를 포함해서 하위 패키지를 다 뒤져서 Servlet을 다 찾아 자동으로 Servlet을 등록

 

참고❗

서블릿은 톰캣 같은 웹 애플리케이션 서버를 직접 설치하고,그 위에 서블릿 코드를 클래스 파일로 빌드해서
올린다음, 톰캣 서버를 실행하면 된다. 하지만 이 과정은 매우 번거롭다.
스프링 부트는 톰캣 서버를 내장하고 있으므로, 톰캣 서버 설치 없이 편리하게 서블릿 코드를 실행할 수 있다.

 

 

서블릿 등록하기

 

처음으로 실제 동작하는 서블릿 코드를 등록해보자.

package hello.servlet.basic;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "helloServlet", urlPatterns = "/hello") // 이 서블릿이 호출되면 service가 호출된다.
public class HelloServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        System.out.println("HelloServlet.service");
        System.out.println("request = " + request);
        System.out.println("response = " + response);
        
        // 요청 메세지 받기
        String username = request.getParameter("username");
        System.out.println("username = " + username);

        // 응답 메세지 보내기
        response.setContentType("text/plain"); // 단순 문자(컨텐트 타입: 헤더 정보에 들어감)
        response.setCharacterEncoding("UTF-8"); // 인코딩 정보 알려줌(컨텐트 타입: 헤더 정보에 들어감)
        response.getWriter().write("hello " + username); // HTTP 메세지 바디에 데이터가 들어감
    }
}

 

 

@WebServlet 서블릿 애노테이션

  • ➙ name: 서블릿 이름
  • ➙ urlPatterns: URL 매핑
  • 참고❗: 서블릿 이름이나 URL 매핑같은 것은 겹치면 안된다. 중복 X

 

✅ HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 다음 메서드를 실행한다.

protected void service(HttpServletRequest request, HttpServletResponse response)

 

 

웹 브라우저 실행

  • http://localhost:8080/hello?username=ssambbong
  • 결과: hello ssambbong


콘솔 실행결과

HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@6bc221eb
response = org.apache.catalina.connector.ResponseFacade@4fc92d04
username = ssambbong

 

 

 

 

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

🤔 개발을 할 때 편리하게 HTTP 요청 메시지를 눈으로 다 보고싶다면 ?

// application.properties
logging.level.org.apache.coyote.http11=debug // trace

 

주의🚫: 스프링 부트 3.2부터 debug 대신에 trace 를 사용해야 로그가 출력된다.

 

참고: 운영서버에 이렇게 모든 요청 정보를 다 남기면 성능저하가 발생할 수 있다. 개발 단계에서만 적용하자.

 

 

서블릿 컨테이너 동작 방식 설명

 

1️⃣ 내장 톰켓 서버 생성

 

 

2️⃣HTTP 요청, HTTP 응답 메시지

 

 

 

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

  • request, response 객체를 만들어 싱글톤으로 되어있는 helloServlet 를 호출해준다.
  • 이후에 해당 서블릿의 service 메서드를 호출하면서 request, response를 파라미터 넘겨준다.
  • helloServlet은 service 메서드에서 비지니스 로직을 수행하고 종료하고 나가면서 WAS 서버가
  • response 정보를 가지고 HTTP 응답 메세지를 만들어 반환해준다. 

참고: HTTP 응답에서 Content-Length는 웹 애플리케이션 서버가 자동으로 생성해준다.

 

 

 

 

Welcome 페이지 추가

 main.webapp 경로에 index.html 을 두면 http://localhost:8080 호출시 index.html 페이지가 열린다.

<!-- main/webapp/index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<ul>
    <li><a href="basic.html">서블릿 basic</a></li>
</ul>
</body>
</html>

 


HttpServletRequest 

HttpServletRequest 역할

  • HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만, 매우 불편할 것이다. 
  • 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다. 
  • 그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다.

 

  HttpServletRequest를 사용하면 다음과 같은 HTTP 요청 메시지를 편리하게 조회할 수 있다.

 

HTTP 요청 메시지

POST /save HTTP/1.1 // START LINE
Host: localhost:8080 // START LINE
Content-Type: application/x-www-form-urlencoded // HEADER
username=kim&age=20 // BODY

 

START LINE

  • HTTP 메소드
  • URL쿼리 스트링
  • 스키마, 프로토콜

헤더

  • 헤더 조회

바디

  • form 파라미터 형식 조회
  • message body 데이터 직접 조회

 

 

HttpServletRequest 객체의 여러가지 부가기능

HttpServletRequest 객체는 추가로 여러가지 부가기능도 함께 제공한다.

HTTP 요청 메세지는 클라이언트의 요청이 오고 요청의 응답이 나갈 때까지 생존한다.


임시 저장소 기능

  • 해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능
  • 저장: request.setAttribute(name, value)
  • 조회: request.getAttribute(name)

 

세션 관리 기능

  • request.getSession(create: true)

 

중요 🌟
HttpServletRequest, HttpServletResponse를 사용할 때 가장 중요한 점은 이 객체들이 HTTP 요청 메시지, 
HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점이다. 
따라서 이 기능에 대해서 깊이있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다.

 

 

 

HttpServletRequest - 기본 사용법

 

🤔 HttpServletRequest가 제공하는 기본 기능들을 알아보자.

 

start-line 정보

 

HTTP 요청 또는 응답의 첫 번째 줄로, 요청 또는 응답의 주요 정보를 담고 있다.

    • HTTP 메서드
    • 요청 URI
    • HTTP 버전
// http://localhost:8080/request-header?username=ssambbong 으로 입력
private static void printStartLine(HttpServletRequest request) {
    System.out.println("--- REQUEST-LINE - start ---");

    System.out.println("request.getMethod() = " + request.getMethod()); // HTTP 메서드(GET/POST 정보)
    System.out.println("request.getProtocol() = " + request.getProtocol()); // HTTP 1.1 프로토콜
    System.out.println("request.getScheme() = " + request.getScheme()); //http

    // 전체 URL: http://localhost:8080/request-header
    System.out.println("request.getRequestURL() = " + request.getRequestURL());

    // 매핑한 URL: /request-header
    System.out.println("request.getRequestURI() = " + request.getRequestURI());
    
    // username=ssambbong
    System.out.println("request.getQueryString() = " + request.getQueryString());

    System.out.println("request.isSecure() = " + request.isSecure()); //https 사용유무

    System.out.println("--- REQUEST-LINE - end ---");
    System.out.println();
}
--- REQUEST-LINE - start ---
request.getMethod() = GET
request.getProtocol() = HTTP/1.1
request.getScheme() = http
request.getRequestURL() = http://localhost:8080/request-header
request.getRequestURI() = /request-header
request.getQueryString() = username=ssambbong
request.isSecure() = false
--- REQUEST-LINE - end ---

 

 

헤더 정보

 

요청 헤더 (Request Headers)

  • Host: 요청을 보내는 서버의 호스트명과 포트 번호
  • User-Agent: 클라이언트 소프트웨어 정보
  • Accept: 클라이언트가 이해할 수 있는 미디어 타입
  • Content-Type: 요청 본문의 미디어 타입
//Header 모든 정보
private void printHeaders(HttpServletRequest request) {
    System.out.println("--- Headers - start ---");

    // 헤더 정보를 가져오는 방법 1
    /*Enumeration<String> headerNames = request.getHeaderNames(); // HTTP에 있는 모든 헤더정보 꺼내기
    while (headerNames.hasMoreElements()) { // 다음요소가 있나?
        String headerName = headerNames.nextElement();
        System.out.println(headerName + " : " + headerName);
    }*/

    // 헤더 정보를 가져오는 방법 2 (요즘 스타일)
    request.getHeaderNames().asIterator()
                    .forEachRemaining(headerName -> System.out.println(headerName +" : " + headerName));
    
    // 헤더 정보를 단건으로 가져오는 방법
    String host = request.getHeader("host");

    System.out.println("--- Headers - end ---");
    System.out.println();
}
--- Headers - start ---
host : host
connection : connection
cache-control : cache-control
sec-ch-ua : sec-ch-ua
sec-ch-ua-mobile : sec-ch-ua-mobile
sec-ch-ua-platform : sec-ch-ua-platform
upgrade-insecure-requests : upgrade-insecure-requests
user-agent : user-agent
accept : accept
sec-fetch-site : sec-fetch-site
sec-fetch-mode : sec-fetch-mode
sec-fetch-user : sec-fetch-user
sec-fetch-dest : sec-fetch-dest
accept-encoding : accept-encoding
accept-language : accept-language
cookie : cookie
--- Headers - end ---

 

 

Header 편리한 조회

//Header 편리한 조회
private void printHeaderUtils(HttpServletRequest request) {
    System.out.println("--- Header 편의 조회 start ---");

    System.out.println("[Host 편의 조회]");
    System.out.println("request.getServerName() = " + request.getServerName()); //Host 헤더(Localhost)
    System.out.println("request.getServerPort() = " + request.getServerPort()); //Host 헤더(PORT)
    System.out.println();

    System.out.println("[Accept-Language 편의 조회]");
    request.getLocales().asIterator()
            .forEachRemaining(locale -> System.out.println("locale = " +
                    locale));
    System.out.println("request.getLocale() = " + request.getLocale()); // 제일 높은 locale 정보
    System.out.println();

    System.out.println("[cookie 편의 조회]");
    if (request.getCookies() != null) {
        for (Cookie cookie : request.getCookies()) {
            System.out.println(cookie.getName() + ": " + cookie.getValue());
        }
    }
    System.out.println();
    
    System.out.println("[Content 편의 조회]");
    // POST 방식으로 넘어온 데이터 정보
    System.out.println("request.getContentType() = " + request.getContentType()); 
    System.out.println("request.getContentLength() = " + request.getContentLength());
    System.out.println("request.getCharacterEncoding() = " + request.getCharacterEncoding());
    
    System.out.println("--- Header 편의 조회 end ---");
    System.out.println();
}
--- Header 편의 조회 start ---
[Host 편의 조회]
request.getServerName() = localhost
request.getServerPort() = 8080

[Accept-Language 편의 조회]
locale = ko_KR
request.getLocale() = ko_KR

[cookie 편의 조회]

[Content 편의 조회]
request.getContentType() = text/plain
request.getContentLength() = 5
request.getCharacterEncoding() = UTF-8
--- Header 편의 조회 end ---
 
 
기타 정보

기타 정보는 HTTP 메시지의 정보는 아니다.
//기타 정보
private void printEtc(HttpServletRequest request) {
    System.out.println("--- 기타 조회 start ---");

    System.out.println("[Remote 정보]"); // 요청이 온 정보
    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 정보]"); // 나의 SERVER에 대한 정보
    System.out.println("request.getLocalName() = " + request.getLocalName());
    System.out.println("request.getLocalAddr() = " + request.getLocalAddr());
    System.out.println("request.getLocalPort() = " + request.getLocalPort());
    System.out.println("--- 기타 조회 end ---");
    System.out.println();
}
--- 기타 조회 start ---
[Remote 정보]
request.getRemoteHost() = 0:0:0:0:0:0:0:1
request.getRemoteAddr() = 0:0:0:0:0:0:0:1
request.getRemotePort() = 59090

[Local 정보]
request.getLocalName() = 0:0:0:0:0:0:0:1
request.getLocalAddr() = 0:0:0:0:0:0:0:1
request.getLocalPort() = 8080
--- 기타 조회 end ---
참고 : 로컬에서 테스트하면 IPv6 정보가 나오는데, IPv4 정보를 보고 싶으면 다음 옵션을 VM options에 넣어주면 된다.
-Djava.net.preferIPv4Stack=true​

 

HttpServletRequest를 통해서 HTTP 메시지의 start-line, header 정보 조회 방법을 알아봤다.

➙ 이제 HTTP 요청 데이터를 어떻게 조회하는지 알아보자.


HTTP 요청 데이터

 HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.
❝ 주로 다음 3가지 방법을 사용한다.❞


1️⃣ GET - 쿼리 파라미터

  • /url**?username=hello&age=20**
  • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
  • 예) 검색, 필터, 페이징등에서 많이 사용하는 방식

 

2️⃣ POST - HTML Form

  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
  • 예) 회원 가입, 상품 주문, HTML Form 사용

 

3️⃣  HTTP message body에 데이터를 직접 담아서 요청

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

 

POST- HTML Form 예시

 

 


 

GET 쿼리 파라미터

다음 데이터를 클라이언트에서 서버로 전송해보자.

 

전달 데이터

  • username=hello
  • age=20

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


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

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


서버에서는 HttpServletRequest 가 제공하는 다음 메서드를 통해 쿼리 파라미터를 편리하게 조회할 수 있다.

 

  쿼리 파라미터 조회 메서드

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

 

 

RequestParamServlet

@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {

        // * http://localhost:8080/request-param?username=hello&age=20
        System.out.println("[전체 파라미터 조회] - start");
        request.getParameterNames().asIterator()
                .forEachRemaining
                (paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));
        System.out.println("[전체 파라미터 조회] - end");
        System.out.println();

        // 파라미터 전송 기능
        System.out.println("[단일 파라미터 조회]");
        String username = request.getParameter("username");
        String age = request.getParameter("age");
        System.out.println("username = " + username);
        System.out.println("age = " + age);
        System.out.println();

        // * http://localhost:8080/request-param?username=hello&username=kim&age=20
        // 동일한 파라미터 전송 가능
        System.out.println("[이름이 같은 복수 파라미터 조회]");
        String[] usernames = request.getParameterValues("username");
        for (String name : usernames) {
            System.out.println("username = " + name);
        }
        response.getWriter().write("OK");
    }
}
[전체 파라미터 조회] - start
username = kim2
age = 20
[전체 파라미터 조회] - end

[단일 파라미터 조회]
username = kim2
age = 20

[이름이 같은 복수 파라미터 조회]
username = kim2
username = kim1

 

 

복수 파라미터에서 단일 파라미터 조회
🤔 username=hello&username=kim 과 같이 파라미터 이름은 하나인데, 값이 중복이면 어떻게 될까?

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

참고 : 이렇게 중복일 때 request.getParameter( ) 를 사용하면 request.getParameterValues() 의 첫번째 값을 반환한다.

username=hello&username=kim  request.getParameter(단일 파라미터 조회) ➙ hello


 POST HTML Form

 HTML의 Form을 사용해서 클라이언트에서 서버로 데이터를 전송해보자.
  주로 회원 가입, 상품 주문 등에서 사용하는 방식이다.


특징

  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 데이터를 전달한다. username=hello&age=20
<!-- http://localhost:8080/basic/hello-form.html -->
<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
<form action="/request-param" method="post">
 username: <input type="text" name="username" />
 age: <input type="text" name="age" />
 <button type="submit">전송</button>
</form>
</body>
</html>
@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {

        // 파라미터 전송 기능
        System.out.println("[단일 파라미터 조회]");
        String username = request.getParameter("username");
        String age = request.getParameter("age");
        System.out.println("username = " + username);
        System.out.println("age = " + age);
        System.out.println();
    }
}

 

✍ POST의 HTML Form을 전송하면 웹 브라우저는 다음 형식으로 HTTP 메시지를 만든다.

  • 요청 URL: http://localhost:8080/request-param
  • 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 형식도둘 다 지원한다.

 

 

참고❗: content-type은 HTTP 메시지 바디의 데이터 형식을 지정한다.
GET URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없다. POST HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 content-type을 꼭 지정해야 한다. 

이렇게 폼으로 데이터를 전송하는 형식을 application/x-www-form-urlencoded 라 한다.

Content-Type: null // GET URL 방식
Content-Type: application/x-www-form-urlencoded // POST HTML FORM 방식

 

 

 

Postman을 사용한 테스트


이런 간단한 테스트에 HTML form을 만들기는 귀찮다. 이때는 Postman을 사용하면 된다.

 

Postman 테스트 주의사항 ⚠️

  • POST 전송시 Body x-www-form-urlencoded 선택
  • Headers에서 content-type: application/x-www-form-urlencoded 로 지정된 부분 꼭 확인

API 메시지 바디 - 단순 텍스트

HTTP message body에 데이터를 직접 담아서 요청

   HTTP API에서 주로 사용, JSON, XML, TEXT
   데이터 형식은 주로 JSON 사용
   POST, PUT, PATCH


먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.
HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있다.

@WebServlet(name = "requestBodyStringServlet", urlPatterns = "/request-body-string")
public class RequestBodyStringServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        
        // 메세지 Body 내용을 바로 바이트 코드로 얻을 수 있음
        ServletInputStream inputStream = request.getInputStream();
        
        // 바이트 코드를 스트링으로 받아야 한다.
        // 매개변수: 바이트코드, 인코딩 정보
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        System.out.println("messageBody = " + messageBody);

        response.getWriter().write("OK");
    }
}

 

 

  Postman을 사용해서 테스트 해보자

messageBody = HELLO

 

 

참고
inputStream은 byte 코드를 반환한다. 

byte 코드를 우리가 읽을 수 있는 문자(String)로 보려면 문자표(Charset)를 지정해주어야 한다. 

여기서는 UTF_8 Charset을 지정해주었다.

 

 

문자 전송

  • POST http://localhost:8080/request-body-string
  • content-type: text/plain
  • message body: hello
  • 결과: messageBody = hello

 

 

API 메시지 바디 - JSON

이번에는 HTTP API에서 주로 사용하는 JSON 형식으로 데이터를 전달해보자.

 

  JSON 형식 전송

  • POST http://localhost:8080/request-body-json
  • content-type: application/json
  • message body: {"username": "hello", "age": 20}
  • 결과: messageBody = {"username": "hello", "age": 20}

 

1️⃣ JSON 형식 파싱 추가
  JSON 형식으로 파싱할 수 있게 객체를 하나 생성하자.

@Getter @Setter
public class HelloData {

    private String username;
    private int age;

    // 보통 제이슨 라이브러리가 setXXX 메서드를 호출한다(자바 프로퍼티 접근법)
    public void setUsername(String username) {
        this.username = username;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

 

✅  롬북이 제대로 작동하지 않는다면 Annotation Processors ➙ Enable annotation processing 체크

 

 

2️⃣ 서블릿 생성

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {

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

        System.out.println("messageBody = " + messageBody);
    }
}

 

 

Postman으로 실행해보자

  • POST http://localhost:8080/request-body-json
  • content-type: application/json (Body raw, 가장 오른쪽에서 JSON 선택)
  • message body: {"username": "hello", "age": 20}
  • 결과: messageBody =  {"username": "hello", "age": 20}

💡 HelloData 객체에 데이터를 넣으려면 제이슨 라이브러리가 필요하다. 스프링부트는 기본적으로 지원해준다.

 

 

3️⃣ 코드 추가

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
    // 추가
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {

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

        System.out.println("messageBody = " + messageBody);

        // 추가
        // 파라미터: 데이터, 클래스 타입
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        System.out.println("helloData.username = " + helloData.getUsername());
        System.out.println("helloData.age = " + helloData.getAge());

        response.getWriter().write("OK");
    }
}
messageBody =  {"username": "hello", "age": 20}
helloData.username = hello
helloData.age = 20

 

 

💡Jackson 라이브러리가 제공하는 objectMapper.readValue( ) 를 사용하면 json 형식으로 넘어온 데이터를 객체에 넣을 수 있다.

 

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


참고
HTML form 데이터도 메시지 바디를 통해 전송되므로 직접 읽을 수 있다. 

하지만 편리한 파리미터 조회 기능(getParameter( ))을 이미 제공하기 때문에 파라미터 조회 기능을 사용하면 된다.


HttpServletResponse

 HttpServletResponse 역할

 


HTTP 응답 메시지 생성

  • HTTP 응답코드 지정(200, 400, 500, 404 등)
  • 헤더 생성
  • 바디 생성

편의 기능 제공

  • Content-Type, 쿠키, Redirect

 

HttpServletResponse - 기본 사용법

@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {

        //[status-line]
        // response.setStatus(200); 응답 코드
        response.setStatus(HttpServletResponse.SC_OK); // = 200 OK
        // response.setStatus(HttpServletResponse.SC_BAD_REQUEST); // = 200 OK

        //[response-headers]
        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-header

        //[Header 편의 메서드]
        content(response); // Content 편의 메서드
        cookie(response); // 쿠키 편의 메서드
        redirect(response); // redirect 편의 메서드

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

    private void content(HttpServletResponse response) {
        //Content-Type: text/plain;charset=utf-8
        //Content-Length: 2
        //response.setHeader("Content-Type", "text/plain;charset=utf-8");
        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");
        //response.setContentLength(2); //(생략시 자동 생성)
    }

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

    private void redirect(HttpServletResponse response) throws IOException {
        //Status Code 302
        //Location: /basic/hello-form.html
        //response.setStatus(HttpServletResponse.SC_FOUND); //302
        //response.setHeader("Location", "/basic/hello-form.html");
        response.sendRedirect("/basic/hello-form.html");
    }
}

HTTP 응답 데이터 - 단순 텍스트, HTML

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

 

1️⃣ 단순 텍스트 응답 ( writer.println("ok"); )
2️⃣ HTML 응답
3️⃣ HTTP API - MessageBody JSON 응답

 

 

HttpServletResponse - HTML 응답

// http://localhost:8080/response-html
@WebServlet(name = "responseHtmlServlet", urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {

        // Content-Type 지정: text/html;charset=utf-8
        // response.setHeader("Content-Type", "text/html; charset=utf-8");
        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>");
    }
}

 

💡 HTTP 응답으로 HTML을 반환할 때는 content-type을 text/html 로 지정해야 한다.


HTTP 응답 데이터 - API JSON

@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {

    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {

        // Content-Type 지정: application/json
        response.setHeader("Content-Type", "application/json; charset=utf-8");

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

        // JSON 형태로 바꿔줘야 함(ObjectMapper) => {"username" : "kim", "age" : 20}
        String result = objectMapper.writeValueAsString(helloData);
        System.out.println("result = " + result);

        response.getWriter().write(result);

    }
}

result = {"username":"kim","age":20}

 

💡 HTTP 응답으로 JSON을 반환할 때는 content-type을 application/json 로 지정해야 한다.
💡Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString( ) 를 사용하면 객체를 JSON 문자로 변경 가능

 

참고❗

application/json 은 스펙상 utf-8 형식을 사용하도록 정의되어 있다. 

그래서 스펙에서 charset=utf-8 과 같은 추가 파라미터를 지원하지 않는다.

따라서 application/json 이라고만 사용해야지 application/json;charset=utf-8 이라고 전달하는 것은 의미 없는 파라미터를 추가한 것이 된다. response.getWriter( )를 사용하면 추가 파라미터를 자동으로 추가해버린다. 

이때는 response.getOutputStream( )으로 출력하면 그런 문제가 없다.

 

 

'MVC' 카테고리의 다른 글

[MVC] 스프링 MVC  (0) 2024.07.18
[MVC] 프론트 컨트롤러 패턴  (0) 2024.07.16
[MVC] 서블릿, JSP, MVC 패턴 적용  (0) 2024.07.15
[MVC] 자바 백엔드 웹 기술 역사  (0) 2024.07.11
[MVC] 웹 서버, 웹 애플리케이션 차이점  (0) 2024.07.11