본문 바로가기
Spring

[Spring] @RequestBody @ResponseBody HTTP message body 데이터 송수신

by jinjin98 2022. 8. 31.

이전 포스팅에서 스프링 프레임워크를 사용할 떄 HTTP 요청 파라미터를 받는 방법에 대해 설명했는데요.

이번에는 HTTP message body로 데이터를 요청하고 응답하는 방법에 대해 알아보겠습니다.

HTTP message body에 데이터를 직접 담아서 요청하는 방식은 주로 HTTP API(REST API)통신에서

사용하고 데이터 형식은 JSON, XML, TEXT 가 있지만 대부분 JSON을 많이 사용합니다.

HTTP 메시지 바디를 통해 직접 데이터가 넘어올 때는 

@RequestParam , @ModelAttribute 를 사용할 수 없습니다.

@RequestParam , @ModelAttribute 는 URL 형식으로 GET 방식의 요청이나

HTML Form 형식으로 POST 방식의 요청 파라미터를 받을 때 사용합니다.

 

그렇다면 HTTP 메시지 바디를 통해 직접 데이터가 넘어오면 어떻게 값을 받고

 HTTP 메시지 바디로 어떻게 값을 담을 수 있을까요?

스프링에서는 HTTP 메시지 바디 데이터를 개발자가 원하는 문자나 객체로 변환해 읽거나

전달 할 수 있는데 이 때 사용하는게 HttpMessageConverter 입니다.

 

 

이 메시지 컨버터를 이용해 HTTP 메시지 바디에 있는 데이터를 받는 클래스와 어노테이션이

HttpEntity, RequestEntity, @RequestBody 이고

이 메시지 컨버터를 이용해 HTTP 메시지 바디에 데이터를 내보내는 클래스와 어노테이션이

HttpEntity, ResponseEntity, @ResponseBody 입니다.

 

컨트롤러에서 문자열로 값을 반환하면 ViewResolver(뷰 리졸버) 가 등록하지만

HTTP 메시지 바디에 값을 직접 반환하게 되면

ViewResolver 대신 HttpMessageConverter 가 동작하게 됩니다.

어떤 타입의 요청 값을 받고 응답 값을 내보내냐에 따라

HttpMessageConverter 의 구현체가 동작하게 되는데 대표적인 구현체로는 

기본 문자처리를 하는 StringHttpMessageConverter

기본 객체처리를 하는 MappingJackson2HttpMessageConverter 가 있습니다.

이 외에도 byte 처리 ByteArrayHttpMessageConverter 등 기타 여러 HttpMessageConverter 가

기본으로 등록되어 있습니다.

 

스프링 부트 기본 메시지 컨버터 (일부 생략)

0 = ByteArrayHttpMessageConverter

1 = StringHttpMessageConverter

2 = MappingJackson2HttpMessageConverter

앞에 숫자는 메시지 컨버터 순서입니다.

 

스프링 부트가 기본으로 제공하는 메시지 컨버터는

대상 클래스 타입과 미디어 타입둘을 체크해서 사용여부를 결정합니다.

만약 만족하지 않으면 다음 메시지 컨버터로 우선순위가 넘어합니다.

 

ByteArrayHttpMessageConverter : byte[] 데이터를 처리합니다.

클래스 타입: byte[] , 미디어타입: */* 

요청

ex)  @RequestBody byte[] data

응답

ex)  @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream

 

StringHttpMessageConverter : String 문자로 데이터를 처리합니다.

클래스 타입: String , 미디어타입: */*

요청

ex) @RequestBody String data

응답

ex) @ResponseBody return "ok" 쓰기 미디어타입 text/plain

 

MappingJackson2HttpMessageConverter : application/json

클래스 타입: 객체 또는 HashMap , 미디어타입: application/json 관련

요청

ex) @RequestBody HelloData data

응답

ex) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련

(HelloData 는 직접 생성한 클래스입니다.)

 

 

 

HttpMessageConverter 인터페이스 코드입니다.

HttpMessageConverter 는 HTTP 요청, HTTP 응답 둘 다 사용되는데요.

 

canRead() , canWrite(): 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크합니다.

read() , write(): 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능입니다.

 

HTTP 요청 데이터 읽기

