본문 바로가기

Advance I/Spring MVC

22.08.13

HTTP 요청 메시지 - JSON

서블릿을 사용할 때는 ObjectMapper를 사용하여 Json을 객체로 변환하였다.

스프링을 사용할 때도 같은 방식이 가능하다.

@RequestBody 객체 파라미터

HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.

HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환할 수 있다.

 

@RequestBody는 생략 불가능

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

 

String , int , Integer 같은 단순 타입 = @RequestParam

나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

 

즉, 직접 정의한 객체(API용)에 @RequestBody 를 생략하면 @ModelAttribute 가 적용된다.

HelloData data -> @ModelAttribute HelloData data

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

 

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

HttpEntity<T>

httpEntity.getBody() 하면 선언한 T 타입으로 객체를 변환하여 받아올 수 있다.

@ResponseBody

객체를 반환하면 HTTP 메시지 컨버터가 Json으로 변환하여 메시지 바디에 직접 넣어준다. 

물론 이 경우에도 HttpEntity 를 사용해도 된다.

 

정리

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

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

HTTP 응답 - 정적 리소스, 뷰 템플릿

요청에서 일부 다룬 내용 있음

 

서버에서 응답 데이터를 만드는 방법

  • 정적 리소스: 웹 브라우저에 정적인 HTML, css, js 등 파일을 그대로 제공
  • 뷰 템플릿: 웹 브라우저에 동적 HTML을 제공할 때 사용
  • HTTP 메시지: HTTP API를 제공할 때, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 직접 전달

정적 리소스

src/main/resources: 리소스를 보관, 클래스패스의 시작 경로

 

스프링 부트가 제공하는 정적 리소스 경로

src/main/resources 이하의 /static , /public , /resources , /META-INF/resources

 

ex. src/main/resources/static/basic/hello-form.html

=> http://localhost:8080/basic/hello-form.html 으로 접속

뷰 템플릿

뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다.

일반적으로 HTML을 동적으로 생성하는 용도로 사용하지만, 다른 것들도 가능하다.

 

스프링 부트가 제공하는 뷰 템플릿 경로

src/main/resources/templates

 

뷰 템플릿 호출

<String을 반환하는 경우> - View or HTTP 메시지

@ResponseBody 의 유무

  1. @ResponseBody 가 없으면 반환 문자열을 뷰의 논리 이름으로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 한다. 
  2. @ResponseBody 가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 문자가 입력된다. 

 

<Void를 반환하는 경우>

@Controller 를 사용하고, HttpServletResponse , OutputStream(Writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용 요청

URL: /response/hello

실행: templates/response/hello.html

참고로 이 방식은 명시성이 너무 떨어지고 이렇게 딱 맞는 경우도 많이 없어서, 권장하지 않는다.

Thymeleaf 스프링 부트 설정

라이브러리 추가

in build.gradle

  implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

 

스프링 부트가 자동으로 ThymeleafViewResolver 와 필요한 스프링 빈들을 등록한다.

그리고 다음 설정도 사용한다. 이 설정은 기본 값 이기 때문에 변경이 필요할 때만 설정하면 된다.

 

스프링 부트 설정

in application.properties

  spring.thymeleaf.prefix=classpath:/templates/

  spring.thymeleaf.suffix=.html

 

참고: 스프링 부트의 타임리프 관련 추가 설정 (페이지 안에서 thymeleaf 검색)

https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/appendix-applicationproperties.html#common-application-properties-templating

HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

 

참고: HTML이나 뷰 템플릿을 사용해도 HTTP 응답 메시지 바디에 HTML 데이터가 담겨서 전달된다. 여기서 설명하는 내용은 정적 리소스나 뷰 템플릿을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우를 말한다.

 

1. HttpServletResponse 객체 파라미터

HTTP 메시지 바디에 직접 전달

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

 

2. ResponseEntity 객체 반환

HttpEntity: HTTP 메시지의 헤더, 바디 정보 포함

HttpEntity를 상속받은 ResponseEntity 는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.

return new ResponseEntity<>(String/Object, HttpStatus.OK)

 

3. @ResponseBody + @ResponseStatus

view를 사용하지 않고 HTTP 메시지 컨버터를 통해서 반환한 문자나 객체를 HTTP 메시지 바디에 입력

ResponseEntity와 같은 방식

@ResponseStatus: HTTP 응답 코드 설정. 애노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다. 프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity 를 사용하면 된다.

 

@RestController

Rest API(HTTP API)를 만들 때 사용하는 컨트롤러 (@Controller+@ResponseBody)

@Controller 대신에 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody 가 적용되는 효과가 있다. 따라서 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다.

 

참고: @ResponseBody 는 클래스 레벨에 두면 전체 메서드에 적용되는데, @RestController 에노테이션 안에 @ResponseBody 가 포함

HTTP 메시지 컨버터

@ResponseBody 사용 원리

@ResponseBody 사용 시

  • HTTP의 BODY에 문자 내용을 직접 반환 (json도 문자)
  • viewResolver 대신에 HttpMessageConverter 가 동작
  • 기본 문자처리: StringHttpMessageConverter
  • 기본 객체처리: MappingJackson2HttpMessageConverter
  • byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있음

*스프링 MVC가 HTTP 메시지 컨버터를 적용하는 경우*

HTTP 요청: @RequestBody , HttpEntity(RequestEntity)

HTTP 응답: @ResponseBody , HttpEntity(ResponseEntity)

 

HTTP 메시지 컨버터 인터페이스

> org.springframework.http.converter.HttpMessageConverter

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

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

 

스프링 부트 기본 메시지 컨버터 

0 = ByteArrayHttpMessageConverter (-> byte)

1 = StringHttpMessageConverter (->String)

2 = MappingJackson2HttpMessageConverter (String<->Json)

(일부 생략)

 

스프링 부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입(요청/content-type) 둘을 체크해서 사용여부를 결정한다. 만약 만족하지 않으면 다음 메시지 컨버터로 우선순위가 넘어간다.

 

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

  • 클래스 타입: byte[] , 미디어타입: */* (모든 타입)
  • 요청 예) @RequestBody byte[] data
  • 응답 예) @ResponseBody return byte[] , 쓰기 미디어타입 application/octet-stream

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

  • 클래스 타입: String , 미디어타입: */*
  • 요청 예) @RequestBody String data
  • 응답 예) @ResponseBody return "ok" , 쓰기 미디어타입 text/plain

MappingJackson2HttpMessageConverter : application/json

  • 클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련
  • 요청 예) @RequestBody HelloData data
  • 응답 예) @ResponseBody return helloData , 쓰기 미디어타입 application/json 관련

HTTP 요청 데이터 읽기

HTTP 요청이 오고, 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용한다.

메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 를 호출한다.

  -대상 클래스 타입을 지원하는가. 예) @RequestBody 의 대상 클래스 ( byte[] , String , HelloData )

  -HTTP 요청의 Content-Type 미디어 타입을 지원하는가. 예) text/plain , application/json , */*

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

 

HTTP 응답 데이터 생성

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

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

  -대상 클래스 타입을 지원하는가. 예) return의 대상 클래스 ( byte[] , String , HelloData )

  -HTTP 요청의 Accept 미디어 타입을 지원하는가.(더 정확히는 @RequestMapping 의 produces )

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

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

요청 매핑 핸들러 어뎁터 구조

HTTP 메시지 컨버터는 스프링 MVC 어디쯤에서 사용되는 것일까?

 

모든 비밀은 애노테이션 기반의 컨트롤러을 호출하는, 그러니까 @RequestMapping 을 처리하는 핸들러 어댑터 RequestMappingHandlerAdapter (요청 매핑 헨들러 어뎁터)에 있다.

RequestMappingHandlerAdapter 동작 방식

 

ArgumentResolver

애노테이션 기반 컨트롤러가 실행되기 위해서는 모든 파라미터가 세팅되어야 한다.

애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다.

그리고 이렇게 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.

스프링은 30개가 넘는 ArgumentResolver 를 기본으로 제공한다.

 

참고: 가능한 파라미터 목록

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments

 

public interface HandlerMethodArgumentResolver { /* 줄여서 ArgumentResolver */
    boolean supportsParameter(MethodParameter parameter);
    @Nullable
    Object resolveArgument(MethodParameter parameter, 
                           @Nullable ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest, 
                           @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

 

ArgumentResolver 의 supportsParameter() 를 호출해서 해당 파라미터를 지원하는지 체크하고, 지원하면 resolveArgument() 를 호출해서 실제 객체를 생성한다.

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

 

원한다면 이 인터페이스를 직접 확장해서 원하는 ArgumentResolver 를 만들 수 있다. -> 로그인 처리 예제

 

ReturnValueHandler *HandlerMethodReturnValueHandler*

응답 값을 변환하고 처리한다.

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

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

예) ModelAndView , @ResponseBody , HttpEntity , String

 

참고: 가능한 응답 값 목록

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-type

HTTP 메시지 컨버터

요청의 경우

@RequestBody 와 HttpEntity 를 처리하는 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성

응답의 경우

@ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 들이 HTTP 메시지 컨버터를 호출해서 응답 결과를 생성

 

스프링 MVC는 @RequestBody @ResponseBody 가 있으면 RequestResponseBodyMethodProcessor (ArgumentResolver),

HttpEntity 가 있으면 HttpEntityMethodProcessor (ArgumentResolver) 를 사용한다.

 

확장

스프링은 다음을 모두 인터페이스로 제공한다. 따라서 필요하면 언제든지 기능을 확장할 수 있다.

-HandlerMethodArgumentResolver

-HandlerMethodReturnValueHandler

-HttpMessageConverter

스프링이 필요한 대부분의 기능을 제공하기 때문에 실제 기능을 확장할 일이 많지는 않다.

기능 확장은 WebMvcConfigurer 를 상속 받아서 스프링 빈으로 등록하면 된다. 실제 자주 사용하지는 않으니 실제 기능 확장이 필요할 때 WebMvcConfigurer 를 검색해보자.

'Advance I > Spring MVC' 카테고리의 다른 글

22.08.16  (0) 2022.08.16
22.08.15  (0) 2022.08.15
22.08.11  (0) 2022.08.11
22.08.10  (0) 2022.08.10
22.08.09  (0) 2022.08.09