[급하게 작성하는 RestTemplate -> WebClient 변환] - WebClient HTTP Method별 사용 (File Download(다운로드), Upload(업로드) 포함)

YouMakeMeSmile·2022년 2월 28일
1

해당 글은 spring-webmvc에서 외부 호출만을 위해 spring-webfluxWebClient를 사용하기 위한 설정에 대한 내용입니다. 이로 인해 모든 호출이 block으로 되어 있는점 참고 부탁드립니다.


1. Get

1.1. MediaType.APPLICATION_JSON

MSA 서버 통신간 가장 기본적인 조회 방식으로 앞서 WebClient BeanBaseUrl를 설정하였기 때문에 이후에 정의되는 path, PathVariable, RequestParamUriBuilder를 통해 쉽게 정의할 수 있으며 따로 UriEncoder로 문자열을 치환할 필요 없다.
이후 bodyToMono 메소드에 리턴 받을 ClassGeneric 형태를 정의한 후 block 메소드를 호출하면 블록킹 형식으로 결과를 원하는 Type으로 리턴받게 된다.

```java
sampleWebClient
        .get()
        .uri("/callee/get/path-variable", uriBuilder ->
                uriBuilder.path("/{pathVariable1}/{pathVariable2}")
                        .queryParam("requestParam1", "requestParam1")
                        .queryParam("requestParam2", 2)
                        .build("pathVariable1", 1))
        .accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .bodyToMono(new ParameterizedTypeReference<String>() {
        })
        .block();
```

1.2. MediaType.APPLICATION_OCTET_STREAM(파일 다운로드)

서버간의 File를 전달하기 위해서는 byte[] 또는 InputStream으로 응답하며 요청하는 WebClient에서는 요청HeaderacceptMediaType.APPLICATION_OCTET_STREAM를 설정한다.
byte[]InputStream의 차이점은 byte[]의 경우 응답을 하는 서버에서 파일의 byte[]를 모두 읽어서 응답을 하며 InpuStream의 경우 filebytechunked로 나누어 응답하여 메모리적인 차이가 존재한다.

이러한 응답에 상관없이 WebClient에서도 리턴 ClassInputStreamResourcebyte[]로 받을 수 있다. bodyToMono 를 통해 InputStreamResource로 결과를 받을 경우HeaderCONTENT_DISPOSITION에서 FileName도 가져올수 있으나 InputStream은 한번만 읽을 수 있다. byte[]로 응답을 받을 경우에는 FileName을 가져오기 위해서는 exchangeToMono에서 toEntity를 통해 ResponseEntity를 통해서 Header에서 FileName를 가져와야 한다.

    // 파일 다운로드 EndPoint
    @GetMapping("/callee/get/image/{id}")
    public ResponseEntity<byte[]> calleeJson(
            @PathVariable String id) throws IOException {
        ClassPathResource resource = new ClassPathResource("한글명.png");
        InputStreamResource inputStreamResource = new InputStreamResource(resource.getInputStream(), "test.png");
        ContentDisposition build = ContentDisposition.attachment().filename(resource.getFilename(), Charset.defaultCharset()).build();
        return ResponseEntity
                .ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .header(HttpHeaders.CONTENT_DISPOSITION, build.toString())
                .body(inputStreamResource.getInputStream().readAllBytes());
    }
    InputStreamResource block = sampleWebClient.get()
            .uri("/callee/get/image/{id}", 1)
            .accept(MediaType.APPLICATION_OCTET_STREAM)
            .retrieve()
            .bodyToMono(new ParameterizedTypeReference<InputStreamResource>() {
            })
            .block();
    return ResponseEntity
            .ok()
            .contentType(MediaTypeFactory.getMediaType(block.getFilename()).get())
            .body(block.getInputStream().readAllBytes());
    ResponseEntity<byte[]> block = sampleWebClient.get()
        	.uri("/callee/get/image/{id}", 1)
        	.accept(MediaType.APPLICATION_OCTET_STREAM)
        	.exchangeToMono(value -> value.toEntity(new ParameterizedTypeReference<byte[]>() {
       		 }))
        	.block();

    List<String> strings = block.getHeaders().get(HttpHeaders.CONTENT_DISPOSITION);
    ContentDisposition parse = ContentDisposition.parse(strings.get(0));
    return ResponseEntity
            .ok()
            .contentType(MediaTypeFactory.getMediaType(parse.getFilename()).get())
            .body(block.getBody());

2. POST

2.1. MediaType.APPLICATION_JSON

GET 방식과 마찬가지로 URI에 대해서는 동일하게 적용하며 RequestBodybody 메소드에 BodyInserters.fromValue를 통해 문자열 또는 객체를 편성하면 JSON으로 요청을 보내게 된다.

    return sampleWebClient.post()
        	.uri("/callee/post/json/{id}", 1)
        	.accept(MediaType.APPLICATION_JSON)
        	.body(BodyInserters.fromValue("TEST!!!"))
        	.retrieve()
        	.bodyToMono(String.class)
        	.block();

2.2. MediaType.MULTIPART_FORM_DATA(파일 업로드)

파일 업로드를 위해서는 ContentTypeMediaType.MULTIPART_FORM_DATA로 설정하며 MultipartBodyBuilder를 이용하여 partkey,value를 편성하며 다건인 경우 같은 이름으로 편성하게 되면 배열로 추가가 되며 fileName 또는 header를 통해 추가적인 설정도 가능하다.
이렇게 생성된 MultipartBodyBuilderBodyInserters.fromMultipartData를 사용하여 body에 편성하여 요청을 보낸다.

    // 파일 업로드 EndPoint
    @PostMapping("/callee/post/files/{id}")
    public void calleePostFiles(@RequestParam MultipartFile[] files) throws IOException {
        for (MultipartFile multipartFile : files) {
            FileOutputStream fileOutputStream = new FileOutputStream(new File("./" + multipartFile.getOriginalFilename()));
            fileOutputStream.write(multipartFile.getBytes());
        }
    }
    @PostMapping("/caller/post/files/{id}")
    public void callerPostFiles() throws FileNotFoundException {
        MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder();
        multipartBodyBuilder.part("files", new InputStreamResource(new FileInputStream("./arrow-down1.png"))).filename("한글명.png");
        multipartBodyBuilder.part("files", new ClassPathResource("한글명.png")).filename("한글명11.png");
        sampleWebClient
                .post()
                .uri("/callee/post/files/{id}",1)
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(BodyInserters.fromMultipartData(multipartBodyBuilder.build()))
                .retrieve()
                .toBodilessEntity()
                .block();
    }

다음과 같이 WebClient를 이용하여 GET, POST 메소드를 호출하기 위한 URI, RequestBody, MultipartForm를 구성하여 요청을 보내는 방법에 대해 작성하였다. 위에 작성한 호출방법은 WebClient의 특징인 논블록킹을 전혀 활용하지 않고 사용하는 방법으로 RestTemplate를 대체하기 위한 내용이다.
앞으로 논블록킹은 피할수 없는 방식으로 반드시 한번은 공부를 해야될것으로 생각이 들었으며 이번 글을 작성하며 파일 다운로드시 byte[], InputStream 리턴에 따른 차이점과 Http HeaderAccept, ContentType, ContentDisposition에 역활에 대해 정리할 수 있었던 글이였다.

profile
어느새 7년차 중니어 백엔드 개발자 입니다.

1개의 댓글

comment-user-thumbnail
2023년 2월 21일

감사합니다

답글 달기