클라이언트가 요청하면 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 를 호출합니다.

1. 대상 클래스 타입을 지원하는지 확인합니다.

ex)  @RequestBody 의 대상 클래스가 byte[] 이면 ByteArrayHttpMessageConverter, 

String 이면 StringHttpMessageConverter, 객체는 MappingJackson2HttpMessageConverter

2. HTTP 요청의 Content-Type 미디어 타입을 지원하는지 확인합니다.

ex)  text/plain , application/json , */*

canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 반환합니다.

 

HTTP 응답 데이터 생성

컨트롤러에서 @ResponseBody , ResponseEntity, HttpEntity 로 값이 반환된다.

메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출합니다.

1. 대상 클래스 타입을 지원하는지 확인합니다.

예) return의 대상 클래스 ( byte[] , String , HelloData )

2. HTTP 요청의 Accept 미디어 타입을 지원하는지 확인합니다.(더 정확히는 @RequestMapping 의 produces )

예) text/plain , application/json , */*

canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성합니다.

 

 스프링 MVC 구조에서 HttpMessageConverter 가 사용되는 시점

 

 

클라이언트의 HTTP 요청을 받을 때는 대부분 @RequestMapping 을 사용해

애노테이션 기반의 컨트롤러를 사용합니다.

이 때  @RequestMapping 을 처리하는 핸들러 어댑터는 RequestMappingHandlerAdapter 인데요.

 

 

RequestMappingHandlerAdapter 동작 방식

RequestMappingHandlerAdapter 는 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는

다양한 파라미터의 값(객체)을 생성합니다. 생성이 완료되어 파라미터의 값이 준비되면 컨트롤러를

호출하면서 값을 넘겨줍니다. 스프링에서는 30개가 넘는 ArgumentResolver 를 기본으로 제공하는데요.

이렇게 많은 ArgumentResolver 가 존재해 애노테이션 기반의 컨트롤러는 HttpServletRequest , Model 은

물론이고, @RequestParam , @ModelAttribute 같은 애노테이션 그리고 @RequestBody , HttpEntity 까지

매우 다양한 파라미터를 사용할 수 있었던 것입니다.

 

 

 ArgumentResolver 의 동작 방식

 ArgumentResolver 의 정확한 이름은 HandlerMethodArgumentResolver 이지만 줄여서 

 ArgumentResolver 로 부르고 있습니다. ArgumentResolver 의 supportsParameter() 메서드를 호출해서

해당 파라미터를 지원하는지 체크하고, 지원하면 resolveArgument() 메서드를 호출해서 실제 객체를 생성합니다.

그리고 이렇게 생성된 객체가 컨트롤러 호출시 넘어가게 됩니다.

 

ReturnValueHandler

ArgumentResolver 와 비슷한데, 이것은 응답 값을 변환하고 처리합니다.

컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler 덕분입니다.

스프링은 10여개가 넘는 ReturnValueHandler 를 지원합니다.

ex) ModelAndView , @ResponseBody , HttpEntity , String

 

 

ReturnValueHandler 의 동작 방식

ReturnValueHandle 의 정확한 이름은 HandlerMethodReturnValueHandler 이지만 줄여서

ReturnValueHandle 라 부릅니다.

 

 

ArgumentResolver 와 ReturnValueHandle 를 구현한 HttpEntityMethodProcessor 코드입니다.

요청 파라미터 타입이 HttpEntity, RequestEntity 이거나

응답 값 타입이 HttpEntity, RequestEntity 이면 호출됩니다.

 

 

ArgumentResolver 와 ReturnValueHandle 를 구현한 RequestResponseBodyMethodProcessor 코드입니다.

요청 파라미터 타입에 @RequestBody 이 붙어있거

응답 값 타입에 @ResponseBody 붙어있으면 호출됩니다.

 

 

 

HTTP 메시지 컨버터 위치

요청

컨트롤러 파라미터에 @RequestBody 가 있다면 ArgumentResolver 중에

RequestResponseBodyMethodProcessor 가 호출되어

HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성합니다.

컨트롤러 파라미터에 HttpEntity, RequestEntity 가 있다면 ArgumentResolver 중에

HttpEntityMethodProcessor 가 호출되어 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성합니다.

 

