본문 바로가기

MVC

[MVC] HTTP 요청

목차

  • HTTP 요청 - 기본, 헤더 조회
  • HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
  • HTTP 요청 파라미터 - @RequestParam
  • HTTP 요청 파라미터 - @ModelAttribute
  • HTTP 요청 메시지 - 단순 텍스트
  • HTTP 요청 메시지 - JSON

 

HTTP 요청 - 기본, 헤더 조회

애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다.
이번 시간에는 HTTP 헤더 정보를 조회하는 방법을 알아보자.

@Slf4j
@RestController
public class RequestHeaderController {

    @RequestMapping("/headers")
    public String headers(HttpServletRequest request,
                          HttpServletResponse response,
                          HttpMethod httpMethod,
                          Locale locale,
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value = "myCookie", required = false) String cookie) {

        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);
        return "ok";
    }
}

 

 

HttpServletRequest


HttpServletResponse


HttpMethod

  • HTTP 메서드를 조회한다. org.springframework.http.HttpMethod


Locale

  • Locale 정보를 조회한다.


@RequestHeader MultiValueMap<String, String> headerMap

  • 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.


@RequestHeader("host") String host

  • 특정 HTTP 헤더를 조회한다.
  • 속성
    • 필수 값 여부: required
    • 기본 값 속성: defaultValue


@CookieValue(value = "myCookie", required = false) String cookie

  • 특정 쿠키를 조회한다.
  • 속성
    • 필수 값 여부: required
    • 기본 값: defaultValue

 

MultiValueMap

  • MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
  • HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다. 
  • keyA=value1&keyA=value2
MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("keyA", "value1");
map.add("keyA", "value2");
//[value1,value2]
List<String> values = map.get("keyA");

 

 

✏️ @Slf4j
다음 코드를 자동으로 생성해서 로그를 선언해준다. 개발자는 편리하게 log 라고 사용하면 된다.

private static final org.slf4j.Logger log = 
org.slf4j.LoggerFactory.getLogger(RequestHeaderController.class);


참고
@Controller 의 사용 가능한 파라미터 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-annarguments

 

Redirecting...

 

docs.spring.io

 

 

참고
@Controller 의 사용 가능한 응답 값 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-annreturn-types

 

Redirecting...

 

docs.spring.io

 


✍ HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

 

HTTP 요청 데이터 조회 - 개요


HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.
클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

 


1️⃣ GET - 쿼리 파라미터

  • /url?username=hello&age=20
  • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
  • 예) 검색, 필터, 페이징등에서 많이 사용하는 방식

 


2️⃣ POST - HTML Form

  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
  • 예) 회원 가입, 상품 주문, HTML Form 사용

 


3️⃣ HTTP message body에 데이터를 직접 담아서 요청

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH



✍ 요청 파라미터 - 쿼리 파라미터, HTML Form

HttpServletRequest 의 request.getParameter() 를 사용하면 다음 두가지 요청 파라미터를 조회할 수 있다.

 


GET, 쿼리 파라미터 전송


예시

http://localhost:8080/request-param?username=hello&age=20

 


POST, HTML Form 전송 예시

POST /request-param ...
content-type: application/x-www-form-urlencoded
username=hello&age=20


GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다.

이것을 간단히 요청 파라미터(request parameter) 조회라 한다.
지금부터 스프링으로 요청 파라미터를 조회하는 방법을 단계적으로 알아보자.

@Slf4j
@Controller
public class RequestParamController {

    @RequestMapping("/request-param-v1")
    public void requestParamV1(HttpServletRequest request, HttpServletResponse response) 
    throws IOException {
    
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        log.info("username:{}, age:{}", username, age);

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

 

request.getParameter()

  • 여기서는 단순히 HttpServletRequest가 제공하는 방식으로 요청 파라미터를 조회했다.

GET 실행

  • http://localhost:8080/request-param-v1?username=hello&age=20

Post Form 페이지 생성

  • 먼저 테스트용 HTML Form을 만들어야 한다.
  • 리소스는 /resources/static 아래에 두면 스프링 부트가 자동으로 인식한다.
<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
 <form action="/request-param-v1" method="post">
 username: <input type="text" name="username" />
 age: <input type="text" name="age" />
 <button type="submit">전송</button>
 </form>
</body>
</html>

 

Post Form 실행

  • http://localhost:8080/basic/hello-form.html


참고

  • Jar 를 사용하면 webapp 경로를 사용할 수 없다. 
  • 이제부터 정적 리소스도 클래스 경로에 함께 포함해야 한다.

 


✍ HTTP 요청 파라미터 - @RequestParam


스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다.

 

requestParamV2

/** * @RequestParam 사용
 * - 파라미터 이름으로 바인딩
 * @ResponseBody 추가
 * - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
 */
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
        @RequestParam("username") String memberName,
        @RequestParam("age") int memberAge) {

    log.info("username:{}, age:{}", memberName, memberAge);
    return "ok";
}
  • @RequestParam : 파라미터 이름으로 바인딩
  • @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력


@RequestParam의 name(value) 속성이 파라미터 이름으로 사용

  • @RequestParam("username") String memberName
  • ➔ request.getParameter("username")

 

requestParamV3

/**
 * @RequestParam 사용
 * HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능
 */
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
        @RequestParam String username,
        @RequestParam int age) {

    log.info("username:{}, age:{}", username, age);
    return "ok";
}
  • HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능

 

requestParamV4

/**
 * @RequestParam 사용
 * String, int 등의 단순 타입이면 @RequestParam 도 생략 가능
 */
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {

    log.info("username:{}, age:{}", username, age);
    return "ok";
}
  • String , int , Integer 등의 단순 타입이면 @RequestParam 도 생략 가능


주의 ⚠️

  • @RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다. 

참고

  • 이렇게 애노테이션을 완전히 생략해도 되는데, 너무 없는 것도 약간 과하다는 주관적 생각이 있다.
  • @RequestParam 이 있으면 명확하게 요청 파리미터에서 데이터를 읽는 다는 것을 알 수 있다.

 

주의 ⚠️

스프링 부트 3.2 파라미터 이름 인식 문제

  • 다음 예외가 발생하면 해당 내용을 참고하자.

발생하는 예외

java.lang.IllegalArgumentException: Name for argument of type [java.lang.String] 
not specified, and parameter name information not found in class file either.

 

스프링 부트 3.2부터 자바 컴파일러에 -parameters 옵션을 넣어주어야 애노테이션에 적는 이름을 생략할 수 있다.
주로 다음 두 애노테이션에서 문제가 발생한다.

  • @RequestParam
  • @PathVariable

 

@RequestParam 관련

//애노테이션에 username이라는 이름이 명확하게 있다. 문제 없이 작동한다.
@RequestMapping("/request")
public String request(@RequestParam("username") String username) { ...
}

//애노테이션에 이름이 없다. -parameters 옵션 필요
@RequestMapping("/request")
public String request(@RequestParam String username) {
 ...
}

//애노테이션도 없고 이름도 없다. -parameters 옵션 필요
@RequestMapping("/request")
public String request(String username) {
 ...
}

 

 

 

해결 방안1️⃣(권장)
애노테이션에 이름을 생략하지 않고 다음과 같이 이름을 항상 적어준다. 이 방법을 권장한다.

  • @RequestParam("username") String username
  • @PathVariable("userId") String userId


해결 방안2️⃣

컴파일 시점에 -parameters 옵션 적용

  1. IntelliJ IDEA에서 File Settings를 연다. (Mac은 IntelliJ IDEA Settings)
  2. Build, Execution, Deployment → Compiler → Java Compiler로 이동한다.
  3. Additional command line parameters라는 항목에 다음을 추가한다. - parameters
  4. out 폴더를 삭제하고 다시 실행한다. 꼭 out 폴더를 삭제해야 다시 컴파일이 일어난다.


해결 방안3️⃣

  • Gradle을 사용해서 빌드하고 실행한다.


문제 원인 ✅
참고로 이 문제는 Build, Execution, Deployment Build Tools Gradle에서

Build and run using를 IntelliJ IDEA로 선택한 경우에만 발생한다.
Gradle로 선택한 경우에는 Gradle이 컴파일 시점에 해당 옵션을 자동으로 적용해준다.
자바를 컴파일할 때 매개변수 이름을 읽을 수 있도록 남겨두어야 사용할 수 있다. 

컴파일 시점에 -parameters 옵션을 사용하면 매개변수 이름을 사용할 수 있게 남겨둔다.
스프링 부트 3.2 전까지는 바이트코드를 파싱해서 매개변수 이름을 추론하려고 시도했다. 

하지만 스프링 부트 3.2 부터는 이런 시도를 하지 않는다.

 


✏️ 파라미터 필수 여부 - requestParamRequired

/**
 * @RequestParam.required
 * /request-param-required -> username이 없으므로 예외
 *
 * 주의!
 * /request-param-required?username= -> 빈문자로 통과
 *
 * 주의!
 * /request-param-required
 * int age -> null을 int에 입력하는 것은 불가능, 
 *따라서 Integer 변경해야 함(또는 다음에 나오는 defaultValue 사용)
 */
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
        @RequestParam(required = true) String username, // BAD_REQUEST
        @RequestParam(required = false) int age // 500 에러(int는 null이 들어갈 수 없다.)
        //@RequestParam(required = false) Integer age // OK
) {

    log.info("username:{}, age:{}", username, age);
    return "ok";
}

 

 

@RequestParam.required

  • 파라미터 필수 여부
  • 기본값이 파라미터 필수( true )이다.


/request-param-required 요청

  • username 이 없으므로 400 예외가 발생한다

.
주의 ⚠️ - 파라미터 이름만 사용

/request-param-required?username=
  • 파라미터 이름만 있고 값이 없는 경우 ➔ 빈문자로 통과


주의 ⚠️ - 기본형(primitive)에 null 입력

  • /request-param 요청
  • @RequestParam(required = false) int age
  • null 을 int 에 입력하는 것은 불가능(500 예외 발생)
  • 따라서 null 을 받을 수 있는 Integer 로 변경하거나, 또는 다음에 나오는 defaultValue 사용

 


✏️ 기본 값 적용 - requestParamDefault

/**
 * @RequestParam
 * - defaultValue 사용
 *
 * 참고: defaultValue는 빈 문자의 경우에도 적용
 * /request-param-default?username=
 */
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username, // 없으면 guest
        @RequestParam(required = false, defaultValue = "-1") int age
) {

    log.info("username:{}, age:{}", username, age);
    return "ok";
}
  • 파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다.
  • 이미 기본 값이 있기 때문에 required 는 의미가 없다.
  • defaultValue 는 빈 문자의 경우에도 설정한 기본 값이 적용된다.

 

실행

http://localhost:8080/request-param-default
http://localhost:8080/request-param-default?username=
http://localhost:8080/request-param-default?username=&age=

 

 

 

✏️ 파라미터를 Map으로 조회하기 - requestParamMap

/**
 * @RequestParam Map, MultiValueMap
 * Map(key=value)
 * MultiValueMap(key=[value1, value2, ...]) ex) (key=userIds, value=[id1, id2])
 */
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {

    log.info("username:{}, age:{}", paramMap.get("username"), paramMap.get("age"));
    return "ok";
}

 

파라미터를 Map, MultiValueMap으로 조회할 수 있다.


@RequestParam Map , 

  • Map(key=value)

@RequestParam MultiValueMap

  • MultiValueMap(key=[value1, value2, ...]
  • ex) (key=userIds, value=[id1, id2])

파라미터의 값이 1개가 확실하다면 Map 을 사용해도 되지만, 그렇지 않다면 MultiValueMap 을 사용하자.


✍ HTTP 요청 파라미터 - @ModelAttribute

실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다. 

보통 다음과 같이 코드를 작성할 것이다. 

@ResponseBody
    @RequestMapping("/model-attribute-v1")
    public String modelAttributeV0(
            @RequestParam String username,
            @RequestParam int age
    ) {
        HelloData helloData = new HelloData();
        helloData.setUsername(username);
        helloData.setAge(age);
        return "ok";
    }
  • 스프링은 이 과정을 완전히 자동화해주는 ❝@ModelAttribute❞ 기능을 제공한다.

 

HelloData

 

먼저 요청 파라미터를 바인딩 받을 객체를 만들자.

@Data
public class HelloData {

    private String username;
    private int age;
    
}

 

💡롬복 @Data

  • @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 를 자동으로 적용해준다.

 

 

@ModelAttribute 적용 - modelAttributeV1

/**
     * @ModelAttribute 사용
     * 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨
     */
    @ResponseBody
    @RequestMapping("/model-attribute-v1")
    public String modelAttributeV1(@ModelAttribute HelloData helloData) {

        log.info("username:{}, age:{}", helloData.getUsername(), helloData.getAge());
        log.info("HelloData:{}", helloData);
        return "ok";
    }
  • 마치 마법처럼 HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가 있다.


스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

  • HelloData 객체를 생성한다.
  • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 
  • 그리고 해당 프로퍼티의 setter를 호출해서파라미터의 값을 입력(바인딩) 한다.
  • 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.

 

