§篩選器
Play 提供一個簡單的篩選器 API,用於將全域篩選器套用至每個要求。
§篩選器與動作組合
此篩選器 API 適用於不分青紅皂白套用至所有路由的跨切入關注事項。例如,以下是篩選器的部分常見使用案例
相較之下,動作合成適用於特定路由的關注事項,例如驗證和授權、快取等等。如果您不希望篩選器套用至每個路由,請考慮改用動作合成,它的功能強大許多。別忘了您可以建立自己的動作建構器,將自己定義的動作自訂集合合成至每個路由,以減少樣板程式碼。
§一個簡單的記錄篩選器
以下是 Play Framework 中一個簡單的篩選器,它會計時並記錄請求在執行中所花費的時間,並實作Filter
特質
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import javax.inject.Inject;
import org.apache.pekko.stream.Materializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.mvc.*;
public class LoggingFilter extends Filter {
private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);
@Inject
public LoggingFilter(Materializer mat) {
super(mat);
}
@Override
public CompletionStage<Result> apply(
Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,
Http.RequestHeader requestHeader) {
long startTime = System.currentTimeMillis();
return nextFilter
.apply(requestHeader)
.thenApply(
result -> {
long endTime = System.currentTimeMillis();
long requestTime = endTime - startTime;
log.info(
"{} {} took {}ms and returned {}",
requestHeader.method(),
requestHeader.uri(),
requestTime,
result.status());
return result.withHeader("Request-Time", "" + requestTime);
});
}
}
讓我們了解這裡發生了什麼事。首先要注意的是 apply
方法的簽章。第一個參數 nextFilter
是接收請求標頭並產生結果的函式。第二個參數 requestHeader
是傳入請求的實際請求標頭。
nextFilter
參數代表篩選器鏈中的下一個動作。呼叫它會導致呼叫動作。在大部分情況下,您會希望在將來的某個時間點呼叫它。如果您因為某些原因想要封鎖請求,您可以決定不呼叫它。
我們在呼叫鏈中的下一個篩選器之前儲存時間戳記。呼叫下一個篩選器會傳回一個 CompletionStage<Result>
,它最終會被兌現。請參閱處理非同步結果章節,以取得有關非同步結果的更多詳細資料。然後,我們透過呼叫帶有封閉項式的 thenApply
方法來處理未來的 Result
,該封閉項式會接收 Result
。我們計算請求所花費的時間,記錄它,並透過呼叫 result.withHeader("Request-Time", "" + requestTime)
在回應標頭中將它傳送回用戶端。
§使用篩選器
使用篩選器的最簡單方法是在根目錄中提供 HttpFilters
介面的實作,稱為 Filters
。通常您應該擴充 DefaultHttpFilters
類別,並將您的篩選器傳遞給 varargs 建構函式
import javax.inject.Inject;
import play.filters.gzip.GzipFilter;
import play.http.DefaultHttpFilters;
public class Filters extends DefaultHttpFilters {
@Inject
public Filters(GzipFilter gzip, LoggingFilter logging) {
super(gzip, logging);
}
}
如果您想要在不同的環境中使用不同的篩選器,或不想將此類別放入根目錄,您可以透過在 application.conf
中將 play.http.filters
設定為類別的完全限定類別名稱,來設定 Play 應該在哪裡尋找類別。例如
play.http.filters=com.example.Filters
§篩選器在哪裡發揮作用?
在路由器查詢到動作後,篩選器會包裝動作。這表示您無法使用篩選器來轉換路徑、方法或查詢參數,以影響路由器。但是,您可以透過直接從篩選器呼叫動作,將要求導向不同的動作,但請注意這會繞過其餘的篩選器鏈。如果您確實需要在呼叫路由器之前修改要求,更好的方法是將您的邏輯放在 `HttpRequestHandler` 中。
由於篩選器是在路由完成後套用,因此可以透過 RequestHeader
上的 attrs
地圖,從要求存取路由資訊。例如,您可能想要針對動作方法記錄時間。在這種情況下,您可以將篩選器更新為如下所示
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import javax.inject.Inject;
import org.apache.pekko.stream.Materializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.api.routing.HandlerDef;
import play.mvc.*;
import play.routing.Router;
public class RoutedLoggingFilter extends Filter {
private static final Logger log = LoggerFactory.getLogger(RoutedLoggingFilter.class);
@Inject
public RoutedLoggingFilter(Materializer mat) {
super(mat);
}
@Override
public CompletionStage<Result> apply(
Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,
Http.RequestHeader requestHeader) {
long startTime = System.currentTimeMillis();
return nextFilter
.apply(requestHeader)
.thenApply(
result -> {
HandlerDef handlerDef = requestHeader.attrs().get(Router.Attrs.HANDLER_DEF);
String actionMethod = handlerDef.controller() + "." + handlerDef.method();
long endTime = System.currentTimeMillis();
long requestTime = endTime - startTime;
log.info("{} took {}ms and returned {}", actionMethod, requestTime, result.status());
return result.withHeader("Request-Time", "" + requestTime);
});
}
}
注意:路由屬性是 Play 路由器的功能。如果您使用自訂路由器,這些參數可能無法使用。
§更強大的篩選器
Play 提供一個較低階的篩選器 API,稱為 EssentialFilter
,它讓你能夠完全存取請求主體。這個 API 讓你能夠使用另一個動作包裝 EssentialAction。
以下是上述篩選器範例,改寫為 EssentialFilter
import java.util.concurrent.Executor;
import javax.inject.Inject;
import org.apache.pekko.util.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.libs.streams.Accumulator;
import play.mvc.*;
public class EssentialLoggingFilter extends EssentialFilter {
private static final Logger log = LoggerFactory.getLogger(EssentialLoggingFilter.class);
private final Executor executor;
@Inject
public EssentialLoggingFilter(Executor executor) {
super();
this.executor = executor;
}
@Override
public EssentialAction apply(EssentialAction next) {
return EssentialAction.of(
request -> {
long startTime = System.currentTimeMillis();
Accumulator<ByteString, Result> accumulator = next.apply(request);
return accumulator.map(
result -> {
long endTime = System.currentTimeMillis();
long requestTime = endTime - startTime;
log.info(
"{} {} took {}ms and returned {}",
request.method(),
request.uri(),
requestTime,
result.status());
return result.withHeader("Request-Time", "" + requestTime);
},
executor);
});
}
}
除了建立一個新的 EssentialAction
來包裝傳入的 next
動作之外,這裡的主要差異在於當我們呼叫 next 時,我們會取得一個 Accumulator
。如果你願意,你可以使用 through
方法將它與 Pekko Streams Flow 組合,對串流進行一些轉換。然後我們將 map
迭代器的結果,並處理它。
注意:儘管看起來有兩個不同的篩選器 API,但只有一個,
EssentialFilter
。較簡單的Filter
API 在較早的範例中延伸EssentialFilter
,並透過建立一個新的EssentialAction
來實作它。傳入的回呼讓它看起來可以透過為Result
建立一個承諾來略過主體剖析,而主體剖析和動作的其餘部分會非同步執行。
下一步:錯誤處理
在這個文件中發現錯誤了嗎?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 之後,請隨時貢獻一個 pull request。有問題或建議要分享嗎?前往 我們的社群論壇 與社群展開對話。