응답

반환 타입에 @ResponseBody 가 있다면 ReturnValueHandler  중에

RequestResponseBodyMethodProcessor 가 호출되어

HTTP 메시지 컨버터를 사용해서 응답 결과를 생성합니다.

컨트롤러 파라미터에 HttpEntity, ResponseEntity 가 있다면 ReturnValueHandler  중에

HttpEntityMethodProcessor 가 호출되어 HTTP 메시지 컨버터를 사용해서 응답 결과를 생성합니다.

 

예제를 살펴보겠습니다.

 

 HttpEntity

 

@Controller
public class RequestController {
    
    @PostMapping("/request")
    public HttpEntity<String> httpmessagerequest(HttpEntity<String> student) {

        return new HttpEntity<>(student.getBody());
    }
}

 

Stirng 타입 데이터 요청을 받아보았습니다.

컨트롤러 메서드 파라미터에  HttpEntity를 붙여주고 요청 값의 타입을 제네릭안에 넣어줍니다.

응답 할 때도 반환 타입을 HttpEntity 으로 해주면 HTTP 메시지 바디에 값을 담아 응답하게 됩니다.

HttpEntity 는 요청 바디, 헤더의 내용을 편리하게 조회할 수 있지만 요청 파라미터를 조회하는 기능은 없습니다.

 

@Controller
public class RequestController {

    @PostMapping("/request")
    public HttpEntity<Student> httpmessagerequest(HttpEntity<Student> student) {

        Student student1 = new Student();
        student1.setName(student.getBody().getName());
        student1.setGrade(student.getBody().getGrade());

        return new HttpEntity<>(student1);
    }

    @Data
    static class Student{

        String name;
        int grade;
    }
}

 

 

이번엔 JSON 데이터 형식으로 요청해봤습니다.

JSON 데이터에서 속성과 이름이 같은 필드를 가진 클래스를 생성해

HttpEntity 제네릭에 넣어줘 요청 값을 받을 수 있게 했습니다.

응답할 때도 반환타입을 HttpEntity로 해주고 제네릭에 요청을 받을 때 지정한 클래스 타입을 넣어줍니다.

응답할 데이터는 요청을 받을 때 사용한 객체를 그대로 보내지는 못하고

따로 객체를 생성해 요청을 받을 때 사용한 객체에서 값을 꺼내

생성한 객체 필드에 넣어준 후 그 객체로 응답합니다.

이렇게 하면 메시지 컨버터가 응답 값을 자동으로 JSON 데이터 형식으로 변경해즙니다.

 

̱ RequestEntity, ResponseEntity

 

@Controller
public class RequestController {

    @PostMapping("/request")
    public ResponseEntity<String> httpmessagerequest(RequestEntity<String> student) {

        return new ResponseEntity<>(student.getBody(), HttpStatus.ACCEPTED);
    }
}

 

 

 

RequestEntity, ResponseEntity 클래스를 사용해봤습니다.

이 두 개의 클래스는 HttpEntity 를 상속하고 있습니다.

요청을 받을 때는 컨트롤러 메서드 파라미터 앞에 RequestEntity 를 붙이고

응답을 할 때는 반환 타입을 ResponseEntity 로 해주면 됩니다.

응답을 할 때 HttpEntity와의 차이점은 HTTP 상태 코드 무조건 설정해서 값을 보내야 합니다.

HttpEntity 는 HTTP 메시지의 헤더, 바디 정보를 가지고 있는데

ResponseEntity 는 여기에 더해서 HTTP 응답 코드를 설정할 수 있습니다.

 

@Controller
public class RequestController {

    @PostMapping("/request")
    public ResponseEntity<Student> httpmessagerequest(RequestEntity<Student> student) {

        Student student1 = new Student();
        student1.setName(student.getBody().getName());
        student1.setGrade(student.getBody().getGrade());

        return new ResponseEntity<>(student1, HttpStatus.ACCEPTED);
    }

    @Data
    static class Student{

        String name;
        int grade;
    }
}

 

JSON 으로 요청 할 때입니다

HttpEntity 처럼 RequestEntity 제네릭에 클래스 타입을 지정하고

