HttpServletRequest, Response

servlet 컨테이너는 Http 메세지에 대한 파싱을 자동으로 진행해서 request 객체에 담아서 business logic으로 전달해준다. 사용자는 request 객체를 이용해서 필요한 부분을 이용하면 되고, response 객체에 응답을 넣어주기만 하면된다. 해당 부분에서는 request 객체 및 response 객체를 어떻게 다루는 지 알아보자.

HttpServlet

서블릿은 Spring boot에 내장된 Tomcat Server에서 호출되어 서블릿 컨테이너에 저장되게 된다.(이때 Spring bean 처럼 싱글톤 컨테이너로 기본적으로 저장되게 된다.) 그러면 이런 서블릿 컨테이너는 사용자의 요청을 처리하고 이에 대한 응답을 처리한다.

ServletComponentScan

기본적으로 @ServletComponentScan annotation을 명시하면 자동으로 servlet을 찾아서 servlet container에 등록되게 된다.

@ServletComponentScan //서블릿 자동 등록
@SpringBootAplication
public class ServletApplication{
  public static void main(String[] args){
    ...
  }
}

Servlet 등록 방법

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse
  response)
}

@WebServlet 명시를 통해 해당 class가 서블릿임을 알리고, urlPatterns는 url mapping을 의미하며 해당 url로 요청이 왔을때, servlet이 동작하게 된다.

Servlet으로 이용하는 class들은 HttpServlet을 상속해서 service 메소드 오버라이드를 통해서 request, response 객체를 활용할 수 있다.

HttpServletRequest

서블릿은 HTTP message를 요청받게 되면 이를 파싱해서 개발자가 사용하기 쉬운 형태로 전달하게 된다.

//HTTP startline method,url,protocol 정보
POST /save HTTP/1.1
//Http Header 
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
//Http Body
username=kim&age=20

위의 http message에 대한 파싱을 실행해 메소드 형태로 데이터에 접근할 수 있도록 지원한다.

추가로 아래의 기능도 제공한다.

//임시 저장소 기능
request.setAttribute(name,value);
request.getAttribute(name);

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

HttpRequest builtin methods

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://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();

Results

--- 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=hello
request.isSecure() = false
--- REQUEST-LINE - end ---

Header Parsing

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

Results

--- Headers - start ---
host: localhost:8080
connection: keep-alive
cache-control: max-age=0
sec-ch-ua: "Chromium";v="88", "Google Chrome";v="88", ";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_2_0) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/88.0.4324.150 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,en-US;q=0.9,en;q=0.8,ko-KR;q=0.7
--- Headers - end ---

Utility Parsing

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();

Results

--- Header 편의 조회 start ---
[Host 편의 조회]
request.getServerName() = localhost
request.getServerPort() = 8080
[Accept-Language 편의 조회]
locale = ko
locale = en_US
locale = en
locale = ko_KR
request.getLocale() = ko
[cookie 편의 조회]
[Content 편의 조회]
request.getContentType() = null
request.getContentLength() = -1
request.getCharacterEncoding() = UTF-8
--- Header 편의 조회 end ---

Extra Parsing

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();

Results

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

HttpRequest Data

HttpRequest로 데이터를 전달받을 수 있는 방법은 크게 3가지이다.

  1. GET방식으로, 쿼리 파라미터 형식으로 데이터를 전달받는 것이다.
    • /url?username=hello&age=20 와 같이 url을 통해 전달하는 방식
    • 따로 message body가 존재하지 않는다.
  2. Post 방식으로, Html form으로 전달받는 방식
    • 이때, content-type는 /application/x-www-form-unlencoded 방식이다.
    • username=hello&age=20으로 GET 방식과 유사하게 받을 수 있다.
  3. Http Message Body에 JSON 형태로 데이터를 전달받는 방식

HTTP Get 방식

다음과 같이 url을 통해 전달하는 방식이다.

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

url에 쿼리 파라미터 형태로 전달하게 된다.

System.out.println("전체 파라미터 조회");
request.getParameterNames().asIterator()
        .forEachRemaining((paramName) ->{
            System.out.println(paramName + "=" + request.getParameter(paramName));
        });
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("이름이 같은 복수 파라미터 조회");
String[] usernames = request.getParameterValues("username");
for(String name: usernames){
    System.out.println("username = " + name);
}

Results

[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회]
request.getParameter(username) = hello
request.getParameter(age) = 20
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello

다양한 방법으로 query parameter을 활용할 수 있다.

HTML Form 형태로 전달받기

HTML Form

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

위와 같은 html form으로 전달할떄, 내부적으로 content-type을 /application/x-www-form-urlencoded으로 설정해서, message body에 전달하게 된다.

form action을 보면 /request-param으로 전달하는 것을 확인할 수 있는데, 이는 이전의 GET-방식으로 전달된 Http Request을 parsing 하기 위한 servlet인데, 여기에 요청을 보내도 정상적으로 파싱을 하는 것을 알 수 있다.

이는, Get 방식과 Form 방식으로 전달하는 메세지의 형태가 유사하기 때문이다. 다만, Form 방식에서는 content-type이 있고, Get방식에는 message body가 없어 content-type이 없는 것이 차이점이다.

HTTP API 방식

message body에 JSON형태로 데이터를 전달하기도 한다.

InputStream 방식으로 읽어들이는 방법

ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);

response.getWriter().write("Ok");

위와 같이 InputStream 형태로 읽게 되면 Message Body를 문자열로 인식해서 그대로 출력하게 된다.

Results:

가령 아래의 JSON 형태를 message body로 담아 전달하게 되면

{
  "username":"hello",
  "age":20
}

아래와 같이 string으로 출력하게 된다.

messageBody={"username":"hello","age":20}

JSON 형태로 인식하기 위해서는 JSON 형태로 parsing 해줄 수 있는 library를 활용해야하는데, spring boot에서는 ObjectMapper라는 것을 제공해준다.

Data class

JSON 형태를 저장하기 위한 클래스 생성

@Getter
@Setter
public class HelloData {
    private String username;
    private int age;
}

ObjectMapper

ObjectMapper objectMapper=new ObjectMapper();
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        System.out.println("helloData.getUsername() = " + helloData.getUsername());
        System.out.println("helloData.getAge() = " + helloData.getAge());
    }

objectMapper의 readValue 메소드를 이용해서 객체 형태로 매핑해줄 수 있다.

HttpServletResponse

서블릿은 Http 응답 메세지를 담당하는 Response 객체도 생성해준다. Response 객체에는 대표적으로 응답코드(Http Status), Header, Body 데이터가 있다.

Using HttpServletResponse

Status Code

//[status-line]
response.setStatus(HttpServletResponse.SC_OK); //200

setStatus 메소드를 활용해서 응답코드를 지정한다.

Content 설정

//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.setHeader을 이용해서 필요한 header 정보 옵션, 값을 지정해서 설정해줄 수 있지만, 이렇게 명확하게 설정하는 것이 어려워, 더 세분하게 구성할 수 있도록, setContentType, setCharacterEncoding과 같은 메소드를 제공한다.

Cookie 설정

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

cookie를 생성해서 response에 넣을 수 있다.

Redirect 설정

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

sendRedirect 메소드를 이용해서 redirect Url을 지정할 수 있다.

HttpRequest Data 전달

response.getWriter().write("Ok");

위의 write 메소드를 이용해서 String 형태의 data를 전달할 수 있다.

HTML 전달 방식

//html 데이터를 보낼때는 content_type이 text/html이다.
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<body>");
writer.println(" <div>안녕?</div>");
writer.println("</body>");
writer.println("</html>");

위와 같이 html 형태의 data를 전달하게 되면 해당 html이 client에게 출력되게 된다.

API 전달 방식

API 형태의 data를 전달하기 위해 Request에서 처럼 ObjectMapper을 이용해서 JSON 형태의 data를 전달할 수 있다.

 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 s = objectMapper.writeValueAsString(helloData);
        response.getWriter().write(s);
    }

이번에는 objectMapper의 writeValueAsString을 이용해서 객체의 형태의 data를 JSON format의 String로 변환하게 된다.

References

link: inflearn

link:springmvc

댓글남기기