쌩로그
스프링 MVC 1편 - Ch02. 서블릿 본문
목록
- 포스팅 개요
- 본론
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 - 요약
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.1
와Host: 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)
- 저장:
- 해당 HTTP 요청이 시작부터 끝날 때 까지 생존하는데, 그동안 유지되는 임시 저장소 기능
세션 관리 기능
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
을 사용해서 직접 읽을 수 있다.
- HTTP 메시지 바디의 데이터를
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}
- POST
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-type
을 text/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의 요청 데이터, 응답 데이터에 대해 자세히 알아봤다.
'Spring > Spring & Spring Boot' 카테고리의 다른 글
스프링 MVC 1편 - Ch04. MVC 프레임워크 만들기 (1) | 2024.03.28 |
---|---|
스프링 MVC 1편 - Ch03. 서블릿, JSP, MVC 패턴 (0) | 2024.03.22 |
스프링 MVC 1편 - Ch. 01 웹 애플리케이션 이해 (0) | 2024.01.12 |
스프링 핵심원리 기본편 - Ch09. 빈 스코프 (0) | 2023.12.20 |
스프링 핵심원리 기본편 - Ch08. 빈 생명주기 콜백 (0) | 2023.12.19 |