6.20 Writing Response Data

Reactively Writing Response Data

Micronaut’s HTTP server supports writing chunks of response data by returning a Publisher that emits objects that can be encoded to the HTTP response.

The following table summarizes example return type signatures and the behaviour the server exhibits to handle them:

Return TypeDescription

Publisher<String>

A Publisher that emits each chunk of content as a String

Flux<byte[]>

A reactor:Flux[] that emits each chunk of content as a byte[] without blocking

Flux<ByteBuf>

A Reactor Flux that emits each chunk as a Netty ByteBuf

Flux<Book>

When emitting a POJO, each emitted object is encoded as JSON by default without blocking

Flowable<byte[]>

A reactor:Flux[] that emits each chunk of content as a byte[] without blocking

Flowable<ByteBuf>

A Reactor Flux that emits each chunk as a Netty ByteBuf

Flowable<Book>

When emitting a POJO, each emitted object is encoded as JSON by default without blocking

When returning a reactive type, the server uses a Transfer-Encoding of chunked and keeps writing data until the Publisher onComplete method is called.

The server requests a single item from the Publisher, writes it, and requests the next, controlling back pressure.

It is up to the implementation of the Publisher to schedule any blocking I/O work that may be done as a result of subscribing to the publisher.
To use Project Reactor‘s Flux or Mono you need to add the Micronaut Reactor dependency to your project to include the necessary converters.
To use RxJava‘s Flowable, Single or Maybe you need to add the Micronaut RxJava dependency to your project to include the necessary converters.

Performing Blocking I/O

In some cases you may wish to integrate a library that does not support non-blocking I/O.

Writable

In this case you can return a Writable object from any controller method. The Writable interface has various signatures that allow writing to traditional blocking streams like Writer or OutputStream.

When returning a Writable, the blocking I/O operation is shifted to the I/O thread pool so the Netty event loop is not blocked.

See the section on configuring Server Thread Pools for details on how to configure the I/O thread pool to meet your application requirements.

The following example demonstrates how to use this API with Groovy’s SimpleTemplateEngine to write a server side template:

Performing Blocking I/O With Writable

  1. import groovy.text.SimpleTemplateEngine;
  2. import groovy.text.Template;
  3. import io.micronaut.core.io.Writable;
  4. import io.micronaut.core.util.CollectionUtils;
  5. import io.micronaut.http.MediaType;
  6. import io.micronaut.http.annotation.Controller;
  7. import io.micronaut.http.annotation.Get;
  8. import io.micronaut.http.server.exceptions.HttpServerException;
  9. @Controller("/template")
  10. public class TemplateController {
  11. private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
  12. private final Template template = initTemplate(); (1)
  13. @Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
  14. Writable render() { (2)
  15. return writer -> template.make( (3)
  16. CollectionUtils.mapOf(
  17. "firstName", "Fred",
  18. "lastName", "Flintstone"
  19. )
  20. ).writeTo(writer);
  21. }
  22. private Template initTemplate() {
  23. try {
  24. return templateEngine.createTemplate(
  25. "Dear $firstName $lastName. Nice to meet you."
  26. );
  27. } catch (Exception e) {
  28. throw new HttpServerException("Cannot create template");
  29. }
  30. }
  31. }

Performing Blocking I/O With Writable

  1. import groovy.text.SimpleTemplateEngine
  2. import groovy.text.Template
  3. import io.micronaut.core.io.Writable
  4. import io.micronaut.http.MediaType
  5. import io.micronaut.http.annotation.Controller
  6. import io.micronaut.http.annotation.Get
  7. import io.micronaut.http.server.exceptions.HttpServerException
  8. @Controller("/template")
  9. class TemplateController {
  10. private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine()
  11. private final Template template = initTemplate() (1)
  12. @Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
  13. Writable render() { (2)
  14. { writer ->
  15. template.make( (3)
  16. firstName: "Fred",
  17. lastName: "Flintstone"
  18. ).writeTo(writer)
  19. }
  20. }
  21. private Template initTemplate() {
  22. try {
  23. return templateEngine.createTemplate(
  24. 'Dear $firstName $lastName. Nice to meet you.'
  25. )
  26. } catch (Exception e) {
  27. throw new HttpServerException("Cannot create template")
  28. }
  29. }
  30. }

