Web/MVC

EP7. HTTP 응답 데이터 처리

HTTP 응답 데이터를 만드는 방법에는 세가지가 있다.

  1. 정적 리소스
  2. 뷰 템블릿 사용
  3. HTTP 메세지 사용

 

정적리소스


웹 브라우저에 정적인 HTML, CSS, JS를 제공할 때는 정적 리소스를 사용한다.

 

스프링부트 상에서는

src/main/resources/static

위치에 해당하는 부분이다.

 

 

URL경로 상에서

/index.html

/basic/hello-form

으로 접근하면 정적리소스 (src/main/resources/static)에서 해당 경로를 읽는다./p>

(참고로 static/index.html 은 자동으로 웰컴페이지로 등록이 된다.)

 

 

 

 

 

 

 

 

뷰 템플릿 사용


웹 브라우저에 동적인 HTML 을 제공할 때 뷰 템플릿을 사용한다.

 

스프링 부트 상에서는

src/main/resources/templates/

에 해당하는 부분이다.

 

HTTP 응답으로 뷰 템플릿을 전달하기 위해서는 Controller에서 보내야 한다.

 

package hello.springmvc.basic.response;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ResponseViewController {

    @RequestMapping("/response-view-v2")
    public String reponseViewV2(Model model) {
        model.addAttribute("data", "hello!");
        return "response/hello";
    }

}

 

뷰 템플릿을 사용할 때는 HTML에 데이터를 전달하여 동적으로 변할때 사용한다.

그래서 Model을 파라미터로 받아 데이터를 담고, 뷰 템플릿의 경로를 (templates 다음부터 "response/hello") 반환하면 된다.

 

 

 

주의

뷰 템플릿을 반환할때 해당 메서드에 @ResponseBody 애노테이션을 사용하면 안된다.

@ResponseBody은 반환값을 HTTP Body에 직접 쓰기 위해 사용하는 애노테이션이다.

 

 

 

정리

  뷰 템플릿 HTTP Body 직접
반환 데이터형 String o o
@ResponseBody x o

 

 

 

 

 

 

 

 

 

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


 

 

HTTP Body에 텍스트를 전달

 

@ResponseBody 애노테이션을 사용하면 반환값을 그대로 Response Body에 적는다

반환값을 String 으로 주면 그대로 Body에 적혀서 전달된다.

    @ResponseBody
    @GetMapping("/response-body-string-v3")
    public String responseBodyV3() {
        return "ok";
    }

 

 

HTTP Body에 JSON 전달

 

두가지 버전이 있다.

 

첫번째 버전

ResponseEntity<객체> 사용하기

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

 

ResponseEntity에 전달하고 싶은 객체를 담아 반환하면 스프링이 JSON으로 변환해준다.

이때 상태코드도 지정할 수 있다.

 

 

두번째 버전

ResponseEntity를 사용하지않고 객체를 그대로 반환한다.

    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @GetMapping("/response-body-json-v2")
    public HelloData responseBodyJsonV2() {
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);

        return helloData;
    }

객체를 그대로 반환해도 스프링이 JSON으로 변환해준다.

첫번째 방법과의 차이점은 상태코드를 지정해주지 못하므로 @ResponseStatus를 이용해 상태코드를 지정한다.

 

첫번째 방법은 동적으로 상태코드를 지정할 수 있지만,

두번째 방법은 애노테이션으로 상태코드를 박아버리기 때문에 동적으로 지정할 수 없다.

 

 

 

 

@RestController

메소드마다 @ResponseBody를 적어주기 귀찮다.

 

스프링에서는 클래스레벨에 @ResponseBody를 지정할 수 있다.

 

그리고 

@RestController = @ResponseBody + @Controller

이기 때문에

 

@ResponseBody

@Controller

 

대신에

 

@RestController

하나 박아주면 된다.

 

 

package hello.springmvc.basic.response;

import hello.springmvc.basic.HelloData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@RestController
public class ResponseBodyController {

    @GetMapping("/response-body-string-v1")
    public void responseBodyV1(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }

    @GetMapping("/response-body-string-v2")
    public ResponseEntity<String> responseBodyV2() throws IOException {
        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;
    }
}

 

 

 

 

 

 

HTTP 메시지 컨버터 동작과정

 

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

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 요청 데이터 읽기

 

  1. Http 요청이 오고, 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용한다. -> 진행
  2. canRead()를 호출한다.
    - 대상 클래스 타입을 지원하는가 (ex. @RequestBody의 대상클래스 : byte[], String, HelloData)
    - HTTP요청의 Content-type 미디어 타입을 지원하는가 (ex. text/plain, application/json, */*)
  3. canRead를 만족하면 read()를 호출해 객체를 생성하고 컨트롤러에 반환한다.

 

 

 

HTTP 응답데이터 생성하기

 

  1. 컨트롤러에서 @ResponseBody, HttpEntity 로 값이 반환된다. -> 진행
  2. canWrite() 를 호출한다.
    - 대상 클래스 타입을 지원하는가 (ex. return 의 대상 클래스 : byte[], String, HelloData)
    - HTTP 요청의 Accept 미디어 타입을 지원하는가.
    (더 정확히는 @RequestMapping의 produces)
    (ex. text/plain, application/json, */*)
  3. canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.

 

 

 

미디어 타입 지원하는지 검사시 차례로

  1. ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다. 
    클래스 타입: byte[] , 미디어타입: */* , 
    요청 예) @RequestBody byte[] data 
    응답 예) @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream 
  2. StringHttpMessageConverter : String 문자로 데이터를 처리한다. 
    클래스 타입: String , 미디어타입: */* 
    요청 예) @RequestBody String data 
    응답 예) @ResponseBody return "ok" 쓰기 미디어타입 text/plain 
  3. MappingJackson2HttpMessageConverter : application/json 
    클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련 
    요청 예) @RequestBody HelloData data 
    응답 예) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련

 

 

 

예시)

StringHttpMessageConverter

바이트 -> 아님 -> String 맞음 -> 미디어타입 */* 이니까 됨. 그래서 StringHttpMessageConverter 동작

content-type: application/json
@RequestMapping
void hello(@RequetsBody String data) {}

 

 

 

 

MappingJackson2HttpMessageConverter

바이트 -> 아님 -> String -> 아님 -> 객체나 HashMap임 -> json임 -> MappingJackson2HttpMessageConverter 동작

content-type: application/json
@RequestMapping
void hello(@RequetsBody HelloData data) {}

 

 

 

 

?

바이트 -> 아님 -> String -> 아님 -> 객체나 HashMap임 -> json 아님 -> 동작 안함

@RequestMapping
void hello(@RequetsBody HelloData data) {}

 

 

 

 

 

 

 

HTTP 메시지 컨버터의 위치

 

요청 매핑 핸들러 구조

컨트롤러에서는 아주 다양한 매개변수를 받는다.

그럼 이 매개변수들은 스프링에서 어떻게 제공되는 것일까?

 

 

 

일단, 요청 매핑 핸들러 어댑터는 핸들러(컨트롤러)를 호출하는 부분에 있다.

 

 

 

그리고, 핸들러를 호출하기 전에 Argument Resover를 호출해서 컨트롤러가 사용하는 파라미터(객체)를 생성한다.

 

 

 

 

 

ReturnValueHandler

컨트롤러에서 반환할때도 어떨땐 ModelAndView를 반환하고, 어떨 땐 String을 반환하고, @ReponseBody가 붙은 String은 http body에 썼었다.

 

이렇게 컨트롤러의 return값이 달라도 스프링에서 각자 다르게 동작하게 하는 이유도 ReturnValueHandelr 덕분이다.

 

 

 

 

Http 메시지 컨버터의 위치

 

 

HTTP 메시지 컨버터의 위치를 알고싶은데 왜 요청매핑 핸들러 구조를 먼저 살펴봤을까?

 

ArgumentResorver

  • @RequestBody를 처리하는 ArgumentResorver
  • Http Entity를 처리하는 ArgumentResorver -> 여기에 Http 메시지 컨버터가 존재한다.

 

 

'Web > MVC' 카테고리의 다른 글

EP9. PRG (Post/Redirect/Get) 패턴  (0) 2021.04.01
EP8. Thymeleaf 타임리프  (0) 2021.04.01
EP6. HTTP 요청 데이터 처리  (0) 2021.03.29
EP5. Spring MVC 기본 기능 (요청 매핑)  (0) 2021.03.25
EP4. 스프링 MVC 구조  (0) 2021.03.25