Spring MVC

Spring MVC없이 Servlet, JSP로 MVC 구현해보기 - 1

cwchoiit 2024. 4. 25. 15:59
728x90
반응형
SMALL

참고자료:

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 | 김영한 - 인프런

김영한 | 웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습

www.inflearn.com

 

Spring MVC를 배우기 전에 어디서부터 시작되어 Spring MVC까지 도착했는지를 공부하고 싶어졌다. Spring MVC가 어떤 것을 나 대신해주고 어떤 것 때문에 사용하는지 좀 더 자세히 이해하기 위해 Servlet부터 시작해 보고자 한다. 

 

프로젝트 만들기

우선 프로젝트를 만들자. 만들 때 따로 톰캣을 설치하고 띄운 상태가 아니라면 스프링 부트의 도움을 받아서 바로 WAS 서버를 실행할 수 있게 스프링 프레임워크를 사용할거다. 서블릿은 스프링이고 스프링부트고 아무것도 없어도 되는데 딱 이 이유때문에 스프링 프레임워크를 사용할 거다.

 

IntelliJ > New Project > Spring Boot

 

중요: Packaging을 Jar말고 War로 선택한다. JSP를 사용해야 하기 때문이다. Jar와 War의 차이는 서버가 따로 있고 그 서버에 따로 톰캣을 설치하고 그 톰캣 위에 배포하는 경우 War를 사용하는데 꼭 그렇게 안하더라도 스프링 부트를 사용하면 War에 WAS서버가 내장되어 있다. 근데 JSP를 사용하기 위해선 War를 사용해야 한다.

 

Dependencies는 저 두개만 있으면 된다. Spring Web이 필요한 이유는 Apache Tomcat을 내장시키려면 저게 필요하기 때문.

 

우선 먼저 실행해봐야 한다. 잘 실행된다면 다음과 같이 로그가 찍힌다.

톰캣이 실행됐다는 로그가 찍힌다. 스프링 부트가 내장하고 있는 WAS가 띄워지는 것이다.

다시 말하지만 이거 때문에 서블릿으로 만들건데도 스프링 부트를 사용했다. 딱 이 이유뿐이다. 서블릿은 스프링과 아무런 관련이 없다. 서블릿은 톰캣 같은 웹 애플리케이션 서버를 직접 본인의 서버위에 설치하고 그 위에 서블릿 코드를 클래스 파일로 빌드해서 올린 다음 톰캣 서버를 실행하면 된다. 근데 이 과정이 번거롭기 때문에 톰캣 서버가 내장되어 있는 스프링 부트를 사용한다.

 

스프링에서 서블릿을 사용하려면 @ServletComponentScan 애노테이션을 붙여야 한다. 애노테이션을 붙이면 저 패키지 org.example.servlet 하위에 있는 모든 패키지에서 서블릿을 다 뒤지고 찾아서 자동으로 등록해준다.

 

이제 간단한 Servlet을 하나 만들어보자.

HelloServlet

package org.example.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 {
        super.service(req, resp);
    }
}

패키지는 org.example.servlet.basic이다.

Servlet을 만드려면 HttpServlet을 상속받아야 한다.

 

상속 받으면 service(HttpServletRequest req, HttpServletResponse resp) 메서드를 오버라이딩 할 수 있다. 이 메서드가 /hello로 요청했을 때 실행되는 메서드이다.

 

@WebServlet(name = "helloServlet", urlPatterns = "/hello") 라는 애노테이션을 붙여서 이 서블릿의 이름과 어떤 path로 이동하면 이 서블릿을 가져다가 사용할 건지에 대한 urlPatterns를 명명해준다.

 

다음처럼 클래스와 메서드명을 찍어보자.

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("HelloServlet.service");
}

 

이 상태로 다시 서버를 띄우고 "localhost:8080/hello"로 접속해보면 다음과 같이 찍힌다.

 

이제 서버로 Path가 "/hello" 요청이 들어오면 이 서블릿이 요청을 받아 개발자가 작성한 코드를 실행해준다.

 

이제 요청 정보에서 원하는 정보들을 알아오기 위해 다음 코드를 보자.

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("HelloServlet.service");

    String username = req.getParameter("username");
    System.out.println("username = " + username);

}

요청 시 파라미터가 있을 때 그 파라미터를 getParameter()로 가져올 수 있다. 

이 상태로 실행하고 다음과 같이 입력해보자.

http://localhost:8080/hello?username=hi

그럼 이렇게 찍히게 된다.

HelloServlet.service
username = hi

 

그래서 요청 데이터에서 원하는 값을 저 req 객체로부터 가져올 수 있다.

그리고 응답도 서블릿이 대신 다 해준다고 했다. 내가 원하는 응답 데이터만 서블릿한테 말해주면 된다.

아래 코드를 보자.

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("HelloServlet.service");

    String username = req.getParameter("username");
    System.out.println("username = " + username);

    resp.setContentType("text/plain");
    resp.setCharacterEncoding("UTF-8");
    resp.getWriter().println("Hello " + username);
}

 

응답 데이터에 Content-Type, Encoding, Body값을 넣어준다. getWriter().println()을 사용하면 Response Body에 데이터를 넣어줄 수 있다. 이 상태로 다시 요청을 해보면 다음과 같이 보인다.

 

이렇게 서블릿을 이용해서 HTTP 프로토콜을 통해 요청과 응답을 할 수 있게 됐다.

정리를 하자면

스프링 부트를 이용해서 프로젝트를 만들었다. 스프링 부트를 사용한 이유는 스프링 부트에 톰캣과 같은 WAS가 내장되어 있기 때문이다. 그래서 서블릿을 만들고 서블릿을 스캔하도록 설정하면 스프링 부트가 띄워질 때 서블릿 컨테이너에 만든 서블릿을 다 넣어둔다. 그리고 외부에서 이 서버로부터 요청이 들어올 때 요청을 처리하는 서블릿을 찾아 적절한 응답을 보내줬다.

 

HttpServletRequest

서블릿에서 요청 정보를 가져오려면 파라미터로 들어오는 HttpServletRequest 객체로부터 가능하다. 이 녀석은 말 그대로 HTTP 요청에 대한 모든 정보를 다 가지고 있어서 원하는 정보를 내가 쏙쏙 뽑아올 수 있다.

 

예를 들면 다음 코드를 보자.

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("req.getMethod() = " + req.getMethod());
}

 

보면 요청정보에 Method를 가져온다. GET, POST 등 요청이 어떤 Method인지 가져올 수 있단 소리고 이 말은 GET, POST, PUT, DELETE 어떤 Method라도 이 서블릿이 다 처리할 수 있다는 뜻이다. 포스트맨으로 POST로 날려보자.

실행결과:

req.getMethod() = POST

 

이번엔 GET으로 날려보자.

실행결과:

req.getMethod() = GET

 

어떤 Method라도 이 서블릿이 만능으로 다 받아줄 수 있다. 요청정보에 대한 Headers 정보도 가져올 수 있다.

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    req
        .getHeaderNames()
        .asIterator()
        .forEachRemaining(header -> System.out.println("header = " + header));
}

실행결과:

header = accept
header = user-agent
header = cache-control
header = postman-token
header = host
header = accept-encoding
header = connection

요청정보에 담긴 Headers 정보들이다. 이런 Key에 대한 Headers가 요청에 들어왔다는 소리이고 이 키로 값도 가져올 수 있다.

이렇게 서블릿은 요청에 대한 원하는 모든 정보를 다 가져올 수가 있다. 

 

HTTP 요청 데이터

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법은 크게 3가지다.

  • GET - 쿼리 파라미터
    • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
  • POST - HTML Form
    • Content-Type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파라미터 형식으로 전달 (예: username=hello&age=20)
  • HTTP message body 
    • HTTP API에서 주로 사용하고 JSON, XML 형식으로 전달 가능

 

GET - 쿼리 파라미터를 통해 데이터를 전달할 때 받아보는 방법을 알아보자.

RequestParamServlet

package org.example.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 = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("[전체 파라미터 조회] - start");

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

        System.out.println("[전체 파라미터 조회] - end");

        System.out.println("[단일 파라미터 조회] - start");

        String username = req.getParameter("username");
        String age = req.getParameter("age");
        System.out.println("username = " + username);
        System.out.println("age = " + age);

        System.out.println("[단일 파라미터 조회] - end");

        System.out.println("[이름이 같은 복수 파라미터 조회] - start");

        String[] usernames = req.getParameterValues("username");
        for (String name : usernames) {
            System.out.println("username = " + name);
        }

        System.out.println("[이름이 같은 복수 파라미터 조회] - end");
    }
}

