Reactively Writing Response Data(反應(yīng)性地寫入響應(yīng)數(shù)據(jù))
Micronaut 的 HTTP 服務(wù)器通過返回一個 Publisher 來支持編寫響應(yīng)數(shù)據(jù)塊,該 Publisher 發(fā)出可以編碼為 HTTP 響應(yīng)的對象。
下表總結(jié)了示例返回類型簽名以及服務(wù)器為處理它們而表現(xiàn)出的行為:
返回類型 |
描述 |
Publisher<String>
|
將每個內(nèi)容塊作為字符串發(fā)出的發(fā)布者
|
Flux<byte[]>
|
將每個內(nèi)容塊作為 byte[] 發(fā)出而不阻塞的 Flux
|
Flux<ByteBuf>
|
將每個塊作為 Netty ByteBuf 發(fā)出的 Reactor Flux
|
Flux<Book>
|
發(fā)出 POJO 時,每個發(fā)出的對象默認(rèn)編碼為 JSON,不會阻塞
|
Flowable<byte[]>
|
將每個內(nèi)容塊作為 byte[] 發(fā)出而不阻塞的 Flux
|
Flowable<ByteBuf>
|
將每個塊作為 Netty ByteBuf 發(fā)出的 Reactor Flux
|
Flowable<Book>
|
發(fā)出 POJO 時,每個發(fā)出的對象默認(rèn)編碼為 JSON,不會阻塞
|
當(dāng)返回一個反應(yīng)類型時,服務(wù)器使用分塊的傳輸編碼并保持寫入數(shù)據(jù),直到調(diào)用 Publisher onComplete 方法。
服務(wù)器從發(fā)布者請求一個項目,寫入它,然后請求下一個,控制背壓。
由發(fā)布者的實現(xiàn)來安排任何可能因訂閱發(fā)布者而完成的阻塞 I/O 工作。
要使用 Project Reactor 的 Flux 或 Mono,您需要將 Micronaut Reactor 依賴項添加到您的項目以包含必要的轉(zhuǎn)換器。
要使用 RxJava 的 Flowable、Single 或 Maybe,您需要將 Micronaut RxJava 依賴項添加到您的項目以包含必要的轉(zhuǎn)換器。
執(zhí)行阻塞 I/O
在某些情況下,您可能希望集成一個不支持非阻塞 I/O 的庫。
Writable
在這種情況下,您可以從任何控制器方法返回一個 Writable 對象。 Writable 接口具有各種簽名,允許寫入傳統(tǒng)的阻塞流,如 Writer 或 OutputStream。
當(dāng)返回 Writable 時,阻塞的 I/O 操作被轉(zhuǎn)移到 I/O 線程池,因此 Netty 事件循環(huán)不會被阻塞。
以下示例演示了如何將此 API 與 Groovy 的 SimpleTemplateEngine 結(jié)合使用來編寫服務(wù)器端模板:
使用可寫執(zhí)行阻塞 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")
}
}
}
|
控制器創(chuàng)建一個簡單的模板
- 控制器方法返回一個 Writable
返回的函數(shù)接收 Writer 并在模板上調(diào)用 writeTo。
InputStream
另一種選擇是返回輸入流。這對于許多與公開流的其他 API 交互的場景很有用。
使用 InputStream 執(zhí)行阻塞 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)
}
|
返回輸入流,其內(nèi)容將是響應(yīng)主體
如果在事件循環(huán)上執(zhí)行控制器方法,流的讀取將被卸載到 IO 線程池。
404 Responses
通常,當(dāng)您在持久層或類似場景中找不到某個項目時,您希望響應(yīng) 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 會觸發(fā) 404(未找到)響應(yīng)。
返回空的 Mono 會觸發(fā) 404(未找到)響應(yīng)。
如果內(nèi)容類型為 JSON,則使用空 Publisher 或 Flux 進行響應(yīng)會導(dǎo)致返回一個空數(shù)組。
更多建議: