MVC1 2nd Step

최보현·2022년 7월 22일
0

MVC

목록 보기
2/18
post-thumbnail

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - sec02
출처 : 스프링 MVC 1편

서블릿 만들어보기

JSP를 사용하기 위해서 War을 사용함 - War은 주로 톰캣 서버를 따로 설치하고 빌드할 때 쓰인다고 함

package hello.servlet.basic;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@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);
	}
}

@WebServlet 서블릿 애노테이션

  • name: 서블릿 이름
  • urlPatterns: URL 매핑

HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 다음 메서드를 실행한다.
protected void service(HttpServletRequest request, HttpServletResponse response)

🚨중요🚨
서블릿 이름과 url을 겹치면 안됨

HTTP 요청 메시지 로그로 확인하는 법

application.properties에 logging.level.org.apache.coyote.http11=debug 를 추가하기
=> 운영 서버에서 모든 요청 정보를 다 남기면 성능 저하가 발생함 (개발 단계에서만 적용 권장)

서블릿 컨테이너 동작 방식


서블릿 http 요청이 오면 서블릿 컨테이너가 response 객체를 만들어서 서블릿에 던져줌
eX) /hello가 오면 웹브라우저가 http요청을 만들어서 던져줌 그에 대한 response 응답을 서버에서 찍어줌 => url의 ?username=kim 같은 것들(파라미터, parameter)을 서블릿에서는 reuqest.getParameter를 통해 꺼내옴 => 응답은 response에 값을 넣어서 응답 메세지에 데이터가 담겨서 나가도록 해줌 => response.getwrite()는 메세지 바디 내용을 작성하게 되고 setContentType이나 encoding은 헤더 정보로 들어가게 됨

+) HTTP 응답에서 Content-Length같은 부가적인 정보는 웹 애플리케이션 서버가 자동으로 생성해줌

HttpServletRequest

역할

개발자가 HTTP요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱함 -> 그 결과를 HttpServletRequest 객체에 담아서 제공
임시 저장소 기능
해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능
저장: request.setAttribute(name, value)
조회: request.getAttribute(name)
세션 관리 기능 request.getSession(create: true)

사용법

start-line 정보 조회

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사용 유무

헤더 정보 조회

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

헤더 편리한 조회

private void printHeaderUtils(HttpServletRequest request) {
  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());
}

request.getparameter같은 기능으로 되게 편리하게 읽을 수 있도록 지원해주고 json도 다 받아줌!
json 파싱 라이브러리는 따로 또 있음! => ex) jackson

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

쿼리 파라미터 조회 메서드

String username = request.getParameter("username"); //단일 파라미터 조회
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들
모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map
으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회
/**
* 1. 파라미터 전송 기능
* http://localhost:8080/request-param?username=hello&age=20
* <p>
* 2. 동일한 파라미터 전송 가능
* http://localhost:8080/request-param?username=hello&username=kim&age=20
*/
@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse
  resp) throws ServletException, IOException { System.out.println("[전체 파라미터 조회] - start");
  /*
  Enumeration<String> parameterNames = request.getParameterNames();
  while (parameterNames.hasMoreElements()) {
  String paramName = parameterNames.nextElement();
  System.out.println(paramName + "=" +
  request.getParameter(paramName));
  }
  */
  request.getParameterNames().asIterator()
  	.forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName)));
  System.out.println("[전체 파라미터 조회] - end");
  System.out.println();
  System.out.println("[단일 파라미터 조회]");
  String username = request.getParameter("username");
  System.out.println("request.getParameter(username) = " + username);
  String age = request.getParameter("age");
  System.out.println("request.getParameter(age) = " + age);
  System.out.println();
  System.out.println("[이름이 같은 복수 파라미터 조회]");
  System.out.println("request.getParameterValues(username)");
  String[] usernames = request.getParameterValues("username");
  for (String name : usernames) {
  System.out.println("username=" + name);
  	}
  resp.getWriter().write("ok");
  }
}

복수 파라미터에서 단일 파라미터 조회

username=hello&username=kim 과 같이 파라미터 이름은 하나인데, 값이 중복이면?
request.getParameter() 는 하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용해야 함! 중복일 때는 request.getParameterValues() 를 사용하면 됨!
중복일 때 request.getParameter() 를 사용하면 request.getParameterValues() 의 첫 번째 값을 반환함!

POST HTML Form

  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 데이터를 전달 username=hello&age=20
    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 형식은 앞서 나온 쿼리 파라미터 형식과 동일 => 쿼리 파라미터 조회 메서드를 그대로 사용
클라이언트(웹 브라우저) 입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 둘의 형식이 동일하므로, request.getParameter() 로 편리하게 구분없이 조회 가능!

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

API 메시지 바디 - 단순 텍스트

HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있음

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

API 메시지 바디 - JSON

JSON변환 라이브러리가 Spring MVC에 기본적으로 Jackson이 들어가 있어서 괜춘
json은 보통 데이터 그대로 쓰지 않고 객체로 변환해서 사용함

JSON 형식 파싱 추가

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

JSON 형식 전송

/**
* http://localhost:8080/request-body-json
*
* JSON 형식 전송
* content-type: application/json
* message body: {"username": "hello", "age": 20}
*
*/
@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-bodyjson")
public class RequestBodyJsonServlet 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);
  //messageBody(문자)를 HelloData객체로 싹 바꿔줌
  System.out.println("helloData.username = " + helloData.getUsername());
  System.out.println("helloData.age = " + helloData.getAge());
  response.getWriter().write("ok");
	}
}

HttpServletResponse

역할

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

기본 사용법

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

Content 편의 메서드

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

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

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

HTTP 응답 메시지 포함 내용

  • 단순 텍스트 응답 ex) writer.println("ok");
  • HTML 응답
  • HTTP API - MessageBody JSON 응답

HttpServletResponse - HTML 응답

HTTP응답으로 HTML을 변환하고자 할 때는 content-type을 text/html으로 지정해야 함!

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

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

//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);
profile
Novice Developer's Blog

0개의 댓글