전체 파라미터를 가져오는 방법은 getParameterNames()를 통해 가져올 수 있다.

 

근데 보통은 저렇게 사용안하고 내가 어떤걸 받을지 이미 알기 때문에 getParameter()로 딱 원하는 쿼리파라미터를 가져온다.

 

그리고 거의 없지만 가끔 이런 경우가 있다. http://localhost:8080/request-param?username=hello&age=20&username=hi 

이렇게 같은 키의 파라미터(username)가 있는 경우에는 getParameterValues()를 호출해서 전체 값을 다 가져올 수 있다.

 

(http://localhost:8080/request-param?username=hello&age=20) 실행결과:

[전체 파라미터 조회] - start
username=hello
age=10
[전체 파라미터 조회] - end
[단일 파라미터 조회] - start
username = hello
age = 10
[단일 파라미터 조회] - end
[이름이 같은 복수 파라미터 조회] - start
username = hello
[이름이 같은 복수 파라미터 조회] - end

 

이게 서블릿을 사용할 때 제공해주는 HttpServletRequest 객체를 통해 요청 정보 중 쿼리 파라미터 데이터를 가져오는 방법이다.

그리고 스프링을 사용해도 결국 이 서블릿의 이 기능을 통해 가져오는건데 우리를 위해 더 편리하게 해줄뿐이다.

 

이제, POST - HTML Form을 통해 데이터를 전송할 때 어떻게 데이터를 받는지 보자.

우선 그러려면 Form이 있는 HTML 파일 하나가 있어야한다.

 

src/main/webapp/basic/hello-form.html

<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
    <form action="/request-param" method="post">
        username: <input type="text" name="username" />
        age: <input type="text" name="age" />
        <button type="submit">전송</button>
    </form>
</body>
</html>

 

간단한 Form 하나를 만들어서 테스트 해보자. 이 폼을 웹 브라우저에서 열려면 저 webapp 경로 아래의 경로 그대로를 브라우저에 입력하면 된다. 그러니까 http://localhost:8080/basic/hello-form.html 이라고 입력하면 된다.

여기에 입력을 하고 전송버튼을 누르면 서버에 어떻게 들어올까? 

실행결과:

[전체 파라미터 조회] - start
username=choi
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회] - start
username = choi
age = 20
[단일 파라미터 조회] - end
[이름이 같은 복수 파라미터 조회] - start
username = choi
[이름이 같은 복수 파라미터 조회] - end

 

GET 방식으로 받았던 코드 그대로인 상태로 수행했는데 잘 가져와진다. 그 이유는 Form으로 보내는 데이터 형식은 GET 방식의 쿼리 파라미터랑 형식이 같기 때문이다. 다음은 개발자 도구에서 네트워크 탭에서 요청 시 보여지는 Form Data이다. 쿼리 파라미터랑 형식이 똑같기 때문에 getParameter()로 여전히 가져올 수가 있다.

그리고 한가지 더 HTML Form으로 데이터를 보낼 때 Content-Type을 보면 application/x-www-form-urlencoded로 되어 있다. 이게 Form을 통해 데이터를 전달할 때 Content-Type이다. 

 

그럼 GETContent-Type이 어떻게 될까? Null이다. 왜냐하면 GET은 바디에 데이터를 태우지 않기 때문이다. 그래서 GET - 쿼리 파라미터POST - HTML Form 으로 데이터를 전송할 때 받는 방법은 둘 다 형식이 같아서 Parameter 받아오는 방법을 사용하면 된다는 것을 알았다. 물론, POST의 HTML Form으로 전송하는 데이터는 바디로 들어오는 데이터를 읽어서 가져와도 된다. 상관없다. 근데 좀 귀찮다 그 작업이 InputStream으로 가져와서 변환하고 어쩌구 해야해서 그럴 필요가 없이 getParameter()를 호출하면 된다.

 

 

HTTP Message Body로 데이터를 전송할 때 방식이 나뉜다.

  • 단순 스트링
  • JSON, XML

먼저 단순 스트링으로 보낼 때 데이터를 어떻게 받는지 보자.

RequestBodyStringServlet

package org.example.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 = "requestBodyStringServlet", urlPatterns = "/request-body-string")
public class RequestBodyStringServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        System.out.println(messageBody);
    }
}

 

HttpServletRequest 객체에서 getInputStream()을 호출하면 Body의 데이터를 가져올 수 있다. 그 데이터가 바이트 형식으로 되어 있는데 이것을 스트링으로 변환할 때 여러 방법이 있는데 지금은 스프링 부트를 사용중이니까 (앞으로도 그럴거니까) 스프링이 제공하는 StreamUtils라는 클래스가 있다. 이 녀석을 사용하면 매우 편리하다.

 

한번 실행해보자. Body에 데이터를 태워야 하니까 Postman으로 테스트 해보자.

우선 POST로 설정하고 Body탭에 raw - Text를 선택하면 단순 스트링으로 바디에 데이터를 보낸다.

이렇게 전송을 하면 다음과 같이 보낸 데이터를 받아올 수 있다.

그리고 이렇게 단순 텍스트로 데이터를 보내면 Request Headers에는 Content-Type이 어떻게 되어 있을까? text/plain이다.

그러니까 Request HeadersContent-Typetext/plain으로 되어있는건 "아 요청할 때 단순 텍스트로 바디에 값을 넣었구나!"라고 생각하면 된다.

 

 

하지만, 이렇게 보내는 경우는 거의 없다. 그래서 JSON으로 데이터를 주고 받는 방법에 대해 자세히 알아야 한다. 이제 JSON으로 데이터를 주고 받을 때 어떻게 하는지 보자.

 

들어오는 JSON 데이터를 객체로 변환하려고 한다. 자바는 모든게 객체이기 때문에.

그래서 변환할 객체에 대한 클래스를 하나 만들자.

HelloData

package org.example.servlet.basic.model;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class HelloData {
    private String username;
    private int age;
}

 

RequestBodyJsonServlet

package org.example.servlet.basic.request;

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;

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        System.out.println("messageBody = " + messageBody);
    }
}

 

JSON이라고 뭐 엄청 다른게 아니다. 단순 스트링으로 변환할 때처럼 똑같이 할 수 있다. JSON도 텍스트다. 저 상태로 한번 실행해보자.

 

Postman으로 다음과 같이 요청한다. JSON으로 바디에 데이터를 넣으려면 raw - JSON을 선택하면 된다.

이렇게 JSON으로 바디에 데이터를 넣으면 Request Headers에서 Content-Typeapplication/json이 된다.

 

요청해보면 서버에서는 다음과 같이 스트링으로 잘 받아진다.

 

근데 원하는건 이렇게 문자열로 들어오는걸로 끝이 아니라 들어오는 데이터를 객체로 변환해서 객체로 다루고 싶은 것이다. 객체로 변환하려면 라이브러리를 사용해야 한다. 스프링에서는 Jackson 라이브러리가 공식적으로 지원해주고 있다. 이 라이브러리에서 ObjectMapper라는 녀석이 있는데 이 녀석을 사용하면 된다. 가장 좋은 방식은 빈으로 딱 하나만 등록해놓고 가져다가 사용하는 것이다. 그래서 우선 빈으로 등록하자. 

 

ServletApplication

package org.example.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;

@ServletComponentScan
@SpringBootApplication
public class ServletApplication {

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

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

}

빈으로 등록하는 방법은 여러가지가 있는데, 가장 간단한 건 @SpringBootApplication 애노테이션이 붙은 클래스에서 저렇게 @Bean 애노테이션으로 등록하는 것이다. 등록하면 이제 주입(DI)을 할 수 있다.

 

RequestBodyJsonServlet

package org.example.servlet.basic.request;

import com.fasterxml.jackson.databind.ObjectMapper;
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 lombok.RequiredArgsConstructor;
import org.example.servlet.basic.model.HelloData;
import org.springframework.util.StreamUtils;

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

@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
@RequiredArgsConstructor
public class RequestBodyJsonServlet extends HttpServlet {

    private final ObjectMapper objectMapper;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream inputStream = req.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        System.out.println("helloData.getUsername() = " + helloData.getUsername());
        System.out.println("helloData.getAge() = " + helloData.getAge());
    }
}

