在 Spring Boot Filter 中取得回應主體
一、簡介
在本文中,我們將探討如何從 Spring Boot 濾鏡中的ServletResponse
檢索回應正文。
本質上,我們將定義問題,然後使用快取回應正文的解決方案,使其在 Spring Boot 過濾器中可用。讓我們開始。
2. 理解問題
首先,讓我們了解我們要解決的問題。
使用 Spring Boot 過濾器時,從ServletResponse
存取回應主體是很棘手的。這是因為響應主體不容易取得,因為它是在過濾器鏈完成執行後寫入輸出流的。
但是,某些操作(例如產生哈希簽名)需要回應正文的內容,然後才能將其傳送到客戶端。因此,我們需要找到一種方法來讀取body的內容。
3. 在過濾器中使用ContentCachingResponseWrapper
為了克服前面定義的問題,我們將建立一個自訂篩選器並使用 Spring Framework 提供的ContentCachingResponseWrapper
類別:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
ContentCachingResponseWrapper responseCacheWrapperObject =
new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);
filterChain.doFilter(servletRequest, responseCacheWrapperObject);
byte[] responseBody = responseCacheWrapperObject.getContentAsByteArray();
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
byte[] md5Hash = md5Digest.digest(responseBody);
String md5HashString = DatatypeConverter.printHexBinary(md5Hash);
responseCacheWrapperObject.getResponse().setHeader("Response-Body-MD5", md5HashString);
// ...
}
簡而言之,包裝類別允許我們包裝HttpServletResponse
來快取回應正文內容,並呼叫doFilter()
將請求傳遞到下一個過濾器。
請記住,我們一定不要忘記這裡的doFilter()
呼叫。否則,傳入的請求將不會進入 Spring 過濾器鏈中的下一個過濾器,應用程式也不會按照我們的預期處理該請求。事實上,不呼叫doFilter()
是違反servlet規範的。
另外,我們一定不要忘記使用responseCacheWrapperObject
呼叫doFilter()
。否則,回應正文將不會被快取。簡而言之, ContentCachingResponseWrapper
將過濾器放置在回應輸出流和發出 HTTP 請求的客戶端之間。因此,在建立回應正文輸出流後(在本例中是在doFilter()
呼叫之後),內容可在過濾器內處理。
使用包裝器後,可以使用getContentAsByteArray()
方法在篩選器中取得回應正文。我們使用這個方法來計算MD5哈希值。
首先,我們使用MessageDigest
類別建立回應正文的 MD5 雜湊值。其次,我們將位元組數組轉換為十六進位字串。第三,我們使用setHeader()
方法將產生的雜湊字串設定為回應物件的標頭。
如果需要,我們可以將位元組數組轉換為字串,並使正文的內容更加明確。
最後,在退出doFilter()
方法之前呼叫copyBodyToResponse()
至關重要,以將更新後的回應正文複製回原始回應:
responseCacheWrapperObject.copyBodyToResponse();
在退出doFilter()
方法之前呼叫copyBodyToResponse()
至關重要。否則,客戶端將不會收到完整的回應。
4. 配置過濾器
現在,我們需要在 Spring Boot 中新增過濾器:
@Bean
public FilterRegistrationBean loggingFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MD5Filter());
return registrationBean;
}
在這裡,我們使用我們之前建立的過濾器的實作來配置來建立一個FilterRegistrationBean
。
5. 測試MD5
最後,我們可以使用 Spring 中的整合測試來測試一切是否如預期般運作:
@Test
void whenExampleApiCallThenResponseHasMd5Header() throws Exception {
String endpoint = "/api/example";
String expectedResponse = "Hello, World!";
String expectedMD5 = getMD5Hash(expectedResponse);
MvcResult mvcResult = mockMvc.perform(get(endpoint).accept(MediaType.TEXT_PLAIN_VALUE))
.andExpect(status().isOk())
.andReturn();
String md5Header = mvcResult.getResponse()
.getHeader("Response-Body-MD5");
assertThat(md5Header).isEqualTo(expectedMD5);
}
在這裡,我們呼叫/api/example
控制器,它返回“Hello, World!”正文中的文字。我們定義了getMD5Hash()
方法,它將回應轉換為 MD5,類似於我們在過濾器中使用的:
private String getMD5Hash(String input) throws NoSuchAlgorithmException {
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
byte[] md5Hash = md5Digest.digest(input.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printHexBinary(md5Hash);
}
六,結論
在本文中,我們學習如何使用ContentCachingResponseWrapper
類別從 Spring Boot 篩選器中的ServletResponse
檢索回應正文。我們使用此機制來展示如何在 HTTP 回應標頭中實現正文的 MD5 編碼。
像往常一樣,我們可以在 GitHub 上找到完整的程式碼。