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

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

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

