스프링 부트에서 서블릿을 등록하고 사용해보자. 스프링 부트는 톰캣 서버를 내장하고 있기 때문에 편리하게 서블릿 코드를 실행할 수 있다.

스프링 부트는 서블릿을 직접 등록해서 사용할 수 있게 @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);
    }
}

HTTP 요청을 통해 매핑된 “/hello” 가 호출되면, 서블릿 컨테이너는 service() 메서드를 실행하는 것이다. 스프링 부트를 실행하고, http://localhost:8080/hello?username=world로 접속하고, 콘솔을 확인해보면…

 

📦 서블릿 컨테이너 동작 방식

스프링 부트를 실행하면, 스프링 부트가 내장 톰캣 서버(WAS)를 띄워준다. 보다시피 톰캣은 서블릿 컨테이너를 통해서 서블릿을 생성해준다.

그리고 나서 username“world” 라고 해줬는데, 그럼 웹 브라우저가 HTTP 요청 메시지를 만들어준다.

요청을 받은 웹 애플리케이션 서버가 requestresponse 객체를 만들어서 싱글톤으로 떠 있는 helloServlet을 호출한다. 그리고 필요한 작업이 끝나면 WAS 서버가 response 정보를 가지고 HTTP 응답 메시지를 만들어서 반환해주는 것이다.

그러면 웹 브라우저에서 “hello world” 라고 볼 수 있는 것이다.


📤 HttpServletRequest

HTTP 요청 메시지를 직접 파싱할 수도 있지만, 그럼 너무 번거롭다. 서블릿은 HTTP 요청 메시지를 편리하게 사용할 수 있게 대신 메시지를 파싱해준다. 그 파싱된 결과를 HttpServletRequest 객체에 담아서 주는거다.

HttpServletRequest를 사용하면 아래와 같은 HTTP 요청 메시지에서 정보를 입맛에 맞게 뽑아낼 수 있다.

START LINE(HTTP 메서드, URL, 쿼리 스트링, 스키마 및 프로토콜)헤더, 바디(폼 파라미터 형식 조회, 메시지 바디 직접 조회)와 같은 식으로 읽을 수 있도록 지원한다. 추가로, 여러가지 부가기능도 제공하는데, 대표적으로 임시 저장소 기능세션 관리 기능이 있다.

임시 저장소 기능은 HTTP 메시지가 살아있는 동안 request.setAttribute(name, value)로 값을 넣고, request.getAttribute(name)으로 값을 꺼낼 수 있도록 한다. 세션 관리 기능은 request.getSession(create: true)로 로그인 유지 같은 기능에 사용할 수 있다.

하지만, 여기서 중요한 점은 HttpServletRequest, HttpServletResponse를 사용할 때 이 객체들이 HTTP 요청 메시지, HTTP 응답 메시지를 편리하게 사용하도록 도와주는 객체라는 것이다. 이 기능에 대해 깊이 있게 이해하기 위해서는 HTTP 스펙이 제공하는 요청과 응답 메시지 자체를 이해해야 한다.

 

🔨 기본 사용법

이제 HttpServletRequest가 제공하는 기본 기능들에 대해 코드로 작성해보자.

package hello.servlet.basic.request;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Enumeration;

@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Start Line 정보 불러오기
        printStartLine(request);
        printHeaders(request);
        printHeaderUtils(request);
        printEtc(request);

    }

    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-LINE - start ---
        request.getMethod() = GET
        request.getProtocol() = HTTP/1.1
        request.getScheme() = http
        request.getRequestURL() = http://localhost:8080/request-header
        request.getRequestURI() = /request-header
        request.getQueryString() = null
        request.isSecure() = false
        --- REQUEST-LINE - end ---
         */
    }

    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 + " = " + headerName);
