Spring

[Spring] 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 섹션2 서블릿

고쩡이 2024. 4. 12. 20:11

본 내용은 인프런 김영한T 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의를 정리한 내용입니다:)

[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

 

🟢 서블릿 컨테이너 동작 방식 설명

내장 톰캣 서버 생성 /  웹 애플리케이션 서버 요청 응답 구조

  1. 웹 애플리케이션 서버는 HTTP 요청 메시지를 기반으로 request, response 객체를 만들고 helloServlet을 실행한다.
  2. helloServlet에서는 response 객체를 변경한다.
  3. 웹 애플리케이션 서버는 변경된 response 객체 정보로 HTTP 응답을 생성하여 웹 브라우저에 전송한다.

🟢 welcome 페이지 추가

webapp 폴더 > index.html

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