Micronaut HTTP 過濾器

2023-03-07 14:11 更新

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()
    }
}
  1. Mono 類型創(chuàng)建執(zhí)行潛在阻塞操作的邏輯,以從請求中寫入跟蹤數(shù)據(jù)

  2. 由于這只是一個示例,因此該邏輯尚未執(zhí)行任何操作

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

}
  1. Filter 注釋定義過濾器匹配的 URI 模式

  2. 該類實現(xiàn)了 HttpServerFilter 接口

  3. 之前定義的 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)
        }
}
  1. 調(diào)用 TraceService 來跟蹤請求

  2. 如果調(diào)用成功,過濾器將使用 Project Reactor 的 switchMap 方法恢復請求處理,該方法調(diào)用 ServerFilterChain 的 proceed 方法

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


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號