주입하는 방법은 여러가지가 있는데 생성자 주입을 통해 하는것을 스프링도 권장하고 있다. 나도 생성자 주입으로 가져왔다. "어? 생성자가 없는데요?" Lombok에서 제공하는 @RequiredArgsConstructor를 공부하고 오면 된다. 

 

그래서 스트링으로 변환된 JSON 데이터를 가지고 ObjectMapperreadValue()를 통해 객체로 변환해낸다. 실행해보자.

실행결과:

helloData.getUsername() = Choi
helloData.getAge() = 20

 

자, 흐름을 한번 짚고 넘어가보면 HttpServletRequest 객체에서 바디의 데이터를 꺼내와서 스트림에 바이트를 스트링으로 변환하고 그 스트링 형태의 데이터를 ObjectMapper를 통해 객체로 변환해내는 과정을 직접 해봤다. 이거 Spring MVC 사용하면 파라미터에 service(HelloData helloData)로 끝낼 수 있다. 그럼 스프링은 저 과정을 다 대신해준다. 근데 이런 과정을 스프링이 해준다는 것을 알고 쓰는거랑 모르고 쓰는거랑은 천지차이라고 누가 그러더라. 맞는 말인거 같고. 그래서 직접 이 과정을 한번은 해보자는 취지에서 공부중이다.

 

이제 HttpServletRequest 객체에 대해서 알아봤으니 HttpServletResponse에 대해서도 알아보자.

HttpServletResponse

요청에 대한 응답을 줄 때도 역시나 여러 정보들이 포함된다. 응답 코드, Response Body, Response Headers 요청과 똑같은 스펙에 필요한 데이터를 넣어서 응답해준다. 그래서 하나씩 살펴보자.

 

ResponseHeaderServlet

package org.example.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;

@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // [response status-line]
        resp.setStatus(HttpServletResponse.SC_OK);

        // [response-headers]
        resp.setHeader("Content-Type", "text/plain");
        resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        resp.setHeader("Pragma", "no-cache");
        resp.setHeader("custom-header", "hello");

        // [response body]
        resp.getWriter().write("ok");
    }
}

 

서블릿 하나를 만들었다. "/response-header"로 들어오는 요청에 대한 응답 정보를 이렇게 보낸다. 한번 테스트 해보자.

요청에 대한 응답 내용을 보면 상태 코드 200, Headers 정보엔 넣은 Key/Value가 고대로 고스란히 들어가 있다. 그리고 Body에도 역시 다음과 같이 내가 작성한 코드 그대로 들어가 있다.

이렇게 응답 정보를 만들어서 요청에 대한 응답으로 돌려줄 수 있다.

 

근데 Content-Type에 Charset 정보를 넣어주지 않았더니 자동으로 ISO-8859-1로 들어가 있는게 보이는가? 이러면 이제 한글같은 경우가 깨질수도 있다. 응답 바디에 한글로 넣으면 어떻게 나오는지 보자.

resp.getWriter().write("안녕");

단순 텍스트로 한글로 된 문자 "안녕"을 응답 바디에 넣어 돌려준다. 결과는 "??"로 나온다. 이건 서버에서 Content-Type에 대한 Charset 설정을 UTF-8로 해주지 않았기에 일어난 일이다.

 

 

그래서 응답에 Content-Type에 대한 Charset 정보를 넣어줄 수 있다. 이렇게 한 줄 수정하고 다시 돌려보자.

resp.setHeader("Content-Type", "text/plain;charset=utf-8");

이제 한글도 잘 나온다.

 

근데 이 Headers에 데이터를 추가할 때 저렇게 두 개의 파라미터를 받아서 Key, Value를 직접 작성할 수도 있고 아예 이런 메서드를 사용할 수도 있다. 이런 편의 메서드도 제공을 한다는 걸 알아두면 좋을 것 같다.

// resp.setHeader("Content-Type", "text/plain;charset=utf-8");
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");

 

그리고 Content-Length 라는건 말 그대로 바디에 있는 데이터의 길이를 의미한다. Request, Response 둘 다 헤더에 넣을 수 있는데 안 넣으면 자동으로 계산이 된다. 그래서 지금 나의 코드는 헤더에 저 Content-Length를 넣지 않았지만 응답 정보를 보면 이렇게 자동으로 계산해서 보여준다.

 

