Micronaut HTTP 过滤器

2023-03-07 14:11 更新

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()
    }
}
  1. Mono 类型创建执行潜在阻塞操作的逻辑,以从请求中写入跟踪数据

  2. 由于这只是一个示例,因此该逻辑尚未执行任何操作

  3. 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 {

}
  1. Filter 注释定义过滤器匹配的 URI 模式

  2. 该类实现了 HttpServerFilter 接口

  3. 之前定义的 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)
        }
}
  1. 调用 TraceService 来跟踪请求

  2. 如果调用成功,过滤器将使用 Project Reactor 的 switchMap 方法恢复请求处理,该方法调用 ServerFilterChain 的 proceed 方法

  3. 最后,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。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号