티스토리 뷰

Spring/Spring MVC

[스프링 MVC 1편] 서블릿

최진영 2021. 4. 12. 23:41

개발환경

 프로젝트 생성 : https://start.spring.io/

 GitHub Repository :  https://github.com/jinyoungchoi95/learn_servlet

  • Gradle 6.8.3
  • Java 11
  • Spring Boot 2.4.4
  • Packaging : War
  • Dependency : Spring web, Lombok

 Spring Boot 프로젝트를 생성하였는데 Packaging 방식을 Jar가 아닌 War로 한 이유는 차후 JSP를 사용하기 위함.

 

Servlet 환경 구성

 서블릿은 원래 Tomcat 등의 WAS를 직접 설치하고, 그 위에 서블릿 코드를 클래스 파일로 빌드해서 WAS를 실행하는 방식으로 사용한다.

 하지만 Spring Boot는 이런 고질점을 해결하고자 Tomcat을 내장하고 있어 굳이 Tomcat을 따로 띄우지 않아도 바로 서블릿을 사용할 수 있기 때문에 해당 방식으로 진행한다.

package hello.servlet;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

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

   public static void main(String[] args) {
      SpringApplication.run(ServletApplication.class, args);
   }

}
  • @ServletComponentScan : 스프링이 자동으로 하위 패키지들에서 서블릿을 찾아서 등록해준다.

 

HelloServlet

package hello.servlet.basic;


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
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);
    }
}
[출력]
HelloServlet.service
request = org.apache.catalina.connector.RequestFacade@76bb41a6
response = org.apache.catalina.connector.ResponseFacade@77c60156
username = test

 

  • @WebServlet : 해당 어노테이션의 속성값을 통해 서블릿의 이름과 URL을 매핑한다.
    • name : 서블릿 이름
    • urlPatterns : URL 매핑
  • HttpServlet : 해당 클래스를 상속받았을 때 service(), doGet(), doPost() 등의 메소드를 오버라이딩하여 서블릿으로서 사용할 수 있다.

 톰캣과 같은 WAS 서버들이 서블릿 표준 스펙을 구현하는데 이 구현체들이 request와 response에 해당한다. 출력에 나타난 주소들이 서블릿 표준 스펙을 구현한 구현체인 것이다. 이전 포스트에서 이야기했듯이 request에 요청 정보들을 담아서 받아오고 response에 반환 정보를 담아서 내보낸다.

 실제로 서버에 요청은 /hello만 매핑한 것이 아니라 http://localhost:8080/hello?username=test로 매핑했다. 즉, username라는 파라메터에 test를 담아서 전송한 것이다. 따라서 출력에도 마찬가지로 내가 받은 request의 파라메터를 출력했을 때 test가 출력된다.

 

 다음 코드는 내가 클라이언트로 반환할 때 response에 헤더에 들어갈 내용들을 담아서 직접 보내는 역할을 한다. 웹 브라우저로 가서 확인해보면 실제 response 헤더에 해당 내용들이 담겨있음을 확인할 수 있다.


 

RequestHeaderServlet

 서블릿을 사용하면 Http의 많은 헤더와 요청사항들을 우리가 신경쓰지 않고 필요한 부분만 담아서 사용할 수 있다. 이를 전부 확인해보려면 application.propertieslogging.level.org.apache.coyote.http11=debug을 입력하면 요청이 들어올 때마다 Http의 내용을 다 알 수 있다.

Received [GET /hello?username=test HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7

 이런 Http 요청 메시지들이 들어올 때 직접 파싱되지만 HttpServletRequest객체 안에 담겨와서 우리는 편하게 사용할 수 있다.

 지금부터는 Request에서 직접 Header에 담겨져서 나오는 내용에 대해 출력해보면서 알아보려고 한다.

  • Start Line
    • Http 메소드
    • URL
    • 쿼리 스트링
    • 스키마, 프로토콜
  • Header
    • 헤더 조회
  • Body
    • form 파라미터 형식 조회
    • Message body 데이터 직접 조회

 단, HttpServletRequest 객체는 추가로 다른 부가기능도 같이 제공해 준다.

  • 임시 저장소 기능 : 해당 Http 요청 시작부터 종료까지 유지되는 임시 저장소 제공
    • 저장 : request.setAttribute(name, value)
    • 조회 : request.getAttribute(name)
  • 세션 관리 기능
    • requeset.getSession(create: true)

 

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {

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

        printStartLink(request);
        printHeaders(request);
        printHeaderUtils(request);
        printEtc(request);
    }
}

 해당 서블릿을 하나 만들어두어서 내부에 메소드를 넣어 출력하는 방식으로 알아보았다.

 

Start Line

private void printStartLink(HttpServletRequest request) {
    System.out.println("--- REQUEST-LINE - start ---");
    System.out.println("request.getMethod() = " + request.getMethod()); //GET
    System.out.println("request.getProtocal() = " + 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-test
    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();
}
[출력]
--- REQUEST-LINE - start ---
request.getMethod() = GET
request.getProtocal() = HTTP/1.1
request.getScheme() = http
request.getRequestURL() = http://localhost:8080/request-header
request.getRequestURI() = /request-header
request.getQueryString() = null
request.isSecure() = false
--- REQUEST-LINE - end ---

 첫번째로 start line에는 http 메소드가 들어간다. 흔히 이야기하는 GET, POST, DELETE, PUT 등이 이에 해당한다.

 두번째로 URL이 들어온다. Request Target으로서 Http 요청이 전송되는 목표 주소라고 할 수 있다. 위 출력에서 request.getRequestURL()로 그 URL 주소를 전체 다 볼 수 있고, URI를 통해서 전달된 api 주소만 볼 수도 있다.

 마지막으로는 Http 버전이 들어온다. request.getProtocal()에서 HTTP/1.1로 보아 http 1.1 버전으로 요청이 들어왔고 이를 표기한 이유는 버전에 따라 요청 메시지 구조나 데이터가 다를 수 있기 때문에 명시한다.

 

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 + ": " + request.getHeader(headerName));
     }
    */
    request.getHeaderNames().asIterator()
            .forEachRemaining(headerName -> System.out.println(headerName + ":" + request.getHeader(headerName)));
    System.out.println("--- Headers - end ---");
    System.out.println();
}
//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();
}
[출력]
--- Headers - start ---
host:localhost:8080
connection:keep-alive
sec-ch-ua:"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
sec-ch-ua-mobile:?0
upgrade-insecure-requests:1
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site:none
sec-fetch-mode:navigate
sec-fetch-user:?1
sec-fetch-dest:document
accept-encoding:gzip, deflate, br
accept-language:ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
cookie:Idea-bf58347d=710b1c6d-63d7-4a92-a44e-c785e3cd75a6
--- Headers - end ---

--- 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 편의 조회]
Idea-bf58347d: 710b1c6d-63d7-4a92-a44e-c785e3cd75a6

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

 Header는 대소문자 구분없는 문자열에서 콜론(":")으로 구별지어 들어오기 때문에 printHeader메소드와 같이 Header의 모든 값을 무분별하게 받아올 수 있다. 단, servlet에서는 printHeaderUtils메소드와 같이 간편하게 이를 조회할 수 있는 메소드들을 제공해준다.

 특이점은 Accept-Language인데 locale 정보를 웹브라우저에서 순차적으로 값을 보내게 되는데 가장 높은 locale을 뽑고싶으면 request.getLocale()로 가장 높은 locale을 받아올 수 있다.

(현재는 get방식이기 때문에 content를 담아서 보내지 않아서 null로 되어있다.)

 

기타 정보

//기타 정보
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() = 55591

[Local 정보]
request.getLocalName() = localhost
request.getLocalAddr() = 0:0:0:0:0:0:0:1
request.getLocalPort() = 8080
--- 기타 조회 end ---

 http 요청 메시지에서 오는 것은 아니고 네트워크 커넥션에서 들어오는 정보들을 받아준다.

 

HTTP 요청 데이터

 클라이언트에서 서버로 데이터를 주로 전달할 때 사용하는 3가지 방법에 대해서 알아보고자 한다.

  • GET - 쿼리 파라미터
  • POST - HTML Form
  • HTTP mesage body에 담아서 요청

 

GET 쿼리 파라미터

 메시지 바디 없이 URL에 쿼리 파라미터를 담아서 전달하는 방식이다. 검색, 필터, 페이징 등에서 사용하는데 항상 url을 치면 보는 익숙한 구문이 바로 이에 해당한다.

https://jinyoungchoi95.tistory.com/?page=2

 

 전달 데이터로는

  • username = hello
  • age = 20

 을 전단하도록 한다.

 위 예시에서 보여줬다시피 쿼리 파라미터는 URL에 ?를 시작으로 하여 작성하여 보내며 파라미터들은 &로 구분한다.

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

 클라이언트에서는 url에 이렇게 담아서 보내고 그럼 서버는? 지금까지 이야기했던대로 http 요청 메시지를 받아서 처리해주는 HttpServletRequset 객체에서 관리할 수 있다.

package hello.servlet.basic.request;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

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

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

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

        System.out.println("[이름이 같은 복수 파라미터 조회]");
        String[] usernames = request.getParameterValues("username");
        for(String name : usernames){
            System.out.println("username = " + name);
        }
    }
}
요청 url : https://localhost:8080/request-param?username=hello&age=20&username=bye
[출력]
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end

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

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

 

전체 파라미터 조회

 request 객체에서 개별적으로 필요한 파라미터만 조회하는 것이 아닌 전체 파라미터를 순차적으로 받아내는 방식이다. 무분별하게 다 받아야 하기 때문에 원하는 파라미터만 받아내기 힘들다. 따라서 다음 방법이 존재한다.

 

단일 파라미터 조회

 말그대로 단일 파라미터에 대해서 조회하는 방법으로 파라미터 이름만 알면 객체로 개별적으로 뽑아낼 수 있다.

 

이름이 같은 복수의 파라미터 조회

 그럼 궁금증이 생긴다. username이라는 파라미터를 보냈는데 username이름으로 여러 개의 파라미터를 보내면 어떻게될까? 다음과 같이 말이다.

https://localhost:8080/request-param?username=hello&age=20&username=bye
[출력]
[단일 파라미터 조회]
uesrname = hello
age = 20

 단일 파라미터 조회에서 사용한 request.getParameter("username")을 사용하면 먼저 호출된 파라미터만 받아낼 수 있다.

 

 따라서 복수의 파라미터를 전부 조회하고 싶으면 다른 메소드를 사용해야한다.

//복수의 파라미터를 조회하고 싶을 때
String[] usernames = request.getParameterValues("username");

 단, 복수의 파라미터가 들어왔기 때문에 이는 배열로써 받아야하며 들어온 순서대로 배열에 저장된다.

[출력]
[이름이 같은 복수 파라미터 조회]
username = hello
username = bye

 hello와 bye를 모두 받아내는 것을 확인할 수 있다. 즉, request.getParameter는 파라미터에 대해서 단일 값만 있을 때 사용해야하며 중복일 때는 request.getParameterValues()를 사용해야 한다.

 

POST - HTML Form

 url에 포함해서 데이터를 전송하는 것이 아닌 HTML의 Form을 사용해서 클라이언트에서 서버로 데이터를 전송하는 방법이다. 다음 html 파일을 만들어서 실행 후 action을 날리면 결과는 어떻게될까?

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

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

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

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

 결과가 GET 파라미터 전송 방식과 동일하다. 즉 POST의 HTML Form을 전송하면 파라미터를 전송했을 때와 동일한 데이터를 보내게 되는데 단, body에 포함된 content-type을 반드시 지정해주어야 한다. 지금은 html 웹에서 자동으로 content-type을 지정해서 보냈는데 이 content-type은 application/x-www-form-urlencoded형식으로 HTML Form에서 message body를 전송하기 위한 타입인 것이다.

 실제로 Postman을 통해서 body를 application/x-www-form-urlencoded형식으로 전송했을 때 똑같은 결과물을 얻어낼 수 있음을 확인할 수 있다.


 물론 GET 방식을 전송할 때 Postman이 자동으로 content-type을 POST와 동일하게 설정해주기는 한다.

 

HTTP message body

 HTTP 메시지 바디에 담아서 보낼 때는 주로 JSON 데이터 형식을 많이 사용하지만 일단 text로 발송하는 것을 알아보자.

package hello.servlet.basic.request;

import org.springframework.util.StreamUtils;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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 ServletException, IOException {

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

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

        response.getWriter().write("ok");
    }
}
  • getInputStream() : message body 부분을 bytecode로 받아냄.
  • StreamUtils.copyToString() : bytecode를 문자열로 변환. 단, 인코딩 형태를 항상 명시.

[출력]
messageBody = hello

 messageBody에 원하는 String을 넣어 전송할 수 있음을 확인할 수 있다. 단, 요즘은 JSON 데이터 형식으로 많이 주고받기 때문에 JSON 형식을 알아보자.

 

HTTP message body - JSON

 보통 JSON은 전송할 때 그냥 보내기보단 객체로 바꾸어서 전달받는다. 따라서 JSON을 파싱할 수 있도록 별도의 클래스 객체를 생성한다.

package hello.servlet.basic;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class HelloData {

    private String username;
    private int age;
}
package hello.servlet.basic.request;

import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.HelloData;
import org.springframework.util.StreamUtils;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@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

 

 일단 첫번째로 보아야할 것은 JSON 형태를 text와 똑같이 문자열로 받았을 때 그 값이 그대로 들어온다는 점이다. JSON도 결국 문자이기 때문에 단순히 bytecode로 받아서 String으로 변환했을 때는 가공되지 않은 값을 받아내는 것이다.

 가공된 객체로서 JSON을 받아내고자한다면 ObjectMapper(Jackson 라이브러리)를 사용한다면 아래 출력과 같이 객체로 값을 받아낼 수 있음을 확인할 수 있다.

 

 이 전에 이야기했던 POST HTML Form 방식 또한 body에 담아서 전송되는 것이기 때문에 messageBody를 받아냈을 때처럼 String으로 직접 읽을 수도 있긴하다. 단, 파라미터 조회방식으로 조회할 수 있기 때문에 굳이 쉬운 방식을 두고 messageBody로 읽지는 않는다.

 

HttpServletResponse

 우리가 Servlet에서 Request와 Response를 이야기할 때 어떻게 이야기를 했었는가? 긴 Http 요청 메시지 스펙을 편하게 Request로 받고 return해야할 값을 Response 객체에 담아서 전송한다고 했었다.

 즉, HttpServletResponse가 하는 역할은

  • HTTP 응답코드 지정
  • 헤더 생성
  • 바디 생성
  • Content-Type, 쿠키, Redirect 기능 제공

이 있다. 하나하나 알아보도록 하자.

 먼저, 편의기능 없이 Header를 정의하는 방법부터 본다.

package hello.servlet.basic.response;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.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_BAD_REQUEST);

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

 

1. status-line

 홈페이지를 사용하다보면 가장 많이 보는 숫자가 있다. "404"에러. 우리는 전송되는 response에 응답코드를 담아서 전송할 수 있다. 단, 직접 코드번호를 쳐도되지만 HttpServletResponse에서 에러들을 상수로 정의해두었기 때문에 의미있는 매직넘버로 사용하기 위해서 해당 방식을 사용하는 것을 권고한다.

2. response-header

 직접 setHeader()를 이용하여 헤더에 들어있는 값을 조정해서 전송할 수 있고, "my-header"와 같이 내가 원하는 헤더를 임의로 생성해서 보낼 수도 있다.


 이야기했던 부분이 실제 Header에 다 담겨있다. set으로 변경한 값들은 다 변경되고, my-header라는 새로운 헤더를 생성해낸 것까지 확인할 수 있다. 400에러는 직접 400에러 status로 설정하였기 때문에 생겼다고 할 수 있다.

 

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

 물론 친절하게도 메소드로 ResponseHeader를 조정할 수 있도록 제공하기 때문에 편한 방법을 사용하면 된다.

 

HTML 응답 데이터

 HttpServletResponse 객체에 필요한 데이터를 담아 전달한다고 했으니 당연히 응답 데이터에는 여러가지 정보들을 담아서 넣을 수 있다. text도 가능하고 html까지도 가능하다. 단, html을 전송할 때는 주의사항이 있다.

package hello.servlet.basic.response;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.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>");
    }
}

 반드시 content-type을 text/html로 지정하여야 한다.

 

 최근에 데이터 전송 방식이 대부분 JSON으로 전달한다고 했었다. 물론 Response에도 JSON형태로 담아서 보낼 수 있으며 전달 방식이 text와 유사하게 보낼 수 있기도 하다(content-type을 application/json으로 지정했을 경우). 하지만 우리는 HttpServletRequest에서 JSON에 파싱할 클래스를 만든 적이 있다. 따라서 이를 활용해서 더 쉽게 JSON을 전달하고자 한다.

package hello.servlet.basic.response;

import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.HelloData;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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("hello");
        helloData.setAge(20);

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

 전송할 때도 messagebody에서 ObjectMapper를 이용해 객체로 뽑아낸 것처럼 동일하게 ObjectMapper를 통해 객체를 담아서 전송한다. 마찬가지로 content-type을 application/json으로 지정해주어야 JSON으로 반환할 수 있다.

 

* 김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 듣고 기록한 학습 자료입니다.

댓글
최근에 올라온 글
Total
Today
Yesterday