springboot 解決過濾器 HttpServletRequest body IO流只能讀取一次的問題
問題
老手都知道過濾器的作用,但是如果想要打印請求的參數(shù)就有點(diǎn)難度了, url 上拼接的參數(shù)可以輕而易舉的得到,調(diào)用 request.getParameterMap() 方法就可以拿到;
但是要拿body 體就沒這么簡單了,request 里面有個(gè) getInputStream(),這個(gè)流 是一次性的, 如果你在這里讀出來,那么在控制層就拿不到body參數(shù)了;

錯(cuò)誤方式:直接讀取原始流打?。▽?dǎo)致控制器無法獲?。?/h3>
如果在過濾器中直接調(diào)用原始請求的 getInputStream() 或 getReader() 打印,會直接消耗流,導(dǎo)致控制器后續(xù)讀取時(shí)無數(shù)據(jù):
// 錯(cuò)誤示例:直接讀取原始流,導(dǎo)致流被消耗
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 直接讀取原始流并打印(錯(cuò)誤?。?
String body = IOUtils.toString(httpRequest.getInputStream(), StandardCharsets.UTF_8);
System.out.println("Filter打印body:" + body);
// 此時(shí)流已被消耗,控制器無法讀取
chain.doFilter(request, response);
}
方案
問題本質(zhì):輸入流的 “一次性” 特性
ServletRequest 的輸入流(getInputStream()/getReader())是單向流動(dòng)的字節(jié)流,默認(rèn)情況下,一旦讀?。ㄈ缭谶^濾器中打?。?,流的指針會移動(dòng)到末尾,后續(xù)(如控制器)再讀取時(shí)就會獲取到空數(shù)據(jù)。
因此,問題的核心不是 “是否在過濾器中打印”,而是 “打印時(shí)是否消耗了原始流且未緩存”。
正確方式:通過包裝類緩存后打?。刂破骺烧+@?。?/h3>
如果先通過 HttpServletRequestWrapper 緩存流數(shù)據(jù),再在過濾器中讀取緩存的數(shù)據(jù)打印,原始流不會被消耗,控制器可通過包裝類的緩存讀??;
過濾器內(nèi)容
@Component
@0rder(o)
@Slf4j
public class WebFilter extends OncePerRequestFilter {
@0verride
protected void dofilterInternal(HttpservletRequest request, HttpservletResponse response, Filterchain filterchain)throws servletException, I0Exception{
// 正確示例:基于包裝類的緩存打印,不消耗原始流
if (request instanceof HttpServletRequest) {
// 先包裝請求,緩存body
CachedServletRequestWrapper wrappedRequest = new CachedServletRequestWrapper((HttpServletRequest) request);
// 從包裝類的緩存中讀取并打?。ú挥绊懺剂鳎?
String body = new String(wrappedRequest.getCachedBody(), StandardCharsets.UTF_8);
System.out.println("Filter打印body:" + body);
// 傳遞包裝后的請求,控制器可從緩存中讀取
chain.doFilter(wrappedRequest, response);
} else {
chain.doFilter(request, response);
}
}
CachedServletRequestWrapper.java
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* 緩存請求體的包裝類,解決ServletRequest輸入流只能讀取一次的問題
*/
public class CachedServletRequestWrapper extends HttpServletRequestWrapper {
// 緩存的請求體字節(jié)數(shù)組
private final byte[] cachedBody;
/**
* 構(gòu)造方法:讀取原始請求的輸入流并緩存
*/
public CachedServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.cachedBody = readRequestBody(request);
}
/**
* 讀取原始請求的輸入流并轉(zhuǎn)換為字節(jié)數(shù)組
*/
private byte[] readRequestBody(HttpServletRequest request) throws IOException {
// GET/DELETE等方法通常沒有請求體,直接返回空數(shù)組
String method = request.getMethod();
if ("GET".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) {
return new byte[0];
}
// 讀取輸入流并緩存
try (InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
return outputStream.toByteArray();
}
}
/**
* 重寫getInputStream(),返回基于緩存的輸入流
*/
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true; // 同步讀取時(shí)始終就緒
}
@Override
public void setReadListener(ReadListener listener) {
// 異步場景可忽略,同步場景無需實(shí)現(xiàn)
}
};
}
/**
* 重寫getReader(),基于緩存的輸入流構(gòu)建字符讀取器
*/
@Override
public java.io.BufferedReader getReader() throws IOException {
return new java.io.BufferedReader(
new java.io.InputStreamReader(getInputStream(), StandardCharsets.UTF_8)
);
}
/**
* 提供獲取緩存的請求體字節(jié)數(shù)組的方法(方便過濾器/攔截器直接使用)
*/
public byte[] getCachedBody() {
return cachedBody;
}
/**
* 提供獲取緩存的請求體字符串的方法(簡化使用)
*/
public String getCachedBodyAsString() {
return new String(cachedBody, StandardCharsets.UTF_8);
}
}
可以看到,正常打印了body數(shù)據(jù)

到此這篇關(guān)于springboot 解決過濾器 HttpServletRequest body IO流只能讀取一次的問題的文章就介紹到這了,更多相關(guān)springboot HttpServletRequest body IO讀取一次內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Web容器啟動(dòng)過程中如何執(zhí)行Java類
這篇文章主要介紹了Web容器啟動(dòng)過程中如何執(zhí)行Java類,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
SpringBoot使用GraphQL開發(fā)Web API實(shí)現(xiàn)方案示例講解
這篇文章主要介紹了SpringBoot使用GraphQL開發(fā)Web API實(shí)現(xiàn)方案,GraphQL是一個(gè)從服務(wù)端檢數(shù)據(jù)的查詢語言。某種程度上,是REST、SOAP、或者gRPC的替代品2023-04-04
SpringBoot+Redis實(shí)現(xiàn)分布式鎖的深度剖析
分布式鎖是在分布式系統(tǒng)中協(xié)調(diào)多個(gè)節(jié)點(diǎn)對共享資源進(jìn)行互斥訪問的機(jī)制,這篇文章主要為大家詳細(xì)介紹了如何使用SpringBoot和Redis實(shí)現(xiàn)分布式鎖,感興趣的小伙伴可以了解下2026-01-01
SpringBoot實(shí)現(xiàn)賬號登錄錯(cuò)誤次數(shù)的限制和鎖定功能
本文介紹了如何使用SpringBoot和Redis實(shí)現(xiàn)賬號登錄錯(cuò)誤次數(shù)限制和鎖定功能,通過自定義注解和AOP切面,結(jié)合配置文件靈活設(shè)置最大嘗試次數(shù)和鎖定時(shí)長,感興趣的朋友跟隨小編一起看看吧2024-12-12
JavaWeb中使用JavaMail實(shí)現(xiàn)發(fā)送郵件功能實(shí)例詳解
這篇文章主要介紹了JavaWeb中使用JavaMail實(shí)現(xiàn)發(fā)送郵件功能的實(shí)例代碼,非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-05-05
Vue3源碼解讀effectScope API及實(shí)現(xiàn)原理
這篇文章主要為大家介紹了Vue3源碼解讀effectScope API及實(shí)現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Java編程實(shí)現(xiàn)多線程TCP服務(wù)器完整實(shí)例
這篇文章主要介紹了Java編程實(shí)現(xiàn)多線程TCP服務(wù)器完整實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
Java基礎(chǔ)入門篇之邏輯控制練習(xí)題與猜數(shù)字游戲
猜數(shù)字游戲是一款經(jīng)典的游戲,該游戲說簡單也很簡單,說不簡單確實(shí)也很難,這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)入門篇之邏輯控制練習(xí)題與猜數(shù)字游戲的相關(guān)資料,需要的朋友可以參考下2023-06-06

