Micronaut HTTP 服務器支持以與傳統(tǒng) Java 應用程序中的 Servlet 過濾器類似(但反應性)的方式將過濾器應用于請求/響應處理。
過濾器支持以下用例:
裝飾傳入的HttpRequest
傳出 HttpResponse 的修改
實施橫切關(guān)注點,例如安全性、跟蹤等。
對于服務端應用,可以實現(xiàn)HttpServerFilter接口的doFilter方法。
doFilter 方法接受 HttpRequest 和 ServerFilterChain 的實例。
ServerFilterChain 接口包含已解析的過濾器鏈,其中鏈中的最終條目是匹配的路由。 ServerFilterChain.proceed(io.micronaut.http.HttpRequest) 方法恢復請求的處理。
proceed(..) 方法返回一個 Reactive Streams Publisher,它發(fā)出要返回給客戶端的響應。過濾器的實現(xiàn)者可以訂閱發(fā)布者并改變發(fā)出的 MutableHttpResponse 以在將響應返回給客戶端之前修改響應。
為了將這些概念付諸實踐,讓我們看一個例子。
過濾器在事件循環(huán)中執(zhí)行,因此必須將阻塞操作卸載到另一個線程池。
編寫過濾器
假設您希望使用某個外部系統(tǒng)將每個請求跟蹤到 Micronaut“Hello World”示例。該系統(tǒng)可以是數(shù)據(jù)庫或分布式跟蹤服務,并且可能需要 I/O 操作。
你不應該在你的過濾器中阻塞底層的 Netty 事件循環(huán);相反,過濾器應該在任何 I/O 完成后繼續(xù)執(zhí)行。
例如,考慮使用 Project Reactor 組合 I/O 操作的 TraceService:
使用 Reactive Streams 的 TraceService 示例
Java |
Groovy |
Kotlin |
import io.micronaut.http.HttpRequest;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.inject.Singleton;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@Singleton
public class TraceService {
private static final Logger LOG = LoggerFactory.getLogger(TraceService.class);
Publisher<Boolean> trace(HttpRequest<?> request) {
return Mono.fromCallable(() -> { // (1)
LOG.debug("Tracing request: {}", request.getUri());
// trace logic here, potentially performing I/O (2)
return true;
}).subscribeOn(Schedulers.boundedElastic()) // (3)
.flux();
}
}
|
import io.micronaut.http.HttpRequest
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import jakarta.inject.Singleton
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import java.util.concurrent.Callable
@Singleton
class TraceService {
private static final Logger LOG = LoggerFactory.getLogger(TraceService.class)
Flux<Boolean> trace(HttpRequest<?> request) {
Mono.fromCallable(() -> { // (1)
LOG.debug('Tracing request: {}', request.uri)
// trace logic here, potentially performing I/O (2)
return true
}).flux().subscribeOn(Schedulers.boundedElastic()) // (3)
}
}
|
import io.micronaut.http.HttpRequest
import org.slf4j.LoggerFactory
import jakarta.inject.Singleton
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
@Singleton
class TraceService {
private val LOG = LoggerFactory.getLogger(TraceService::class.java)
internal fun trace(request: HttpRequest<*>): Flux<Boolean> {
return Mono.fromCallable {
// (1)
LOG.debug("Tracing request: {}", request.uri)
// trace logic here, potentially performing I/O (2)
true
}.subscribeOn(Schedulers.boundedElastic()) // (3)
.flux()
}
}
|
Mono 類型創(chuàng)建執(zhí)行潛在阻塞操作的邏輯,以從請求中寫入跟蹤數(shù)據(jù)
由于這只是一個示例,因此該邏輯尚未執(zhí)行任何操作
Schedulers.boundedElastic 執(zhí)行邏輯
然后,您可以將此實現(xiàn)注入到您的過濾器定義中:
HttpServerFilter 示例
Java |
Groovy |
Kotlin |
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@Filter("/hello/**") // (1)
public class TraceFilter implements HttpServerFilter { // (2)
private final TraceService traceService;
public TraceFilter(TraceService traceService) { // (3)
this.traceService = traceService;
}
}
|
import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Filter
import io.micronaut.http.filter.HttpServerFilter
import io.micronaut.http.filter.ServerFilterChain
import org.reactivestreams.Publisher
@Filter("/hello/**") // (1)
class TraceFilter implements HttpServerFilter { // (2)
private final TraceService traceService
TraceFilter(TraceService traceService) { // (3)
this.traceService = traceService
}
}
|
import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Filter
import io.micronaut.http.filter.HttpServerFilter
import io.micronaut.http.filter.ServerFilterChain
import org.reactivestreams.Publisher
@Filter("/hello/**") // (1)
class TraceFilter(// (2)
private val traceService: TraceService)// (3)
: HttpServerFilter {
}
|
Filter 注釋定義過濾器匹配的 URI 模式
該類實現(xiàn)了 HttpServerFilter 接口
之前定義的 TraceService 是通過構(gòu)造函數(shù)注入的
最后一步是編寫 HttpServerFilter 接口的 doFilter 實現(xiàn)。
doFilter 實現(xiàn)
Java |
Groovy |
Kotlin |
@Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
ServerFilterChain chain) {
return Flux.from(traceService
.trace(request)) // (1)
.switchMap(aBoolean -> chain.proceed(request)) // (2)
.doOnNext(res ->
res.getHeaders().add("X-Trace-Enabled", "true") // (3)
);
}
|
@Override
Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
ServerFilterChain chain) {
traceService
.trace(request) // (1)
.switchMap({ aBoolean -> chain.proceed(request) }) // (2)
.doOnNext({ res ->
res.headers.add("X-Trace-Enabled", "true") // (3)
})
}
|
override fun doFilter(request: HttpRequest<*>,
chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
return traceService.trace(request) // (1)
.switchMap { aBoolean -> chain.proceed(request) } // (2)
.doOnNext { res ->
res.headers.add("X-Trace-Enabled", "true") // (3)
}
}
|
調(diào)用 TraceService 來跟蹤請求
如果調(diào)用成功,過濾器將使用 Project Reactor 的 switchMap 方法恢復請求處理,該方法調(diào)用 ServerFilterChain 的 proceed 方法
最后,Project Reactor 的 doOnNext 方法將 X-Trace-Enabled 標頭添加到響應中。
前面的示例演示了一些關(guān)鍵概念,例如在處理請求和修改傳出響應之前以非阻塞方式執(zhí)行邏輯。
這些示例使用 Project Reactor,但是您可以使用任何支持反應流規(guī)范的反應框架
Filter可以通過設置patternStyle使用不同風格的pattern進行路徑匹配。默認情況下,它使用 AntPathMatcher 進行路徑匹配。使用 Ant 時,映射使用以下規(guī)則匹配 URL:
?匹配一個字符
* 匹配零個或多個字符
** 匹配路徑中的零個或多個子目錄
表 1. @Filter 注解路徑匹配示例
路徑 |
示例匹配路徑 |
/**
|
任何路徑
|
customer/j?y
|
customer/joy, customer/jay
|
customer/*/id
|
customer/adam/id, com/amy/id
|
customer/**
|
customer/adam, customer/adam/id, customer/adam/name
|
customer/*/.html
|
customer/index.html, customer/adam/profile.html, customer/adam/job/description.html
|
另一個選項是基于正則表達式的匹配。要使用正則表達式,請設置 patternStyle = FilterPatternStyle.REGEX。 pattern 屬性應包含一個正則表達式,該正則表達式應與提供的 URL 完全匹配(使用 Matcher#matches)。
首選使用 FilterPatternStyle.ANT,因為模式匹配比使用正則表達式更高效。當您的模式無法使用 Ant 正確編寫時,應使用 FilterPatternStyle.REGEX。
錯誤狀態(tài)
從 chain.proceed 返回的發(fā)布者永遠不應該發(fā)出錯誤。在上游過濾器發(fā)出錯誤或路由本身拋出異常的情況下,應該發(fā)出錯誤響應而不是異常。在某些情況下,可能需要知道錯誤響應的原因,為此目的,如果響應是由于發(fā)出或拋出異常而創(chuàng)建的,則響應中存在一個屬性。原始原因存儲為屬性 EXCEPTION。
更多建議: