HTTP 服務(wù)器具有許多配置選項(xiàng)。它們?cè)跀U(kuò)展 HttpServerConfiguration 的 NettyHttpServerConfiguration 配置類中定義。
以下示例顯示了如何通過(guò)配置文件(例如 application.yml)調(diào)整服務(wù)器的配置選項(xiàng):
配置 HTTP 服務(wù)器設(shè)置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.maxRequestSize=1MB
micronaut.server.host=localhost
micronaut.server.netty.maxHeaderSize=500KB
micronaut.server.netty.worker.threads=8
micronaut.server.netty.childOptions.autoRead=true
|
micronaut:
server:
maxRequestSize: 1MB
host: localhost
netty:
maxHeaderSize: 500KB
worker:
threads: 8
childOptions:
autoRead: true
|
[micronaut]
[micronaut.server]
maxRequestSize="1MB"
host="localhost"
[micronaut.server.netty]
maxHeaderSize="500KB"
[micronaut.server.netty.worker]
threads=8
[micronaut.server.netty.childOptions]
autoRead=true
|
micronaut {
server {
maxRequestSize = "1MB"
host = "localhost"
netty {
maxHeaderSize = "500KB"
worker {
threads = 8
}
childOptions {
autoRead = true
}
}
}
}
|
{
micronaut {
server {
maxRequestSize = "1MB"
host = "localhost"
netty {
maxHeaderSize = "500KB"
worker {
threads = 8
}
childOptions {
autoRead = true
}
}
}
}
}
|
{
"micronaut": {
"server": {
"maxRequestSize": "1MB",
"host": "localhost",
"netty": {
"maxHeaderSize": "500KB",
"worker": {
"threads": 8
},
"childOptions": {
"autoRead": true
}
}
}
}
}
|
默認(rèn)情況下,Micronaut 綁定到所有網(wǎng)絡(luò)接口。使用 localhost 僅綁定到環(huán)回網(wǎng)絡(luò)接口
maxHeaderSize 設(shè)置標(biāo)題的最大大小
worker.threads 指定 Netty 工作線程數(shù)
autoRead 啟用請(qǐng)求正文自動(dòng)讀取
表 1. NettyHttpServerConfiguration 的配置屬性
屬性 |
類型 |
描述 |
micronaut.server.netty.child-options
|
java.util.Map
|
設(shè)置 Netty 子工作者選項(xiàng)。
|
micronaut.server.netty.options
|
java.util.Map
|
設(shè)置通道選項(xiàng)。
|
micronaut.server.netty.max-initial-line-length
|
int
|
設(shè)置 HTTP 請(qǐng)求的最大初始行長(zhǎng)度。默認(rèn)值 (4096)。
|
micronaut.server.netty.max-header-size
|
int
|
設(shè)置任何一個(gè)標(biāo)題的最大大小。默認(rèn)值 (8192)。
|
micronaut.server.netty.max-chunk-size
|
int
|
設(shè)置任何單個(gè)請(qǐng)求塊的最大大小。默認(rèn)值 (8192)。
|
micronaut.server.netty.max-h2c-upgrade-request-size
|
int
|
設(shè)置用于將連接升級(jí)到 HTTP2 明文 (h2c) 的 HTTP1.1 請(qǐng)求正文的最大大小。此初始請(qǐng)求無(wú)法流式傳輸,而是完全緩沖,因此默認(rèn)值 (8192) 相對(duì)較小。 <i>如果此值對(duì)于您的用例而言太小,請(qǐng)考慮使用空的初始“升級(jí)請(qǐng)求”(例如 {@code OPTIONS /}),或切換到普通 HTTP2。</i> <p> <i>不影響正常的 HTTP2 (TLS)。</i>
|
micronaut.server.netty.chunked-supported
|
boolean
|
設(shè)置是否支持分塊傳輸編碼。默認(rèn)值(真)。
|
micronaut.server.netty.validate-headers
|
boolean
|
設(shè)置是否驗(yàn)證傳入的標(biāo)頭。默認(rèn)值(真)。
|
micronaut.server.netty.initial-buffer-size
|
int
|
設(shè)置初始緩沖區(qū)大小。默認(rèn)值 (128)。
|
micronaut.server.netty.log-level
|
io.netty.handler.logging.LogLevel
|
設(shè)置 Netty 日志級(jí)別。
|
micronaut.server.netty.compression-threshold
|
int
|
設(shè)置請(qǐng)求主體必須的最小大小才能被壓縮。默認(rèn)值 (1024)。
|
micronaut.server.netty.compression-level
|
int
|
設(shè)置壓縮級(jí)別 (0-9)。默認(rèn)值 (6)。
|
micronaut.server.netty.use-native-transport
|
boolean
|
如果可用,設(shè)置是否使用 netty 的本地傳輸(epoll 或 kqueue)。默認(rèn)值(假)。
|
micronaut.server.netty.fallback-protocol
|
java.lang.String
|
設(shè)置通過(guò) ALPN 協(xié)商時(shí)要使用的回退協(xié)議。
|
micronaut.server.netty.keep-alive-on-server-error
|
boolean
|
是否發(fā)送連接在內(nèi)部服務(wù)器錯(cuò)誤時(shí)保持活動(dòng)狀態(tài)。默認(rèn)值({@value DEFAULT_KEEP_ALIVE_ON_SERVER_ERROR})。
|
micronaut.server.netty.pcap-logging-path-pattern
|
java.lang.String
|
用于記錄到 pcap 的傳入連接的路徑模式。這是一個(gè)不受支持的選項(xiàng):行為可能會(huì)改變,或者可能會(huì)完全消失,恕不另行通知!
|
micronaut.server.netty.listeners
|
java.util.List
|
設(shè)置顯式的 netty 偵聽器配置,或者 {@code null} 如果它們應(yīng)該是隱式的。
|
使用本機(jī)傳輸
與基于 NIO 的傳輸相比,本機(jī) Netty 傳輸添加特定于特定平臺(tái)的功能,產(chǎn)生更少的垃圾,并且通常提高性能。
要啟用本機(jī)傳輸,首先添加依賴項(xiàng):
對(duì)于 x86 上的 macOS:
Gradle |
Maven |
runtimeOnly("io.netty:netty-transport-native-kqueue::osx-x86_64")
|
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-kqueue</artifactId>
<scope>runtime</scope>
<classifier>osx-x86_64</classifier>
</dependency>
|
對(duì)于 M1 上的 macOS:
Gradle |
Maven |
runtimeOnly("io.netty:netty-transport-native-kqueue::osx-aarch_64")
|
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-kqueue</artifactId>
<scope>runtime</scope>
<classifier>osx-aarch_64</classifier>
</dependency>
|
對(duì)于 x86 上的 Linux:
Gradle |
Maven |
runtimeOnly("io.netty:netty-transport-native-epoll::linux-x86_64")
|
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<scope>runtime</scope>
<classifier>linux-x86_64</classifier>
</dependency>
|
對(duì)于 ARM64 上的 Linux:
Gradle |
Maven |
runtimeOnly("io.netty:netty-transport-native-epoll::linux-aarch_64")
|
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<scope>runtime</scope>
<classifier>linux-aarch_64</classifier>
</dependency>
|
然后將默認(rèn)事件循環(huán)組配置為更喜歡本機(jī)傳輸:
配置默認(rèn)事件循環(huán)以優(yōu)先使用本機(jī)傳輸
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.netty.event-loops.default.prefer-native-transport=true
|
micronaut:
netty:
event-loops:
default:
prefer-native-transport: true
|
[micronaut]
[micronaut.netty]
[micronaut.netty.event-loops]
[micronaut.netty.event-loops.default]
prefer-native-transport=true
|
micronaut {
netty {
eventLoops {
'default' {
preferNativeTransport = true
}
}
}
}
|
{
micronaut {
netty {
event-loops {
default {
prefer-native-transport = true
}
}
}
}
}
|
{
"micronaut": {
"netty": {
"event-loops": {
"default": {
"prefer-native-transport": true
}
}
}
}
}
|
Netty 支持簡(jiǎn)單的采樣資源泄漏檢測(cè),以少量開銷為代價(jià)報(bào)告是否存在泄漏。您可以通過(guò)將屬性 netty.resource-leak-detector-level 設(shè)置為以下之一來(lái)禁用它或啟用更高級(jí)的檢測(cè):SIMPLE(默認(rèn))、DISABLED、PARANOID 或 ADVANCED。
配置服務(wù)器線程池
HTTP 服務(wù)器建立在 Netty 之上,Netty 被設(shè)計(jì)為事件循環(huán)模型中的非阻塞 I/O 工具包。
Netty worker 事件循環(huán)使用“默認(rèn)”命名的事件循環(huán)組。這可以通過(guò) micronaut.netty.event-loops.default 進(jìn)行配置。
micronaut.server.netty.worker 下的事件循環(huán)配置僅在事件循環(huán)組設(shè)置為不對(duì)應(yīng)于任何 micronaut.netty.event-loops 配置的名稱時(shí)使用。此行為已棄用,將在未來(lái)版本中刪除。將 micronaut.netty.event-loops.* 用于除了通過(guò) event-loop-group 設(shè)置名稱之外的任何事件循環(huán)組配置。這不適用于父事件循環(huán)配置(micronaut.server.netty.parent)。
表 1. Worker 的配置屬性
屬性 |
類型 |
描述 |
micronaut.server.netty.worker
|
NettyHttpServerConfiguration$Worker
|
設(shè)置 Worker 事件循環(huán)配置。
|
micronaut.server.netty.worker.event-loop-group
|
java.lang.String
|
設(shè)置要使用的名稱。
|
micronaut.server.netty.worker.threads
|
int
|
設(shè)置事件循環(huán)組的線程數(shù)。
|
micronaut.server.netty.worker.io-ratio
|
java.lang.Integer
|
設(shè)置 I/O 比率。
|
micronaut.server.netty.worker.executor
|
java.lang.String
|
設(shè)置執(zhí)行者的名字。
|
micronaut.server.netty.worker.prefer-native-transport
|
boolean
|
設(shè)置是否首選本地傳輸(如果可用)
|
micronaut.server.netty.worker.shutdown-quiet-period
|
java.time.Duration
|
設(shè)置關(guān)機(jī)靜默期
|
micronaut.server.netty.worker.shutdown-timeout
|
java.time.Duration
|
設(shè)置關(guān)機(jī)超時(shí)時(shí)間(必須>= shutdownQuietPeriod)
|
可以使用具有相同配置選項(xiàng)的 micronaut.server.netty.parent 配置父事件循環(huán)。
服務(wù)器也可以配置為使用不同的命名工作事件循環(huán):
為服務(wù)器使用不同的事件循環(huán)
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.netty.worker.event-loop-group=other
micronaut.netty.event-loops.other.num-threads=10
|
micronaut:
server:
netty:
worker:
event-loop-group: other
netty:
event-loops:
other:
num-threads: 10
|
[micronaut]
[micronaut.server]
[micronaut.server.netty]
[micronaut.server.netty.worker]
event-loop-group="other"
[micronaut.netty]
[micronaut.netty.event-loops]
[micronaut.netty.event-loops.other]
num-threads=10
|
micronaut {
server {
netty {
worker {
eventLoopGroup = "other"
}
}
}
netty {
eventLoops {
other {
numThreads = 10
}
}
}
}
|
{
micronaut {
server {
netty {
worker {
event-loop-group = "other"
}
}
}
netty {
event-loops {
other {
num-threads = 10
}
}
}
}
}
|
{
"micronaut": {
"server": {
"netty": {
"worker": {
"event-loop-group": "other"
}
}
},
"netty": {
"event-loops": {
"other": {
"num-threads": 10
}
}
}
}
}
|
線程數(shù)的默認(rèn)值是系統(tǒng)屬性 io.netty.eventLoopThreads 的值,或者如果未指定,則為可用處理器 x 2。
請(qǐng)參閱下表以配置事件循環(huán):
表 2. DefaultEventLoopGroupConfiguration 的配置屬性
屬性 |
類型 |
描述 |
micronaut.netty.event-loops.*.num-threads
|
int
|
|
micronaut.netty.event-loops.*.io-ratio
|
java.lang.Integer
|
|
micronaut.netty.event-loops.*.prefer-native-transport
|
boolean
|
|
micronaut.netty.event-loops.*.executor
|
java.lang.String
|
|
micronaut.netty.event-loops.*.shutdown-quiet-period
|
java.time.Duration
|
|
micronaut.netty.event-loops.*.shutdown-timeout
|
java.time.Duration
|
|
阻塞操作
在處理阻塞操作時(shí),Micronaut 默認(rèn)將阻塞操作轉(zhuǎn)移到未綁定的緩存 I/O 線程池。您可以使用名為 io 的 ExecutorConfiguration 配置 I/O 線程池。例如:
配置服務(wù)器 I/O 線程池
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.executors.io.type=fixed
micronaut.executors.io.nThreads=75
|
micronaut:
executors:
io:
type: fixed
nThreads: 75
|
[micronaut]
[micronaut.executors]
[micronaut.executors.io]
type="fixed"
nThreads=75
|
micronaut {
executors {
io {
type = "fixed"
nThreads = 75
}
}
}
|
{
micronaut {
executors {
io {
type = "fixed"
nThreads = 75
}
}
}
}
|
{
"micronaut": {
"executors": {
"io": {
"type": "fixed",
"nThreads": 75
}
}
}
}
|
上面的配置創(chuàng)建了一個(gè)有 75 個(gè)線程的固定線程池。
@Blocking
您可以使用 @Blocking 注釋將方法標(biāo)記為阻塞。
如果將 micronaut.server.thread-selection 設(shè)置為 AUTO,Micronaut 框架會(huì)將使用 @Blocking 注釋的方法的執(zhí)行卸載到 IO 線程池(請(qǐng)參閱:TaskExecutors)。
@Blocking 僅在您使用自動(dòng)線程選擇時(shí)才有效。自 Micronaut 2.0 以來(lái),Micronaut 框架默認(rèn)為手動(dòng)線程選擇。我們推薦使用@ExecuteOn 注解在不同的線程池上執(zhí)行阻塞操作。 @ExecutesOn 適用于手動(dòng)和自動(dòng)線程選擇。
Micronaut 框架在某些地方內(nèi)部使用了@Blocking:
Micronaut Data 還在內(nèi)部利用 @Blocking 進(jìn)行一些事務(wù)操作、CRUD 攔截器和存儲(chǔ)庫(kù)。
配置 Netty 客戶端管道
您可以通過(guò)編寫偵聽注冊(cè)表創(chuàng)建的 Bean 事件偵聽器來(lái)自定義 Netty 客戶端管道。
ChannelPipelineCustomizer 接口為各種處理程序 Micronaut 寄存器的名稱定義常量。
作為示例,以下代碼示例演示了注冊(cè) Logbook 庫(kù),其中包括用于執(zhí)行請(qǐng)求和響應(yīng)日志記錄的其他 Netty 處理程序:
為 Logbook 定制 Netty 服務(wù)器管道
Java |
Groovy |
Kotlin |
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.http.client.netty.NettyClientCustomizer;
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import jakarta.inject.Singleton;
import org.zalando.logbook.Logbook;
import org.zalando.logbook.netty.LogbookClientHandler;
@Requires(beans = Logbook.class)
@Singleton
public class LogbookNettyClientCustomizer
implements BeanCreatedEventListener<NettyClientCustomizer.Registry> { // (1)
private final Logbook logbook;
public LogbookNettyClientCustomizer(Logbook logbook) {
this.logbook = logbook;
}
@Override
public NettyClientCustomizer.Registry onCreated(
BeanCreatedEvent<NettyClientCustomizer.Registry> event) {
NettyClientCustomizer.Registry registry = event.getBean();
registry.register(new Customizer(null)); // (2)
return registry;
}
private class Customizer implements NettyClientCustomizer { // (3)
private final Channel channel;
Customizer(Channel channel) {
this.channel = channel;
}
@Override
public NettyClientCustomizer specializeForChannel(Channel channel, ChannelRole role) {
return new Customizer(channel); // (4)
}
@Override
public void onStreamPipelineBuilt() {
channel.pipeline().addLast( // (5)
"logbook",
new LogbookClientHandler(logbook)
);
}
}
}
|
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import io.micronaut.http.client.netty.NettyClientCustomizer
import io.netty.channel.Channel
import jakarta.inject.Singleton
import org.zalando.logbook.Logbook
import org.zalando.logbook.netty.LogbookClientHandler
@Requires(beans = Logbook.class)
@Singleton
class LogbookNettyClientCustomizer
implements BeanCreatedEventListener<NettyClientCustomizer.Registry> { // (1)
private final Logbook logbook;
LogbookNettyClientCustomizer(Logbook logbook) {
this.logbook = logbook
}
@Override
NettyClientCustomizer.Registry onCreated(
BeanCreatedEvent<NettyClientCustomizer.Registry> event) {
NettyClientCustomizer.Registry registry = event.getBean()
registry.register(new Customizer(null)) // (2)
return registry
}
private class Customizer implements NettyClientCustomizer { // (3)
private final Channel channel
Customizer(Channel channel) {
this.channel = channel
}
@Override
NettyClientCustomizer specializeForChannel(Channel channel, ChannelRole role) {
return new Customizer(channel) // (4)
}
@Override
void onStreamPipelineBuilt() {
channel.pipeline().addLast( // (5)
"logbook",
new LogbookClientHandler(logbook)
)
}
}
}
|
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import io.micronaut.http.client.netty.NettyClientCustomizer
import io.micronaut.http.client.netty.NettyClientCustomizer.ChannelRole
import io.netty.channel.Channel
import jakarta.inject.Singleton
import org.zalando.logbook.Logbook
import org.zalando.logbook.netty.LogbookClientHandler
@Requires(beans = [Logbook::class])
@Singleton
class LogbookNettyClientCustomizer(private val logbook: Logbook) :
BeanCreatedEventListener<NettyClientCustomizer.Registry> { // (1)
override fun onCreated(event: BeanCreatedEvent<NettyClientCustomizer.Registry>): NettyClientCustomizer.Registry {
val registry = event.bean
registry.register(Customizer(null)) // (2)
return registry
}
private inner class Customizer constructor(private val channel: Channel?) :
NettyClientCustomizer { // (3)
override fun specializeForChannel(channel: Channel, role: ChannelRole) = Customizer(channel) // (4)
override fun onStreamPipelineBuilt() {
channel!!.pipeline().addLast( // (5)
"logbook",
LogbookClientHandler(logbook)
)
}
}
}
|
LogbookNettyClientCustomizer 監(jiān)聽一個(gè) Registry 并且需要一個(gè) Logbook bean 的定義
根定制器在沒有通道的情況下初始化并注冊(cè)
實(shí)際的定制器實(shí)現(xiàn)了 NettyClientCustomizer
創(chuàng)建新頻道時(shí),會(huì)為該頻道創(chuàng)建一個(gè)新的專用定制器
當(dāng)客戶端發(fā)出流管道已完全構(gòu)建的信號(hào)時(shí),將注冊(cè)日志處理程序
Logbook 有一個(gè)主要錯(cuò)誤,限制了它在 netty 中的實(shí)用性。
配置 Netty 服務(wù)器管道
您可以通過(guò)編寫監(jiān)聽 Registry 創(chuàng)建的 Bean 事件監(jiān)聽器來(lái)自定義 Netty 服務(wù)器管道。
ChannelPipelineCustomizer 接口為各種處理程序 Micronaut 寄存器的名稱定義常量。
作為示例,以下代碼示例演示了注冊(cè) Logbook 庫(kù),其中包括用于執(zhí)行請(qǐng)求和響應(yīng)日志記錄的其他 Netty 處理程序:
為 Logbook 定制 Netty 服務(wù)器管道
Java |
Groovy |
Kotlin |
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer;
import io.micronaut.http.server.netty.NettyServerCustomizer;
import io.netty.channel.Channel;
import org.zalando.logbook.Logbook;
import org.zalando.logbook.netty.LogbookServerHandler;
import jakarta.inject.Singleton;
@Requires(beans = Logbook.class)
@Singleton
public class LogbookNettyServerCustomizer
implements BeanCreatedEventListener<NettyServerCustomizer.Registry> { // (1)
private final Logbook logbook;
public LogbookNettyServerCustomizer(Logbook logbook) {
this.logbook = logbook;
}
@Override
public NettyServerCustomizer.Registry onCreated(
BeanCreatedEvent<NettyServerCustomizer.Registry> event) {
NettyServerCustomizer.Registry registry = event.getBean();
registry.register(new Customizer(null)); // (2)
return registry;
}
private class Customizer implements NettyServerCustomizer { // (3)
private final Channel channel;
Customizer(Channel channel) {
this.channel = channel;
}
@Override
public NettyServerCustomizer specializeForChannel(Channel channel, ChannelRole role) {
return new Customizer(channel); // (4)
}
@Override
public void onStreamPipelineBuilt() {
channel.pipeline().addBefore( // (5)
ChannelPipelineCustomizer.HANDLER_HTTP_STREAM,
"logbook",
new LogbookServerHandler(logbook)
);
}
}
}
|
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer
import io.micronaut.http.server.netty.NettyServerCustomizer
import io.netty.channel.Channel
import org.zalando.logbook.Logbook
import org.zalando.logbook.netty.LogbookServerHandler
import jakarta.inject.Singleton
@Requires(beans = Logbook.class)
@Singleton
class LogbookNettyServerCustomizer
implements BeanCreatedEventListener<NettyServerCustomizer.Registry> { // (1)
private final Logbook logbook;
LogbookNettyServerCustomizer(Logbook logbook) {
this.logbook = logbook
}
@Override
NettyServerCustomizer.Registry onCreated(
BeanCreatedEvent<NettyServerCustomizer.Registry> event) {
NettyServerCustomizer.Registry registry = event.getBean()
registry.register(new Customizer(null)) // (2)
return registry
}
private class Customizer implements NettyServerCustomizer { // (3)
private final Channel channel
Customizer(Channel channel) {
this.channel = channel
}
@Override
NettyServerCustomizer specializeForChannel(Channel channel, ChannelRole role) {
return new Customizer(channel) // (4)
}
@Override
void onStreamPipelineBuilt() {
channel.pipeline().addBefore( // (5)
ChannelPipelineCustomizer.HANDLER_HTTP_STREAM,
"logbook",
new LogbookServerHandler(logbook)
)
}
}
}
|
import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.BeanCreatedEvent
import io.micronaut.context.event.BeanCreatedEventListener
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer
import io.micronaut.http.server.netty.NettyServerCustomizer
import io.micronaut.http.server.netty.NettyServerCustomizer.ChannelRole
import io.netty.channel.Channel
import jakarta.inject.Singleton
import org.zalando.logbook.Logbook
import org.zalando.logbook.netty.LogbookServerHandler
@Requires(beans = [Logbook::class])
@Singleton
class LogbookNettyServerCustomizer(private val logbook: Logbook) :
BeanCreatedEventListener<NettyServerCustomizer.Registry> { // (1)
override fun onCreated(event: BeanCreatedEvent<NettyServerCustomizer.Registry>): NettyServerCustomizer.Registry {
val registry = event.bean
registry.register(Customizer(null)) // (2)
return registry
}
private inner class Customizer constructor(private val channel: Channel?) :
NettyServerCustomizer { // (3)
override fun specializeForChannel(channel: Channel, role: ChannelRole) = Customizer(channel) // (4)
override fun onStreamPipelineBuilt() {
channel!!.pipeline().addBefore( // (5)
ChannelPipelineCustomizer.HANDLER_HTTP_STREAM,
"logbook",
LogbookServerHandler(logbook)
)
}
}
}
|
LogbookNettyServerCustomizer 監(jiān)聽一個(gè) Registry 并且需要一個(gè) Logbook bean 的定義
根定制器在沒有通道的情況下初始化并注冊(cè)
實(shí)際的定制器實(shí)現(xiàn)了 NettyServerCustomizer
創(chuàng)建新頻道時(shí),會(huì)為該頻道創(chuàng)建一個(gè)新的專用定制器
當(dāng)服務(wù)器發(fā)出流管道已完全構(gòu)建的信號(hào)時(shí),將注冊(cè)日志處理程序
Logbook 有一個(gè)主要錯(cuò)誤,限制了它在 netty 中的實(shí)用性。
高級(jí)偵聽器配置
您也可以手動(dòng)指定每個(gè)偵聽器,而不是配置單個(gè)端口。
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.netty.listeners.httpListener.host=127.0.0.1
micronaut.server.netty.listeners.httpListener.port=8086
micronaut.server.netty.listeners.httpListener.ssl=false
micronaut.server.netty.listeners.httpsListener.port=8087
micronaut.server.netty.listeners.httpsListener.ssl=true
|
micronaut:
server:
netty:
listeners:
httpListener:
host: 127.0.0.1
port: 8086
ssl: false
httpsListener:
port: 8087
ssl: true
|
[micronaut]
[micronaut.server]
[micronaut.server.netty]
[micronaut.server.netty.listeners]
[micronaut.server.netty.listeners.httpListener]
host="127.0.0.1"
port=8086
ssl=false
[micronaut.server.netty.listeners.httpsListener]
port=8087
ssl=true
|
micronaut {
server {
netty {
listeners {
httpListener {
host = "127.0.0.1"
port = 8086
ssl = false
}
httpsListener {
port = 8087
ssl = true
}
}
}
}
}
|
{
micronaut {
server {
netty {
listeners {
httpListener {
host = "127.0.0.1"
port = 8086
ssl = false
}
httpsListener {
port = 8087
ssl = true
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"netty": {
"listeners": {
"httpListener": {
"host": "127.0.0.1",
"port": 8086,
"ssl": false
},
"httpsListener": {
"port": 8087,
"ssl": true
}
}
}
}
}
}
|
如果您手動(dòng)指定監(jiān)聽器,其他配置如 micronaut.server.port 將被忽略。
可以為每個(gè)偵聽器單獨(dú)啟用或禁用 SSL。啟用后,SSL 將按上述方式配置。
嵌入式服務(wù)器還支持使用 netty 綁定到 unix 域套接字。這需要以下依賴項(xiàng):
Gradle |
Maven |
implementation("io.netty:netty-transport-native-unix-common")
|
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-unix-common</artifactId>
</dependency>
|
服務(wù)器還必須配置為使用本地傳輸(epoll 或 kqueue)。
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.netty.listeners.unixListener.family=UNIX
micronaut.server.netty.listeners.unixListener.path=/run/micronaut.socket
micronaut.server.netty.listeners.unixListener.ssl=true
|
micronaut:
server:
netty:
listeners:
unixListener:
family: UNIX
path: /run/micronaut.socket
ssl: true
|
[micronaut]
[micronaut.server]
[micronaut.server.netty]
[micronaut.server.netty.listeners]
[micronaut.server.netty.listeners.unixListener]
family="UNIX"
path="/run/micronaut.socket"
ssl=true
|
micronaut {
server {
netty {
listeners {
unixListener {
family = "UNIX"
path = "/run/micronaut.socket"
ssl = true
}
}
}
}
}
|
{
micronaut {
server {
netty {
listeners {
unixListener {
family = "UNIX"
path = "/run/micronaut.socket"
ssl = true
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"netty": {
"listeners": {
"unixListener": {
"family": "UNIX",
"path": "/run/micronaut.socket",
"ssl": true
}
}
}
}
}
}
|
要使用抽象域套接字而不是普通套接字,請(qǐng)?jiān)诼窂角凹由?nbsp;NUL 字符,例如“\0/run/micronaut.socket”
配置 CORS
Micronaut 開箱即用地支持 CORS(跨源資源共享)。默認(rèn)情況下,拒絕 CORS 請(qǐng)求。
CORS 通過(guò)配置
要啟用 CORS 請(qǐng)求處理,請(qǐng)修改應(yīng)用程序配置文件中的配置:
CORS 配置示例
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
|
micronaut:
server:
cors:
enabled: true
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
|
micronaut {
server {
cors {
enabled = true
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true
}
}
}
}
|
通過(guò)僅啟用 CORS 處理,采用“完全開放”策略,允許來(lái)自任何來(lái)源的請(qǐng)求。
要更改所有來(lái)源或特定來(lái)源的設(shè)置,請(qǐng)更改配置以提供一個(gè)或多個(gè)“配置”。通過(guò)提供任何配置,不配置默認(rèn)的“全開”配置。
CORS 配置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
micronaut.server.cors.configurations.all=...
micronaut.server.cors.configurations.web=...
micronaut.server.cors.configurations.mobile=...
|
micronaut:
server:
cors:
enabled: true
configurations:
all:
...
web:
...
mobile:
...
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
[micronaut.server.cors.configurations]
all="..."
web="..."
mobile="..."
|
micronaut {
server {
cors {
enabled = true
configurations {
all = "..."
web = "..."
mobile = "..."
}
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
configurations {
all = "..."
web = "..."
mobile = "..."
}
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true,
"configurations": {
"all": "...",
"web": "...",
"mobile": "..."
}
}
}
}
}
|
在上面的示例中,提供了三種配置。它們的名稱(all、web、mobile)并不重要,在 Micronaut 中沒有任何意義。它們的存在純粹是為了能夠輕松識(shí)別配置的預(yù)期用戶。
相同的配置屬性可以應(yīng)用于每個(gè)配置。有關(guān)可以定義的屬性,請(qǐng)參閱 CorsOriginConfiguration。提供的每個(gè)配置的值將默認(rèn)為相應(yīng)字段的默認(rèn)值。
當(dāng)發(fā)出 CORS 請(qǐng)求時(shí),將在配置中搜索完全匹配或通過(guò)正則表達(dá)式匹配請(qǐng)求源的允許源。
Allowed Origins
要允許給定配置的任何來(lái)源,請(qǐng)不要在您的配置中包含 allowedOrigins 鍵。
對(duì)于多個(gè)有效來(lái)源,將配置的 allowedOrigins 鍵設(shè)置為字符串列表。每個(gè)值可以是靜態(tài)值 (http://www.foo.com) 或正則表達(dá)式 (^http(|s)://www\.google\.com$)。
正則表達(dá)式被傳遞給 Pattern#compile 并與 Matcher#matches 的請(qǐng)求源進(jìn)行比較。
示例 CORS 配置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
micronaut.server.cors.configurations.web.allowedOrigins[0]=http://foo.com
micronaut.server.cors.configurations.web.allowedOrigins[1]=^http(|s):\/\/www\.google\.com$
|
micronaut:
server:
cors:
enabled: true
configurations:
web:
allowedOrigins:
- http://foo.com
- ^http(|s):\/\/www\.google\.com$
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
[micronaut.server.cors.configurations]
[micronaut.server.cors.configurations.web]
allowedOrigins=[
"http://foo.com",
"^http(|s):\\/\\/www\\.google\\.com$"
]
|
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowedOrigins = ["http://foo.com", "^http(|s):\\/\\/www\\.google\\.com$"]
}
}
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowedOrigins = ["http://foo.com", "^http(|s):\\/\\/www\\.google\\.com$"]
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true,
"configurations": {
"web": {
"allowedOrigins": ["http://foo.com", "^http(|s):\\/\\/www\\.google\\.com$"]
}
}
}
}
}
}
|
Allowed Methods
要允許給定配置的任何請(qǐng)求方法,請(qǐng)不要在您的配置中包含 allowedMethods 鍵。
對(duì)于多個(gè)允許的方法,將配置的 allowedMethods 鍵設(shè)置為字符串列表。
示例 CORS 配置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
micronaut.server.cors.configurations.web.allowedOrigins[0]=http://foo.com
micronaut.server.cors.configurations.web.allowedOrigins[1]=^http(|s):\/\/www\.google\.com$
|
micronaut:
server:
cors:
enabled: true
configurations:
web:
allowedOrigins:
- http://foo.com
- ^http(|s):\/\/www\.google\.com$
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
[micronaut.server.cors.configurations]
[micronaut.server.cors.configurations.web]
allowedOrigins=[
"http://foo.com",
"^http(|s):\\/\\/www\\.google\\.com$"
]
|
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowedOrigins = ["http://foo.com", "^http(|s):\\/\\/www\\.google\\.com$"]
}
}
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowedOrigins = ["http://foo.com", "^http(|s):\\/\\/www\\.google\\.com$"]
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true,
"configurations": {
"web": {
"allowedOrigins": ["http://foo.com", "^http(|s):\\/\\/www\\.google\\.com$"]
}
}
}
}
}
}
|
Allowed Headers
要允許給定配置的任何請(qǐng)求標(biāo)頭,請(qǐng)不要在您的配置中包含 allowedHeaders 鍵。
對(duì)于多個(gè)允許的標(biāo)頭,將配置的 allowedHeaders 鍵設(shè)置為字符串列表。
示例 CORS 配置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
micronaut.server.cors.configurations.web.allowedHeaders[0]=Content-Type
micronaut.server.cors.configurations.web.allowedHeaders[1]=Authorization
|
micronaut:
server:
cors:
enabled: true
configurations:
web:
allowedHeaders:
- Content-Type
- Authorization
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
[micronaut.server.cors.configurations]
[micronaut.server.cors.configurations.web]
allowedHeaders=[
"Content-Type",
"Authorization"
]
|
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowedHeaders = ["Content-Type", "Authorization"]
}
}
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowedHeaders = ["Content-Type", "Authorization"]
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true,
"configurations": {
"web": {
"allowedHeaders": ["Content-Type", "Authorization"]
}
}
}
}
}
}
|
Exposed Headers
要配置在通過(guò) Access-Control-Expose-Headers 標(biāo)頭響應(yīng) CORS 請(qǐng)求時(shí)發(fā)送的標(biāo)頭,請(qǐng)?jiān)谀呐渲弥邪?nbsp;exposedHeaders 鍵的字符串列表。默認(rèn)情況下沒有公開。
示例 CORS 配置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
micronaut.server.cors.configurations.web.exposedHeaders[0]=Content-Type
micronaut.server.cors.configurations.web.exposedHeaders[1]=Authorization
|
micronaut:
server:
cors:
enabled: true
configurations:
web:
exposedHeaders:
- Content-Type
- Authorization
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
[micronaut.server.cors.configurations]
[micronaut.server.cors.configurations.web]
exposedHeaders=[
"Content-Type",
"Authorization"
]
|
micronaut {
server {
cors {
enabled = true
configurations {
web {
exposedHeaders = ["Content-Type", "Authorization"]
}
}
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
configurations {
web {
exposedHeaders = ["Content-Type", "Authorization"]
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true,
"configurations": {
"web": {
"exposedHeaders": ["Content-Type", "Authorization"]
}
}
}
}
}
}
|
Allow Credentials
CORS 請(qǐng)求默認(rèn)允許憑據(jù)。要禁止憑據(jù),請(qǐng)將 allowCredentials 選項(xiàng)設(shè)置為 false。
示例 CORS 配置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
micronaut.server.cors.configurations.web.allowCredentials=false
|
micronaut:
server:
cors:
enabled: true
configurations:
web:
allowCredentials: false
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
[micronaut.server.cors.configurations]
[micronaut.server.cors.configurations.web]
allowCredentials=false
|
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowCredentials = false
}
}
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
configurations {
web {
allowCredentials = false
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true,
"configurations": {
"web": {
"allowCredentials": false
}
}
}
}
}
}
|
Max Age
預(yù)檢請(qǐng)求可以緩存的默認(rèn)最長(zhǎng)期限為 30 分鐘。要更改該行為,請(qǐng)以秒為單位指定一個(gè)值。
示例 CORS 配置
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.enabled=true
micronaut.server.cors.configurations.web.maxAge=3600
|
micronaut:
server:
cors:
enabled: true
configurations:
web:
maxAge: 3600 # 1 hour
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
enabled=true
[micronaut.server.cors.configurations]
[micronaut.server.cors.configurations.web]
maxAge=3600
|
micronaut {
server {
cors {
enabled = true
configurations {
web {
maxAge = 3600
}
}
}
}
}
|
{
micronaut {
server {
cors {
enabled = true
configurations {
web {
maxAge = 3600
}
}
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"enabled": true,
"configurations": {
"web": {
"maxAge": 3600
}
}
}
}
}
}
|
Multiple Header Values
默認(rèn)情況下,當(dāng)一個(gè)標(biāo)頭有多個(gè)值時(shí),將發(fā)送多個(gè)標(biāo)頭,每個(gè)標(biāo)頭都有一個(gè)值。通過(guò)設(shè)置配置選項(xiàng),可以更改行為以發(fā)送帶有逗號(hào)分隔值列表的單個(gè)標(biāo)頭。
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.cors.single-header=true
|
micronaut:
server:
cors:
single-header: true
|
[micronaut]
[micronaut.server]
[micronaut.server.cors]
single-header=true
|
micronaut {
server {
cors {
singleHeader = true
}
}
}
|
{
micronaut {
server {
cors {
single-header = true
}
}
}
}
|
{
"micronaut": {
"server": {
"cors": {
"single-header": true
}
}
}
}
|
Securing the Server with HTTPS
Micronaut 開箱即用地支持 HTTPS。默認(rèn)情況下 HTTPS 被禁用,所有請(qǐng)求都使用 HTTP 服務(wù)。要啟用 HTTPS 支持,請(qǐng)修改您的配置。例如:
HTTPS 配置示例
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.ssl.enabled=true
micronaut.server.ssl.buildSelfSigned=true
|
micronaut:
server:
ssl:
enabled: true
buildSelfSigned: true
|
[micronaut]
[micronaut.server]
[micronaut.server.ssl]
enabled=true
buildSelfSigned=true
|
micronaut {
server {
ssl {
enabled = true
buildSelfSigned = true
}
}
}
|
{
micronaut {
server {
ssl {
enabled = true
buildSelfSigned = true
}
}
}
}
|
{
"micronaut": {
"server": {
"ssl": {
"enabled": true,
"buildSelfSigned": true
}
}
}
}
|
默認(rèn)情況下,支持 HTTPS 的 Micronaut 在端口 8443 上啟動(dòng),但您可以使用屬性 micronaut.server.ssl.port 更改端口。
為了生成自簽名證書,Micronaut HTTP 服務(wù)器將使用 netty。 Netty 使用兩種方法之一來(lái)生成證書。
如果您使用預(yù)先生成的證書(出于安全考慮,您應(yīng)該這樣做),則不需要執(zhí)行這些步驟。
Netty 可以使用 JDK 內(nèi)部的 sun.security.x509 包。在較新的 JDK 版本上,此包受到限制,可能無(wú)法使用。您可能需要添加 --add-exports=java.base/sun.security.x509=ALL-UNNAMED 作為 VM 參數(shù)。
或者,netty 將使用 Bouncy Castle BCPKIX API。這需要一個(gè)額外的依賴:
Gradle |
Maven |
implementation("org.bouncycastle:bcpkix-jdk15on")
|
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
|
此配置將在瀏覽器中生成警告。
使用有效的 x509 證書
也可以將 Micronaut 配置為使用現(xiàn)有的有效 x509 證書,例如使用 Let's Encrypt 創(chuàng)建的證書。您將需要 server.crt 和 server.key 文件并將它們轉(zhuǎn)換為 PKCS #12 文件。
$ openssl pkcs12 -export \
-in server.crt \ (1)
-inkey server.key \ (2)
-out server.p12 \ (3)
-name someAlias \ (4)
-chain -CAfile ca.crt -caname root
原始 server.crt 文件
原始 server.key 文件
要?jiǎng)?chuàng)建的 server.p12 文件
證書的別名
在創(chuàng)建 server.p12 文件期間,有必要定義一個(gè)密碼,稍后在 Micronaut 中使用證書時(shí)將需要該密碼。
現(xiàn)在修改你的配置:
HTTPS 配置示例
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.ssl.enabled=true
micronaut.ssl.key-store.path=classpath:server.p12
micronaut.ssl.key-store.password=mypassword
micronaut.ssl.key-store.type=PKCS12
|
micronaut:
ssl:
enabled: true
key-store:
path: classpath:server.p12
password: mypassword
type: PKCS12
|
[micronaut]
[micronaut.ssl]
enabled=true
[micronaut.ssl.key-store]
path="classpath:server.p12"
password="mypassword"
type="PKCS12"
|
micronaut {
ssl {
enabled = true
keyStore {
path = "classpath:server.p12"
password = "mypassword"
type = "PKCS12"
}
}
}
|
{
micronaut {
ssl {
enabled = true
key-store {
path = "classpath:server.p12"
password = "mypassword"
type = "PKCS12"
}
}
}
}
|
{
"micronaut": {
"ssl": {
"enabled": true,
"key-store": {
"path": "classpath:server.p12",
"password": "mypassword",
"type": "PKCS12"
}
}
}
}
|
使用此配置,如果我們啟動(dòng) Micronaut 并連接到 https://localhost:8443,我們?nèi)匀粫?huì)在瀏覽器中看到警告,但如果我們檢查證書,我們可以檢查它是否是由 Let's Encrypt 生成的。
最后,我們可以通過(guò)在 /etc/hosts 文件中為域添加別名來(lái)測(cè)試證書對(duì)瀏覽器是否有效:
$ cat /etc/hosts
...
127.0.0.1 my-domain.org
...
現(xiàn)在我們可以連接到 https://my-domain.org:8443:
使用 Java 密鑰庫(kù) (JKS)
不建議使用這種類型的證書,因?yàn)楦袷绞菍S械?nbsp;- 首選 PKCS12 格式。不管怎樣,Micronaut 也支持它。
將 p12 證書轉(zhuǎn)換為 JKS 證書:
$ keytool -importkeystore \
-deststorepass newPassword -destkeypass newPassword \ (1)
-destkeystore server.keystore \ (2)
-srckeystore server.p12 -srcstoretype PKCS12 -srcstorepass mypassword \ (3)
-alias someAlias (4)
有必要為密鑰庫(kù)定義密碼
要?jiǎng)?chuàng)建的文件
之前創(chuàng)建的PKCS12文件,以及創(chuàng)建時(shí)定義的密碼
之前使用的別名
如果 srcstorepass 或別名與 p12 文件中定義的不同,轉(zhuǎn)換將失敗。
現(xiàn)在修改你的配置:
HTTPS 配置示例
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.ssl.enabled=true
micronaut.ssl.key-store.path=classpath:server.keystore
micronaut.ssl.key-store.password=newPassword
micronaut.ssl.key-store.type=JKS
|
micronaut:
ssl:
enabled: true
key-store:
path: classpath:server.keystore
password: newPassword
type: JKS
|
[micronaut]
[micronaut.ssl]
enabled=true
[micronaut.ssl.key-store]
path="classpath:server.keystore"
password="newPassword"
type="JKS"
|
micronaut {
ssl {
enabled = true
keyStore {
path = "classpath:server.keystore"
password = "newPassword"
type = "JKS"
}
}
}
|
{
micronaut {
ssl {
enabled = true
key-store {
path = "classpath:server.keystore"
password = "newPassword"
type = "JKS"
}
}
}
}
|
{
"micronaut": {
"ssl": {
"enabled": true,
"key-store": {
"path": "classpath:server.keystore",
"password": "newPassword",
"type": "JKS"
}
}
}
}
|
啟動(dòng) Micronaut,應(yīng)用程序?qū)⑹褂妹荑€庫(kù)中的證書在 https://localhost:8443 上運(yùn)行。
刷新/重新加載 HTTPS 證書
在 HTTPS 證書過(guò)期后保持最新狀態(tài)可能是一個(gè)挑戰(zhàn)。一個(gè)很好的解決方案是自動(dòng)證書管理環(huán)境 (ACME) 和 Micronaut ACME 模塊,它支持從證書頒發(fā)機(jī)構(gòu)自動(dòng)刷新證書。
如果無(wú)法使用證書頒發(fā)機(jī)構(gòu)并且您需要從磁盤手動(dòng)更新證書,那么您應(yīng)該使用 Micronaut 對(duì)應(yīng)用程序事件的支持觸發(fā) RefreshEvent,其中包含定義 HTTPS 配置的密鑰,Micronaut 將從磁盤重新加載證書并應(yīng)用服務(wù)器的新配置。
您還可以使用刷新管理端點(diǎn),但這僅適用于磁盤上證書的物理位置已更改的情況
例如,以下將從磁盤重新加載先前列出的 HTTPS 配置并將其應(yīng)用于新的傳入請(qǐng)求(例如,此代碼可以運(yùn)行輪詢證書以獲取更改的計(jì)劃作業(yè)):
手動(dòng)刷新 HTTPS 配置
import jakarta.inject.Inject;
import io.micronaut.context.event.ApplicationEventPublisher;
import io.micronaut.runtime.context.scope.refresh.RefreshEvent;
import java.util.Collections;
...
@Inject ApplicationEventPublisher<RefreshEvent> eventPublisher;
...
eventPublisher.publishEvent(new RefreshEvent(
Collections.singletonMap("micronaut.ssl", "*")
));
啟用 HTTP 和 HTTPS
Micronaut 支持綁定 HTTP 和 HTTPS。要啟用雙協(xié)議支持,請(qǐng)修改您的配置。例如:
雙協(xié)議配置示例
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.ssl.enabled=true
micronaut.server.ssl.build-self-signed=true
micronaut.server.dual-protocol=true
|
micronaut:
server:
ssl:
enabled: true
build-self-signed: true
dual-protocol : true
|
[micronaut]
[micronaut.server]
[micronaut.server.ssl]
enabled=true
build-self-signed=true
dual-protocol=true
|
micronaut {
server {
ssl {
enabled = true
buildSelfSigned = true
}
dualProtocol = true
}
}
|
{
micronaut {
server {
ssl {
enabled = true
build-self-signed = true
}
dual-protocol = true
}
}
}
|
{
"micronaut": {
"server": {
"ssl": {
"enabled": true,
"build-self-signed": true
},
"dual-protocol": true
}
}
}
|
也可以自動(dòng)將所有 HTTP 請(qǐng)求重定向到 HTTPS。除了前面的配置外,您還需要啟用此選項(xiàng)。例如:
啟用 HTTP 到 HTTPS 重定向
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.ssl.enabled=true
micronaut.server.ssl.build-self-signed=true
micronaut.server.dual-protocol=true
micronaut.server.http-to-https-redirect=true
|
micronaut:
server:
ssl:
enabled: true
build-self-signed: true
dual-protocol : true
http-to-https-redirect: true
|
[micronaut]
[micronaut.server]
[micronaut.server.ssl]
enabled=true
build-self-signed=true
dual-protocol=true
http-to-https-redirect=true
|
micronaut {
server {
ssl {
enabled = true
buildSelfSigned = true
}
dualProtocol = true
httpToHttpsRedirect = true
}
}
|
{
micronaut {
server {
ssl {
enabled = true
build-self-signed = true
}
dual-protocol = true
http-to-https-redirect = true
}
}
}
|
{
"micronaut": {
"server": {
"ssl": {
"enabled": true,
"build-self-signed": true
},
"dual-protocol": true,
"http-to-https-redirect": true
}
}
}
|
Enabling Access Logger
本著 apache mod_log_config 和 Tomcat 訪問日志閥的精神,可以為 HTTP 服務(wù)器啟用訪問記錄器(這適用于 HTTP/1 和 HTTP/2)。
要啟用和配置訪問記錄器,請(qǐng)?jiān)谀呐渲梦募ɡ?nbsp;application.yml)中設(shè)置:
Enabling the access logger
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.netty.access-logger.enabled=true
micronaut.server.netty.access-logger.logger-name=my-access-logger
micronaut.server.netty.access-logger.log-format=common
|
micronaut:
server:
netty:
access-logger:
enabled: true
logger-name: my-access-logger
log-format: common
|
[micronaut]
[micronaut.server]
[micronaut.server.netty]
[micronaut.server.netty.access-logger]
enabled=true
logger-name="my-access-logger"
log-format="common"
|
micronaut {
server {
netty {
accessLogger {
enabled = true
loggerName = "my-access-logger"
logFormat = "common"
}
}
}
}
|
{
micronaut {
server {
netty {
access-logger {
enabled = true
logger-name = "my-access-logger"
log-format = "common"
}
}
}
}
}
|
{
"micronaut": {
"server": {
"netty": {
"access-logger": {
"enabled": true,
"logger-name": "my-access-logger",
"log-format": "common"
}
}
}
}
}
|
過(guò)濾訪問日志
如果你不想記錄對(duì)某些路徑的訪問,你可以在配置中指定正則表達(dá)式過(guò)濾器:
過(guò)濾訪問日志
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.server.netty.access-logger.enabled=true
micronaut.server.netty.access-logger.logger-name=my-access-logger
micronaut.server.netty.access-logger.log-format=common
micronaut.server.netty.access-logger.exclusions[0]=/health
micronaut.server.netty.access-logger.exclusions[1]=/path/.+
|
micronaut:
server:
netty:
access-logger:
enabled: true
logger-name: my-access-logger
log-format: common
exclusions:
- /health
- /path/.+
|
[micronaut]
[micronaut.server]
[micronaut.server.netty]
[micronaut.server.netty.access-logger]
enabled=true
logger-name="my-access-logger"
log-format="common"
exclusions=[
"/health",
"/path/.+"
]
|
micronaut {
server {
netty {
accessLogger {
enabled = true
loggerName = "my-access-logger"
logFormat = "common"
exclusions = ["/health", "/path/.+"]
}
}
}
}
|
{
micronaut {
server {
netty {
access-logger {
enabled = true
logger-name = "my-access-logger"
log-format = "common"
exclusions = ["/health", "/path/.+"]
}
}
}
}
}
|
{
"micronaut": {
"server": {
"netty": {
"access-logger": {
"enabled": true,
"logger-name": "my-access-logger",
"log-format": "common",
"exclusions": ["/health", "/path/.+"]
}
}
}
}
}
|
登錄配置
除了啟用訪問記錄器之外,您還必須為指定的或默認(rèn)的記錄器名稱添加一個(gè)記錄器。例如,使用默認(rèn)記錄器名稱進(jìn)行 logback:
登錄配置
<appender
name="httpAccessLogAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<file>log/http-access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>log/http-access-%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%msg%n</pattern>
</encoder>
<immediateFlush>true</immediateFlush>
</appender>
<logger name="HTTP_ACCESS_LOGGER" additivity="false" level="info">
<appender-ref ref="httpAccessLogAppender" />
</logger>
該模式應(yīng)該只有消息標(biāo)記,因?yàn)槠渌貙⒂稍L問記錄器處理。
日志格式
語(yǔ)法基于 Apache httpd 日志格式。
這些是支持的標(biāo)記:
-
%a - 遠(yuǎn)程IP地址
-
%A - 本地IP地址
-
%b - 發(fā)送的字節(jié)數(shù),不包括 HTTP 標(biāo)頭,如果未發(fā)送任何字節(jié),則為“-”
-
%B - 發(fā)送的字節(jié)數(shù),不包括 HTTP 標(biāo)頭
-
%h - 遠(yuǎn)程主機(jī)名
-
%H - 請(qǐng)求協(xié)議
-
%{<header>}i - 請(qǐng)求標(biāo)頭。如果參數(shù)被省略 (%i) 所有的標(biāo)題都被打印出來(lái)
-
%{<header>}o - 響應(yīng)頭。如果省略參數(shù) (%o),則打印所有標(biāo)題
-
%{<cookie>}C - 請(qǐng)求 cookie (COOKIE)。如果省略參數(shù) (%C),則打印所有 cookie
-
%{<cookie>}c - 響應(yīng) cookie (SET_COOKIE)。如果省略參數(shù) (%c),則打印所有 cookie
-
%l - 來(lái)自 identd 的遠(yuǎn)程邏輯用戶名(始終返回“-”)
-
%m - 請(qǐng)求方法
-
%p - 本地端口
-
%q - 查詢字符串(不包括“?”字符)
-
%r - 請(qǐng)求的第一行
-
%s - 響應(yīng)的 HTTP 狀態(tài)代碼
-
%{<format>}t - 日期和時(shí)間。如果省略參數(shù),則使用通用日志格式 ("'['dd/MMM/yyyy:HH:mm:ss Z']'")。
-
%{property}u - 遠(yuǎn)程認(rèn)證用戶。當(dāng) micronaut-session 在類路徑上時(shí),如果參數(shù)被省略則返回會(huì)話 ID,否則指定的屬性會(huì)打印“-”
-
%U - 請(qǐng)求的 URI
-
%v - 本地服務(wù)器名稱
-
%D - 處理請(qǐng)求所花費(fèi)的時(shí)間,以毫秒為單位
-
%T - 處理請(qǐng)求所花費(fèi)的時(shí)間,以秒為單位
此外,您可以為常見模式使用以下別名:
- common - %h %l %u %t "%r" %s %b 通用日志格式 (CLF)
- combined - %h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" 組合日志格式
啟動(dòng)二級(jí)服務(wù)器
Micronaut支持通過(guò)NettyEmbeddedServerFactory接口以編程方式創(chuàng)建額外的Netty服務(wù)器。
這在你需要通過(guò)不同的端口和潛在的不同配置(HTTPS,線程資源等)暴露不同的服務(wù)器的情況下非常有用。
下面的例子演示了如何定義一個(gè)Factory Bean,使用程序化創(chuàng)建的配置啟動(dòng)一個(gè)額外的服務(wù)器。
以編程方式創(chuàng)建二級(jí)服務(wù)器
Java |
Groovy |
Kotlin |
import java.util.Collections;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Context;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.core.util.StringUtils;
import io.micronaut.discovery.ServiceInstanceList;
import io.micronaut.discovery.StaticServiceInstanceList;
import io.micronaut.http.server.netty.NettyEmbeddedServer;
import io.micronaut.http.server.netty.NettyEmbeddedServerFactory;
import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration;
import io.micronaut.http.ssl.ServerSslConfiguration;
import jakarta.inject.Named;
@Factory
public class SecondaryNettyServer {
public static final String SERVER_ID = "another"; // (1)
@Named(SERVER_ID)
@Context
@Bean(preDestroy = "close") // (2)
@Requires(beans = Environment.class)
NettyEmbeddedServer nettyEmbeddedServer(NettyEmbeddedServerFactory serverFactory) { // (3)
// configure server programmatically
final NettyHttpServerConfiguration configuration =
new NettyHttpServerConfiguration(); // (4)
final ServerSslConfiguration sslConfiguration = new ServerSslConfiguration(); // (5)
sslConfiguration.setBuildSelfSigned(true);
sslConfiguration.setEnabled(true);
sslConfiguration.setPort(-1); // random port
final NettyEmbeddedServer embeddedServer = serverFactory.build(configuration, sslConfiguration); // (6)
embeddedServer.start(); // (7)
return embeddedServer; // (8)
}
@Bean
ServiceInstanceList serviceInstanceList( // (9)
@Named(SERVER_ID) NettyEmbeddedServer nettyEmbeddedServer) {
return new StaticServiceInstanceList(
SERVER_ID,
Collections.singleton(nettyEmbeddedServer.getURI())
);
}
}
|
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Context
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.core.util.StringUtils
import io.micronaut.discovery.ServiceInstanceList
import io.micronaut.discovery.StaticServiceInstanceList
import io.micronaut.http.server.netty.NettyEmbeddedServer
import io.micronaut.http.server.netty.NettyEmbeddedServerFactory
import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration
import io.micronaut.http.ssl.ServerSslConfiguration
import jakarta.inject.Named
@Factory
class SecondaryNettyServer {
static final String SERVER_ID = "another" // (1)
@Named(SERVER_ID)
@Context
@Bean(preDestroy = "stop") // (2)
@Requires(beans = Environment.class)
NettyEmbeddedServer nettyEmbeddedServer(NettyEmbeddedServerFactory serverFactory) { // (3)
def configuration =
new NettyHttpServerConfiguration() // (4)
def sslConfiguration = new ServerSslConfiguration() // (5)
sslConfiguration.setBuildSelfSigned(true)
sslConfiguration.enabled = true
sslConfiguration.port = -1 // random port
// configure server programmatically
final NettyEmbeddedServer embeddedServer = serverFactory.build(configuration, sslConfiguration) // (5)
embeddedServer.start() // (6)
return embeddedServer // (7)
}
@Bean
ServiceInstanceList serviceInstanceList( // (8)
@Named(SERVER_ID) NettyEmbeddedServer nettyEmbeddedServer) {
return new StaticServiceInstanceList(
SERVER_ID,
[ nettyEmbeddedServer.URI ]
)
}
}
|
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Context
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.core.util.StringUtils
import io.micronaut.discovery.ServiceInstanceList
import io.micronaut.discovery.StaticServiceInstanceList
import io.micronaut.http.server.netty.NettyEmbeddedServer
import io.micronaut.http.server.netty.NettyEmbeddedServerFactory
import io.micronaut.http.server.netty.configuration.NettyHttpServerConfiguration
import io.micronaut.http.ssl.ServerSslConfiguration
import jakarta.inject.Named
@Factory
class SecondaryNettyServer {
companion object {
const val SERVER_ID = "another" // (1)
}
@Named(SERVER_ID)
@Context
@Bean(preDestroy = "close") // (2)
@Requires(beans = [Environment::class])
fun nettyEmbeddedServer(
serverFactory: NettyEmbeddedServerFactory // (3)
) : NettyEmbeddedServer {
val configuration = NettyHttpServerConfiguration() // (4)
val sslConfiguration = ServerSslConfiguration() // (5)
sslConfiguration.setBuildSelfSigned(true)
sslConfiguration.isEnabled = true
sslConfiguration.port = -1 // random port
// configure server programmatically
val embeddedServer = serverFactory.build(configuration, sslConfiguration) // (6)
embeddedServer.start() // (7)
return embeddedServer // (8)
}
@Bean
fun serviceInstanceList( // (9)
@Named(SERVER_ID) nettyEmbeddedServer: NettyEmbeddedServer
): ServiceInstanceList {
return StaticServiceInstanceList(
SERVER_ID, setOf(nettyEmbeddedServer.uri)
)
}
}
|
- 為服務(wù)器定義一個(gè)唯一的名稱
使用服務(wù)器名稱定義一個(gè) @Context 范圍的 bean,并包括 preDestroy="close" 以確保在上下文關(guān)閉時(shí)關(guān)閉服務(wù)器
將 NettyEmbeddedServerFactory 注入工廠 Bean
以編程方式創(chuàng)建 NettyHttpServerConfiguration
可選擇創(chuàng)建 ServerSslConfiguration
使用build方法構(gòu)建服務(wù)器實(shí)例
使用start方法啟動(dòng)服務(wù)器
將服務(wù)器實(shí)例作為托管 bean 返回
如果您希望通過(guò)服務(wù)器名稱注入 HTTP 客戶端,可選擇定義 ServiceInstanceList 的實(shí)例
有了這個(gè)類,當(dāng) ApplicationContext 啟動(dòng)時(shí),服務(wù)器也將以適當(dāng)?shù)呐渲脝?dòng)。
由于在步驟 8 中定義了 ServiceInstanceList,您可以將客戶端注入到測(cè)試中以測(cè)試輔助服務(wù)器:
注入服務(wù)器或客戶端
Java |
Groovy |
Kotlin |
@Client(path = "/", id = SecondaryNettyServer.SERVER_ID)
@Inject
HttpClient httpClient; // (1)
@Named(SecondaryNettyServer.SERVER_ID)
EmbeddedServer embeddedServer; // (2)
|
@Client(path = "/", id = SecondaryNettyServer.SERVER_ID)
@Inject
HttpClient httpClient // (1)
@Named(SecondaryNettyServer.SERVER_ID)
EmbeddedServer embeddedServer // (2)
|
@Inject
@field:Client(path = "/", id = SecondaryNettyServer.SERVER_ID)
lateinit var httpClient : HttpClient // (1)
@Inject
@field:Named(SecondaryNettyServer.SERVER_ID)
lateinit var embeddedServer : EmbeddedServer // (2)
|
使用服務(wù)器名稱通過(guò) ID 注入客戶端
使用@Named 注釋作為限定符來(lái)注入嵌入式服務(wù)器實(shí)例
更多建議: