diff --git a/src/main/docs/guide/httpServer/transfers.adoc b/src/main/docs/guide/httpServer/transfers.adoc index bdc105a129a..347920b98be 100644 --- a/src/main/docs/guide/httpServer/transfers.adoc +++ b/src/main/docs/guide/httpServer/transfers.adoc @@ -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>` does not cause +the entire CSV to be loaded into memory. Micronaut will still *stream* +the data as it is produced. Returning `HttpResponse` 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. diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/server/transfer/DownloadController.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/server/transfer/DownloadController.groovy new file mode 100644 index 00000000000..7f782d222f8 --- /dev/null +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/server/transfer/DownloadController.groovy @@ -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> downloadCsv() { + Flux 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[] +} diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/server/transfer/DownloadController.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/server/transfer/DownloadController.kt new file mode 100644 index 00000000000..ba5c0167f3b --- /dev/null +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/server/transfer/DownloadController.kt @@ -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> { + 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[] +} \ No newline at end of file diff --git a/test-suite/src/test/java/io/micronaut/docs/server/transfer/DownloadController.java b/test-suite/src/test/java/io/micronaut/docs/server/transfer/DownloadController.java new file mode 100644 index 00000000000..037498c1d57 --- /dev/null +++ b/test-suite/src/test/java/io/micronaut/docs/server/transfer/DownloadController.java @@ -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> downloadCsv() { + Flux 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[] +}