[Spring] 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 섹션2 서블릿
[Spring] 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 섹션2 서블릿
◼️ Hello 서블릿
@ServletComponentScan: 스프링 부트는 서블릿을 직접 등록해서 사용할 수 있도록 @ServletComponentScan 을 지원
@ServletComponentScan // 서블릿 자동 등록
@SpringBootApplication
public class MvcLecture1Application {
public static void main(String[] args) {
SpringApplication.run(MvcLecture1Application.class, args);
}
}
servlet http 요청이 오면 Servlet Container가 Request,Response 객체를 만들어 서블릿에 던져준다.서블릿을 등록해보자.
package hello.servlet.basic;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@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");
System.out.println("request = " + req);
System.out.println("resp = " + resp);
String username = req.getParameter("username");
System.out.println("username = " + username);
resp.setContentType("text/plain");
resp.setCharacterEncoding("utf-8"); // 헤더 정보
resp.getWriter().write("hello " + username);
}
}
🟢 HTTP 요청 메시지 로그로 확인
application.properties에 아래를 추가한다. (실무에서 debug기능을 켜두면 성능 저하가 올 수 있으니 개발 단계에만 적용)
logging.level.org.apache.coyote.http11=debug
🟢 서블릿 컨테이너 동작 방식 설명
- 웹 애플리케이션 서버는 HTTP 요청 메시지를 기반으로 request, response 객체를 만들고 helloServlet을 실행한다.
- helloServlet에서는 response 객체를 변경한다.
- 웹 애플리케이션 서버는 변경된 response 객체 정보로 HTTP 응답을 생성하여 웹 브라우저에 전송한다.
🟢 welcome 페이지 추가
webapp 경로에 index.html 을 두면 http://localhost:8080 호출시 index.html 페이지가 열린다.
◼️ HttpServletRequest - 개요
🟢 HttpServletRequest 역할
서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱한다. 그리고 그 결과를
HttpServletRequest 객체에 담아서 제공한다. HTTP 요청 메시지를 편리하게 사용하도록 도와주는 객체이다.
HTTP 요청 메시지 예시
POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded username=kim&age=20
임시 저장소 기능
- 해당 HTTP 요청이 시작부터 끝날 때 까지 유지되는 임시 저장소 기능
- 저장: request.setAttribute(name, value)
- 조회: request.getAttribute(name)
세션 관리 기능
- equest.getSession(create: true)
◼️ HttpServletRequest - 기본 사용법
RequestHeaderServlet
//http://localhost:8080/request-header?username=hello
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
printStartLine(request);
printHeaders(request);
printHeaderUtils(request);
printEtc(request);
response.getWriter().write("ok");
}
}
start-line 정보
//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.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();
}
헤더 정보
//Header 모든 정보
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();
}
Header 편리 조회
//Header 편리한 조회
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());
◼️ HTTP 요청 데이터 - 개요
🟢 클라이언트에서 서버로 데이터를 전달하는 방법
- Get-쿼리 파라미터
- /url?username=hello&age=20
- Post-HTML Form
- content-type: application/x-www-form-urlencoded
- 쿼리 파라미터 형식으로 전달
- HTTP message body에 데이터를 직접 담아 요청
- JSON, XML, TEXT
🟢 쿼리 파라미터 조회 메서드
String username = request.getParameter("username"); //단일 파라미터 조회
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회]
request.getParameter(username) = hello
request.getParameter(age) = 20
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello
🪄 username=hello&username=kim 같은 중복 이름 조회는 request.getParameterValues()를 사용해야 한다.
request.getParameter() 는 하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용해야 한다.
◼️ HTTP 요청 데이터 - POST HTML Form
application/x-www-form-urlencoded 형식은 앞서 GET에서 살펴본 쿼리 파라미터 형식과 같다. 따라서 위 쿼리 파라미터 조회 메서드를 그대로 사용하면 된다.
◼️ HTTP 요청 데이터 - API 메시지 바디 - 단순 텍스트
텍스트 메시지는 InputStream을 사용해서 직접 읽을 수 있다. inputStream은 byte코드를 변환한다.
@WebServlet(name = "requestBodyStringServlet", urlPatterns = "/request-bodystring")
public class RequestBodyStringServlet extends HttpServlet {
@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);
response.getWriter().write("ok");
}
}
◼️ 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}
@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-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");
}
}
messageBody={"username": "hello", "age": 20}
data.username=hello
data.age=20
🪄 스프링부트로 SpringMVC를 선택하면 JSON 결과를 파싱해서 자바 객체로 변환하는 Jackson 라이브러리(ObjectMapper)를 제공한다.
◼️ HttpServletResponse - 기본 사용법
🟢 HTTP 응답 메시지 생성
- HTTP 응답코드 지정
- 헤더, 바디 생성
- Content-Type, 쿠키, Redirect같은 편의 기능 제공
/**
* http://localhost:8080/response-header
*
*/
@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponseresponse)
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");
}
}
Header 편의 메서드 1 - 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); //(생략시 자동 생성)
}
Header 편의 메서드 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);
}
Header 편의 메서드 2 - 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 응답으로 HTML반환할 때는 text/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");
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 로 지정해야 한다.
objectMapper.writeValueAsString(): 객체 → JSON 문자
/**
* http://localhost:8080/response-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.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);
}
}