//        }

        request.getHeaderNames().asIterator()
                        .forEachRemaining(headerName -> System.out.println(headerName + " = " + headerName));

        System.out.println("--- Headers - end ---");
        System.out.println();

        /*
        --- Headers - start ---
        host = host
        connection = connection
        cache-control = cache-control
        sec-ch-ua = sec-ch-ua
        sec-ch-ua-mobile = sec-ch-ua-mobile
        sec-ch-ua-platform = sec-ch-ua-platform
        upgrade-insecure-requests = upgrade-insecure-requests
        user-agent = user-agent
        accept = accept
        sec-fetch-site = sec-fetch-site
        sec-fetch-mode = sec-fetch-mode
        sec-fetch-user = sec-fetch-user
        sec-fetch-dest = sec-fetch-dest
        accept-encoding = accept-encoding
        accept-language = accept-language
        cookie = cookie
        --- Headers - end ---
         */
    }

    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());
        System.out.println("--- Header 편의 조회 end ---");
        System.out.println();

        /*
        --- 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 편의 조회]
        JSESSIONID: 3C011FA5B1E74348DA237197DA296731

        [Content 편의 조회]
        request.getContentType() = null
        request.getContentLength() = -1
        request.getCharacterEncoding() = UTF-8
        --- Header 편의 조회 end ---
         */
    }

    private void printEtc(HttpServletRequest request) {
        System.out.println("--- 기타 조회 start ---");
        System.out.println("[Remote 정보]");
        System.out.println("request.getRemoteHost() = " + request.getRemoteHost());
        System.out.println("request.getRemoteAddr() = " + request.getRemoteAddr());
        System.out.println("request.getRemotePort() = " + request.getRemotePort());
        System.out.println();
        System.out.println("[Local 정보]");
        System.out.println("request.getLocalName() = " + request.getLocalName());
        System.out.println("request.getLocalAddr() = " + request.getLocalAddr());
        System.out.println("request.getLocalPort() = " + request.getLocalPort());
        System.out.println("--- 기타 조회 end ---");
        System.out.println();

        /*
        --- 기타 조회 start ---
        [Remote 정보]
        request.getRemoteHost() = 0:0:0:0:0:0:0:1
        request.getRemoteAddr() = 0:0:0:0:0:0:0:1
        request.getRemotePort() = 50899

        [Local 정보]
        request.getLocalName() = localhost
        request.getLocalAddr() = 0:0:0:0:0:0:0:1
        request.getLocalPort() = 8080
        --- 기타 조회 end ---
         */
    }
}

🛰️ HTTP 요청 데이터

HTTP 메시지의 START LINE, 헤더 정보에 대한 조회 방법을 봤으니, 이제 HTTP 요청 데이터를 통해 클라이언트에서 서버로 데이터를 어떤 식으로 전달하는지 알아보자.

 

아래 3가지 방법을 잘 알아야 나중에 헷갈리지 않는다.

첫 번째는 GET 방식의 쿼리 파라미터다. 보통 URL들을 보면, /url?username=hello&age=20과 같은 형식으로 되어 있는 것을 볼 수 있다. 보통 GET 방식은 Body에 데이터를 보내지 않고, URL의 쿼리 파라미터에 데이터를 포함해서 전달한다. 예를 들어, 검색을 하거나 어떤 내용을 필터링 하든가 페이징과 같은 작업에서 많이 사용된다.

 

두 번째는 POST 방식 중에서 HTML 폼을 전송할 때다. 대표적으로 회원가입 할 때를 생각해보면 된다.

전송 버튼을 누르면 웹 브라우저가 HTML 메시지를 생성한다. Content-Type을 보면 application/x-www-form-urlencoded로 되어 있다. 메시지 Body에 데이터를 넣는다. 근데 여기서 주의할 점이 있는데 보면 쿼리 파라미터랑 비슷하게 생겼다. username=hello&age=20과 같은 형식이 x-www-form-urlencoded라는 방식인 것이다.

 

세 번째는 HTTP 메시지 바디에 데이터를 직접 담아서 요청한다. HTTP API에 주로 사용된다. 그냥 안에 JSON, XML 또는 텍스트 정보를 그대로 담아서 클라이언트에서 서버로 전달한다. POST, PUT, PATCH 등 여러 방식을 사용할 수 있다.

 

1️⃣ GET 쿼리 파라미터 형식

username“hello”, age20를 서버로 메시지 Body 없이 그냥 URL의 쿼리 파라미터를 사용해서 전달해보자. 쿼리 파라미터는 URL에 다음과 같이 ?를 시작으로 보낼 수 있다. 추가 파라미터는 &로 구분하면 된다.

package hello.servlet.basic.request;

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;

/**
 * 1. 파라미터 전송 기능
 * http://localhost:8080/request-param?username=hello&age=20
 * */
@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
    @Override
    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)));
        request.getParameterNames();

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

        System.out.println("[단일 파라미터 조회] - start");
        String username = request.getParameter("username");
        String age = request.getParameter("age");

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

        System.out.println("[이름이 같은 복수 파라미터 조회] - start");
        String[] usernames = request.getParameterValues("username");
        for (String s : usernames) {
            System.out.println("username = " + s);
        }
        System.out.println("[이름이 같은 복수 파라미터 조회] - end");
        System.out.println();
    }
}

/*
[전체 파라미터 조회] - start
username = hello
age = 20
[전체 파라미터 조회] - end

[단일 파라미터 조회] - start
username = hello
age = 20
[단일 파라미터 조회] - end

[이름이 같은 복수 파라미터 조회] - start
username = hello
[이름이 같은 복수 파라미터 조회] - end
*/

