Spring

[SpringMVC] HTTP 요청, 응답처리

ppusda 2022. 5. 23. 02:07

이번에는 어노테이션 기반 컨트롤러에서 요청을 처리하는 방법에 대해서 알아보려고한다.

이전에는 어떻게 처리했는지와 어노테이션 기반의 처리방법들을 살펴보자.

HttpRequest

헤더 처리

Servlet 이용

  • 먼저 HttpServlet를 이용한 방법이다.
@RestController
public class RequestHeaderController {
    @RequestMapping("/headers")
    public String headers(HttpServletRequest request, HttpServletResponse response){ 
        return "ok";
    }
}
  • HttpServletRequest, HttpServletResponse
    • 우리가 이전에도 사용해봤었던 요소이다.
    • 요청, 응답 정보에 대한 정보를 가져오거나 넣을 수 있다.
    • ex_) request.getHeader(), response.setHeader() .. 등등을 이용할 수 있다.

어노테이션 이용

  • 다음으로는 어노테이션을 이용한 방법이다.
@RestController
public class RequestHeaderController {
    @RequestMapping("/headers")
    public String headers(@RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value = "myCookie", required = false) String cookie)){ 
        return "ok";
    }
}
  • @RequestHeader
    • @RequestHeader(”host”) String Host 처럼 헤더 값을 가져와 사용할 수 있다.
    • 혹은 헤더 하나에 여러 변수가 들어올 떄 MultiValueMap을 사용할 수 있다.
    • MultiValueMap
      • Map이랑 유사한데, 하나의 키에 여러가지 값을 받아올 수 있다.
      • ex_) key1:[value1, value2, ...] 와 같은 형식
  • @CookieValue
    • 특정 쿠키를 조회할 수 있다.
    • 필수 값 여부를 required 속성으로 지정할 수 있다.

그외에도 여러가지 가져오는 방법들이 있다.

@RestController
public class RequestHeaderController {
    @RequestMapping("/headers")
    public String headers(HttpMethod httpMethod,
                          Locale locale,){ 
        return "ok";
    }
}

파라미터 처리

  • 다음에는 파라미터 처리에 대해서 알아보려고 한다.

Servlet 이용

  • 이번에도 먼저 HttpServlet를 이용한 방법이다.
@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
	String username = request.getParameter("username");
	int age = Integer.parseInt(request.getParameter("age"));
	log.info("username = {}, age={}", username, age);

	response.getWriter().write("ok");
}
  • 이전에 사용해봤던 것 처럼 request.getParameter() 를 이용하여 파라미터를 꺼내올 수 있다.

어노테이션 이용

  • 다음으로는 @RequestParam을 이용한 방법이다.
@ResponseBody // RestController와 같은 기능을함, 반환 값을 메시지에 바디에 넣는다.
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName,
                             @RequestParam(defaultValue ="-1") int age) {
	log.info("username={}, age={}", memberName, memberAge);
    return "ok";
} // @RequestParam => request.getParameter와 같은 효과이다.
  • @RequestParam() 을 이용해서 파라미터에 매핑 할 수 있다.
  • age 처럼 메서드 파라미터의 이름과 요청 파라미터의 이름이 같으면 생략이 가능하다.
    • 심지어는 @RequestParam 까지 생략이 가능하다.
  • 이용가능한 속성
    • required
      • true, false 값이 들어가며, 해당 파라미터가 필수 값인지를 설정할 수 있다.
      • 기본 값은 true이다.
    • default
      • default 값을 설정해줄 수 있으며, 값이 넘어오지 않을 때 기본 값으로 설정되게 된다.
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
	log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
	return "ok";
} 
  • 위처럼 Map으로 파라미터를 받아 처리 할 수도 있다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData){
	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
	log.info("heolloData={}" + helloData.toString());

	return "ok";
} // @ModelAttribute 를 이용하면 모델(Data) 객체를 생성하고 값을 자동으로 바인딩해준다.
  • @ModelAttribute를 이용해서 @Data 객체를 매핑할 수도 있다.
  • 참고로 Modelattribute도 생략이 가능한데, 스프링은 어노테이션 생략이 다음과 같은 매커니즘으로 어노테이션을 자동으로 붙혀준다.
    • String, Int, Integer 같은 단순 타입들은 @RequestParam
    • 그 외 나머지 것들 ex_) helloData 같은 것들은 @Modelattribute를 붙혀서 처리한다.
      • ArgumentResolver로 지정해둔 타입들은 제외된다.
      • 이에 대해서는 후에 자세히 알아보자.

메시지 바디 처리

  • 이번엔 PlainText, Json과 같은 메시지 바디를 처리하는 방법에 대해서 알아보자.

Servlet 이용

@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException{
	ServletInputStream inputStream = request.getInputStream();
	String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

	log.info("messageBody={}", messageBody);

	response.getWriter().write("ok");
}
  • 메시지 바디를 읽어올 때에는 InputStream을 이용하게 된다.
  • StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8) 을 이용해 UTF_8로 인코딩이 가능하다.
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer reponseWriter) throws IOException{
	String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

	log.info("messageBody={}", messageBody);

	reponseWriter.write("ok");
}
  • InputStream을 파라미터 자체로 받아서 처리할 수도 있다.
  • Json은 파싱을 위한 JsonMapper가 필요하다.
  • private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
	ServletInputStream inputStream = request.getInputStream();
	String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

	log.info("messagebody={}", messageBody);
	HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

	response.getWriter().write("ok");
}
  • 받아오는 방식은 동일하지만 ObjectMapper를 통해서 데이터를 가공해주어야한다.

HttpEntity 사용

  • HttpEntity를 사용해서 메시지 바디의 내용을 가져올 수도 있다.
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity){
	String messageBody = httpEntity.getBody();
	log.info("messageBody={}", messageBody);

	return new HttpEntity<>("ok");
}
  • 헤더와 바디정보를 편하게 조회할 수 있게 도와주는 기능을 한다.
  • 상속받은 RequestEntity, ResponseEntity를 통해 특별 기능을 사용할 수도 있다.
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> data){
	HelloData helloData = data.getBody();
	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

	return "ok";
} 
  • HttpEntity를 이용하면 Http 메시지 컨버터가 Http 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 자동 변환해준다.

어노테이션 사용

@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody){
	log.info("messageBody={}", messageBody);
	return "ok";
}
  • @RequestBody 어노테이션을 이용하여 메시지 바디의 내용을 그대로 가져올 수 있다.
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData){
	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

	return hellodata;
}
  • @RequestBody 또한 HttpEntity 와 같이 Http 메시지 컨버터가 작동하여 메시지 바디의 내용을 우리가 워하는 형식으로 변환해주게된다.
  • 당연히 반환 값에서도 Json 형식으로 출력되게 된다. (위 코드에서는 Json → 객체 → Json의 순서를 거치게 됨)
  • 참고로 @RequestBody는 생략이 불가능하다.
    • 만약 생략하게된다면 스프링의 매커니즘에 의해 @ModelAttribute가 붙게 된다.

HttpResponse

  • 응답은 크게 3가지의 종류로 나뉘는데 이에 대해서 살펴보자.

정적 리소스

  • 정적 리소스는 말그대로 정적 리소스를 제공하는 쓰인다.
  • 경로 자체가 url이 되며, 경로 상에 있는 파일들에 접근할 수 있다.
  • http://localhost:8080/basic/hello-form.html

뷰 템플릿 사용(동적)

  • 동적으로 뷰를 설정하는 방법을 말하며, 우리가 가장 많이 사용할 방법이다.

ModelAndView

@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1(){
	ModelAndView mav = new ModelAndView("response/hello")
				                .addObject("data", "hello!");

	return mav;
}
  • ModelandView를 통해서 뷰와 데이터 모두 설정하여 반환 값으로 넘기는 방법이다.

Model 이용

@RequestMapping("/response-view-v2")
public String responseViewV2(Model model){
	model.addAttribute("data", "hello!");
	return "response/hello";
}
  • Model을 이용하여 데이터를 설정하고, 반환 값으로는 뷰의 논리적 이름을 반환하는 방법이다.
  • 가장 명시적이며 많이 사용되는 방법이다.
  • 만약 반환 값이 없으면 @RequestMapping 에 설정된 경로로 설정된다.

HTTP 메시지 사용(API)

서블릿 이용

@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
	response.getWriter().write("ok");
}
  • 일반적으로 데이터를 넘길 때는 위와 같이 response를 이용하여 메시지 바디에 값을 담는다.

HttpEntity 이용

@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
	return new ResponseEntity<>("ok", HttpStatus.OK);
}
  • HttpEntity를 상속받은 ResponseEntity를 이용해서 메시지 바디에 값을 담을 수 있다.
    • 상태코드 까지 전달 가능
  • Json 형식의 데이터를 전달할 때는 다음과 같다.
@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);
    }
  • 이전에 설명했던 것 처럼 ReponseEntity는 원하는 포맷으로 자동으로 변환해준다.

@ResponseBody

@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
	return "ok";
}
  • @ResponseBody를 이용하여 메시지 바디에 값을 바로 담을 수 있다.
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2(){
	HelloData helloData = new HelloData();
	helloData.setUsername("userA");
  helloData.setAge(20);
	return helloData;
}
  • @ResponseBody도 원하는 포맷으로 자동으로 변환해준다.
  • 또한, @ResponseStatus()을 통해 설정할 수 없었던 상태코드도 설정해줄 수 있다.
  • 클래스 레벨에 @ResponseBody를 사용하면 전체 메서드에 적용되는데, 이를 대체하기 위한 것이 @RestController 이다.
  • @RestController = @Controller + @ResponseBody 인 것을 기억하자.

참고 -

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1