[스프링 MVC] Part2 (서블릿)
서블릿에 대한 구현은 WAS가 처리한다. (개발자는 비즈니스 로직에 집중하면 된다)
helloServlet ( request를 기반으로 서블릿 컨테이너가 request, response 객체가 생성 후 파라미터로 아래 메소드에 전달한다.)
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
//서블릿 메서드 호출시 서비스가 호출된다.
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("helloServlet.service");
// super.service(req, resp);
}
}
/hello 로 요청 수신 시, service() 메소드를 동작한다.
~~~~~~/hello?username=shyswy ->. username, shyswy라는 key, value 쌍을 파라미터로 전달한다.
스프링 부트가 내장 웹서버 톰캣을 띄운다.
톰켓은 내부에 서블릿 컨테이너를 가지고 있다. 이를 통해 서블릿 생성(HelloServlet 생성)
~~/hello를 통해 요청 시(helloServlet이 매핑되는 url) request, response객체가 생성되고 파라미터로 이 두 객체를 넘겨준다.
서블릿의 기능: 임시 저장소 기능
해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능 저장: request.setAttribute(name, value)
조회: request.getAttribute(name)
세션 관리 기능
request.getSession(create: true)
HttpServletRequest, HttpServletResponse를 사용할 때 가장 중요한 점은 이 객체들이 HTTP 요청 메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 점이다. 따라서 이 기능에 대해서 깊이있는 이해를 하려면 HTTP 스펙이 제공하는 요청, 응답 메시지 자체를 이해해야 한다.
이번엔 HttpServletRequest, HttpServletResponse 의 메소드에 대해 살펴보자
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();
}
결과
request에서 헤더 정보를 가져오는 법
예전 방식
private void printHeaders(HttpServletRequest request){
System.out.println("-----Headers -----start");
Enumeration<String> headerNames = request.getHeaderNames(); //헤더들을 받는다.
while (headerNames.hasMoreElements()){//1개씩 넘어가며 탐색
String headerName = headerNames.nextElement();
System.out.println(headerName);
}
System.out.println("-----Headers -----end");
}
새로운 방식
request.getHeaderNames().asIterator().forEachRemaining(headerName -> System.out.println(headerName));
결과
-----Headers -----start
host
connection
cache-control
sec-ch-ua
sec-ch-ua-mobile
sec-ch-ua-platform
upgrade-insecure-requests
user-agent
accept
sec-fetch-site
sec-fetch-mode
sec-fetch-user
sec-fetch-dest
accept-encoding
accept-language
-----Headers -----end
헤더 편의 조회 메서드
--- Header 편의 조회 start ---
[Host 편의 조회]
request.getServerName() = localhost
request.getServerPort() = 8080
[Accept-Language 편의 조회]
locale = ko_KR
locale = ko
locale = en_US
locale = en
request.getLocale() = ko_KR
[cookie 편의 조회]
[Content 편의 조회]
request.getContentType() = null
request.getContentLength() = -1
request.getCharacterEncoding() = UTF-8
--- Header 편의 조회 end ---
위와 같은 메소드들 존재 ( 쿠키, locale의 경우는 아래와 같이 구현함)
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());
}
}
쿠키: HTTP 헤더에 담긴다. 쿠키안에는 다양한 타입의 데이터가 존재한다.
request.getContentType() = null -> get 방식은 보통 contenttype을 세팅하지 않는다.( 보통 body에 넣을 때 세팅 )
기타 정보
--- 기타 조회 start ---
[Remote 정보]
request.getRemoteHost() = 0:0:0:0:0:0:0:1
request.getRemoteAddr() = 0:0:0:0:0:0:0:1
request.getRemotePort() = 61575
[Local 정보]
request.getLocalName() = localhost
request.getLocalAddr() = 0:0:0:0:0:0:0:1
request.getLocalPort() = 8080
--- 기타 조회 end ---
HTML Form 이란?
HTML Form 데이터는 사용자가 웹 페이지에서 입력한 정보를 웹 서버에 제출하는 방법 중 하나.
HTML Form은 사용자가 입력하는 데이터를 받기 위한 텍스트 상자, 선택 상자, 라디오 버튼, 체크 박스 등을 포함한다.
이러한 구성 요소들을 통해 사용자로부터 데이터를 수집하고, 이를 웹 서버에 전송한다.
GET - 쿼리 파라미터
/url?username=hello&age=20
메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
예) 검색, 필터, 페이징등에서 많이 사용하는 방식
POST - HTML Form
content-type: application/x-www-form-urlencoded
메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
HTML Form 데이터를 전송하는 것은 Post 방식으로만 가능
예) 회원 가입, 상품 주문, HTML Form 사용
HTTP message body에 데이터를 직접 담아서 요청
HTTP API에서 주로 사용, JSON, XML, TEXT
데이터 형식은 주로 JSON 사용
POST, PUT, PATCH
[Request 메세지 읽기]
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("[전체 파라미터 조회] - start");
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");
String age = request.getParameter("age");
System.out.println("username = " + username);
System.out.println("age = " + age);
System.out.println();
System.out.println("[이름이 같은 복수 파라미터 조회]"); //ex: ~~~~?username=hello1 & username=hello2
String[] usernames = request.getParameterValues("username"); //"username" 이라는 이름 가진 파라미터 값들을 조회
for (String name : usernames) {
System.out.println("username = " + name);
}
response.getWriter().write("ok");
}
결과
body의 내용을 바로 바이트 코드로 받고 string 으로 변환하기
@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(); // 메세지 body의 내용을 바이트 코드를 바로 받을 수 있다.
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 바이트를 문자로 바꿀때는 어떤 인코딩인지 알려줘야한다(UTF-8)
System.out.println("messageBody = " + messageBody);
response.getWriter().write("ok");
}
}
getParamter()은 항상 String 타입이기에, 다른 타입으로 받고 싶다면 파싱해줘야한다.
int age = Integer.parseInt(request.getParameter("age"));
//getParameter의 리턴은 항상 String. int로 파싱한다.
그럼 JSON으로는 어떻게 주고 받을까?
-> 보통 객체로 파싱해서 사용한다.
JSON 은 결국 key, value의 쌍.
key: username, value: shy
key: age, value: 20
-> objectMapper등을 통해 json -> 적절한 자바 객체로 바로 파싱 가능
(ObjectMapper: jackson, Gson 등 라이브러리 가져와야함 ).
@Getter @Setter
public class HelloData {
private String username;
private int age;
}
body의 JSON 데이터 읽기
@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
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);
System.out.println("helloData.username = " + helloData.getUsername());
System.out.println("helloData.age = " + helloData.getAge());
response.getWriter().write("ok");
}
}
json도 결국은 문자열이다. json 형식의 string으로 inputStream에서 받아온다.
messageBody = {"username": "hello", "age":20}
helloData.username = hello
helloData.age = 20
받아온 JSON 스트링을 ObjectMapper로 앞서 만든 helloData 객체로 파싱 해준다.
Controller 에서 파라미터를 받아 JSON 객체를 자동으로 엔티티로 파싱하는 것도 이와 같은 구조로 진행된다.
[HttpServletResponse]
다양한 메소드들을 활용해서 response http 메세지를 생성할 수 있다.
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//[status-line]
response.setStatus(HttpServletResponse.SC_OK); // ok message.
//[response-headers]
response.setHeader("Content-Type", "text/plain;charset=utf-8");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("my-header", "hello");
//[Header 편의 메서드]
// content(response);
// cookie(response);
// redirect(response);
PrintWriter writer = response.getWriter(); //response body에 작성
writer.println("ok");
}
@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
........................
private void content(HttpServletResponse response) { //response의 헤더 세팅 설정
//Content-Type: text/plain;charset=utf-8
//Content-Length: 2
//response.setHeader("Content-Type", "text/plain;charset=utf-8");
response.setContentType("text/plain"); //setHeader로 이름 지정 말고 따로 메소드 존재
response.setCharacterEncoding("utf-8");
//response.setContentLength(2); //(생략시 body의 길이만큼 자동 생성)
}
private void cookie(HttpServletResponse response) { //response 에 쿠키 추가
//Set-Cookie: myCookie=good; Max-Age=600;
//response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
//굳이 이렇게 할 필요없이 ,Cookie 라는 객체를 임포트 해서 사용하자.
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600); //600초
response.addCookie(cookie);
}
private void redirect(HttpServletResponse response) throws IOException {
//redirect. response에 ~~~/hello-form.html 로 리다이렉트할거라는 정보를 담는다.
//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]
단순 텍스트
PrintWriter writer = response.getWriter();
writer.println("ok");
html로 응답하기
@WebServlet(name = "responseHtmlServlet", urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//Content-Type: text/html;charset=utf-8
response.setContentType("text/html"); //html로 타입 세팅
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter(); //이제부터 html을 한줄한줄 입력
writer.println("<html>");
writer.println("<body>");
writer.println(" <div>안녕?</div>");
writer.println("</body>");
writer.println("</html>");
}
}
API JSON 으로 응답하기
@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);
}
}
JSON과 파싱으로 호환되는 자바 객체에 값을 넣고, objectMapper.writeValueAsString(자바 객체); 로 String으로 body에 담는다.
결국 JSON 도 문자열. 타입을 JSON 으로 지정하고 JSON 형식을 String으로 쏴주면 된다.
JSON 으로 데이터 주고 받는 방식 간단 요약
ServletInputStream inputStream = request.getInputStream();
// [JSON 읽기]
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
//inputStream으로 body에 적힌 내용의 바이트 코드를 받고, String으로 변환
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
//받은 String( JSON ) 을 적절한 객체로 파싱. 이후에 객체로 사용하면 된다.
// [JSON 쓰기]
String result = objectMapper.writeValueAsString(helloData);
//받는 쪽에서 요구하는 Response JSON 과 파싱되는 자바 객체를 String으로 변환
( {"username":"kim","age":20} 이런식의 JSON String으로 파싱됨)
response.getWriter().write(result); //해당 JSON 스트링을 body에 작성