username이라는 파라미터 이름은 하나인데, 값이 중복되면 어떻게 해야 할까? 위의 코드와 같이 값이 하나만 있을 경우에는 그냥 request.getParameter()를 사용하면 된다. 값이 중복될 경우에 request.getParameterValues()를 사용해야 한다. 값이 중복인데 request.getParameter()를 사용하게 되면 request.getParameterValues()의 첫 번째 값만 반환된다. 근데 이렇게 중복으로 보내는 경우는 거의 없다고 봐도 무방하다. 그냥 알아두기만 하자.

 

2️⃣ POST 방식의 HTML 폼 형식

이번엔 HTML의 폼을 사용해서 서버로 데이터를 전송해보자. 이 방식의 특징은 데이터가 일단 메시지 Body에 들어가기 때문에 application/x-www-form-urlencoded라는 Content-Type이 있다. 그리고 메시지 Body에 쿼리 파라미터 형식으로 데이터를 전달한다.

POST로 HTML 폼을 전송하게 되면 웹 브라우저는 아래와 같이 HTTP 메시지를 생성한다.

  • 요청 URL: http://localhost:8080/request-param
  • content-type: application/x-www-form-urlencoded
  • message body: username=hello&age=20

보다시피 전송을 누르면 웹 브라우저가 HTTP 요청 메시지를 만들어준다. Content-Typeapplication/x-www-form-urlencoded, 내용은 쿼리 파라미터와 같은 형식으로 보낸다.

앞서 살펴본 GET 방식에서의 쿼리 파라미터와 같은 형식이기 때문에 request.getParameter()2가지 방식을 다 지원한다. 그냥 쓰면 된다. 클라이언트 입장에서는 두 방식에 차이는 있지만, 서버 입장에서는 request.getParameter()로 편리하게 둘 다 조회 가능하다.

이처럼 Content-Type은 HTTP 메시지 Body의 데이터 형식을 지정한다. GET 방식의 URL 쿼리 파라미터 형식으로 서버로 데이터를 전달할 때는 HTTP 메시지 Body를 사용하지 않기 때문에 Content-Type이 없다. 하지만, POST 방식의 HTML 폼 형식으로 데이터를 전달하면 HTTP 메시지 Body에 해당 데이터를 포함해서 보내기 때문에 Body에 포함된 데이터가 어떤 형식인지 꼭 지정해줘야 한다.

 

3️⃣ API 메시지 바디 형식

먼저 메시지 Body에 아주 단순한 텍스트 메시지를 담아서 전송하고, 읽어보자.

package hello.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 = "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();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

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

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

        // messageBody = hello!
    }
}

http://localhost:8080/request-body-string으로 POST 요청을 보냈다. Content-Typetext/plain, Message Body“hello!” 다.

다음은 JSON 형식으로 데이터를 서버로 전송해보자. JSON이기 때문에 Content-Typeapplication/json이다. 그리고 JSON 형식이기 때문에 JSON 형식의 데이터를 객체로 바꿀 수 있도록 파싱할 수 있게 만들고 서블릿 코드를 작성해야 한다.

package hello.servlet.basic;

import lombok.Getter;
import lombok.Setter;

@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;

@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);  // messageBody = {"username": "hello", "age": 20}

        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        System.out.println("helloData.username = " + helloData.getUsername());  // helloData.username = hello
        System.out.println("helloData.age = " + helloData.getAge());  // helloData.age = 20

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

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


📥 HttpServletResponse

HttpServletResponse는 간단하다. HTTP 응답 코드, 헤더, 바디를 포함한 HTTP 응답 메시지를 생성해준다. 추가로 Content-Type, 쿠키, Redirect와 같은 편의 기능도 제공한다. 바로 서블릿 코드를 작성해보자.

package hello.servlet.basic.response;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
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 = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // [status-line]
        response.setStatus(HttpServletResponse.SC_OK);

        // [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");

        // [Headers 편의 메서드]
        content(response);
        cookie(response);
        redirect(response);

        // [Message Body]
        PrintWriter writer = response.getWriter();
        writer.println("ok");
    }

    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); // (생략 시 자동 생성)
    }

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

    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 API로 응답하는 총 3가지 방식이 있다.

 

먼저 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;

@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://localhost:8080/response-html로 접속해서 소스를 확인해보면 아래와 같은 HTML 응답을 확인할 수 있다.

 

마지막으로 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 {

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

http://localhost:8080/response-json으로 접속하면 아래와 같이 JSON 형식의 데이터 응답을 볼 수 있다.

HTTP 응답으로 JSON을 반환할 때는 Content-Typeapplication/json로 지정해야 한다.

profile
도메인을 이해하는 백엔드 개발자(feat. OOP)

0개의 댓글