Performing Blocking I/O With Writable

  1. import groovy.text.SimpleTemplateEngine
  2. import groovy.text.Template
  3. import io.micronaut.core.io.Writable
  4. import io.micronaut.http.MediaType
  5. import io.micronaut.http.annotation.Controller
  6. import io.micronaut.http.annotation.Get
  7. import io.micronaut.http.server.exceptions.HttpServerException
  8. import java.io.Writer
  9. @Controller("/template")
  10. class TemplateController {
  11. private val templateEngine = SimpleTemplateEngine()
  12. private val template = initTemplate() (1)
  13. @Get(value = "/welcome", produces = [MediaType.TEXT_PLAIN])
  14. internal fun render(): Writable { (2)
  15. return { writer: Writer ->
  16. template.make( (3)
  17. mapOf(
  18. "firstName" to "Fred",
  19. "lastName" to "Flintstone"
  20. )
  21. ).writeTo(writer)
  22. } as Writable
  23. }
  24. private fun initTemplate(): Template {
  25. return try {
  26. templateEngine.createTemplate(
  27. "Dear \$firstName \$lastName. Nice to meet you."
  28. )
  29. } catch (e: Exception) {
  30. throw HttpServerException("Cannot create template")
  31. }
  32. }
  33. }
1The controller creates a simple template
2The controller method returns a Writable
3The returned function receives a Writer and calls writeTo on the template.

InputStream

Another option is to return an input stream. This is useful for many scenarios that interact with other APIs that expose a stream.

Performing Blocking I/O With InputStream

  1. @Get(value = "/write", produces = MediaType.TEXT_PLAIN)
  2. InputStream write() {
  3. byte[] bytes = "test".getBytes(StandardCharsets.UTF_8);
  4. return new ByteArrayInputStream(bytes); (1)
  5. }

Performing Blocking I/O With InputStream

  1. @Get(value = "/write", produces = MediaType.TEXT_PLAIN)
  2. InputStream write() {
  3. byte[] bytes = "test".getBytes(StandardCharsets.UTF_8);
  4. new ByteArrayInputStream(bytes) (1)
  5. }

Performing Blocking I/O With InputStream

  1. @Get(value = "/write", produces = [MediaType.TEXT_PLAIN])
  2. fun write(): InputStream {
  3. val bytes = "test".toByteArray(StandardCharsets.UTF_8)
  4. return ByteArrayInputStream(bytes) (1)
  5. }
1The input stream is returned and its contents will be the response body
The reading of the stream will be offloaded to the IO thread pool if the controller method is executed on the event loop.

404 Responses

Often, you want to respond 404 (Not Found) when you don’t find an item in your persistence layer or in similar scenarios.

See the following example:

  1. @Controller("/books")
  2. public class BooksController {
  3. @Get("/stock/{isbn}")
  4. public Map stock(String isbn) {
  5. return null; (1)
  6. }
  7. @Get("/maybestock/{isbn}")
  8. @SingleResult
  9. public Publisher<Map> maybestock(String isbn) {
  10. return Mono.empty(); (2)
  11. }
  12. }
  1. @Controller("/books")
  2. class BooksController {
  3. @Get("/stock/{isbn}")
  4. Map stock(String isbn) {
  5. null (1)
  6. }
  7. @Get("/maybestock/{isbn}")
  8. Mono<Map> maybestock(String isbn) {
  9. Mono.empty() (2)
  10. }
  11. }
  1. @Controller("/books")
  2. class BooksController {
  3. @Get("/stock/{isbn}")
  4. fun stock(isbn: String): Map<*, *>? {
  5. return null (1)
  6. }
  7. @Get("/maybestock/{isbn}")
  8. fun maybestock(isbn: String): Mono<Map<*, *>> {
  9. return Mono.empty() (2)
  10. }
  11. }
1Returning null triggers a 404 (Not Found) response.
2Returning an empty Mono triggers a 404 (Not Found) response.
Responding with an empty Publisher or Flux results in an empty array being returned if the content type is JSON.