6.19 Writing Response Data
Reactively Writing Response Data
Micronaut’s HTTP server supports writing chunks of response data by returning a Publisher the 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 each of them:
Return Type | Description |
---|---|
| A Flowable that emits each chunk of content as a |
| A Reactor |
| A Publisher that emits each chunk of content as a String |
| When emitting a POJO each emitted object is encoded as JSON by default without blocking |
When returning reactive type the server will use a Transfer-Encoding
of chunked
and keep writing data until the Publisher‘s onComplete
method is called.
The server will request a single item from the Publisher, write the item and then request the next item, 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. |
Performing Blocking I/O
In some cases you may wish to integrate with a library that does not support non-blocking I/O.
In this case you can return a Writable object from any controller method. The Writable has various signatures that allowing writing to traditional blocking streams like Writer or OutputStream.
When returning a Writable object the blocking I/O operation will be shifted to the I/O thread pool so that 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 the requirements of your application. |
The following example demonstrates how to use this API with Groovy’s SimpleTemplateEngine
to write a server side template:
Performing Blocking I/O
import groovy.text.SimpleTemplateEngine;
import groovy.text.Template;
import io.micronaut.core.io.Writable;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.server.exceptions.HttpServerException;
@Controller("/template")
public class TemplateController {
private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
private final Template template;
public TemplateController() {
template = initTemplate(); (1)
}
@Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
Writable render() { (2)
return writer -> template.make( (3)
CollectionUtils.mapOf(
"firstName", "Fred",
"lastName", "Flintstone"
)
).writeTo(writer);
}
private Template initTemplate() {
Template template;
try {
template = templateEngine.createTemplate(
"Dear $firstName $lastName. Nice to meet you."
);
} catch (Exception e) {
throw new HttpServerException("Cannot create template");
}
return template;
}
}
Performing Blocking I/O
import groovy.text.SimpleTemplateEngine;
import groovy.text.Template;
import io.micronaut.core.io.Writable;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.server.exceptions.HttpServerException;
@Controller("/template")
class TemplateController {
private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine()
private final Template template
TemplateController() {
template = initTemplate() (1)
}
@Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
Writable render() { (2)
{ writer ->
template.make( (3)
CollectionUtils.mapOf(
"firstName", "Fred",
"lastName", "Flintstone"
)
).writeTo(writer)
}
}
private Template initTemplate() {
Template template
try {
template = templateEngine.createTemplate(
'Dear $firstName $lastName. Nice to meet you.'
)
} catch (Exception e) {
throw new HttpServerException("Cannot create template")
}
template
}
}
Performing Blocking I/O
import groovy.text.SimpleTemplateEngine
import groovy.text.Template
import io.micronaut.core.io.Writable
import io.micronaut.core.util.CollectionUtils
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.server.exceptions.HttpServerException
import java.io.Writer
@Controller("/template")
class TemplateController {
private val templateEngine = SimpleTemplateEngine()
private val template: Template
init {
template = initTemplate() (1)
}
@Get(value = "/welcome", produces = [MediaType.TEXT_PLAIN])
internal fun render(): Writable { (2)
return { writer: Writer ->
val writable = template.make( (3)
CollectionUtils.mapOf(
"firstName", "Fred",
"lastName", "Flintstone"
)
)
writable.writeTo(writer)
} as Writable
}
private fun initTemplate(): Template {
val template: Template
try {
template = templateEngine.createTemplate(
"Dear \$firstName \$lastName. Nice to meet you."
)
} catch (e: Exception) {
throw HttpServerException("Cannot create template")
}
return template
}
}
1 | The controller creates a simple template |
2 | The controller method returns a Writable |
3 | The returned function receives a Writer and calls writeTo on the template. |
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:
@Controller("/books")
public class BooksController {
@Get("/stock/{isbn}")
public Map stock(String isbn) {
return null; (1)
}
@Get("/maybestock/{isbn}")
public Maybe<Map> maybestock(String isbn) {
return Maybe.empty(); (2)
}
}
@Controller("/books")
class BooksController {
@Get("/stock/{isbn}")
Map stock(String isbn) {
null (1)
}
@Get("/maybestock/{isbn}")
Maybe<Map> maybestock(String isbn) {
Maybe.empty() (2)
}
}
@Controller("/books")
class BooksController {
@Get("/stock/{isbn}")
fun stock(isbn: String): Map<*, *>? {
return null (1)
}
@Get("/maybestock/{isbn}")
fun maybestock(isbn: String): Maybe<Map<*, *>> {
return Maybe.empty() (2)
}
}
1 | Returning null triggers a 404 (Not Found) response. |
2 | Returning an empty Maybe triggers a 404 (Not Found) response. |
Responding with an empty Publisher or Flowable will result in an empty array being returned if the content type is JSON. |