클라이언트에서 서버로 요청시, 브라우저는 HTTP 프로토콜에 맞춰서 요청 메서지를 만들어서 서버로 보낸다.
HTTP 요청 메세지
POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username=kim&age=20
서버에는 위와 같은 HTTP 요청 메세지가 온다. 그렇다면 서버에서는 HTTP 요청 메세지를 어떻게 처리해야하는걸까? HTTP 요청 메세지를 개발자가 직접 파싱해서 사용해도 되지만, 이 경우는 매우 매우 불편할 것이다. 그래서 서블릿은 개발자가 HTTP 요청 메세지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메세지를 파싱한다. 그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한
다.
또한 임시저장소 기능과 세션 관리 기능도 포함하고 있다.
즉, HttpServletRequest 란 HTTP 요청 메세지에 대한 데이터들을 갖고 있는 객체!
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
printStartLine(request);
printHeaders(request);
printHeaderUtils(request);
printEtc(request);
}
// start line 정보
private void printStartLine(HttpServletRequest request){
System.out.println("--REQUEST-LINE - start --");
System.out.println("request.getMethod() = " + request.getMethod());
System.out.println("request.getProtocol() = " + request.getProtocol());
System.out.println("request.getScheme() = " + request.getScheme());
System.out.println("request.getRequestURL() = " + request.getRequestURL());
System.out.println("request.getRequestURI() = " + request.getRequestURI());
System.out.println("request.getQueryString() = " + request.getQueryString());
System.out.println("request.isSecure() = " + request.isSecure());
System.out.println("--- REQUEST-LINE - end ---");
System.out.println();
}
// 헤더 정보
private void printHeaders(HttpServletRequest request){
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();
}
// 헤더 편리한 조회
private void printHeaderUtils(HttpServletRequest request){
System.out.println("--- Header 편의 조희 start ---");
System.out.println("[Host 편의 조회]");
System.out.println("request.getServerName() = " + request.getServerName());
System.out.println("request.getServerPort() = " + request.getServerPort());
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();
}
// 기타 정보 조회
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();
}
}
위 코드로 HTTP 요청 메세지를 HttpServletRequest가 start-line,header정보 조회를 하는지 알 수 있었다. 이번에는 HTTP 요청 데이터를 어떻게 조회하는지 알아보자
먼저 HTTP 요청 메세지를 통해 클라리언트에서 서버로 데이터를 전달하는 방법을 알아보자
주로 다음 3가지 방법을 사용한다.
3.HTTP message body에 데이터 직접 담기
메세지 바디 없이 URL의 쿼리 파라미터를 사용해서 데이터를 전달
서버에서는 HttpServletRequest가 제공하는 메서드들을 통해 쿼리 파라미터를 편리하게 조회할 수 있다.
http://localhost:8080/request-param?username=hello&age=20
위와 같이 URL에 ? 뒤에 데이터를 전달하는 방식이다.
getParmeter("username") // 단일 파라미터 조회
getParameterNames() // 파라미터 이름들 모두 조회
getParameterMap() // 파라미터를 Map으로 조회
getParameterValues("username") // 복수 파라미터 조회
@WebServlet(name = "requestParamServlet",urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("[전체 파라미터 조회] - start");
req.getParameterNames().asIterator()
.forEachRemaining(paramName -> System.out.println(paramName + "=" + req.getParameter(paramName)));
System.out.println("[전체 파라미터 조회] - end");
System.out.println();
System.out.println("[단일 파라미터 조회]");
String username = req.getParameter("username");
System.out.println("request.getParameter(username) = " + username);
String age = req.getParameter("age");
System.out.println("request.getParameter(age) = " + age);
System.out.println();
System.out.println("[이름이 같은 복수 파라미터 조회]");
System.out.println("request.getParameterValues(username)");
String[] usernames = req.getParameterValues("username");
for(String name : usernames){
System.out.println("username="+name);
}
resp.getWriter().write("ok");
}
}
결과
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회]
request.getParameter(username) = hello
request.getParameter(age) = 20
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello
이번에는 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>
POST의 HTML Form을 전송하면 웹 브라우저는 다음 형식으로 HTTP 메시지를 만든다.
요청 URL: http://localhost:8080/request-param
content-type: application/x-www-form-urlencoded
message body: username=hello&age=20
application/x-www-form-urlencoded 형식은 앞서 GET에서 살펴본 쿼리 파라미터 형식과 같다. 메세지 바디에 데이터가 있지만 쿼리 파라미터 형식으로 전달할 뿐이다. 따라서 쿼리 파라미터 조회 메서드를 그대로 사용하면 된다.
클라이언트 입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 둘의 형식이 동일하다. 그렇기에 request.getParameter()로 편리하게 구분없이 조회할 수 있는 것이다.
@WebServlet(name = "requestParamServlet",urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("[전체 파라미터 조회] - start");
req.getParameterNames().asIterator()
.forEachRemaining(paramName -> System.out.println(paramName + "=" + req.getParameter(paramName)));
System.out.println("[전체 파라미터 조회] - end");
System.out.println();
System.out.println("[단일 파라미터 조회]");
String username = req.getParameter("username");
System.out.println("request.getParameter(username) = " + username);
String age = req.getParameter("age");
System.out.println("request.getParameter(age) = " + age);
System.out.println();
System.out.println("[이름이 같은 복수 파라미터 조회]");
System.out.println("request.getParameterValues(username)");
String[] usernames = req.getParameterValues("username");
for(String name : usernames){
System.out.println("username="+name);
}
resp.getWriter().write("ok");
}
}
HTML Form 요청 결과
Host: localhost:8080
Connection: keep-alive
Content-Length: 21
Cache-Control: max-age=0
sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126", "Microsoft Edge";v="126"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0
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.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8080/basic/hello-form.html
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: ko,en;q=0.9,en-US;q=0.8
username=hello&age=20]
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회]
request.getParameter(username) = hello
request.getParameter(age) = 20
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello
여기서 중요한 것은 HTML Form 요청일 경우에는 메세지 바디에 데이터가 들어가지만, content-type: application/x-www-form-urlencoded 이기 때문에 서버입장에서는 쿼리 파라미터 형식으로 받아들여서 처리하게 된다. 그래서 HttpServletRequest 객체인 request가 request.getParameter() 요청 데이터를 조회할 수 있는 것이다.
HTTP message body에 데이터를 직접 담아서 요청하는 방법이다.
- API 메세지 바디 JSON 요청 데이터를 JSON 형식으로 파싱할 객체
@Getter @Setter
public class HelloData {
private String username;
private int age;
}
- 서블릿
@WebServlet(name = "requestBodyJsonServlet",urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet{
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletInputStream inputStream = req.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());
resp.getWriter().write("ok");
}
}
- POST http://localhost:8080/request-body-json
- content-type: application/json
- message body: {"username": "hello", "age": 20}
결과
messageBody = {"username": "hello","age": 20}
helloData.username = hello
helloData.age = 20
HTTP API JSON 형식으로 content-type: appplication/json 요청시에 서버에서는 JSON데이터를 파싱해 줄 필요가 있기 때문에 HelloData 객체가 파싱된 데이터를 입력받았다.
이 때 중요한 것은 서버 요청시 클라이언트에서 어떤 content-type으로 보내는지 확인하는 것이 중요하다.
HttpServletResponse의 역할은 뭘까?
@WebServlet(name = "responseHeaderServlet",urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// [ 상태코드 ]
resp.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
// [ 응답 헤더 ]
resp.setHeader("Content-Type", "text.plain:charset=utf-8");
resp.setHeader("Cache-Control", "no-cache,no-store,must-revalidate");
resp.setHeader("Pragma", "no-cache");
resp.setHeader("my-header", "hello");
// [ 헤더 편의 메서드 ]
content(resp);
cookie(resp);
redirect(resp);
}
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");
response.addCookie(cookie);
}
private void redirect(HttpServletResponse response) throws IOException{
// 상태 코드 302
// Location : /basic/hello-form.html
response.sendRedirect("/basic/hello-form.html");
}
}
브라우져에서 http://localhost8080/response-header로 요청 후
브라우져 검사를 열어서 네트워크 탭에서 header를 확인해보면 된다.
HTTP 응답 메세지는 주로 다음 내용을 담아서 전달한다.
@WebServlet(name = "responsrHtmlServlet",urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Content-Type: text/html;charset=utf-8
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter write = resp.getWriter();
write.println("<html>");
write.println("<body>");
write.println(" <div>안녕?</div>");
write.println("</body>");
write.println("</html>");
}
}
클라이언트에서 http://localhost8080/response-html 요청을 보내면 서버에서는 응답데이터에 위와 같은 데이터를 내려준다. 브라우저는 응답 받은 데이터를 화면에 보여준다.
@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet{
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Content-Type : application/json
resp.setHeader("content-Type", "application/json");
resp.setCharacterEncoding("utf-8");
HelloData data = new HelloData();
data.setUsername("kim");
data.setAge(20);
// {"username":"kim","age":20}
String result = objectMapper.writeValueAsString(data);
resp.getWriter().write(result);
}
}
클라이언트에서 http://localhost:8080/response-json으로 요청하면 응답 헤더의 content-Type은 application/json 형식으로 CharacterEncoding은 utf-8로 그리고 메세지 바디로 받은 응답 데이터를 브라우져가 해석하여 화면에 보여준다.
클라이언트가 요청한 데이터는 HttpServletRequest 객체가 받아서 처리하는 것을 배울 수 있었고, 서버에서 HttpServletResponse 객체를 통해 브라우져에 데어터를 응답값으로 보내는 것을 배울 수 있었다. 여기서 중요한 점은 클라이언트와 서버에서 content-type을 일치시켜야한다는 것이다. 형식이 일치하지 않으면 의도한대로 데이터를 보내거나 받을 수 없기 때문이다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard