김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 보고 공부한 내용을 정리한 것입니다!
dependencies를 web과 lombok만 추가하여 프로젝트를 만든 후, 메인 클래스에 아래와 같은 어노테이션을 추가해준다.
@ServletComponentScan
@ServletComponentScan은 해당 클래스의 패키지를 포함한 하위 패키지의 모든 서블릿을 찾아서 서블릿으로 등록해준다.
(여담이지만 @ComponentScan은 해당 클래스의 패키지를 포함한 하위 퍀지의 모든 Bean들을 찾아서 Context에 bean등록을 해주는 Annotation이당..ㅎㅎ 복습복습)
하위에 basic이란 패키지 만들고 HelloServlet이라는 클래스를 만든다.
@WebServlet(name = "helloservlet",urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
}
HttpServlet을 상속받고 @WebServlet(name = "helloservlet",urlPatterns = "/hello")이라는 어노테이션을 추가하면 서블릿 생성이 끝난것이다.
name은 서블릿의 우리가 정한 서블릿의 임의의 이름이고, /hello라는 요청이오면 이 서블릿이 실행되도록 한 것이다.
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
내부에 이 service매서드를 추가하면 서블릿이 호출되면 이 매서드가 호출되도록 한 것이다.
HttpServletRequest req은 클라이언트가 요청한 객체이고 HttpServletResponse resp은 응답할 객체이다.
http://localhost:8080/hello?username=yoon 이라는 주소가 있을 때, ?username=yoon은 쿼리 파라미터이다.
서블릿은 쿼리파라미터를 편리하게 사용할 수 있다는 특징이 있다.
service매서드에 String username = req.getParameter("username");라고 입력한 후, 위의 주소를 치면 username에 yoon이 담긴다.
이번엔 응답을 보내보자.
resp.setContentType("text/plain");//text타입의 응답을 보낸다
resp.setCharacterEncoding("utf-8");//utf-8로 인코딩해서
resp.getWriter().write("hello "+username);//응답 보낼 값
이렇게 응답을 보낸 것을 확인할 수 있다.
개발할 때 요청을 더 자세히 보고싶을 때-> application.properties에
logging.level.org.apache.coyote.http11=debug 추가
HttpServletRequest 역할
HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만, 매우 불편할 것이다.
서블릿은 개발자가HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다.
그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다.
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 데이터 직접 조회
HttpServletRequest 객체는 추가로 여러가지 부가기능도 함께 제공한다.
해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능
저장: request.setAttribute(name, value)
조회: request.getAttribute(name)
로그인 유지를 위해서 사용한다.
request.getSession(create: true)
HttpServletRequest, HttpServletResponse를 사용할 때 가장 중요한 점은 이 객체들이 HTTP 요청메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점이다.
따라서 이 기능에 대해서깊이있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다.
RequestHeaderServlet라는 서블릿을 만든다.
start line과 관련된 값을 아래와 같은 매서드를 사용하여 알 수 있다.
private void printStartLine(HttpServletRequest request) {
System.out.println("--- REQUEST-LINE - start ---");
System.out.println("request.getMethod() = " + request.getMethod()); //GET
System.out.println("request.getProtocal() = " + 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-test
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();
}
header에 관한 값을 아래와 같은 매서드로 알 수 있다.
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();
}
HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법 중 GET- 쿼리 파라미터 방법에 대해서 알아보자
주로 검색, 필터, 페이징등에서 많이 사용하는 방식이다.
메시지 바디 없이, URL의 쿼리 파라미터를 사용해서 데이터를 전달할 수 있다.
쿼리 파라미터는 URL에 다음과 같이 ? 를 시작으로 보낼 수 있다.
추가 파라미터는 & 로 구분하면 된다.
ex) http://localhost:8080/request-param?username=hello&age=20
String username = request.getParameter("username"); //단일 파라미터 조회 username이라는 파라미터를 조회
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회 username파라미터의 값들이 여러개일 때
username=hello&username=kim 과 같이 파라미터 이름은 하나인데, 값이 중복이면 어떻게 될까?
request.getParameter() 는 하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용해야 한다.
지금처럼 중복일 때는 request.getParameterValues() 를 사용해야 한다.
참고로 이렇게 중복일 때 request.getParameter() 를 사용하면 request.getParameterValues() 의 첫 번째 값만 반환한다.
HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법 중 POST HTML Form 방법에 대해서 알아보자
주로 회원 가입, 상품 주문 등에서 사용하는 방식이다.
메시지 바디에 쿼리 파리미터 형식(username=hello&age=20)으로 전달하므로 content-type(application/x-www-form-urlencoded)을 가지고 있다.
즉, 메시지 바디가 쿼리 파리미터 형식(username=hello&age=20)인 것은 content-type이 application/x-www-form-urlencoded이기 때문이다.
쿼리 파라미터 조회 메서드는 POST HTML Form방식에서도 사용가능하다.
HTTP API에서 주로 사용하며, data는 JSON, XML, TEXT 이 있다.
데이터 형식은 주로 JSON 사용한다.(POST, PUT, PATCH)
먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.
HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있다.
ServletInputStream inputStream = request.getInputStream();
getInputStream이란 메서드를 이용하면 메시지 바디의 내용을 바이트코드로 받을 수 있다.
String messageBody = StreamUtils.copyToString(inputStream,
StandardCharsets.UTF_8);
사용할 수 있도록 String type으로 바꿔준다.
이 때, 어떤 인코딩(여기선 StandardCharsets.UTF_8)인지도 함께 입력해줘야한다.
이번에는 HTTP API에서 주로 사용하는 JSON 형식으로 데이터를 전달해보자.
JSON 형식으로 파싱할 수 있게 객체를 하나 생성해야한다.
@Getter @Setter
public class HelloData {
private String username;
private int age;
}
postman을 이용하여 json메시지를 보낸다.
JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환 라이브러리를 추가해서 사용해야 한다.
스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson 라이브러리( ObjectMapper )를 함께 제공한다.
private 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.username = " + helloData.getUsername());
System.out.println("helloData.age = " + helloData.getAge());
}
HttpServletResponse는 HTTP 응답 메시지 생성하는 일을 한다.
HTTP 응답코드 지정
헤더 생성
바디 생성
편의 기능 제공
Content-Type, 쿠키, Redirect
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");
}
HTTP 응답 메시지는 단순 텍스트 응답(앞에서 살펴봄 writer.println("ok"); ), HTML 응답, HTTP API - MessageBody JSON 응답이 있다.
단순 텍스트 응답 외의 2가지를 알아보자.
@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>");
}
writer에 html code를 넣어 응답할 수 있다.
java code이므로 동적으로 변경할 수도 있다는 장점이 있다.
HTTP 응답으로 HTML을 반환할 때는 content-type을 text/html 로 지정해야 한다.
private 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);
//{"username":"kim","age":20}
String result = objectMapper.writeValueAsString(data);
response.getWriter().write(result);
}
HTTP 응답으로 JSON을 반환할 때는 content-type을 application/json 로 지정해야 한다.
Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString()를 사용하면 객체를 JSON 문자로 변경할 수 있다.