Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add example for streaming data with Content-Disposition #11545

Merged
merged 2 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[]
}
Loading