해당 글은 spring-webmvc
에서 외부 호출만을 위해 spring-webflux
의 WebClient
를 사용하기 위한 설정에 대한 내용입니다. 이로 인해 모든 호출이 block
으로 되어 있는점 참고 부탁드립니다.
MSA
서버 통신간 가장 기본적인 조회 방식으로 앞서 WebClient
Bean
에 BaseUrl
를 설정하였기 때문에 이후에 정의되는 path
, PathVariable
, RequestParam
를 UriBuilder
를 통해 쉽게 정의할 수 있으며 따로 UriEncoder
로 문자열을 치환할 필요 없다.
이후 bodyToMono
메소드에 리턴 받을 Class
의 Generic
형태를 정의한 후 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();
```
서버간의 File
를 전달하기 위해서는 byte[]
또는 InputStream
으로 응답하며 요청하는 WebClient
에서는 요청Header
의 accept
에 MediaType.APPLICATION_OCTET_STREAM
를 설정한다.
byte[]
와 InputStream
의 차이점은 byte[]
의 경우 응답을 하는 서버에서 파일의 byte[]
를 모두 읽어서 응답을 하며 InpuStream
의 경우 file
의 byte
를 chunked
로 나누어 응답하여 메모리적인 차이가 존재한다.
이러한 응답에 상관없이 WebClient
에서도 리턴 Class
를 InputStreamResource
와 byte[]
로 받을 수 있다. bodyToMono
를 통해 InputStreamResource
로 결과를 받을 경우Header
의 CONTENT_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());
GET
방식과 마찬가지로 URI
에 대해서는 동일하게 적용하며 RequestBody
를 body
메소드에 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();
파일 업로드를 위해서는 ContentType
을 MediaType.MULTIPART_FORM_DATA
로 설정하며 MultipartBodyBuilder
를 이용하여 part
의 key
,value
를 편성하며 다건인 경우 같은 이름으로 편성하게 되면 배열로 추가가 되며 fileName
또는 header
를 통해 추가적인 설정도 가능하다.
이렇게 생성된 MultipartBodyBuilder
를 BodyInserters.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 Header
의 Accept
, ContentType
, ContentDisposition
에 역활에 대해 정리할 수 있었던 글이였다.
감사합니다