ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring MVC (6) - 스프링 MVC 기본기능 (2) : Http Body
    Web/Spring 2023. 10. 14. 23:49

     

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

     

    스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다.

     

    > 정적리소스 : 웹 브라우저에 정적인 HTML,css,js를 제공할 때 

    > 뷰 템플릿 : 동적 페이지 제공

    > HTTP 메시지 : HTTP API를 제공하는 경우, HTTP 메시지 바디에 JSON 같은 형식으로 데이터 실어 보냄

     

    1.1 정적 리소스 

     

    - 스프링 부트는 클래스 패스의 다음 디렉토리에 있는 정적 리소스 제공

      /static,/public,/resources,/META-INF/resources 등

     

    - src/main/resources는 리소스를 보관하는 곳이자 클래스 패스 시작 경로이다.

      따라서 다음 디렉토리에 리소스 넣어두면 스프링 부트가 정적 리소스로 제공

     

    * 내장 톰캣의 경우 webapp같은 경로를 따로 제공하지 않음 

    * 빌드될때 최종적으로 java와 resources같은 곳에 빌드 

     

    1.2 뷰 템플릿 

     

    뷰 템플릿을 통해 HTML이 생성되고, 뷰가 응답으로 만들어짐 

    HTML을 동적으로 생성하는 용도로 사용 

     

    뷰 템플릿 경로:src/main/resources/templates

    //뷰 템플릿을 호출하는 3가지 방법 (컨트롤러에서)
    
    public ModelAndView responseViewV1() {
     ModelAndView mav = new ModelAndView("response/hello")
     .addObject("data", "hello!");
     return mav;
     }
     // ModelAndView 이용
    
    
     public String responseViewV2(Model model) {
     model.addAttribute("data", "hello!!");
     return "response/hello";
     }
     // String을 return해서 핸들러 어답터가 처리하도록 함 
     
     @RequestMapping("/response/hello")
     public void responseViewV3(Model model) {
     model.addAttribute("data", "hello!!");
     }
     // return값이 void이면, requestMapping된 주소를 뷰 경로로 추정 (권장x)

     

    - String을 반환하는 경우 

      @ResponseBody가 없으면, 리턴 값으로 뷰 리졸버가 실행되어 뷰를 찾고 랜더링

      @ResponseBody가 있으면, 뷰 리졸버 실행하지 않고, HTTP 메시지 바디에 직접 문자 입력

     

    - void 반환하는 경우 

      @Controller를 사용하고, Response, OutputStream같은 HTTP 메시지 바디를 처리하는 파라미터가 없다면,

    뷰이름으로 추정해서 사용

     

    - HTTP 메시지 

     @ResponseBody 혹은 HttpEntity를 사용하면, 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 응답 데이터 출력 가능하다. 

     

    *Thymeleaf 사용할 때 prefix와 subfix고치기

    application.properties
    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.suffix=.html

     

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

     

    - HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 한다. 

    - HTTP 메시지 바디에 JSON같은 형식으로 데이터 실어 보낸다. 

     

    @Slf4j
    @Controller
    //@RestController
    public class ResponseBodyController {
     @GetMapping("/response-body-string-v1")
     public void responseBodyV1(HttpServletResponse response) throws IOException 
    {
     response.getWriter().write("ok");
     }
     /**
     * HttpEntity, ResponseEntity(Http Status 추가)
     * @return
     */
     @GetMapping("/response-body-string-v2")
     public ResponseEntity<String> responseBodyV2() {
     return new ResponseEntity<>("ok", HttpStatus.OK);
     }
     @ResponseBody
     @GetMapping("/response-body-string-v3")
     public String responseBodyV3() {
     return "ok";
     }
     @GetMapping("/response-body-json-v1")
     public ResponseEntity<HelloData> responseBodyJsonV1() {
     HelloData helloData = new HelloData();
     helloData.setUsername("userA");
     helloData.setAge(20);
     return new ResponseEntity<>(helloData, HttpStatus.OK);
     }
     @ResponseStatus(HttpStatus.OK)
     @ResponseBody
     @GetMapping("/response-body-json-v2")
     public HelloData responseBodyJsonV2() {
     HelloData helloData = new HelloData();
     helloData.setUsername("userA");
     helloData.setAge(20);
     return helloData;
     }
    }

     

    - responseBodyV1 

      > 서블릿과 유사 -> response객체사용

     

    - responseBodyV2

      > ResponseEntity는 HttpEntity 상속받음 (HttpEntity 메시지 헤더,바디 정보를 담고있음) 

         ResponseEntity는 HTTP응답코드 설정할 수 있음 

       

    - responseBodyV3

     > @ResponseBody를 사용하면, HTTP 메시지 컨버터를 통해 HTTP 메시지를 직접 입력할 수 있다.

        (ResponseEntity도 동일한 방식으로 작동한다고 함)

     

    - responseBodyJsonV1

      ResponseEntity를 반환 -> HTTP 메시지 컨버터 -> JSON 변환 

     

    - responseBodyJsonV2

      @ResponseStatus 애노테이션을 사용해서 상태설정 

      단, HTTP 응답코드를 동적으로 수정하고 싶으면 ResponseEntity 사용하면됨

     

    @RestController

        컨트롤러에 모두 @ResponseBody가 적용됨 -> HTTP 메시지 바디에 직접 데이터 입력 

        RestAPI만들때 사용하는 컨트롤러임  

     

    *HttpEntity는 response,request처럼 http메시지 자체에 접근할 수 있음

    (wrapprt임 @RequestBody에 사용한 경우 get()메서드 해서 뽑아야함)

     

     

    3.메시지 컨버터 

     

     

    출처: 스프링 MVC(1) - 김영한

     

    - @ResponseBody를 사용

          HTTP BODY에 문자 내용을 직접 반환 

          viewResolver 대신에 HttpMessageConverter가 동작함 

          이때 기본문자StringHttpMessageConverter가 작동 

                  기본 객체MappingJackson2HttpMessageConverter사 동작 

           byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있음 

     

    * 응답의 경우 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 

       HttpMessageConverter가 선택됨 

     

     3.1 스프링 MVC는 다음 경우에 HTTP 메시지 컨버터를 적용한다.

     

     HTTP 요청: @RequestBody, HttpEntity(RequestEntity)  > 컨트롤러 호출 이전에 컨버터 작동함 

     HTTP 응답: @ResponseBody, HttpEntity(ResponseEntity)

     

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

    package org.springframework.http.converter;
    public interface HttpMessageConverter<T> {
    
      boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
      boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
      List<MediaType> getSupportedMediaTypes();
      
      T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
      void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
                 throws IOException, HttpMessageNotWritableException;
    }

     

    HTTP 메시지 컨버터는 요청과 응답 둘 다 사용됨

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

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

     

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

    0 = ByteArrayHttpMessageConverter
    1 = StringHttpMessageConverter 
    2 = MappingJackson2HttpMessageConverter

     

    - 스프링 부트는 다양한 메시지 컨버터를 제공함

    - 클래스 타입과 미디어 타입을 체크해서 사용여부를 결정한다. 

       조건을 만족하지 않으면, 다음 메시지 컨버터로 우선순위 넘어감 

     

    * 클래스 타입은 return 혹은 매개변수로 등록된 클래스 타입을 의미 

    * 미디어 타입은 요청에 타입, 쓰기 미디어 타입은 응답 타입 

    * 요청이던 응답이던 HTTP MessageBody에 데이터를 넣을꺼면 미디어 타입 무조건 지정해야함 

    * content-type <=> 미디어 타입 

     

    > ByteArrayHttpMessageConverter: byte[] 데이터 처리함 

       클래스타입: byte[], 미디어 타입 */*  (Content-type)

       요청 @RequestBody byte[] data

       응답 @ResponseBody return byte[] + 쓰기 미디어타입 application/octet-stream (Accpet, produces)

     

    > StringHttpMessageConverter : String 문자로 데이터 처리함 

       클래스 타입 : String, 미디어 타입: */* (Content-type)

       요청 @RequestBody String data

       응답 @ResponseBody return "string" + 쓰기 미디어타입 text/plain (Accpet, produces)

     

    > MappingJackson2HttpMessageConverter : json관련

       클래스 타입 : 객체 혹은 HashMap 미디어 타입 : application/json 관련  (Content-type)

       요청 @RequestBody HelloData data

       응답 @ResponseBody return helloData + 쓰기 미디어 타입 application/json (Accpet, produces)

     

     

     

    3.2 HTTP 요청 데이터 읽기 

     

    - HTTP 요청이 오고, 컨트롤러에서 @RequestBody or HttpEntity 사용

     

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

        > 대상 클래스 타입 지원하는가 (해당 클래스로 컨버팅 가능 한가?

        > HTTP 요청의 Content-Type 미디어 타입을 지원하는가 (해당 미디어 타입의  메시지 자체 읽을 수 있는가?)

     

    - canRead() 조건을 만족하면 read()를 호출해서 객체를 생성하고 반환함  

     

    3.3 HTTP 응답 데이터 읽기 

     

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

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

       > 대상 클래스 타입을 지원하는가? (해당 클래스를 컨버팅 가능 한가?

       > HTTP 요청의 Accept 미디어 타입을 지원하는가 (해당 미디어 타입으로 메시지를 작성 할 수  있는가?)

           (정확히는 @RequestMapping의 produces -> produces지정지 확인, 아니면 Accept확인

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

     

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

         

    출처: 스프링 MVC (인프런) 김영한

     - 메시지 컨버터는 요청 매핑 핸들러 어뎁터 안에서 작동한다. 

     - 핸들러 어뎁터는 기본적으로 컨트롤러의 argument를 확인해서 필요한 매개변수 ArgumentResolver 호출해서 만들고

        모든 매개변수가 준비되면, 그때 컨트롤러를 호출한다.

     

    4.1 ArgumentResolver 

     

    다양한 파라미터를 매개변수로 받을 수 있는 이유는 위 덕분임 

    ArgumentResolver 호출해서 컨트롤러가 필요로하는 다양한 파라미터 값(객체)생성

    기본적으로 30개가 넘는 ArgumentResolver를 제공함 

     

    public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter parameter);
    @Nullable
    Object resolveArgument(
    MethodParameter parameter, 
    @NullableModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, 
    @Nullable WebDataBinderFactory binderFactory) throws Exception;
    }

     

    - 동작방식 : supportsParameter() 호출해서 해당 파라미터 지원하는지 체크 

                        지원하면, resolveArgument()를 호출해서 실제 객체를 생성함 

                        생성된 객체가 컨트롤러 호출시 넘어감 

     

    * 위 인터페이스를 확장해서 원하는 ArgumentResolver를 만들 수도 있다.

     

    4.2 ReturnValueHandler 

     

    - 컨트롤러에서 String으로 뷰이름 반환해도 동작하는 이유는 ReturnValueHandler덕분임

    - 스프링은 10개가 넘는 ReturnValueHandler를 지원함 

    - 컨트롤러의 반환 값을 변환함 

     

    4.3 HTTP 메시지 컨버터 

     

    출처: 스프링 MVC (인프런) 김영한

     - HTTP 메시지 컨버터를 사용하는 @RequestBody도

       컨트롤러가 필요로 하는 파라미터의 값에 사용된다.

     - @ResponseBody의 경우도 컨트롤러 반환 값을 이용한다. 

     

    요청의 경우 : @RequestBody를 처리하는 ArgumentResolver사 있고,

                          HttpEntity를 처리하는 resolvert가 있다. 

                          이 Resolver들이 HTTP 메시지 컨버터를 사용해서 필요한 순간에 객체를 생성하는 것임 

     

    응답의 경우 : @ResponseBody와 HttpEntity를 처리하는 ReturnValueHandler가 있고 여기서 HTTP 메시지 컨버터를 호출함 

     

     

    확장 : 스프링은 다음 인터페이스를 모두 제공함 > 따라서 필요하면 확장가능 

    HandlerMethodArgumentResolver
    HandlerMethodReturnValueHandler
    HttpMessageConverter
    
    //실제로 확장할 일은 많지 않음 WebMvcConfigurer 검색해보자 (확장하고 싶으면)

     

    정리하자면, 요청 파라미터는 ArgumentResolver선에서 해결 

                        메시지 바디를 읽어서 객체를 생성해야할 때, ArgumentResolver가 컨버터를 사용해서 바디의 내용을 읽고

                        객체 변환해서 반환함 -> 핸들러 어답터는 이 값을 받아서 핸들러에 전달 하며 호출 

                        응답도 비슷한 과정 HTTP 메시지를 Body에 직접 입력할 필요가 있을 때 (정적 or 뷰템플릿 없이)

                         메시지 컨버터에 도움을 받음

     

    메시지 컨버터의 역할은 HTTP 요청 메시지 본문과 HTTP 응답 메시지 본문을 통째로 메시지로 다루어준다.

     

    *스프링 MVC는 @RequestBody @ResponseBody 가 있으면

    RequestResponseBodyMethodProcessor (ArgumentResolver)

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

     

     

Designed by Tistory.