SPRING/Spring MVC

[SpringMVC] HTTP 요청 메시지 - TEXT, JSON

IT록흐 2023. 8. 12. 00:08
반응형

 

 

[SpringMVC] 요청(Request)의 종류 ( GET, POST, JSON )

[SpringMVC] 웹서비스에서 Request(요청)가 처리되는 원리 ( + Servlet ) JAVA Runtime Enviroment(JRE)는 하나의 프로세스, 다수의 스레드 환경이다. JVM은 하나의 프로세스로 동작하고 다수의 스레드로 병렬처리

lordofkangs.tistory.com

 

 

 

지난 포스팅에서 요청데이터가 전달되는 종류를 알아 보았다. 

 

GET 방식은 Header에 담기고 POST, JSON, TEXT는 BODY에 담겨 전달된다. GET과 POST 방식은 담기는 위치만 다를뿐, 전달되는 데이터 형식이 같다. 그리고 HttpServletRequest의 getParameter 메소드로 요청 데이터가 추출되는 것도 동일하다.

 

 

[SpringMVC] HTTP 요청 파라미터 - @RequestParam

[SpringMVC] 요청(Request)의 종류 ( GET, POST, JSON ) [SpringMVC] 웹서비스에서 Request(요청)가 처리되는 원리 ( + Servlet ) JAVA Runtime Enviroment(JRE)는 하나의 프로세스, 다수의 스레드 환경이다. JVM은 하나의 프로

lordofkangs.tistory.com

 

[SpringMVC] HTTP 요청 파라미터 - @ModelAttribute

[SpringMVC] HTTP 요청 파라미터 - @RequestParam [SpringMVC] 요청(Request)의 종류 ( GET, POST, JSON ) [SpringMVC] 웹서비스에서 Request(요청)가 처리되는 원리 ( + Servlet ) JAVA Runtime Enviroment(JRE)는 하나의 프로세스, 다

lordofkangs.tistory.com

 

이전 포스팅에서 다룬 @RequestParam이나 @ModelAttribute 어노테이션은 HttpServletRequest의 getParameter 메소드로 추출된 데이터를 파라미터로 전달하라는 의미를 담은 어노테이션이다. 자세한 내용은 위 포스팅을 참고하면 된다. 

 

그렇다면 getParameter로 추출되지 않는 JSON, TEXT 같은 데이터는 어떻게 가져와야 할까?

 

 

 

 

지난 포스팅에서 JSON데이터가 JAVA 환경으로 추출되는 과정을 다루어 보았다. JSON 데이터는 getParameter 같은 메소드가 제공되지 않기에, 먼저 Body 영역의 데이터를 InputStream으로 가져와야 한다. Stream 객체로 가져온 데이터는 이진데이터(BinaryData) 이다. 이를 UTF-8로 인코딩하고 문자열로 변환하면 JSON 데이터를 표현한 문자열이 된다. 이를 ObjectMapper를 통해 JAVA객체로 변환하면 JAVA 환경에서 JSON 데이터를 사용할 수 있게 된다. 

 

이처럼 BODY 영역에 담긴 데이터는 InputStream으로 이진데이터를 가져와 인코딩하고 변환하는 과정이 필요하다. 

 

Text 데이터

 

Controller

@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
    ServletInputStream inputStream = request.getInputStream(); // InputStream
    String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); //InputStream으로 가져온 이진데이터 인코딩하고 문자열로 변환하기
    log.info("message = {}",messageBody);

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

 

Request가 제공하는 InputStream 객체는 HTTP Body 영역에 담긴 이진데이터를 가져오는 역할을 한다. 이진데이터를 가져오면 StreamUtils가 제공하는 기능으로 UTF-8로 인코딩하고 문자열로 변환할 수 있다. 문자열로 변환된 Body 영역의 데이터는 JAVA환경에서 사용가능한 데이터가 된다. 

 

그런데 문제는 Controller가 직접 Request 객체에 의존하면 좋지 않다는 것이다. 

 

 

실제 SpringMVC는 위 그림 구조로 동작한다. Request가 들어오면 DispatcherServlet은 Request url과 매핑되는 Handler 객체를 탐색한다. 그리고 매핑되는 Handler를 실행하는 권한을 HandlerAdapter에 위임한다. 여기서 Handler가 바로 Controller이다. Controller는 필요한 데이터를 메소드의 파라미터로 요구한다. HandlerAdapter는 적합한 데이터를 Request 객체에서 추출하여 파라미터로 넘긴다. 그렇다면 HandlerAdapter에게 InputStream 객체를 요구해보자. 

 

Controller

@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer reponseWriter) throws IOException {
    String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    log.info("message = {}",messageBody);
    reponseWriter.write("OK");
}

 

Controller(Handler)가 HandlerAdapter에게 InputStream과 responseWriter를 요구하면, HandlerAdapter는 두 객체를 파라미터로 넘겨준다. 즉, HandlerAdapter가 request.getInputStream()를 대신 실행하여 파라미터로 넘겨주는 것이다. 그래서 위 코드와 같이, Controller는 InputStream을 그대로 가져와 인코딩하고 문자열로 변환할 수 있게 된다. 

 

그럼 이진데이터를 인코딩하고 문자열로 변환하는 과정도 축약해보자. 

 

Controller

@PostMapping("/request-body-string-v3") // HttpEntity 사용
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
    String messageBody = httpEntity.getBody(); // header와 body 데이터를 편리하게 사용가능
    log.info("message = {}",messageBody);
    return new ResponseEntity<String>("OK",HttpStatus.CREATED); //응답에도 사용 가능
}

 

Controller(Handler)가 HandlerAdapter에게 HttpEntity를 요구하면, 이진데이터는 변환과정없이 바로 문자열로 받을 수 있다. HttpEntity의 제네릭에 String으로 지정되어 있기에,  getBody 메소드는 HTTP Body 영역의 데이터를 문자열 형식으로 변환하여 제공한다. 그리고 별도의 ResponseWriter를 요구할 필요도 없어진다. HttpEntity로 반환하면 ResponseWriter 기능도 같이 한다. 

 

하지만 이 또한   String messageBody = httpEntity.getBody(); 같은 코드가 발생한다. 어노테이션을 활용하여 코드를 줄여보자.

 

Controller

@ResponseBody
@PostMapping("/request-body-string-v4") // HttpEntity의 RequestBody를 읽어 바로 파라미터로 넘겨준다.
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
    log.info("message = {}",messageBody);
    return "OK";
}

 

Controller(Handler)가 HandlerAdapter에게 @RequestBody로 표시된 문자열을 요구하면, HandlerAdapter는 HTTP Body 영역의 데이터를 문자열로 변환하여 파라미터로 넘겨준다. 또한 HttpEntity가 사라졌으니 @ResponseBody 어노테이션을 사용하여 응답데이터를 전달한다. @ResponseBody에 대해서는 나중에 다루어 보겠다.

 

 

JSON 데이터

 

지금까지는 HTTP Body 영역의 데이터를 문자열로 가져와 보았다. 앞서 말했듯, JSON 데이터를 JAVA 환경에서 사용하려면 ObjectMapper 객체의 도움이 필요하다. 

 

Controller

@ResponseBody
@PostMapping("/request-body-json-v1")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {

    HelloData helloData = objectMapper.readValue(messageBody,HelloData.class);

    return "ok"; 
}

 

@RequestBody 어노테이션으로 HTTP Body 영역의 데이터를 문자열로 가져와 보았다. 그리고 문자열을 ObjectMapper로 하여 Json 데이터를 담은 HelloData 객체를 생성했다. 이로써 JSON 데이터를 JAVA 환경에서 사용가능하게 되었다. 

 

그런데 이 과정 또한 생략이 가능하다. 

 

Controller

    @ResponseBody
    @PostMapping("/request-body-json-v2")
    public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException { 
        
        log.info("username= {}, age={}", helloData.getUsername(),helloData.getAge());

        return "ok";
    }

 

@RequestBody로 선언된 파라미터에 특정 클래스 타입이 있다면  HandlerAdapter는 클래스의 객체를 생성하고 JSON 데이터를 주입하여 파라미터로 넘긴다. Spring WEB은 이 수준까지 자동화 되어 있는 것이다. 이런 자동화 과정은 HTTP 메시지 컨버터가 동작한 것이다. 관련해서는 나중에 다루어 보겠다. 추가로 @RequestBody는 절대 생략되면 안된다. 생략되면 Spring WEB은 디폴트로 @ModelAttribute로 인식하게 된다. 

 

Controller

    @ResponseBody
    @PostMapping("/request-body-json-v2")
    public HelloData requestBodyJsonV5(@RequestBody HelloData data) throws IOException {

        log.info("username= {}, age={}", data.getUsername(),data.getAge());
        return data; // HelloData를 return하면 컨버너가 Json으로 변환하여 반환
    }

 

JSON 데이터가 담긴 객체를 return한다면 자동으로 컨버터가 동작하여 JSON 데이터로 변환된다. 이에 관련해서도 나중에 다루어보겠다. 

 

Controller

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

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

 

HttpEntity 클래스의 getBody 메소드도 Json 객체를 반환할 수 있다. HttpEntity의 제네릭으로 클래스 타입을 지정하면, JSON 데이터를 지정된 클래스의 객체에 주입하여  생성한다. 

 

 

이런 원리로  Body 영역에 담겨 전달되는 TEXT와 JSON 데이터는 JAVA 환경에서 사용할 수 있게 변환되어 비즈니스 로직의 데이터로 사용되어 진다.

 

 


 

 

참고자료

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

www.inflearn.com

 

반응형