스프링 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
서블릿 애노테이션
HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 다음 메서드를 실행한다.
protected void service(HttpServletRequest request, HttpServletResponse response)
🚨중요🚨
서블릿 이름과 url을 겹치면 안됨
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같은 부가적인 정보는 웹 애플리케이션 서버가 자동으로 생성해줌
개발자가 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 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달 하는 방법
주로 다음 3가지 방법을 사용한다.
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()
의 첫 번째 값을 반환함!
application/x-www-form-urlencoded
username=hello&age=20
요청 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
라 함
HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있음
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);
JSON변환 라이브러리가 Spring MVC에 기본적으로 Jackson이 들어가 있어서 괜춘
json은 보통 데이터 그대로 쓰지 않고 객체로 변환해서 사용함
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class HelloData {
private String username;
private int age;
}
/**
* 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");
}
}
//[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");
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 응답으로 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);