Cookie도 넣을 수 있다. 한번 넣어보자.

// resp.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600); // 600초
resp.addCookie(cookie);

쿠키도 역시 헤더 정보에 들어가는 데이터이기 때문에 resp.setHeader()로 넣을 수 있지만 귀찮기 때문에 객체를 생성해서 addCookie()를 사용하면 좀 더 가시성도 좋아지고 편리해진다.

 

Redirect도 해보자.

resp.setStatus(HttpServletResponse.SC_FOUND); // 302
resp.setHeader("Location", "/basic/hello-form.html");
//resp.sendRedirect("/basic/hello-form.html");

위에 두 줄이 상태 코드 302로 Redirect임을 알리고 Location"/basic/hello-form.html"로 보내라라는 의미이다.

근데 이것도 역시나 편의메서드를 제공한다. sendRedirect().

 

Network를 보면 response-header로 먼저 요청이 가고 302 Redirect로 hello-form.html로 온 것을 볼 수 있다.

 

이 정도 해두면 응답 시 사용하는 거의 대부분을 확인해본 것이다. 이제 남은건 응답 시 바디 데이터에 데이터를 보내는 것에 초점을 두고 하나씩 알아보자. 여기도 역시나 단순 스트링과 HTML, JSON이 있다.

 

HTTP 응답 메시지는 주로 다음 내용을 담는다.

  • 단순 텍스트
  • HTML
  • JSON

우선 HTML로 응답하는 경우를 보자. 이거 서블릿으로 하려면 상당히 힘들다.

ResponseHtmlServlet

package org.example.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;

@WebServlet(name = "responseHtmlServlet", urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        
        PrintWriter writer = resp.getWriter();
        writer.println("<html>");
        writer.println("<body>");
        writer.println("<h1>안녕</h1>");
        writer.println("</body>");
        writer.println("</html>");
    }
}

우선, Content-Typetext/html로 해준다. 안 해줘도 요즘은 알아서 잘 해주는데 해주는게 정석이긴 하다.

CharacterEncoding은 UTF-8로 해주면 된다.

 

그리고 이제 PrintWriter 객체를 받아서 HTML을 작성하면 되는데, 이거 상당히 힘들다. 이게 서블릿의 엄청난 한계이다. JSP가 탄생한 이유이기도 하고. 그래서 이렇게 힘들게 HTML을 만들어서 뿌리면 다음과 같이 보여진다.

 

 

이게 이제 HTML을 응답 메시지에 넣어주는 방식이다. 가장 중요한 건 이게 아니라 JSON으로 돌려주는 HTTP API이다. 

HTTP API - JSON

ResponseJsonServlet

package org.example.servlet.basic.response;

import com.fasterxml.jackson.databind.ObjectMapper;
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 lombok.RequiredArgsConstructor;
import org.example.servlet.basic.model.HelloData;

import java.io.IOException;

@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
@RequiredArgsConstructor
public class ResponseJsonServlet extends HttpServlet {

    private final ObjectMapper objectMapper;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json");
        resp.setCharacterEncoding("UTF-8");

        HelloData helloData = new HelloData("choi", 30);

        String json = objectMapper.writeValueAsString(helloData);
        resp.getWriter().write(json);
    }
}

 

우선 응답 메시지를 JSON으로 돌려줄거니까 Content-Typeapplication/json으로 설정한다. 

그리고 객체를 하나 만들어서 그 객체를 JSON으로 만들어서 돌려줄거다. 간단하다. JSON을 객체로 변환했다면 객체를 JSON으로 변환하는것도 가능하다. ObjectMapper를 사용해서 변환한다.

 

실행 결과는 이렇다. 응답 메시지에 JSON 데이터로 잘 들어왔다.

 

이제 이 코드가 스프링에서는 그냥 반환 타입이 HelloData고 객체 만들어서 리턴만 해주면 끝난다. 이거 이렇게 된다는 걸 알고 스프링을 써도 써야지 모르면 안된다고 생각한다. 이렇게까지 하면 이제 응답 메시지를 HTML, 단순 텍스트, JSON으로 보내는거까지 다 알아본것이다. 이제 진짜 MVC패턴을 스프링없이 만들어보자.

728x90
반응형
LIST