Reactively Writing Response Data(反应性地写入响应数据)
Micronaut 的 HTTP 服务器通过返回一个 Publisher 来支持编写响应数据块,该 Publisher 发出可以编码为 HTTP 响应的对象。
下表总结了示例返回类型签名以及服务器为处理它们而表现出的行为:
返回类型 |
描述 |
Publisher<String>
|
将每个内容块作为字符串发出的发布者
|
Flux<byte[]>
|
将每个内容块作为 byte[] 发出而不阻塞的 Flux
|
Flux<ByteBuf>
|
将每个块作为 Netty ByteBuf 发出的 Reactor Flux
|
Flux<Book>
|
发出 POJO 时,每个发出的对象默认编码为 JSON,不会阻塞
|
Flowable<byte[]>
|
将每个内容块作为 byte[] 发出而不阻塞的 Flux
|
Flowable<ByteBuf>
|
将每个块作为 Netty ByteBuf 发出的 Reactor Flux
|
Flowable<Book>
|
发出 POJO 时,每个发出的对象默认编码为 JSON,不会阻塞
|
当返回一个反应类型时,服务器使用分块的传输编码并保持写入数据,直到调用 Publisher onComplete 方法。
服务器从发布者请求一个项目,写入它,然后请求下一个,控制背压。
由发布者的实现来安排任何可能因订阅发布者而完成的阻塞 I/O 工作。
要使用 Project Reactor 的 Flux 或 Mono,您需要将 Micronaut Reactor 依赖项添加到您的项目以包含必要的转换器。
要使用 RxJava 的 Flowable、Single 或 Maybe,您需要将 Micronaut RxJava 依赖项添加到您的项目以包含必要的转换器。
执行阻塞 I/O
在某些情况下,您可能希望集成一个不支持非阻塞 I/O 的库。
Writable
在这种情况下,您可以从任何控制器方法返回一个 Writable 对象。 Writable 接口具有各种签名,允许写入传统的阻塞流,如 Writer 或 OutputStream。
当返回 Writable 时,阻塞的 I/O 操作被转移到 I/O 线程池,因此 Netty 事件循环不会被阻塞。
以下示例演示了如何将此 API 与 Groovy 的 SimpleTemplateEngine 结合使用来编写服务器端模板:
使用可写执行阻塞 I/O
Java |
Groovy |
Kotlin |
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 = 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() {
try {
return templateEngine.createTemplate(
"Dear $firstName $lastName. Nice to meet you."
);
} catch (Exception e) {
throw new HttpServerException("Cannot create template");
}
}
}
|
import groovy.text.SimpleTemplateEngine
import groovy.text.Template
import io.micronaut.core.io.Writable
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 = initTemplate() // (1)
@Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
Writable render() { // (2)
{ writer ->
template.make( // (3)
firstName: "Fred",
lastName: "Flintstone"
).writeTo(writer)
}
}
private Template initTemplate() {
try {
return templateEngine.createTemplate(
'Dear $firstName $lastName. Nice to meet you.'
)
} catch (Exception e) {
throw new HttpServerException("Cannot create template")
}
}
}
|
import groovy.text.SimpleTemplateEngine
import groovy.text.Template
import io.micronaut.core.io.Writable
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 = initTemplate() // (1)
@Get(value = "/welcome", produces = [MediaType.TEXT_PLAIN])
internal fun render(): Writable { // (2)
return { writer: Writer ->
template.make( // (3)
mapOf(
"firstName" to "Fred",
"lastName" to "Flintstone"
)
).writeTo(writer)
} as Writable
}
private fun initTemplate(): Template {
return try {
templateEngine.createTemplate(
"Dear \$firstName \$lastName. Nice to meet you."
)
} catch (e: Exception) {
throw HttpServerException("Cannot create template")
}
}
}
|
控制器创建一个简单的模板
- 控制器方法返回一个 Writable
返回的函数接收 Writer 并在模板上调用 writeTo。
InputStream
另一种选择是返回输入流。这对于许多与公开流的其他 API 交互的场景很有用。
使用 InputStream 执行阻塞 I/O
Java |
Groovy |
Kotlin |
@Get(value = "/write", produces = MediaType.TEXT_PLAIN)
InputStream write() {
byte[] bytes = "test".getBytes(StandardCharsets.UTF_8);
return new ByteArrayInputStream(bytes); // (1)
}
|
@Get(value = "/write", produces = MediaType.TEXT_PLAIN)
InputStream write() {
byte[] bytes = "test".getBytes(StandardCharsets.UTF_8);
new ByteArrayInputStream(bytes) // (1)
}
|
@Get(value = "/write", produces = [MediaType.TEXT_PLAIN])
fun write(): InputStream {
val bytes = "test".toByteArray(StandardCharsets.UTF_8)
return ByteArrayInputStream(bytes) // (1)
}
|
返回输入流,其内容将是响应主体
如果在事件循环上执行控制器方法,流的读取将被卸载到 IO 线程池。
404 Responses
通常,当您在持久层或类似场景中找不到某个项目时,您希望响应 404(未找到)。
请参阅以下示例:
Java |
Groovy |
Kotlin |
@Controller("/books")
public class BooksController {
@Get("/stock/{isbn}")
public Map stock(String isbn) {
return null; //(1)
}
@Get("/maybestock/{isbn}")
@SingleResult
public Publisher<Map> maybestock(String isbn) {
return Mono.empty(); //(2)
}
}
|
@Controller("/books")
class BooksController {
@Get("/stock/{isbn}")
Map stock(String isbn) {
null //(1)
}
@Get("/maybestock/{isbn}")
Mono<Map> maybestock(String isbn) {
Mono.empty() //(2)
}
}
|
@Controller("/books")
class BooksController {
@Get("/stock/{isbn}")
fun stock(isbn: String): Map<*, *>? {
return null //(1)
}
@Get("/maybestock/{isbn}")
fun maybestock(isbn: String): Mono<Map<*, *>> {
return Mono.empty() //(2)
}
}
|
返回 null 会触发 404(未找到)响应。
返回空的 Mono 会触发 404(未找到)响应。
如果内容类型为 JSON,则使用空 Publisher 或 Flux 进行响应会导致返回一个空数组。
更多建议: