[스프링] MVC 서블릿

gyeol·2023년 8월 17일

스프링

목록 보기
21/50
post-thumbnail

김영한 님의 '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술'을 듣고 적은 글입니다.

서블릿 실행을 위한 환경 설정

앞서 계속 사용했던 스프링 부트 스타터 사이트로 이동해 프로젝트를 생성한다. 이때 Packaging을 War로 설정해야 한다. Spring Web, Lombok을 추가해주자.

그리고 IntelliJ에서 프로젝트를 열어준다. build.gradle 파일을 열어보면 plugins 안에 id 'war' 를 확인할 수 있다.

Lombok 사용을 위해 Settings의 Annotation Processors를 검색한 후 Enable annotation processing을 체크한다.

스프링 부트 서블릿 환경 구성을 위해 ServletApplication.java 클래스 안에 @ServletComponentScan 애노테이션을 추가해준다.

@ServletComponentScan : 스프링 부트에서 서블릿을 쓸 때 사용한다. 스프링이 자동으로 패키지 내 하위 패키지를 뒤져 서블릿을 찾아 등록해서 사용할 수 있도록 해준다.

package hello.servlet;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class ServletApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServletApplication.class, args);
	}

}

서블릿 등록

서블릿을 등록해보자.

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 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 서블릿 애노테이션을 통해 URL 매핑을 시켜준다.
http://localhost:8080/hello?username=world 를 실행시키면 위의 코드가 실행된다.

스프링부트를 실행하면서 내장톰켓서버 띄워준다.
-> 톰켓 서버는 내부에 서블릿 컨테이너 가지고 있다. 서블릿 컨테이너 통해 서블릿 생성된다.
-> http 요청 메시지 보낸다.
-> 서버는 request, response 객체를 만들어 서블릿 호출하면서 객체를 넘겨준다.
-> 필요한 작업을 하고 종료되고 나가면서 was 서버가 response 정보를 가지고 http 응답 메시지 만들어 반환한다.

HttpServletRequest

HTTP 요청 메시지를 개발자가 직접 파싱해 사용해도 되지만 매우 불편할 것이다. 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 대신 HTTP 요청 메시지를 파싱해 HttpServletRequest 객체에 담아 제공한다.

POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded

username=kim&age=20

HttpServletRequest를 사용하면 위와 같은 HTTP 요청 메시지를 편리하게 조회할 수 있다.

  • START LINE : HTTP 메소드, URL, 쿼리 스트링, 스키마, 프로토콜
  • 헤더 : 헤더 조회
  • 바디 : form 파라미터 형식 조회, message body 데이터 직접 조회

부가기능

  • 임시 저장소 기능 : 해당 HTTP 요청이 시작부터 끝날 때까지 유지되는 임시 저장소 기능
    request.setAttribute(name, value), request.getAttribute(name)
  • 세션 관리 기능 : request.getSession(create: true)

HTTP 요청 데이터

HTTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법에는 주로 3가지 방법을 사용한다.

GET - 쿼리파라미터

/url?username=hello&age=20 과 같은 것을 쿼리 파라미터라고 한다. 쿼리파라미터는 URL에 ?로 시작해 보낼 수 있다. 추가 파라미터는 & 로 구분한다.
메시지 바디없이 URL의 쿼리 파라미터에 데이터를 포함해 전달한다.

ex) 검색, 필터, 페이징 등에서 많이 사용되는 방식

String username = request.getParameter("username"); //단일 파라미터 조회
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회

파라미터들을 모두 조회할 때 출력방식은 아래 코드를 참고하면 편리하다.

request.getParameterNames().asIterator()
	.forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName)));

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

/url?username=hello&age=20&username=hello2 와 같이 하나의 파라미터에 여러 값이 중복되어 들어갈 수도 있다. 이때 내부 우선순위에서 먼저 잡히는 애가 출력된다.

값이 하나일 때는 request.getParameter()를 사용하면 된다. 하지만 중복일 때는 request.getParameterValues()를 사용해야 한다. request.getParameter()를 사용하게 되면 request.getParameterValues()의 첫번째 값을 반환한다.

POST - HTML Form

content-type:application/x-www-form-urlencoded 형식으로 반환한다
메시지 바디에 쿼리 파라미터 형식으로 전달한다. (username=hello&age=20)

application/x-www-form-urlencoded 형식은 앞서 살펴본 쿼리 파라미터 형식과 동일하다. 따라서 쿼리 파라미터 조회 메서드를 그대로 사용하면 된다.
즉, request.getParameter()는 GET URL 쿼리 파라미터 형식도 지원하고, POST HTML Form 형식 둘 다 지원한다.

ex) 회원 가입, 상품 주문, HTML Form에 사용

참고

content-type은 HTTP 메시지 바디의 데이터 형식을 지정한다. GET URL 쿼리 파라미터 형식은 클라이언트에서 서버로 데이터를 전달할 땐 HTTP 메시지 바디를 사용하지 않고 쿼리 파라미터만을 사용하기에 content-type이 없다.
POST HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 content-type을 지정해야한다.

이렇게 간단한 테스트에 HTML form을 만들기 귀찮다면 Postman을 이용해 테스트해보자.

  • POST 전송시
    Body -> x-www-form-urlencoded 선택
    Headers에서 content-type : application/x-www-form-urlencoded로 지정된 부분 확인하기.

HTTP message body에 데이터 직접 담아 요청

HTTP API에서 주로 사용한다. 데이터 형식은 주로 JSON을 사용한다.

단순 텍스트

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

ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

바이트코드를 스트링으로 바꿀 때 StreamUtils 를 사용하면 된다. (스프링이 제공) 꺼낼 때 인코딩 정보를 알려줘야한다. 바이트를 문자로 바꿀때나 문자를 바이트로 바꿀 때 모두 !

JSON

JSON의 형태는 {"username": "hello", "age": 20} 이렇게 생겼다.

먼저 HelloData.java라는 클래스를 만들어주고 롬북이 제공하는 기능을 사용해 접근자 설정자를 만들어준다.

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

그리고 클래스를 새로 하나 더 만들어 준다.

package hello.servlet.basic.request;

import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.HelloData;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;


/**
 * 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.getUsername() = " + helloData.getUsername());
        System.out.println("helloData.getAge() = " + helloData.getAge());

        response.getWriter().write("ok");
    }
}

JSON 결과를 파싱해 사용할 수 있는 자바 객체로 변환하려면 라이브러리를 추가해 사용해야한다. 스프링 부트로 Spring MVC를 선택하면 기본적으로 Jackson 라이브러리(ObjectMapper)를 제공한다.

HTML Form 데이터도 메시지 바디를 통해 전송되므로 직접 읽을 수 있다. 하지만 request.getParameter(...)을 이미 제공하기에 파라미터 조회 기능을 사용하면 된다.

HttpServletResponse

HttpServletResponse 역할

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

HTTP 응답 데이터

HTTP 응답 메시지는 단순 텍스트 응답, HTML 응답, HTML API - MessageBody JSON 응답 내용을 담아 전달한다.

단순 텍스트, HTML

package hello.servlet.basic.response;

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;
import java.io.PrintWriter;
import java.io.Writer;

@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 응답으로 HTML을 반환할 때는 content-type을 text/html로 지정해야 한다.

API JSON

package hello.servlet.basic.response;

import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.HelloData;
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 = "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"} json형식으로 바꿈
        String result = objectMapper.writeValueAsString(helloData);
        response.getWriter().write(result);
    }
}

HTTP 응답으로 JSON을 반환할 때는 content-type을 application/json으로 지정해야 한다. Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString()을 사용하면 객체를 JSON 문자로 변경할 수 있다.

profile
공부 기록 공간 '◡'

0개의 댓글