[Spring boot] HTTP API 만들기 - 클라이언트 Request 처리 @RequestParam 편

이동빈·2021년 10월 12일
4

이전 글의 내용을 바탕으로, 스프링에서 사용할 수 있는 4가지 어노테이션에 대해 다양한 예제를 다뤄보면서 각 차이점들을 알아보도록 하겠습니다.

이번 글은 @RequestParam에 대한 내용입니다.

@RequestParam

공식 문서에 나와있는 설명입니다. 해당 어노테이션을 사용하면 쿼리스트링 및 폼 데이터를 Controller 파라미터로 바인딩할 수 있다고 합니다.

You can use the @RequestParam annotation to bind Servlet request parameters (that is, query parameters or form data) to a method argument in a controller.

지원하는 Content-Type

설명을 보면, 쿼리스트링과 multipart/form-data 형식의 타입을 처리할 수 있다는 것을 알 수 있는데요,

간단한 API를 작성하고, 포스트맨을 통해 여러 타입의 Content-Type으로 API 호출을 해보겠습니다.

아래 코드는 파라미터로 보낸 값을 다시 return 하는 간단한 에코 API 입니다.

    @GetMapping("/api/request-param")
    public ResponseEntity<String> requestGet(@RequestParam String param) {
        return ResponseEntity.ok().body(param);
    }

    @PostMapping("/api/request-param/text")
    public ResponseEntity<String> requestPost(@RequestParam String param) {
        return ResponseEntity.ok().body(param);
    }

    @PostMapping("/api/request-param/file")
    public ResponseEntity<String> requestFile(@RequestParam MultipartFile param) {
        return ResponseEntity.ok().body(param.getOriginalFilename());
    }

GET, 쿼리스트링 전송

GET 방식으로 쿼리스트링을 전송하면 정상적으로 처리됩니다.

POST, multipart/form-data 전송

마찬가지로 텍스트, 파일 잘 됩니다.

POST, application/x-www-form-urlencoded 전송

쿼리스트링과 같이 key=value 형식이라 그런지, x-www-form-urlencoded 타입도 잘 되네요.

POST, text/plain 및 application/json

  1. 일반 텍스트
Content-Type: text/plain

HelloWorld
  1. key=value 포맷
Content-Type: text/plain

param=Hello World!
  1. json 포맷
Content-Type: application/json

{
	"param" : "Hello World!"
}

다양한 방법으로 시도해 보았으나, 아래와 같이 매개변수 param 이 없다는 오류메시지가 발생했습니다.

MissingServletRequestParameterException
"message": "Required request parameter 'param' for method parameter type String is not present"

@RequestParam 어노테이션으로는 해당 Content-Type은 해석이 불가능합니다.

@RequestParam의 활용

Required 설정

@RequestParam을 설정하면 기본적으로 매개변수가 필수(Required=true)로 설정됩니다. 따라서, 요청 시 해당 매개변수를 담지 않으면 위와 같은 MissingServletRequestParameterException 이 발생합니다.

매개변수가 필수가 아니라면 @RequestParam의 Required 옵션이나 java.util.Optional을 통해 선택사항으로 지정할 수 있습니다.

    @GetMapping("/api/request-param")
    public ResponseEntity<String> requestGet(@RequestParam Optional<String> param){
    	...
    }
    @GetMapping("/api/request-param")
    public ResponseEntity<String> requestGet(@RequestParam(required = false) String param) {
	...
    }

동일한 매개변수 여러개 처리

파라미터를 배열로 설정하면 동일한 매개변수 이름을 처리할 수 있습니다.

아래와 같이 파일을 처리할 수 있는 MultipartFile 을 배열로 받는다면

  public ResponseEntity<String> requestFile(@RequestParam List<MultipartFile> param) {
  	// 배열 형태로 파일을 여러개 받을 수 있음.
  }

아래와 같이 여러개의 파일을 첨부하여 전송할 수 있습니다.

여러개 매개변수 처리

서로 다른 이름을 가진 여러개의 매개변수를 처리하는 방법입니다.

Map<String, String> 형태의 파라미터로 설정하게 되면, 각 매개변수의 이름과 값이 key,value 형태로 map에 채워집니다.

    @PostMapping("/api/request-param/map")
    public ResponseEntity<?> requestFile(@RequestParam Map<String, String> param) {
        param.forEach((x, y) -> log.info("{}, {}", x, y));
        return ResponseEntity.ok().body(param);
    }

아래와 같이 요청하면,

각각 잘 받아와지는 것을 볼수 있습니다.

마찬가지로, Map<String, MultipartFile> 이런식으로 설정한다면 서로 다른 매개변수 이름의 파일을 가져올 수 있습니다.

    @PostMapping("/api/request-param/map")
    public ResponseEntity<?> requestFile(@RequestParam Map<String, MultipartFile> param) {
        param.forEach((x, y) -> log.info("{}, {}", x, y.getOriginalFilename()));
        return ResponseEntity.ok().body(param.size());
    }

그러나, Map<String, Object> 이런 형식으로 작성하여 아래와 같이 파일과 텍스트를 동시에 보내는 경우,

텍스트 값만 채워지게됩니다. 그 이유를 살펴보면

아래는 @RequestParam의 Map을 처리하는 RequestParamMapMethodArgumentResolver 클래스 코드의 일부입니다.

Map의 Value 값 클래스 타입을 확인하는데, MultipartFile, Part 나머지는 String 인 것을 확인할 수 있습니다. 즉, Value 클래스 타입으로 Object로 선언하게되면 else로 빠져서 String 값만 가져오게 되는것입니다.

만약, 여러개의 텍스트값과 파일을 동시에 받아오고 싶다면, 아래와 같이 String에 대한 Map과 MultipartFile에 대한 Map 2개를 파라미터로 받아오도록 구현하면 됩니다.

    @PostMapping("/api/request-param/map")
    public ResponseEntity<?> requestFile(@RequestParam Map<String, String> param, @RequestParam Map<String, MultipartFile> files) {
        param.forEach((x, y) -> log.info("{}, {}", x, y));
        files.forEach((x, y) -> log.info("{}, {}", x, y.getOriginalFilename()));
        return ResponseEntity.ok().body(param);
    }

같은 이름 + 다른 이름 매개변수 처리

동일한 매개변수가 여러개이면서 동시에 서로 다른 이름을 가진 매개변수도 여러개 인 경우입니다.

    @PostMapping("/api/request-param/map")
    public ResponseEntity<?> requestFile(@RequestParam MultiValueMap<String, String> param) {
        param.forEach((s, strings) -> log.info("{}, {}", s, strings));
        return ResponseEntity.ok().body(param);
    }

아래과 같이, 매개변수를 param1 2개, param2 2개로 요청하면

각각의 Key의 대한 Value가 배열로 들어오게 됩니다.

결론

@RequestParam 어노테이션으로 쿼리스트링 및 아래 Content-Type에 대해 처리 가능합니다.

  • multipart/form-data
  • application/x-www-form-urlencoded

파일업로드를 위해 클라이언트에서 multipart/form-data로 요청을 한다면, 스프링에서 @RequestParam 어노테이션을 이용하면 되고, 파일과 텍스트가 함께 포함된 데이터들도 큰 문제 없이 처리 가능합니다.

만약 multipart/form-data로 넘어오는 회원가입 요청이나, 파일이 포함된 게시글 등록과 같은 작업의 경우, Map 형태로 받아온 데이터들을 하나씩 추출하여 dto객체로 변환하는 작업이 필요합니다.

// 예시 코드
PersonDto personDto = PersonDto.builder()
        .age(param.get("age"))
        .name(param.get("name"))
        .build();

Person person = personDto.toEntity();

파라미터가 늘어나거나 자주 바뀌는 경우에 유지보수에 어려움이 있으므로, 회원가입 및 게시글 등록과 같은 DB와 연결되는 작업에 사용하기엔 부적합합니다. (이러한 작업에는 @ModelAttribute 어노테이션을 사용합니다.)

@RequestParam 어노테이션은 단순 검색이나 1~2개의 고정된 파라미터 혹은 단일 파일업로드 등 비교적 단순한 요청을 처리하는 용도로 사용하는 것이 가장 좋아보입니다.

profile
호기심 많은 개발자

0개의 댓글