Backend

[Spring] HttpServletRequest와 HttpServletResponse 다루기

pebblepark 2022. 1. 11. 21:47

HttpServletRequest

  • 서블릿이 HTTP 요청 메시지를 파싱해 HttpServletRequest 객체에 담아 제공

 

HTTP 요청 메시지

POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded

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

 

임시 저장소

  • HTTP 요청의 시작 - 끝 동안 유지되는 임시 저장소
    • 저장 : request.setAttribute(name, value)
    • 조회 : request.getAttribute(name)

세션 관리

  • request.getSession(create: true)

 

예제코드

ReqeuestHeaderServlet

package hello.servlet.request.basic;

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;

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

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        printStartLine(req);
        printHeaders(req);
        printHeaderUtils(req);
        printEtc(req);

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

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

    private void printHeaders(HttpServletRequest req) {
        System.out.println("--- Headers - start ---");
        req.getHeaderNames().asIterator()
                .forEachRemaining(headerName -> System.out.println(headerName + ": " + req.getHeader(headerName)));
        System.out.println("--- Headers - end ---");
        System.out.println();
    }

    private void printHeaderUtils(HttpServletRequest req) {
        System.out.println("--- Headers 편의 조회 start ---");
        System.out.println("[Host 편의 조회]");
        System.out.println("req.getServerName() = " + req.getServerName());
        System.out.println("req.getServerPort() = " + req.getServerPort());
        System.out.println();

        System.out.println("[Accept-Language 편의 조회]");
        req.getLocales().asIterator()
                .forEachRemaining(locale -> System.out.println("locale = " + locale));
        System.out.println("req.getLocale() = " + req.getLocale());
        System.out.println();

        System.out.println("[cookie 편의 조회]");
        if(req.getCookies() != null) {
            for (Cookie cookie : req.getCookies()) {
                System.out.println(cookie.getName() + ": " + cookie.getValue());
            }
        }
        System.out.println();

        System.out.println("[Content 편의 조회]");
        System.out.println("req.getContentType() = " + req.getContentType());
        System.out.println("req.getCharacterEncoding() = " + req.getCharacterEncoding());

        System.out.println("--- Headers 편의 조회 end ---");
        System.out.println();
    }

    private void printEtc(HttpServletRequest req) {
        System.out.println("--- 기타 조회 start ---");
        System.out.println("[Remote 정보]");
        System.out.println("req.getRemoteHost() = " + req.getRemoteHost());
        System.out.println("req.getRemoteAddr() = " + req.getRemoteAddr());
        System.out.println("req.getRemotePort() = " + req.getRemotePort());
        System.out.println();

        System.out.println("[Locale 정보]");
        System.out.println("req.getLocalName() = " + req.getLocalName());
        System.out.println("req.getLocalAddr() = " + req.getLocalAddr());
        System.out.println("req.getLocalPort() = " + req.getLocalPort());

        System.out.println("--- 기타 조회 end ---");
        System.out.println();
    }
}

 

결과

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

--- Headers - start ---
host: localhost:8080
connection: keep-alive
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 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: same-origin
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
referer: http://localhost:8080/basic.html
accept-encoding: gzip, deflate, br
accept-language: ko,ko-KR;q=0.9,en;q=0.8,ja;q=0.7
cookie: sidebar_collapsed=false; jenkins-timestamper-offset=-32400000
--- Headers - end ---

--- Headers 편의 조회 start ---
[Host 편의 조회]
req.getServerName() = localhost
req.getServerPort() = 8080

[Accept-Language 편의 조회]
locale = ko
locale = ko_KR
locale = en
locale = ja
req.getLocale() = ko

[cookie 편의 조회]
sidebar_collapsed: false
jenkins-timestamper-offset: -32400000

[Content 편의 조회]
req.getContentType() = null
req.getCharacterEncoding() = UTF-8
--- Headers 편의 조회 end ---

--- 기타 조회 start ---
[Remote 정보]
req.getRemoteHost() = 0:0:0:0:0:0:0:1
req.getRemoteAddr() = 0:0:0:0:0:0:0:1
req.getRemotePort() = 14067

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

 

쿼리 파라미터 조회

RequestParamServlet

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;

/**
 * 1. 파라미터 전송 기능
 * http://localhost:8080/request-param?username=hello&age=20
 * <p>
 * 2. 동일한 파라미터 전송 가능
 * http://localhost:8080/request-param?username=hello&username=kim&age=20
 */

@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse resp) 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");
        System.out.println("request.getParameter(username) = " + username);
        String age = request.getParameter("age");
        System.out.println("request.getParameter(age) = " + age);
        System.out.println();

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

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

 

실행결과

  • http://localhost:8080/request-param?username=hello&username=kim&age=20
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end

[단일 파라미터 조회]
request.getParameter(username) = hello
request.getParameter(age) = 20

[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello
username=kim

 

복수 파라미터와 단일 파라미터 조회

  • 파라미터 이름은 하나이지만 값이 중복일 경우
  • request.getParameter() : 하나의 파라미터 이름에 대해 값 출력, 중복일 경우 request.getParameterValues()의 첫 번째 값 반환
  • reqeust.getParameterValues() : 값이 중복일 경우 사용, 배열로 값을 반환함

 

HTML Form으로 데이터 전송

<form action="/request-param" method="post">
    username: <input type="text" name="username" />
    age: <input type="text" name="age" />
    <button type="submit">전송</button>
</form>
  • Content-Type: application/x-www-form-urlencoded
  • message body: username=hello&age=20
  • Content-Type은 HTTP 메시지 바디의 데이터 형식 지정, GET 형식은 메시지 바디가 없기 때문에 사용 x
  • 메시지 바디에 쿼리 파라미터 형식으로 데이터 전달

 

JSON으로 데이터 전달

RequestBodyJsonServlet

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;

/**
 * http://localhost:8080/request-body-json
 *
 * JSON 형식 전송
 * content-type: application/json
 * message body: {"username": "hello", "age": 20}
 *
 */
@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
    private final 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");
    }
}
  • Content-Type: application/json
  • message body: {"username": "hello", "age": 20}
  • JSON 결과를 파싱해서 객체로 변환하기 위해 JSON 라이브러리 추가 사용이 필요함
  • 스프링 부트는 기본으로 Jackson 라이브러리를 제공함(ObjectMapper)

 

HttpServletResponse

HTTP 응답 메시지 생성

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

편의 기능

  • Content-Type, Cookie, Redirect

 

예제코드

ResponseHeaderServlet

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;

/**
 * http://localhost:8080/response-header
 */
@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); //200

        //[response-headers]
        response.setHeader("Content-Type", "text/plain;charset=utf-8");
        response.setHeader("Cache-Control", "no-cache, no-store, mustrevalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("my-header","hello");

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

        //[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");
    }
}
  • redirect 시 status code 302로 반환 => /basic/hello-form.html 로 리다이렉트 됨

 

응답결과

 

단순 텍스트, HTML 응답

  • 단순 텍스트 응답 : writer.println("ok");

ResponseHtmlServlet

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>");
    }
}
  • HTML을 응답으로 반환할 때는 Content-Type을 text/html로 지정

 

JSON 응답

ResponseJsonServler

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;

/**
 * http://localhost:8080/response-json
 */
@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
    private final 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");
        response.setCharacterEncoding("utf-8");
        HelloData data = new HelloData();
        data.setUsername("kim");
        data.setAge(20);

        String result = objectMapper.writeValueAsString(data); //{"username":"kim","age":20}
        response.getWriter().write(result);
    }
}
  • JSON을 응답으로 반환할 때는 Content-Type을 application/json로 지정
application/json은 스펙상 utf-8형식을 사용하도록 정의되어 있으므로 application/json;charset=utf-8은 의미없는 파라미터를 추가한 것이 된다. 따라서 response.setCharacterEncoding("utf-8"); 부분을 제거하고 추가 파라미터를 자동으로 추가하는 response.getWriter() 대신 response.getOutputStream()을 사용하면 된다.