LostCatBox

SpringMVC-CH02

Word count: 2.4kReading time: 14 min
2022/12/24 Share

스프링 MVC 1편 CH02

Created Time: June 24, 2022 4:24 PM
Last Edited Time: June 28, 2022 3:27 PM
References: https://mangkyu.tistory.com/14

단축키 정리(인텔리J)

1
2
3
4
5
6
7
8
9
10
command+shift + T // 테스트 케이스 만들기
command +shift+enter //자동완성
command +D // 선택영역 복사
command +B // 선택함수가 어디에서 호출되는지 볼수있음
command + option +V // 변수로 바로 생성
command + E //최근에 봣던 목록
command + option + m //extract method
command + option + n //inline으로 바꿔줌
command + option + b //해당 대상 구현체 조회가능
control + T >>method // 선택된 블록 함수로 extracted

서블릿

  • 서블릿은 클라이언트의 요청을 처리하고, 그결과를 반환하는 Servlet클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술!

스크린샷 2022-06-24 오후 4.29.13.png

실습 준비

  • start.spring 에서 gradle, 2.7.1, hello, servlet, servlet,hello.servlet이며, packaging: war, java:11
  • Spring web, lombok을 Dependencies로 선택한다
  • Lombok을 사용하기위해, IJ 에서 preference에서 plugin 에서 lombok설치 및 annotation processors를 enable로 바꿔줌

Hello 서블릿

서블릿은 톰캣 같은 웹 애플리케이션 서버를 직접설치하고, 그 위에 서블릿 코드를 클래스 파일로 빌드해서 올리고, 서버실행해야한다. 하지만 번거롭기에 스프링 부트의 톰캣서버 내장됨에 따라, 편리하게 서블릿 코드 실행해보겠다

스프링 부트 서블릿 환경 구성

  • @ServletComponentScan 서블릿 자동 등록으로, main()함수 들어있는 파일에 에너테이션 추가

서블릿 등록하기

  • extends HttpServlet이 필요하며, @WebServlet에너테이션필요함,
  • @WebServlet 서블릿 애노테이션 >> name: 서블릿 이름 urlPatterns: URL 매핑
  • HttpServlet의 자손이므로 service()함수를 오버라이딩하여 구현문 작성하면 원하는대로동작가능
  • 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
}
}

Http 요청 메시지 로그 확인 (꿀팁)

1
logging.level.org.apache.coyote.http11=debug
  • 요청시 해당 메타정보및 요청방식 모두 출력됨 편함
  • 하지만 운영서버에 모든 요청 정보를 담기면 성능저하가 발생함. 개발단계에서만 적용하자

서블릿 컨테이너 동작 방식 설명

구성

스크린샷 2022-06-24 오후 5.26.50.png

웹 애플리케이션 서버의 요청 응답 구조

스크린샷 2022-06-24 오후 5.27.49.png

welcome 페이지 추가

  • main/webapp/index.html 생성시 welcome페이지 역할
1
2
3
4
5
6
7
8
9
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body> <ul>
<li><a href="basic.html">서블릿 basic</a></li> </ul>
</body>
</html>
  • main/webapp/basic.html 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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>
<li><a href="/basic/hello-form.html">POST - HTML Form</a></
<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>

HttpServletRequest

HttpServletRequest 역할

  • HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만, 매우 불편할 것이다. 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다. 그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다.

    스크린샷 2022-06-24 오후 5.37.20.png

  • 추가 부가기능도있다

임시 저장소 기능(!!!)

  • 해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능
    • 저장: request.setAttribute(name, value)
    • 조회: request.getAttribute(name)

세션 관리 기능

  • request.getSession(create: true)

HttpServletRequest, HttpServletResponse를 사용할 때 가장 중요한 점은 이 객체들이 HTTP 요청 메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점이다. 따라서 이 기능에 대해서 깊이있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다.

HttpServletRequest - 기본 사용법

start-line 정보

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
}

헤더 정보

  • 이전스타일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
  • 유틸별 조회
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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();
}
  • 간단조회(원하는 이름으로검색)
1
System.out.println(request.getHeader("host"));

기타 정보

기타 정보는 HTTP 메시지의 정보는 아니다.(connection정보임)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 요청 데이터 - 개요

  • 주로 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

Http요청 데이터 - GET쿼리 파라미터

  • 쿼리 파라미터 조회 메서드
1
2
3
4
String username = request.getParameter("username"); //단일 파라미터 조회 
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map 으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회
  • 복수 파라미터에서 단일 파라미터 조회 참조
    • username=hello&username=kim 과 같이 파라미터 이름은 하나인데, 값이 중복이면 어떻게 될까? request.getParameter() 는 하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용해야 한다. 지금처럼 중복일 때는 request.getParameterValues() 를 사용해야 한다. 참고로 이렇게 중복일 때 request.getParameter() 를 사용하면request.getParameterValues() 의 첫 번째 값을 반환한다.

HTTP 요청 데이터 - POST HTML Form

  • 특징
  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 데이터를 전달한다. username=hello&age=20
  • 파일
1
2
3
4
5
6
7
8
9
10
11
12
<!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>
  • application/x-www-form-urlencoded 형식은 앞서 GET에서 살펴본 쿼리 파라미터 형식과 같다. 따라서 쿼리 파라미터 조회 메서드를 그대로 사용하면 된다.
    클라이언트(웹 브라우저) 입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 둘의 형식이 동일하므로, request.getParameter() 로 편리하게 구분없이 조회할 수 있다.

content-type은 HTTP 메시지 바디의 데이터 형식을 지정한다.
GET URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없다.
POST HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 content-type을 꼭 지정해야 한다. 이렇게 폼으로 데이터를 전송하는 형식을 application/x-www-form-urlencoded 라 한다.

Http 요청 데이터 -API 메시지 바디 - 단순텍스트

  • HTTP message body에 데이터를 직접 담아서 요청
    • HTTP API에서 주로 사용, JSON, XML, TEXT
    • 데이터 형식은 주로 JSON 사용
    • POST, PUT, PATCH
  • 먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.
  • HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있다.
1
2
3
4
5
6
7
8
9
10
11
@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();// http body의 내용을 바이트코드로 바로얻음> 스트링 변환필요
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);

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

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}
  • HelloData 라는 데이터 정의 ( lombok활용한 방법)

1
2
3
4
5
6
7
8
9
10
11
package hello.servlet.basic;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class HelloData {

private String username;
private int age;
}
  • Servlet의 request에서 바이너리> 스트링> JSON까지 변환 하는코드
    • HelloData의 객체에 맞게 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@WebServlet(name="requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequeestBodyJsonServlet 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");

}
}

JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환 라이브러리를 추가해서 사용해야 한다. 스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson 라이브러리( ObjectMapper )를 함께 제공한다.

HTML form 데이터도 메시지 바디를 통해 전송되므로 직접 읽을 수 있다. 하지만 편리한 파리미터 조회 기능( request.getParameter(…) )을 이미 제공하기 때문에 파라미터 조회 기능을 사용하면 된다.(messageBody까지 추출하는과정==직접읽는과정)

HttpServletResponse - 기본 사용법

HttpServletResponse 역할

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

직접 입력

1
2
3
4
5
6
7
//[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","nocache");
response.setHeader("my-header", "hello");

메서드활용한 content

1
2
3
4
5
6
7
8
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); //(생략시 자동 생성)
}
  • set cookie로 응답하면 다음 요청은 cookie를 포함한 헤더가 있음
1
2
3
4
5
6
7
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); // 객체에 해당정보 추가함으로써 위와 똑같효과를 보여줌
}

메서드 활용한 redirect

  • 302코드와 redirect를 수행할 링크 담는 location값 필요
1
2
3
4
5
6
7
private void redirect(HttpServletResponse response) throws IOException {
//Status Code 302
//Location: /basic/hello-form.html
// response.setStatus(HttpServletResponse.SC_FOUND); //302 = 302코드로 리다이랙스 location으로하게응답
// response.setHeader("Location", "/basic/hello-form.html"); //location지정
response.sendRedirect("/basic/hello-form.html"); //위에 두줄 통합
}

HTTP 응답 데이터 - 단순 텍스트, HTML

  • HTTP 응답 메시지는 주로 다음 내용을 담아서 전달한다.
    • 단순 텍스트 응답
      • 앞에서 살펴봄 ( writer.println(“ok”); )
    • HTML 응답
    • HTTP API - MessageBody JSON 응답

HTML 응답

1
2
3
4
5
6
7
8
9
10
11
12
13
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 응답 데이터 - API JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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);

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

}
}
  • 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()으로 출력하면 그런 문제가 없다.

CATALOG
  1. 1. 스프링 MVC 1편 CH02
  2. 2. 단축키 정리(인텔리J)
  3. 3. 서블릿
  4. 4. 실습 준비
  5. 5. Hello 서블릿
    1. 5.1. 스프링 부트 서블릿 환경 구성
      1. 5.1.1. 서블릿 등록하기
    2. 5.2. Http 요청 메시지 로그 확인 (꿀팁)
    3. 5.3. 서블릿 컨테이너 동작 방식 설명
      1. 5.3.1. 구성
      2. 5.3.2. 웹 애플리케이션 서버의 요청 응답 구조
    4. 5.4. welcome 페이지 추가
  6. 6. HttpServletRequest
    1. 6.1. HttpServletRequest 역할
      1. 6.1.1. 임시 저장소 기능(!!!)
      2. 6.1.2. 세션 관리 기능
    2. 6.2. HttpServletRequest - 기본 사용법
      1. 6.2.1. start-line 정보
      2. 6.2.2. 헤더 정보
      3. 6.2.3. 기타 정보
  7. 7. HTTP 요청 데이터 - 개요
    1. 7.1. GET - 쿼리 파라미터
    2. 7.2. POST - HTML Form
    3. 7.3. HTTP message body에 데이터를 직접 담아서 요청
    4. 7.4. Http요청 데이터 - GET쿼리 파라미터
    5. 7.5. HTTP 요청 데이터 - POST HTML Form
    6. 7.6. Http 요청 데이터 -API 메시지 바디 - 단순텍스트
    7. 7.7. HTTP 요청 데이터 - API 메시지 바디 - JSON
  8. 8. HttpServletResponse - 기본 사용법
    1. 8.1. HttpServletResponse 역할
    2. 8.2. 직접 입력
    3. 8.3. 메서드활용한 content
    4. 8.4. 메서드 활용한 cookie
    5. 8.5. 메서드 활용한 redirect
  9. 9. HTTP 응답 데이터 - 단순 텍스트, HTML
    1. 9.1. HTML 응답
    2. 9.2. HTTP 응답 데이터 - API JSON