Micronaut HTTP 服务器支持以与传统 Java 应用程序中的 Servlet 过滤器类似(但反应性)的方式将过滤器应用于请求/响应处理。
过滤器支持以下用例:
装饰传入的HttpRequest
传出 HttpResponse 的修改
实施横切关注点,例如安全性、跟踪等。
对于服务端应用,可以实现HttpServerFilter接口的doFilter方法。
doFilter 方法接受 HttpRequest 和 ServerFilterChain 的实例。
ServerFilterChain 接口包含已解析的过滤器链,其中链中的最终条目是匹配的路由。 ServerFilterChain.proceed(io.micronaut.http.HttpRequest) 方法恢复请求的处理。
proceed(..) 方法返回一个 Reactive Streams Publisher,它发出要返回给客户端的响应。过滤器的实现者可以订阅发布者并改变发出的 MutableHttpResponse 以在将响应返回给客户端之前修改响应。
为了将这些概念付诸实践,让我们看一个例子。
过滤器在事件循环中执行,因此必须将阻塞操作卸载到另一个线程池。
编写过滤器
假设您希望使用某个外部系统将每个请求跟踪到 Micronaut“Hello World”示例。该系统可以是数据库或分布式跟踪服务,并且可能需要 I/O 操作。
你不应该在你的过滤器中阻塞底层的 Netty 事件循环;相反,过滤器应该在任何 I/O 完成后继续执行。
例如,考虑使用 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 类型创建执行潜在阻塞操作的逻辑,以从请求中写入跟踪数据
由于这只是一个示例,因此该逻辑尚未执行任何操作
Schedulers.boundedElastic 执行逻辑
然后,您可以将此实现注入到您的过滤器定义中:
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 模式
该类实现了 HttpServerFilter 接口
之前定义的 TraceService 是通过构造函数注入的
最后一步是编写 HttpServerFilter 接口的 doFilter 实现。
doFilter 实现
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)
}
}
|
调用 TraceService 来跟踪请求
如果调用成功,过滤器将使用 Project Reactor 的 switchMap 方法恢复请求处理,该方法调用 ServerFilterChain 的 proceed 方法
最后,Project Reactor 的 doOnNext 方法将 X-Trace-Enabled 标头添加到响应中。
前面的示例演示了一些关键概念,例如在处理请求和修改传出响应之前以非阻塞方式执行逻辑。
这些示例使用 Project Reactor,但是您可以使用任何支持反应流规范的反应框架
Filter可以通过设置patternStyle使用不同风格的pattern进行路径匹配。默认情况下,它使用 AntPathMatcher 进行路径匹配。使用 Ant 时,映射使用以下规则匹配 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。
错误状态
从 chain.proceed 返回的发布者永远不应该发出错误。在上游过滤器发出错误或路由本身抛出异常的情况下,应该发出错误响应而不是异常。在某些情况下,可能需要知道错误响应的原因,为此目的,如果响应是由于发出或抛出异常而创建的,则响应中存在一个属性。原始原因存储为属性 EXCEPTION。
更多建议: