SpringBoot WebFlux provides the framework where we can create reactive APIs.
What if we need to return a zip file as a response to such an API? lets go through the steps that require to send a zip file as a response from a reactive REST API
1. Create Route
We first need to create a GET route which can be used to request for a zip file.
@Configuration(proxyBeanMethods = false)
class RouterConfiguration {
@Bean
fun route(): RouterFunction<ServerResponse> {
return RouterFunctions
.route(GET("/download").and(accept(MediaType.APPLICATION_OCTET_STREAM))) { ServerResponse.ok().build() }
}
}this configures a GET route called /download which match to the request only if it supports receiving octet stream, by declaring it in its request header.
Route upon invocation will just return 200 OK status with an empty response body.
2. Create Controller Function
We now need an function that can build this zip file, so that can be called from the route invocation and returned as a response.
If we understand properly, zip is a series of bytes, which can represented by a Spring provided flux of DataBuffer.
Therefore it can be defined as this to return that flux of databuffers as a response body for a request.
@Component
class DownloadHandler() {
fun createZip(request: ServerRequest): Mono<ServerResponse> {
return ServerResponse.ok().contentType(MediaType.parseMediaType("application/zip"))
.body(BodyInserters.fromDataBuffers(Flux.empty()))
}
}The method accepts a ServerRequest and builds ServerResponse for empty list of bytes with content-type as zip.
Above created controller method can now invoked from the route configuration for request handling.
With above change, altered route configuration method will look as this.
@Bean
fun route(downloadHandler: DownloadHandler): RouterFunction<ServerResponse> {
return RouterFunctions
.route(GET("/download").and(accept(MediaType.APPLICATION_OCTET_STREAM))) { downloadHandler.createZip(it) }
}3. Generate Zip Content
The last step that is remaining is building a zip file from list of files.
Lets assume we have two files /one/first.txt and /two/second.txt, then the Zip content can be build and streamed as DataBuffer.
private fun createZipDataBuffer(dataBufferFactory: DataBufferFactory): Flux<DataBuffer> {
val paths = listOf(Path("/one/first.txt"), Path("/two/second.txt"))
// Create a data buffer of 5 kibibytes
val dataBuffer = dataBufferFactory.allocateBuffer(1024 * 5) // 5 kibibyte
// Create a zip output stream using the data buffer as the output stream
val zipOutputStream = ZipOutputStream(dataBuffer.asOutputStream())
return Flux.create { emitter ->
try {
zipOutputStream.use {
paths.forEach { path ->
zipOutputStream.putNextEntry(ZipEntry(path.subpath(0, path.nameCount - 1).toString()))
Files.copy(path, zipOutputStream)
zipOutputStream.closeEntry()
}
zipOutputStream.finish()
emitter.next(dataBuffer)
emitter.complete()
}
} catch (e: Exception) {
emitter.error(e)
}
}
}Method requires DataBufferFactory to create the DataBuffer sized with 5 kibibyte. and the factory from the request should be used for it.
To represent a zip file,ZipOutputStream is created and the generated data buffer used as its output stream and upon completion, the generated data buffer emitted from the flux, so it can be used to create the response.
With that this method can now invoked from DonloadHandler createZip method like this.
fun createZip(request: ServerRequest): Mono<ServerResponse> {
return ServerResponse.ok().contentType(MediaType.parseMediaType("application/zip"))
.body(BodyInserters.fromDataBuffers(
// use the bufferFactory from the request
createZipDataBuffer(request.exchange().response.bufferFactory()))
)
}With all that, when a GET request is invoked to the /download url, then a zip file, with both files, will be send as a response.
That's all and now it should be possible to create a zip content as a response for a reactive SpringBoot WebFlux API 👌
No comments:
Post a Comment