✅ 프로퍼티

  • 객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다.
  • username 프로퍼티의 값을 변경하면 setUsername() 이 호출되고, 조회하면 getUsername() 이 호출된다
class HelloData {
 getUsername();
 setUsername();
}

 

바인딩 오류

  • age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다. 

 

@ModelAttribute 생략 - modelAttributeV2

/**
 * @ModelAttribute 생략 가능
 * String, int 같은 단순 타입 = @RequestParam
 * argument resolver 로 지정해둔 타입 외 = @ModelAttribute
 */
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {

    log.info("username:{}, age:{}", helloData.getUsername(), helloData.getAge());
    log.info("HelloData:{}", helloData);
    return "ok";
}
  • @ModelAttribute 는 생략할 수 있다.
  • 그런데 @RequestParam 도 생략할 수 있으니 혼란이 발생할 수 있다.


스프링은 해당 생략시 다음과 같은 규칙을 적용한다.

  • String , int , Integer 같은 단순 타입 ➔ @RequestParam
  • 나머지 ➔ @ModelAttribute (argument resolver 로 지정해둔 타입 외)

HTTP 요청 메시지 - 단순 텍스트

 

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

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam , 
@ModelAttribute 를 사용할 수 없다. (물론 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)

  • 먼저 가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보자.
  • HTTP 메시지 바디의 데이터를 InputStream 을 사용해서 직접 읽을 수 있다.

 

RequestBodyStringController

@Slf4j
@Controller
public class RequestBodyStringController {

    @PostMapping("/request-body-string-v1")
    public void requestBodyString(HttpServletRequest request, HttpServletResponse response)
            throws IOException {

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

        log.info("messageBody: {}", messageBody);
        response.getWriter().write("ok");

    }
}

 

Postman을 사용해서 테스트 해보자.

  • POST http://localhost:8080/request-body-string-v1
  • Body ➔ row, Text 선택

 

 

Input, Output 스트림, Reader - requestBodyStringV2

/**
 * InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
 * OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
 */
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter)
        throws IOException {

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

    log.info("messageBody: {}", messageBody);
    responseWriter.write("ok");

}

 

스프링 MVC는 다음 파라미터를 지원한다.

  • InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
  • OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력

 

 

HttpEntity - requestBodyStringV3

/**
 * HttpEntity: HTTP header, body 정보를 편리하게 조회
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 *
 * 응답에서도 HttpEntity 사용 가능
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity)
        throws IOException {

    String messageBody = httpEntity.getBody();

    log.info("messageBody: {}", messageBody);
    return new HttpEntity<>("ok");

}

 

스프링 MVC는 다음 파라미터를 지원한다.


✅ HttpEntity: HTTP header, body 정보를 편리하게 조회

  • 메시지 바디 정보를 직접 조회
  • 요청 파라미터를 조회하는 기능과 관계 없음 @RequestParam X, @ModelAttribute X

✅ HttpEntity는 응답에도 사용 가능

  • 메시지 바디 정보 직접 반환
  • 헤더 정보 포함 가능
  • view 조회X


HttpEntity 를 상속받은 다음 객체들도 같은 기능을 제공한다.

@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(RequestEntity<String> httpEntity)
        throws IOException {

    String messageBody = httpEntity.getBody();

    log.info("messageBody: {}", messageBody);
    return new ResponseEntity<>("ok", HttpStatus.CREATED);

}


✅ RequestEntity

  • HttpMethod, url 정보가 추가, 요청에서 사용


✅ ResponseEntity

  • HTTP 상태 코드 설정 가능, 응답에서 사용
  • return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED)

참고

  • 스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해준다. 
  • 이때 HTTP 메시지 컨버터( HttpMessageConverter )라는 기능을 사용한다. 

 


@RequestBody - requestBodyStringV4

 /**
     * @RequestBody
     * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
     * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     *
     * @ResponseBody
     * - 메시지 바디 정보 직접 반환(view 조회X)
     * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     */
    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody)
            throws IOException {


        log.info("messageBody: {}", messageBody);
        return "ok";
    }

 

 

✏️ @RequestBody

  • @RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다. 
  • 참고로 헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다.

주의 ⚠️
이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와는 전혀 관계가 없다.


요청 파라미터 VS HTTP 메시지 바디

  • 요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
  • HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody

@ResponseBody

  • @ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다.
  • 물론 이 경우에도 view를 사용하지 않는다.

HTTP 요청 메시지 - JSON

