쌩로그

스프링 MVC 1편 - Ch02. 서블릿 본문

Spring/Spring & Spring Boot

스프링 MVC 1편 - Ch02. 서블릿

.쌩수. 2024. 3. 15. 11:35
반응형

목록

  1. 포스팅 개요
  2. 본론
      2-1. Hello 서블릿
      2-2. HttpServletRequest - 개요
      2-3. HttpServletRequest - 기본 사용법
      2-4. HTTP 요청 데이터 - 개요
      2-5. HTTP 요청 데이터 - GET 쿼리 파라미터
      2-6. HTTP 요청 데이터 - POST HTML Form
      2-7. HTTP 요청 데이터 - API 메시지 바디 - 단순 텍스트
      2-8. HTTP 요청 데이터 - API 메시지 바디 - JSON
      2-9. HttpServletResponse - 기본 사용법
      2-10. HTTP 응답 데이터 - 단순 텍스트, HTML
      2-11. HTTP 응답 데이터 - API JSON
  3. 요약

1. 포스팅 개요

인프런에서 영한님의 스프링 MVC 1편 Section 02 서블릿을 학습하며 정리한 포스팅이다.

2. 본론

처음 프로젝트 생성 시엔 jar가 아니라 jsp를 사용해야하기 때문에 war를 선택해줘야 한다.

2-1. Hello 서블릿

먼저 SpringBoot가 서블릿을 사용할 수 있도록 @SpringBootApplication이 있는 클래스에 다음과 같이 선언한다.

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

@ServletComponentScan을 통해서 서블릿을 등록할 수 있다.

서블릿을 만들어보자.

package spring.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")
public class HelloServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("HelloServlet.service");
    }

localhost:8080/hello를 들어가면 다음과 같은 결과가 나온다.

  • @WebServlet을 통해서 서블릿 이름과 url을 매핑한다.
    • 참고로 서블릿이름과 url매핑은 중복되면 안 된다.
  • 서블릿을 사용하기 위해서는 HttpServlet을 상속받아야 한다.
  • HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 service 메서드를 실행한다.

이전에 Servlet으로 http 요청이 오면 Servlet Container가 Request, Response 객체를 만들어서 Servlet에 던져 준다고 했었다.

그래서 서버를 실행시켜서 localhost:8080/hello를 접속하면 웹 브라우저가 HTTP 요청 메세지를 만들고, 그 결과를 서블릿 서버에 던지게 된다.

다음 결과도 보자.

System.out.println("request = " + request);
System.out.println("response = " + response);

// 결과
request = org.apache.catalina.connector.RequestFacade@65ecc711
response = org.apache.catalina.connector.ResponseFacade@2490b89

org.apache 는 톰캣쪽 라이브러리이다.
그리고 RequestFacadexxx, ResponseFacadexxx라고 되어있는 것은 HttpServletRequest와 HttpServletResponse가 인터페이스인데 톰캣, Jetty, Undertow등등 여러개의 WAS 서버가 있는데, 그 WAS 서버들이 이 서블릿의 표준 스펙을 구현한 것이다.

지금은 톰캣구현체의 클래스가 찍힌 것이다.

    String username = request.getParameter("username");
    System.out.println("username = " + username);

그리고 이렇게 요청 파라미터를 받아서 출력할 수 있다.

결과는 다음과 같다.

request = org.apache.catalina.connector.RequestFacade@65ecc711
response = org.apache.catalina.connector.ResponseFacade@2490b89
username = kim

응답 메세지를 보내려면 HttpServletResponse객체에 값을 담아야 한다.

      response.setContentType("text/plain");
      response.setCharacterEncoding("utf-8");
      response.getWriter().write("hello " + username);

그리고 http://localhost:8080/hello?username=kim 이 주소로 요청을 보내면
다음과 같은 응답이 온다.

개발시 요청 메세지를 편하게 눈으로 보고 싶을 때는 application.yml에 다음과 같이 하면 된다.

logging:
  level:
    org:
      apache:
        coyote:
          http11: debug

http://localhost:8080/hello?username=kim 이런 요청을 보냈을 때 다음과 같은 정보를 출력해준다.

