15

I am making a project using Spring WebFlux.

In the past I had used StreamingResponseBody for streaming responses back to the client, but I can't find the equivalent in WebFlux.

Example:

import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

@GetMapping("/video")
public StreamingResponseBody stream() {
    InputStream videoStream = ...
    StreamingResponseBody res = (os) -> { IOUtils.copy(videoStream, os); }
    return res;
}

Is there an equivalent of StreamingResponseBody for WebFlux? or, should I import the traditional Spring MVC and mix them?

Edit: So far I am solving it by accessing the ServerHttpResponse (example below). But I am still wondering about better solutions.

@GetMapping("/video")
fun stream2(response: ServerHttpResponse): Mono<Void> {
    val factory = response.bufferFactory()
    val publisher = videoStream
            .observeVideoParts()
            .map { factory.wrap(it.bytes) }
    return response.writeWith(publisher)
}
ESala
  • 6,878
  • 4
  • 34
  • 55
  • 1
    @Frischling thank you but it's not a duplicate, using `ServerResponse` doesn't work when using annotation-based controllers and request mappings. See: https://stackoverflow.com/a/50026023 – ESala Jun 06 '18 at 19:03
  • he didn't mention `ServerRespons` but `ServerHttpResponse`. – Roman T Jan 17 '22 at 10:16
  • I experience an issue with the approach in `Edit`. There are errors that headers can be set when I use `writeWith()`. E.g. Content-Length. I can't set other headers as well. – Roman T Jan 17 '22 at 10:17

2 Answers2

3

For now, the best solution that I have found is to return a ServerHttpResponse.

Since ServerHttpResponse only allows to write DataBuffer objects but not ByteArray objects, I made an extension function that wraps them before writing:

fun ServerHttpResponse.writeByteArrays(bytes: Flux<ByteArray>): Mono<Void> {
    val factory = this.bufferFactory()
    val dataBuffers = bytes.map { factory.wrap(it) }
    return this.writeWith(dataBuffers)
}

Then a Flux<ByteArray> can simply be written like this:

@GetMapping("/video")
fun stream2(response: ServerHttpResponse): Mono<Void> {
    val videoParts: Flux<ByteArray> = ...
    return response.writeByteArrays(videoParts)
}

I am still open to other solutions.

ESala
  • 6,878
  • 4
  • 34
  • 55
2

A little bit late. However, scanning the recent documentation of Spring core and Webflux, I think the following should work:

@GetMapping("/stream/{path}")
public Flux<DataBuffer> getVideo(
        @PathVariable("path") String path,
        ServerHttpResponse response
) {
    return DataBufferUtils.read(
            new FileSystemResource(path),
            response.bufferFactory(),
            512
    );
}
PeMa
  • 1,559
  • 18
  • 44
  • My bad, I didn't have time testing this approach. Still if you downvote, please leave a comment. Maybe I can improve my answer. – PeMa Jun 22 '20 at 09:50
  • This is not useful since it only works for files on disk, and is not a general OutputStream answer. Unless you first stream the OutputStream to disk, and then read it this way... which seems a hassle. – Stmated Oct 18 '22 at 12:55
  • 2
    @Stmated well, `FileSystemResource` is an example that I chose to be close to the OP question. The `DataBufferUtils.read(...)` method however takes any implementation of `Resource` as an argument. You might want to have a look at https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/Resource.html to find the implementation that suits your specific case. – PeMa Oct 18 '22 at 19:56