이번에는 HTTP API에서 주로 사용하는 JSON 데이터 형식을 조회해보자.
기존 서블릿에서 사용했던 방식과 비슷하게 시작해보자.

 

RequestBodyJsonController

/**
 * {"username":"hello", "age":20}
 * content-type: application/json
 */
@Slf4j
@Controller

public class RequestBodyJsonController {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @PostMapping("/request-body-json-v1")
    public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response)
            throws IOException {

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

        log.info("messageBody: {}", messageBody);
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username: {}, age: {}", helloData.getUsername(), helloData.getAge());

        response.getWriter().write("ok");
    }
}
  • HttpServletRequest를 사용해서 직접 HTTP 메시지 바디에서 데이터를 읽어와서, 문자로 변환한다.
  • 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 를 사용해서 자바 객체로 변환한다.


Postman으로 테스트

  • POST http://localhost:8080/request-body-json-v1
  • raw, JSON, content-type: application/json{"username":"hello", "age":20}

 

 

requestBodyJsonV2 - @RequestBody 문자 변환

/**
 * @RequestBody
 * HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 *
 * @ResponseBody
 * - 모든 메서드에 @ResponseBody 적용
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody)
        throws IOException {

    log.info("messageBody: {}", messageBody);
    HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
    log.info("username: {}, age: {}", helloData.getUsername(), helloData.getAge());

    return "ok";
}
  • 이전에 학습했던 @RequestBody 를 사용해서 HTTP 메시지에서 데이터를 꺼내고 messageBody에 저장한다.
  • 문자로 된 JSON 데이터인 messageBody 를 objectMapper 를 통해서 자바 객체로 변환한다.

🤔 문자로 변환하고 다시 json으로 변환하는 과정이 불편하다. @ModelAttribute처럼 한번에 객체로 변환할 수는 없을까?

 

 

requestBodyJsonV3 - @RequestBody 객체 변환

/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type:
application/json)
 *
 */
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData)
        throws IOException {

    log.info("username: {}, age: {}", helloData.getUsername(), helloData.getAge());
    return "ok";
}

 

 

✅ @RequestBody 객체 파라미터

  • @RequestBody HelloData data
  • @RequestBody 에 직접 만든 객체를 지정할 수 있다.

 

HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다. HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해주는데, 우리가 방금 V2에서 했던 작업을 대신 처리해준다.

 

❝ @RequestBody는 생략 불가능 ❌❞


스프링은 @ModelAttribute , @RequestParam 과 같은 해당 애노테이션을 생략시 다음과 같은 규칙을 적용한다.

  • String , int , Integer 같은 단순 타입 ➔ @RequestParam
  • 나머지 ➔ @ModelAttribute (argument resolver 로 지정해둔 타입 외)

 

따라서 이 경우 HelloData에 @RequestBody 를 생략하면 @ModelAttribute 가 적용되어버린다.

  • HelloData data ➔ @ModelAttribute HelloData data
  • 따라서 생략하면 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다.


주의⚠️

  • HTTP 요청시에 content-type이 application/json인지 꼭! 확인해야 한다. 
  • 그래야 JSON을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다.

 

 

requestBodyJsonV4 - HttpEntity

 

물론 앞서 배운 것과 같이 HttpEntity를 사용해도 된다.

@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity)
        throws IOException {

    HelloData data = httpEntity.getBody();
    log.info("username: {}, age: {}", data.getUsername(), data.getAge());
    return "ok";
}

 

 

requestBodyJsonV5

/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type:
application/json)
 *
 * @ResponseBody 적용
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용(Accept:
application/json)
 */
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data)
        throws IOException {

    log.info("username: {}, age: {}", data.getUsername(), data.getAge());
    return data;
}

 

✅ @ResponseBody

  • 응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.
  • 물론 이 경우에도 HttpEntity 를 사용해도 된다.

@RequestBody 요청

  • JSON 요청 ➔ HTTP 메시지 컨버터 ➔객체


@ResponseBody 응답

  • 객체 ➔ HTTP 메시지 컨버터 ➔ JSON 응답

'MVC' 카테고리의 다른 글

[MVC] Thymeleaf 뷰 템플릿  (1) 2024.07.22
[MVC] HTTP 응답  (0) 2024.07.21
[MVC] 요청 매핑 방법  (1) 2024.07.19
[MVC] 스프링 MVC  (0) 2024.07.18
[MVC] 프론트 컨트롤러 패턴  (0) 2024.07.16