참고로 운영서버에 모든 로그를 남기면 성능 저하가 발생할 우려있다. 고민해봐야 한다.

서블릿의 동작 방식을

  • 스프링부트를 실행했다.
  • 스프링부트는 내장 톰캣 서브를 띄워준다.
  • 톰캣 서버는 Servlet Container 기능을 가지고있다.
    • Servlet Container를 통해서 서블릿을 다 생성해준다.
  • http 요청과 응답메시지이다.
  • http 요청을 보내면 웹 브라우저와 위와 같은 요청메세지를 만들어서 서버로 던진다.
  • 서버는 request. response 객체를 만들어서 싱글톤으로 띄워져 있는 helloServlet을 호출한다.
    • 여기서 service 메서드를 호출하면서 request. response 객체를 넘겨준다.
  • 그리고 필요한 작업을 진행한 후 메서드를 종료하면서 was(톰캣)가 response 메세지를 위의 HTTP 응답 형식으로 만들어서 반환을 한다.

학습할 내용을 편리하게 참고할 수 있는 페이지를 만들었다.

main 디렉터리에서 webapp이라는 하위 디렉터리를 만들어서 사용한다.

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

// 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>

// basic.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    <ul>
      <li>
        hello 서블릿
        <ul>
          <li><a href="/hello?username=servlet">hello 서블릿 호출</a></li>
        </ul>
      </li>
      <li>
        HttpServletRequest
        <ul>
          <li><a href="/request-header">기본 사용법, Header 조회</a></li>
          <li>
            HTTP 요청 메시지 바디 조회
            <ul>
              <li>
                <a href="/request-param?username=hello&age=20"
                  >GET - 쿼리 파라미터</a
                >
              </li>
              <li><a href="/basic/hello-form.html">POST - HTML Form</a></li>
              <li>HTTP API - MessageBody -> Postman 테스트</li>
            </ul>
          </li>
        </ul>
      </li>
      <li>
        HttpServletResponse
        <ul>
          <li><a href="/response-header">기본 사용법, Header 조회</a></li>
          <li>
            HTTP 응답 메시지 바디 조회
            <ul>
              <li><a href="/response-html">HTML 응답</a></li>
              <li><a href="/response-json">HTTP API JSON 응답</a></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </body>
</html>

2-2. HttpServletRequest - 개요

HttpServletRequest 역할

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

HTTP 요청 메시지

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

이와 같이 왔다고 가정했을 때 구성은 다음과 같다.

  • START LINE : POST /save HTTP/1.1Host: localhost:8080에 해당한다.
    • HTTP 메소드
    • URL
    • 쿼리 스트링
    • 스키마, 프로토콜
  • 헤더 : Content-Type: application/x-www-form-urlencoded에 해당한다.
    • 헤더 조회
  • 바디 : username=kim&age=20에 해당한다.
    • form 파라미터 형식 조회
    • message body 데이터 직접 조회

HttpServletRequest 객체는 추가로 여러가지 부가기능도 함께 제공한다.
제공하는 기능들은 다음과 같다.

  • 임시 저장소 기능

    • 해당 HTTP 요청이 시작부터 끝날 때 까지 생존하는데, 그동안 유지되는 임시 저장소 기능
      • 저장: request.setAttribute(name, value)
      • 조회: request.getAttribute(name)
  • 세션 관리 기능

    • request.getSession(create: true)

중요

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

2-3. HttpServletRequest - 기본 사용법

Request 사용법을 알아본다.

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 = "requestHeaderServlet", urlPatterns = "/request-header")  
public class RequestHeaderServlet extends HttpServlet {  

    // service를 override할 때 protected한 것을 만들어야 한다.  
    @Override  
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  

        printStartLine(request);  

    }  

    private void printStartLine(HttpServletRequest request) {  
        System.out.println("--- REQUEST-LINE - start ---");  
        System.out.println("request.getMethod() = " + request.getMethod()); //GET  
        System.out.println("request.getProtocol() = " + request.getProtocol()); // HTTP/1.1  
        System.out.println("request.getScheme() = " + request.getScheme()); //http  
        // http://localhost:8080/request-header        System.out.println("request.getRequestURL() = " + request.getRequestURL());  
        // /request-header  
        System.out.println("request.getRequestURI() = " + request.getRequestURI());  
        //username=hi  
        System.out.println("request.getQueryString() = " + request.getQueryString());  
        System.out.println("request.isSecure() = " + request.isSecure()); //https 사용유무  
        System.out.println("--- REQUEST-LINE - end ---");  
        System.out.println();  
    }  
}

참고로 service()override할 때 protected로 된 것을 만들어야 한다.

위의 코드 결과는 다음과 같다.

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

다음은 header 정보이다.

//Header 모든 정보  
private void printHeaders(HttpServletRequest request) {  
    System.out.println("--- Headers - start ---");  
    Enumeration<String> headerNames = request.getHeaderNames();  
    while(headerNames.hasMoreElements()) {  
        String headerName = headerNames.nextElement();  
        System.out.println("headerName = " + headerName);  
    }  
    System.out.println("--- Headers - end ---");  
    System.out.println();  
}

출력 결과는 다음과 같다.

--- Headers - start ---
headerName = host
headerName = connection
headerName = sec-ch-ua
headerName = sec-ch-ua-mobile
headerName = sec-ch-ua-platform
headerName = upgrade-insecure-requests
headerName = user-agent
headerName = accept
headerName = sec-fetch-site
headerName = sec-fetch-mode
headerName = sec-fetch-user
headerName = sec-fetch-dest
headerName = accept-encoding
headerName = accept-language
--- Headers - end ---

웹 브라우저(크롬) 개발자 도구를 보면 많이 보내는 걸 알 수 있다.

Enumeration<String> headerNames = request.getHeaderNames();  
while(headerNames.hasMoreElements()) {  
    String headerName = headerNames.nextElement();  
    System.out.println("headerName = " + headerName);  
}

위의 코드말고도 Header를 꺼낼 수 있는데, 다음과 같이 하면 된다.

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

결과는 위와 똑같다.

Header를 편리하게 조회할 수 있다.

//Header 편리한 조회  
private void printHeaderUtils(HttpServletRequest request) {  
    System.out.println("--- Header 편의 조회 start ---");  
    System.out.println("[Host 편의 조회]");  
    System.out.println("request.getServerName() = " +  
            request.getServerName()); //Host 헤더  
    System.out.println("request.getServerPort() = " +  
            request.getServerPort()); //Host 헤더  
    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());  
    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 편의 조회]");  
    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
locale = ko
locale = en_US
locale = en
request.getLocale() = ko_KR

[cookie 편의 조회]

[Content 편의 조회]
request.getContentType() = null // ex) text/plain
request.getContentLength() = -1
request.getCharacterEncoding() = UTF-8
--- Header 편의 조회 end ---

만약에 header값 하나만 받고싶다면,
header의 값에 해당하는 key를 통해 가져올 수 있다..

request.getHeader("host");

다음은 기타 정보다.

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 정보]");  
    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() = 53092

[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

2-4. HTTP 요청 데이터 - 개요

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.

주로 다음 3가지 방법을 사용한다

  • GET - 쿼리 파라미터
    • /url?username=hello&age=20
    • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
    • 예) 검색, 필터, 페이징등에서 많이 사용하는 방식
  • POST - HTML Form
    • content-type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
    • 예) 회원 가입, 상품 주문, HTML Form 사용
  • HTTP message body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용 POST, PUT, PATCH

위의 그림은 HTML Form을 통해 전송하는 그림이다.

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

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

  • username=hello

  • age=20

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

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

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

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

사용할 Servlet이다.

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;  

/**  
 * 1. 파라미터 전송 기능  
 * 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 request, HttpServletResponse response) throws ServletException, IOException {  
        System.out.println("[전체 파라미터 조회] - start");  

        // 요즘 방식  
        request.getParameterNames().asIterator()  
                .forEachRemaining(paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));  
        System.out.println("[전체 파라미터 조회] - end");  

        request.getParameterNames();  
    }  
}

http://localhost:8080/request-param?username=hello&age=20 이 url로 요청하면 결과는 다음과 같이 나온다.

[전체 파라미터 조회] - start
username = hello
age = 20
[전체 파라미터 조회] - end

단일 파라미터는 다음과 같이 조회할 수 있다.

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

System.out.println("username = " + username);  
System.out.println("age = " + age);

결과는 다음과 같다.

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

참고로 다음처럼 하나의 쿼리파라미터에 두 개의 값이 들어갈 수도 있는데

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

먼저는 내부 우선순위에서 먼저 잡히는 애가 나오는데,

이와 같은 경우 다음과 같이 조회를 할 수 있다.

System.out.println("[이름이 같은 복수 파라미터 조회]");  
String[] usernames = request.getParameterValues("username");  
for (String name : usernames) {  
    System.out.println("name = " + name);  
}

다음은 그 결과다.

[이름이 같은 복수 파라미터 조회]
name = hello
name = hello2

복수 파라미터에서 단일 파라미터 조회

username=hello&username=kim 과 같이 파라미터 이름은 하나인데, 값이 중복이면 어떻게 될까? request.getParameter()하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용해야 한다.
지금처럼 중복일 때는 request.getParameterValues() 를 사용해야 한다. 참고로 이렇게 중복일 때 request.getParameter() 를 사용하면 request.getParameterValues()첫 번째 값을 반환한다.

2-6. HTTP 요청 데이터 - POST HTML Form

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

특징

  • content-type: application/x-www-form-urlencoded

  • 메시지 바디에 쿼리 파리미터 형식으로 데이터를 전달한다. username=hello&age=20

  • webapp/basic/hello-form.html 을 생성하고 다음과 같이 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>

그리고 localhost:8080/baic/hello-form.html에 들어가면 된다.

그럼 다음과 같이 나온다.

여기에 usernaeme에 kim , 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 라 한다.

참고로 Postman을 사용해서 테스트할 수 있는데,
POST 전송시에 다음과 같이 해주면 된다.

  • Body > x-www-form-urlencoded 선택
  • Headers에서 content-type:application/x-www-form-urlencoded로 지정된 부분을 꼭 확인한다.

2-7. HTTP 요청 데이터 - API 메시지 바디 - 단순 텍스트

  • HTTP message body에 데이터를 직접 담아서 요청한다.

    • HTTP API에서 주로 사용한다. ex) JSON, XML, TEXT
    • 데이터 형식은 주로 JSON을 사용한다.
    • POST, PUT, PATCH
  • 먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.

    • HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있다.
import jakarta.servlet.ServletInputStream;  
import jakarta.servlet.annotation.WebServlet;  
import jakarta.servlet.http.HttpServlet;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import org.springframework.util.StreamUtils;  

import java.io.IOException;  
import java.nio.charset.StandardCharsets;  

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

    @Override  
    protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {  
        ServletInputStream inputStream = request.getInputStream();  
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);  

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

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

포스트맨에서는 다음과 같이 요청한다.

요청시 로그에 다음과 같이 찍힌다.

참고

  • inputStream은 byte 코드를 반환한다. byte 코드우리가 읽을 수 있는 문자(String)로 보려면 문자표 (Charset)를 지정해주어야 한다. 여기서는 UTF_8 Charset을 지정해주었다.

2-8. HTTP 요청 데이터 - 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}

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

/basic 경로에 HelloData 클래스를 아래와 같이 작성한다.

package spring.servlet.basic;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class HelloData {

    private String username;
    private int age;

// getter, setter 롬복으로 적용할 시 필요없음
//    public String getUsername() {
//        return username;
//    }
//
//    public void setUsername(String username) {
//        this.username = username;
//    }
//
//    public int getAge() {
//        return age;
//    }
//
//    public void setAge(int age) {
//        this.age = age;
//    }
}

basic/request 경로에 RequestBodyJsonServlet 클래스를 아래와 같이 작성한다.

package spring.servlet.basic.request;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

@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은 다음과 같이 설정한다.

System.out.println()으로 찍었기 때문에 다음과 같이 나온다.

이제 HelloData를 변환해보자.

참고로 Jackson 라이브러리를 사용해야 하는데, 부트를 사용하면 기본적으로 준다.

컨트롤러의 메서드를 다음과 같이 작성한다.(추가된 부분)

@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.getUsername() = " + helloData.getUsername());
        System.out.println("helloData.getAge() = " + helloData.getAge());

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

다시 요청을 하면 다음과 같이 나온다.

참고

HTML form 데이터도 메시지 바디를 통해 전송되므로 직접 읽을 수 있다.
하지만 편리한 파리미터 조회 기능( request.getParameter(...) )을 이미 제공하기 때문에 파라미터 조회 기능을 사용하면 된다.

다음과 같이 설정해서 보내면

다음과 같은 결과가 나온다.

참고로 ObjectMapper로 파싱하는 부분에서 에러가 난다.

2-9. HttpServletResponse - 기본 사용법

HttpServletResponse 역할

HTTP 응답 메세지 생성

  • HTTP 응답코드 지정할 수 있다.
  • 헤더 생성할 수 있다.
  • 바디 생성할 수 있다.

편의 기능을 제공한다.

  • Content-Type, 쿠키, Redirect

HttpServletResponse - 기본 사용법

basic/response경로에 ResponseHeaderServlet 클래스를 다음과 같이 작성한다.


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;
import java.io.PrintWriter;

@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(HttpServletResponse.SC_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");

        PrintWriter writer = response.getWriter();
        writer.println("안녕하세요");
    }
}

다음은 (Header) Content 편의 메서드다.
위와 같이 직접 정해주는 것이 아니라, 아래처럼 요소마다 직접 정하게 할 수 있는 Setter들이 있다.

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); //(생략시 자동 생성)
    }

content 편의 메서드를 확인해보자.

@Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // [status-line]
        response.setStatus(HttpServletResponse.SC_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");

        // [Header 편의 메서드]
        content(response);

        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); //(생략시 자동 생성)
    }

다음은 쿠키 편의 메서드다.
쿠키도 사실 response에 Set을 이용해서 넣을 수 있다.
그보다 편리하게 사용하는 메서드를 보고 사용해보자.

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // [status-line]
    response.setStatus(HttpServletResponse.SC_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");

    // [Header 편의 메서드]
//        content(response);
    cookie(response);

    PrintWriter writer = response.getWriter();
    writer.println("ok");
}

private void cookie(HttpServletResponse response) {
    // header에 직접 넣기
    //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 편의 메서드다.

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");
}

사용해보자.

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // [status-line]
    response.setStatus(HttpServletResponse.SC_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");

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


    PrintWriter writer = response.getWriter();
    writer.println("ok");
}

아래와 같이 결과가 나왔다.

Header 정보를 보면 302 코드에 location이 적용되었다.

그리고 리다이렉션 된 페이지는 다음과 같다.

redirect를 더 편리하게는 다음과 같이 사용할 수 있다.

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");
}

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

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

  • 단순 텍스트 응답
    • 앞에서 살펴봤다.(writer.println("ok");)
  • HTML 응답
  • HTTP API - MessageBody JSON 응답

HttpServletResponse - HTML 응답

basic/response 경로에 ResponseHtmlServlet 클래스를 작성한다.

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;
import java.io.PrintWriter;

@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.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://localhost:8080/response-html 경로에 접속하자.

다음과 같이 나온다.

페이지 소스는 다음과 같다.

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

2-11. HTTP 응답 데이터 - API JSON

basic/response 경로에 ResponseJsonServlet 클래스를 작성한다.

import com.fasterxml.jackson.databind.ObjectMapper;
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 spring.servlet.basic.HelloData;

import java.io.IOException;

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

    private ObjectMapper objectMapper = new ObjectMapper();


    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //Content-type: application/json
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");

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

        String result = objectMapper.writeValueAsString(helloData);
        response.getWriter().write(result);
    }
}

http://localhost:8080/response-json 경로로 접속하면 다음과 같이 나온다.

페이지 소스는 다음과 같다.

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()으로 출력하면 그런 문제가 없다.

3. 요약

HttpServletRequest와 HttpServletResponse의 개요와 사용법 대해 알아보았다.
그리고 HTTP의 요청 데이터, 응답 데이터에 대해 자세히 알아봤다.

728x90
Comments