Micronaut HTTP 服務(wù)器支持以與傳統(tǒng) Java 應(yīng)用程序中的 Servlet 過(guò)濾器類似(但反應(yīng)性)的方式將過(guò)濾器應(yīng)用于請(qǐng)求/響應(yīng)處理。
過(guò)濾器支持以下用例:
對(duì)于服務(wù)端應(yīng)用,可以實(shí)現(xiàn)HttpServerFilter接口的doFilter方法。
doFilter 方法接受 HttpRequest 和 ServerFilterChain 的實(shí)例。
ServerFilterChain 接口包含已解析的過(guò)濾器鏈,其中鏈中的最終條目是匹配的路由。 ServerFilterChain.proceed(io.micronaut.http.HttpRequest) 方法恢復(fù)請(qǐng)求的處理。
proceed(..) 方法返回一個(gè) Reactive Streams Publisher,它發(fā)出要返回給客戶端的響應(yīng)。過(guò)濾器的實(shí)現(xiàn)者可以訂閱發(fā)布者并改變發(fā)出的 MutableHttpResponse 以在將響應(yīng)返回給客戶端之前修改響應(yīng)。
為了將這些概念付諸實(shí)踐,讓我們看一個(gè)例子。
過(guò)濾器在事件循環(huán)中執(zhí)行,因此必須將阻塞操作卸載到另一個(gè)線程池。
編寫過(guò)濾器
假設(shè)您希望使用某個(gè)外部系統(tǒng)將每個(gè)請(qǐng)求跟蹤到 Micronaut“Hello World”示例。該系統(tǒng)可以是數(shù)據(jù)庫(kù)或分布式跟蹤服務(wù),并且可能需要 I/O 操作。
你不應(yīng)該在你的過(guò)濾器中阻塞底層的 Netty 事件循環(huán);相反,過(guò)濾器應(yīng)該在任何 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í)行潛在阻塞操作的邏輯,以從請(qǐng)求中寫入跟蹤數(shù)據(jù)
由于這只是一個(gè)示例,因此該邏輯尚未執(zhí)行任何操作
Schedulers.boundedElastic 執(zhí)行邏輯
然后,您可以將此實(shí)現(xiàn)注入到您的過(guò)濾器定義中:
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 注釋定義過(guò)濾器匹配的 URI 模式
該類實(shí)現(xiàn)了 HttpServerFilter 接口
之前定義的 TraceService 是通過(guò)構(gòu)造函數(shù)注入的
最后一步是編寫 HttpServerFilter 接口的 doFilter 實(shí)現(xiàn)。
doFilter 實(shí)現(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 來(lái)跟蹤請(qǐng)求
如果調(diào)用成功,過(guò)濾器將使用 Project Reactor 的 switchMap 方法恢復(fù)請(qǐng)求處理,該方法調(diào)用 ServerFilterChain 的 proceed 方法
最后,Project Reactor 的 doOnNext 方法將 X-Trace-Enabled 標(biāo)頭添加到響應(yīng)中。
前面的示例演示了一些關(guān)鍵概念,例如在處理請(qǐng)求和修改傳出響應(yīng)之前以非阻塞方式執(zhí)行邏輯。
這些示例使用 Project Reactor,但是您可以使用任何支持反應(yīng)流規(guī)范的反應(yīng)框架
Filter可以通過(guò)設(shè)置patternStyle使用不同風(fēng)格的pattern進(jìn)行路徑匹配。默認(rèn)情況下,它使用 AntPathMatcher 進(jìn)行路徑匹配。使用 Ant 時(shí),映射使用以下規(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
|
另一個(gè)選項(xiàng)是基于正則表達(dá)式的匹配。要使用正則表達(dá)式,請(qǐng)?jiān)O(shè)置 patternStyle = FilterPatternStyle.REGEX。 pattern 屬性應(yīng)包含一個(gè)正則表達(dá)式,該正則表達(dá)式應(yīng)與提供的 URL 完全匹配(使用 Matcher#matches)。
首選使用 FilterPatternStyle.ANT,因?yàn)槟J狡ヅ浔仁褂谜齽t表達(dá)式更高效。當(dāng)您的模式無(wú)法使用 Ant 正確編寫時(shí),應(yīng)使用 FilterPatternStyle.REGEX。
錯(cuò)誤狀態(tài)
從 chain.proceed 返回的發(fā)布者永遠(yuǎn)不應(yīng)該發(fā)出錯(cuò)誤。在上游過(guò)濾器發(fā)出錯(cuò)誤或路由本身拋出異常的情況下,應(yīng)該發(fā)出錯(cuò)誤響應(yīng)而不是異常。在某些情況下,可能需要知道錯(cuò)誤響應(yīng)的原因,為此目的,如果響應(yīng)是由于發(fā)出或拋出異常而創(chuàng)建的,則響應(yīng)中存在一個(gè)屬性。原始原因存儲(chǔ)為屬性 EXCEPTION。
更多建議: