Hi all, Using spring 6.0.11, I encountered an issue while trying to use DataBufferUtils with Flux (not Mono). I wanted to be able to gracefully handle errors, and use Flux so I could handle the download of large files without using extra memory.
To get around this, i used my own WritableByteChannelSubscriber, which checks the argument to the hookOnObject method. If it's a RuntimeException, I rethrow it. If it's a DataBuffer, I continue.
Should the Spring-supplied version behave the same way or is there a more elegant way to configure the WebClient to handle it?
I asked a question about this here https://stackoverflow.com/questions/77037515/cant-get-spring-webclient-to-work-flux-databuffers-error-handling-and-header
Comment From: poutsma
Next time, please take the time to provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem. A StackOverflow question does not suffice.
I did look at the sample code in your question, and there are a number of oddities in there:
* The response variable is a Flux<Object> that can contain DataBuffer as well as WebClientResponseException instances, which is odd. Typically, you propagate exceptions as error signals, created using Mono.error or Flux.error, so that you end up with a Flux<DataBuffer>, which—like every reactive stream—can also contain error signals.
* The sample uses exchangeToFlux in a way to does exactly what retrieve does, so why not use retrieve?
* DataBuffer.write requires a Flux<DataBuffer> as parameter, so it only operates on DataBuffer instances and does not accept WebClientResponseExceptions. Casting the Flux<?> to a Flux<DataBuffer> is not going to change that, so it's not strange that you're getting ClassCastExceptions.
* DataBufferUtils.write is a reactive method, because it returns a Mono<Void>. That result value is not used anywhere, so the mono is not subscribed to, and nothing will be written.
* The Content-Disposition header is typically not set by servers, but by clients when doing multipart form uploads. In the sample, the header will typically be empty. For a GET request, you can use the filename from the URL instead.
* The sample perform blocking file operations (i.e Files.createTempFile and File.rename) in a reactive stream, when you typically want to schedule those on a different scheduler.
* I have no idea why the sample creates two files, then renames the one to the other.
Instead, to store a GET request as file, do something like the following:
Flux<DataBuffer> buffers = webClient.get()
.uri(uri)
.retrieve()
.bodyToFlux(DataBuffer.class);
Mono<Void> result = Mono.defer((() -> {
try {
Path tempFile = Files.createTempFile("file-", "bin");
return DataBufferUtils.write(buffers, tempFile);
}
catch (IOException e) {
return Mono.error(e);
}
}))
.subscribeOn(Schedulers.boundedElastic());
When result is subscribed to, the result will be written to a temp file.