응답할 때도 반환 타입 ResponseEntity 제네릭에 요청을 받을 때 지정한 클래스 타입을 넣어줍니다.

응답 데이터도 마찬가지로 따로 객체를 생성해 요청을 받을 때 사용한 객체에서 값을 꺼내

생성한 객체 필드에 넣어준 후 그 객체로 응답합니다.

HttpEntity 로 응답 한 것처럼 메시지 컨버터로 인해 JSON 으로 응답된 것을 확인할 수 있습니다.

 

 @RequestBody @ResponseBody

 

@Controller
public class RequestController {

    @ResponseBody
    @PostMapping("/request")
    public String httpmessagerequest(@RequestBody String student) {

        return student;
    }
}

 

@RequestBody, @ResponseBody 어노테이션을 사용해보았습니다.

요청을 받을 때는 컨트롤러 메서드 파라미터 앞에 @RequestBody 를 붙여주고  요청 값 타입을 써주면 됩니다.

이렇게하면 요청을 @RequestBody 로 HTTP 메시지 바디 데이터를 읽어 변수나 객체에 바로 주입해줍니다.

헤더 정보가 필요하다면 @RequestHeader 어노테이션을 사용하여 확인할 수 있습니다.

응답할 때는 컨트롤러 메서드 위에 @ResponseBody 를 붙여주고

반환타입은 반환할 값의 타입에 맞게 써주면 됩니다.

ResponseEntity 처럼 HTTP 응답 코드도 같이 보내주고 싶다면

메서드 위에 @ResponseStatus 어노테이션을 붙여 응답 코드를 설정할 수도 있습니다.

 

주의

@RequestParam 이나 @ModelAttribute 와 달리 @RequestBody 는 생략을 할 수 없습니다.

@RequestBody 를 생략한다면 기본 타입일 때는 @RequestParam 이 동작 클래스 타입이면

@ModelAttribute 가 동작하게 됩니다.

 

@Controller
public class RequestController {

    @ResponseBody
    @PostMapping("/request")
    public Student httpmessagerequest(@RequestBody Student student) {

        return student;
    }

    @Data
    static class Student{

        String name;
        int grade;
    }
}

 

JSON으로 요청 할 때입니다.

일반 문자열을 받을 때처럼 컨트롤러 메서드 파라미터에 @RequestBody 를 붙여주고

제네릭에 타입을 지정할 필요 없이 요청 값을 받을 클래스 타입을 써주면 객체로 매핑해 메시지를 수신합니다.

응답할 때도 컨트롤러 메서드 위에 @ResponseBody 를 붙여주고

반환 타입은 요청 값을 받을 때 클래스 타입으로 지정해준 후 요청 값을 받은 객체를 그대로 반환해줘도

객체 그 상태로 송신해 메시지 컨버터가 응답 값을 자동으로 JSON 데이터 형식으로 변경해즙니다.

 

@RequestBody 요청 JSON 요청 -> HTTP 메시지 컨버터 -> 객체

@ResponseBody 응답 객체 -> HTTP 메시지 컨버터 -> JSON 응답

 

이렇게 HTTP 메시지 바디 데이터를 송수신 하는 방식을 알아봤습니다.

코드를 보시면 알겠지만 스프링에서 제공해주는  @RequestBody, @ResponseBody 를 이용하는 방식이

가장 간단하고 편해 현재 가장 많이 사용하고 있습니다.

 

TIP.

만약 @Controller 가 붙은 클래스 안에 모든 컨트롤러 메서드의 응답 데이터가 HTTP 메시지 바디 형식이면

모든 컨트롤러 메서드 위에 @ResponseBody 를 붙여줄 필요 없이

클래스 위에 @RestController 를 붙여주면 됩니다.

@RestController 에 @Controller와 @ResponseBody 가 둘 다 붙어있어 

@Controller 처럼 클래스가 자동 빈 등록 대상도 되며

모든 컨트롤러 메서드에 @ResponseBody 를 자동으로 붙여줍니다.

 

@RestController
public class RequestController {

    @PostMapping("/request")
    public Student httpmessagerequest(@RequestBody Student student) {

        return student;
    }


    @Data
    static class Student{

        String name;
        int grade;
    }
}

 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController

 

댓글