Skip to content

Commit

Permalink
docs: add example for streaming data with Content-Disposition (#11545)
Browse files Browse the repository at this point in the history
* docs: add reactive file download example

* Add example code snippets (Java, Kotlin, Groovy) with snippet macros
  • Loading branch information
ChaimaaeROUAI authored and sdelamo committed Jan 31, 2025
1 parent 5a3698c commit 2b16e3a
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/main/docs/guide/httpServer/transfers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ The server supports returning `304` (Not Modified) responses if the files being

TIP: To use a custom data source to send data through an input stream, construct a link:{jdkapi}/java.base/java/io/PipedInputStream.html[PipedInputStream] and link:{jdkapi}/java.base/java/io/PipedOutputStream.html[PipedOutputStream] to write data from the output stream to the input. Make sure to do the work on a separate thread so the file can be returned immediately.

== Sending Reactive Streams as File Downloads
Micronaut also supports returning *reactive streams* (e.g., `Flux`, `Flowable`,
or any `Publisher`) without buffering the entire response in memory. If you want to
force the client browser to download the streamed data (for example, CSV lines),
you can set the `Content-Disposition: attachment` header.

snippet::io.micronaut.docs.server.transfer.DownloadController[tags="class,endclass", indent=0, title="Sending Reactive Stream"]

NOTE: Wrapping your stream in `HttpResponse<Flux<String>>` does not cause
the entire CSV to be loaded into memory. Micronaut will still *stream*
the data as it is produced. Returning `HttpResponse<T>` simply allows you
to set any headers or custom status codes.

== Cache Configuration

By default, file responses include caching headers. The following options determine how the `Cache-Control` header is built.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.docs.server.transfer

import io.micronaut.http.HttpHeaders
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import reactor.core.publisher.Flux

@Controller("/download")
class DownloadController {
// tag::class[]
@Get("/csv")
HttpResponse<Flux<String>> downloadCsv() {
Flux<String> data = Flux.just(
"data1,data2",
"data3,data4"
)
return HttpResponse.ok(data)
.header(HttpHeaders.CONTENT_DISPOSITION, 'attachment; filename="data.csv"')
.contentType(MediaType.TEXT_PLAIN_TYPE)
}
// end::class[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.docs.server.transfer

import io.micronaut.http.HttpHeaders
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import reactor.core.publisher.Flux

@Controller("/download")
class DownloadController {
// tag::class[]
@Get("/csv")
fun downloadCsv(): HttpResponse<Flux<String>> {
val data = Flux.just(
"data1,data2",
"data3,data4"
)
return HttpResponse.ok(data)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"data.csv\"")
.contentType(MediaType.TEXT_PLAIN_TYPE)
}
// end::class[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.docs.server.transfer;

import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

import reactor.core.publisher.Flux;

@Controller("/download")
public class DownloadController {
// tag::class[]
@Get("/csv")
public HttpResponse<Flux<String>> downloadCsv() {
Flux<String> data = Flux.just(
"data1,data2",
"data3,data4"
);
return HttpResponse.ok(data)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"data.csv\"")
.contentType(MediaType.TEXT_PLAIN_TYPE);
}
// end::class[]
}

0 comments on commit 2b16e3a

Please sign in to comment.