Micronaut 中基於註解的 HTTP 過濾器
1. 概述
在本教程中,我們將介紹 Micronaut 框架提供的註釋的 HTTP 過濾器。最初,Micronaut 中的 HTTP 過濾器更接近 Java EE Filter
介面和 Spring Boot 過濾器方法。但隨著最新主要版本的發布,過濾器現在可以基於註釋,將請求和回應的過濾器分開。
在本教程中,我們將研究 Micronaut 中的 HTTP 過濾器。更具體地說,我們將重點放在版本 4 中引入的伺服器過濾器,即基於註釋的過濾器方法。
2.HTTP過濾器
HTTP 過濾器是作為 Java EE 中的介面引入的。它是所有 Java Web 框架中實現的「規範」。據記錄:
過濾器是一個對象,它對資源(servlet 或靜態內容)的請求或來自資源的回應或兩者執行過濾任務。
實作 Java EE 介面的過濾器有一個doFilter()
方法,有 3 個參數: ServletRequest
、 ServletResponse
和FilterChain
。這使我們能夠存取請求物件和回應,並使用鏈將請求和回應傳遞給下一個元件。直到今天,即使是較新的框架仍然可能使用相同或相似的名稱和參數。
過濾器非常方便地用於一些常見的現實生活用例:
- 身份驗證過濾器
- 標頭過濾器(從請求中檢索值或在回應中新增值)
- 指標過濾器(例如記錄請求執行時間時)
- 記錄過濾器
3. Micronaut 中的 HTTP 過濾器
Micronaut 中的 HTTP 過濾器在某種程度上遵循 Java EE Filter
規格。例如,Micronaut 的HttpFilter
介面提供了一種doFilter()
方法,該方法的參數為請求對象,參數為鏈結對象。請求參數允許我們過濾請求,然後使用鏈物件來處理它並回傳回應。最後,如果需要,可以對響應對象進行更改。
在 Micronaut 4 中,引入了一些新的過濾器註釋,它們為僅請求、僅回應或兩者提供過濾方法。
Micronaut 使用@ServerFilter
為我們接收的伺服器請求和發送的回應提供過濾器。但它也使用@ClientFilter
為我們的 REST 用戶端、針對第三系統和微服務的請求提供過濾器。
伺服器過濾器有一些使它們非常靈活和有用的概念:
- 接受一些模式來搭配我們想要過濾的路徑
- 可以排序,因為某些過濾器需要在其他過濾器之前執行(例如,身份驗證檢查過濾器應始終位於第一個)
- 提供有關過濾可能屬於錯誤類型的回應的選項(例如過濾可拋出的內容)
我們將在接下來的段落中更詳細地介紹其中一些概念。
4. 過濾模式
Micronaut 中的 HTTP 過濾器根據端點的路徑特定於端點。要配置過濾器應用於哪個端點,我們可以設定一個模式來匹配路徑。此模式可以具有不同的樣式,例如ANT
或REGEX,
而值是實際的模式,例如*/endpoint** 。
模式樣式有不同的選項,但預設是AntPathMatcher
因為它在效能方面更有效率。當使用模式進行匹配時,Regex 是一種更強大的樣式,但它比 Ant 慢得多。因此,當 Ant 不支援我們正在尋找的樣式時,我們應該僅將其用作最後的選擇。
使用過濾器時我們需要的一些樣式範例是:
- /\**將符合任何路徑
- /filters-annotations/\**將符合 `filters-annotations` 下的所有路徑,例如 /filters-annotations/endpoint1 和 /filters-annotations/endpoint2
- /filters-annotations/\1*將符合「filters-annotations」下的所有路徑,但僅當以「1」結尾時
- \*/endpoint1*將符合所有以「endpoint1」結尾的路徑
- \*/endpoint**將匹配所有以“endpoint”結尾的路徑以及末尾的任何額外內容
其中,在預設的FilterPatternStyle.ANT
樣式中:
- *匹配零個或多個字符
- \**符合路徑中的零個或多個子目錄
5. Micronaut 中基於註解的伺服器過濾器
Micronaut 中附註解的 HTTP 過濾器是在 Micronaut 主要版本 4 中新增的,也稱為過濾器方法。過濾器方法允許我們分離請求或回應的特定過濾器。在基於註解的過濾器之前,我們只有一種方法來定義過濾器,並且與過濾請求或回應相同。這樣我們就可以分離關注點,從而保持我們的程式碼更乾淨、更具可讀性。
過濾器方法仍然允許我們定義一個過濾器,它既可以存取請求,也可以根據需要使用FilterContinuation
修改回應。
5.1.過濾方法
根據我們要過濾請求還是回應,我們可以使用@RequestFilter
或@ResponseFilter
註解。在類別級別,我們仍然需要一個註解來定義過濾器,即@ServerFilter
。過濾器的路徑和過濾器的順序是在類別層級定義的。我們也可以選擇根據過濾器方法套用路徑模式。
讓我們結合所有這些資訊來建立一個ServerFilter
,它具有一種過濾請求的方法和另一種過濾回應的方法:
@Slf4j
@ServerFilter(patterns = { "**/endpoint*" })
public class CustomFilter implements Ordered {
@RequestFilter
@ExecuteOn(TaskExecutors.BLOCKING)
public void filterRequest(HttpRequest<?> request) {
String customRequestHeader = request.getHeaders()
.get(CUSTOM_HEADER_KEY);
log.info("request header: {}", customRequestHeader);
}
@ResponseFilter
public void filterResponse(MutableHttpResponse<?> res) {
res.getHeaders()
.add(X_TRACE_HEADER_KEY, "true");
}
}
filterRequest()
方法用@RequestFilter
註解並接受HTTPRequest
參數。這使我們能夠存取該請求。然後,它讀取並記錄請求中的標頭。在現實生活中的範例中,這可能會做更多的事情,例如根據傳遞的標頭值拒絕請求。
filterResponse()
方法使用@ResponseFilter
註解,並接受一個MutableHttpResponse
參數,這是我們要回傳給客戶端的回應物件。不過,在我們回應之前,此方法會在回應中新增一個標頭。
請記住,該請求可能已經由我們擁有的另一個具有較低順序的過濾器處理,並且接下來可能由另一個具有較高順序的過濾器處理。類似地,響應可能已由較高階的濾波器處理,然後將套用較低階的濾波器。有關詳細信息,請參閱“過濾器順序”段落。
5.2.延續
過濾方法是一個很好的功能,可以讓我們的程式碼保持整潔。然而,仍然需要有過濾相同請求和回應的方法。 Micronaut 提供了 Continuations 來處理這個需求。此方法上的註解與請求中的@RequestFilter
相同,但參數不同。我們還必須在類別上使用@ServerFilter
註解。
我們需要存取請求並使用回應上的值的情況的一個典型範例是分散式系統中分散式追蹤模式的追蹤標頭。在較高的層面上,我們使用標頭來追蹤請求,以便我們知道如果傳回錯誤,它到底在哪一步失敗。為此,我們需要在每個請求/訊息中傳遞一個“request-id”或“trace-id”,如果該服務與另一個服務通信,它會傳遞相同的值:
@Slf4j
@ServerFilter(patterns = { "**/endpoint*" })
@Order(1)
public class RequestIDFilter implements Ordered {
@RequestFilter
@ExecuteOn(TaskExecutors.BLOCKING)
public void filterRequestIDHeader(
HttpRequest<?> request,
FilterContinuation<MutableHttpResponse<?>> continuation
) {
String requestIdHeader = request.getHeaders().get(REQUEST_ID_HEADER_KEY);
if (requestIdHeader == null || requestIdHeader.trim().isEmpty()) {
requestIdHeader = UUID.randomUUID().toString();
log.info(
"request ID not received. Created and will return one with value: [{}]",
requestIdHeader
);
} else {
log.info("request ID received. Request ID: [{}]", requestIdHeader);
}
MutableHttpResponse<?> res = continuation.proceed();
res.getHeaders().add(REQUEST_ID_HEADER_KEY, requestIdHeader);
}
}
filterRequestIDHeader()
方法使用@RequestFilter
註解,並有 1 個HttpRequest
和 1 個FilterContinuation
參數。我們從請求參數存取請求,並檢查「Request-ID」標頭是否有值。如果沒有,我們將建立一個並在任何情況下記錄該值。
透過使用continuation.proceed()
方法,我們可以存取回應物件。然後,我們在回應中加入相同的標頭和「Request-ID」標頭的值,以傳播到客戶端。
5.3.過濾順序
在許多用例中,讓特定過濾器在其他過濾器之前或之後執行是有意義的。 Micronaut 中的 HTTP 過濾器提供了兩種方法來處理過濾器執行的順序。一種是@Order
註解,另一種是實作Ordered
介面。兩者都是班級程度。
排序的工作方式是,我們提供一個 int 值,它是過濾器的執行順序。對於請求過濾器來說,這很簡單。訂單-5將在訂單2之前執行,訂單2將在訂單4之前執行。將首先套用順序 4,然後套用順序 2,最後套用順序 -5。
當我們實作該介面時,我們需要手動重寫getOrder()
方法。它預設為零:
@Filter(patterns = { "**/*1" })
public class PrivilegedUsersEndpointFilter implements HttpServerFilter, Ordered {
// filter methods ommited
@Override
public int getOrder() {
return 3;
}
}
當我們使用Annotation時,我們只需要設定值:
@ServerFilter(patterns = { "**/endpoint*" })
@Order(1)
public class RequestIDFilter implements Ordered {
// filter methods ommited
}
請注意,測試@Order
Annotation 和實現Ordered
介面的組合會導致錯誤行為,因此最好選擇這兩種方法之一並將其應用到各處。
六、結論
在本教程中,我們研究了一般過濾器的概念以及 Micronaut 中的 HTTP 過濾器。我們看到了為實現過濾器而提供的不同選項以及一些現實生活中的用例。然後,我們介紹了基於註釋的過濾器的範例,適用於僅請求過濾器、僅回應過濾器以及兩者。最後,我們花了一些時間討論關鍵概念,例如路徑模式和過濾器的順序。
與往常一樣,所有原始程式碼都可以在 GitHub 上取得。