成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何在 Spring Boot 應(yīng)用程序中記錄POST請求的body信息?

開發(fā) 前端
我們可以自己定義一個類CustomHttpRequestWrapper?,繼承自HttpServletRequestWrapper?,定義一個成員變量bodyInStringFormat?,存儲body中獲取到的數(shù)據(jù),其實(shí)字符串底層是字節(jié)數(shù)組,然后重寫getInputStream?方法,構(gòu)造一個ByteArrayInputStream?輸入流,而ByteArrayInputStream?實(shí)現(xiàn)了ma

前言

最近收到一個需求,出于審計的目的,希望可以通過日志記錄下對應(yīng)用程序發(fā)起的post、put請求的body內(nèi)容,面對這樣的一個需求,大家是不是覺得很簡單,但是我在開發(fā)過程中還是遇到了問題,在本文中做一個分享。

輸入流只能讀取一次

既然要記錄所有的請求,我們可以創(chuàng)建一個過濾器LogRequestFilter, 統(tǒng)一攔截所有的請求,讀取里面的輸入流InputStream,我想大家都能想到把,具體代碼如下:

@Component
public class LogRequestFilter implements Filter {

private final Logger logger = LoggerFactory.getLogger(LogRequestFilter.class);

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
// 記錄post和put請求體內(nèi)容
logPostOrPutRequestBody((HttpServletRequest) servletRequest);
filterChain.doFilter(servletRequest, servletResponse);
}

private void logPostOrPutRequestBody(HttpServletRequest httpRequest) throws IOException {
if(Arrays.asList("POST", "PUT").contains(httpRequest.getMethod())) {
String characterEncoding = httpRequest.getCharacterEncoding();
Charset charset = Charset.forName(characterEncoding);
// 讀取輸入流轉(zhuǎn)為字符串
String bodyInStringFormat = readInputStreamInStringFormat(httpRequest.getInputStream(), charset);
logger.info("Request body: {}", bodyInStringFormat);
}
}

private String readInputStreamInStringFormat(InputStream stream, Charset charset) throws IOException {
final int MAX_BODY_SIZE = 1024;
final StringBuilder bodyStringBuilder = new StringBuilder();
if (!stream.markSupported()) {
stream = new BufferedInputStream(stream);
}

stream.mark(MAX_BODY_SIZE + 1);
final byte[] entity = new byte[MAX_BODY_SIZE + 1];
// 讀取流
final int bytesRead = stream.read(entity);

if (bytesRead != -1) {
bodyStringBuilder.append(new String(entity, 0, Math.min(bytesRead, MAX_BODY_SIZE), charset));
if (bytesRead > MAX_BODY_SIZE) {
bodyStringBuilder.append("...");
}
}
stream.reset();

return bodyStringBuilder.toString();
}

}

但是事情往往不是按照你預(yù)期的方向發(fā)展的, 但你按照上面的設(shè)計寫好代碼后,發(fā)一個post請求,卻返回下面的報錯:

DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: 
Required request body is missing

為什么會報錯呢?

原因就是輸入流只能讀取一次。 當(dāng)我們調(diào)用getInputStream()方法獲取輸入流時得到的是一個InputStream對象,而實(shí)際類型是ServletInputStream,它繼承于InputStream。

InputStream的read()方法內(nèi)部有一個postion,標(biāo)志當(dāng)前流被讀取到的位置,每讀取一次,該標(biāo)志就會移動一次,如果讀到最后,read()會返回-1,表示已經(jīng)讀取完了。如果想要重新讀取則需要調(diào)用reset()方法,position就會移動到上次調(diào)用mark的位置,mark默認(rèn)是0,所以就能從頭再讀了。調(diào)用reset()方法的前提是已經(jīng)重寫了reset()方法,當(dāng)然能否reset也是有條件的,它取決于markSupported()方法是否返回true。

InputStream默認(rèn)不實(shí)現(xiàn)reset(),并且markSupported()默認(rèn)也是返回false,這一點(diǎn)查看InputStream源碼便知:

圖片

我們再來看看ServletInputStream,可以看到該類沒有重寫mark(),reset()以及markSupported()方法:

圖片

所以InputStream默認(rèn)不實(shí)現(xiàn)reset的相關(guān)方法,而ServletInputStream也沒有重寫reset的相關(guān)方法,這樣就無法重復(fù)讀取流,這就是我們從request對象中獲取的輸入流就只能讀取一次的原因,最后導(dǎo)致再次讀取流的時候報錯。

那該如何解決呢?

改寫ServeltRequest

既然ServletInputStream不支持重新讀寫,那么為什么不把流讀出來后用容器存儲起來,后面就可以多次利用了。那么問題就來了,要如何存儲這個流呢?

所幸JavaEE提供了一個 HttpServletRequestWrapper類,從類名也可以知道它是一個http請求包裝器,其基于裝飾者模式實(shí)現(xiàn)了HttpServletRequest界面,部分源碼如下:

圖片

從上圖中的部分源碼可以看到,該類并沒有真正去實(shí)現(xiàn)HttpServletRequest的方法,而只是在方法內(nèi)又去調(diào)用HttpServletRequest的方法,所以我們可以通過繼承該類并實(shí)現(xiàn)想要重新定義的方法以達(dá)到包裝原生HttpServletRequest對象的目的。

我們可以自己定義一個類CustomHttpRequestWrapper,繼承自HttpServletRequestWrapper,定義一個成員變量bodyInStringFormat,存儲body中獲取到的數(shù)據(jù),其實(shí)字符串底層是字節(jié)數(shù)組,然后重寫getInputStream方法,構(gòu)造一個ByteArrayInputStream輸入流,而ByteArrayInputStream實(shí)現(xiàn)了mark(),reset()以及markSupported()方法,然后讓ByteArrayInputStream去讀取前面保存的字符串bodyInStringFormat中的數(shù)組,從而達(dá)到重復(fù)使用的目的。

package com.filters;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomHttpRequestWrapper extends HttpServletRequestWrapper {

private static final Logger logger = LoggerFactory.getLogger(CustomHttpRequestWrapper.class);
private final String bodyInStringFormat;

public CustomHttpRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
bodyInStringFormat = readInputStreamInStringFormat(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
logger.info("Body: {}", bodyInStringFormat);
}


private String readInputStreamInStringFormat(InputStream stream, Charset charset) throws IOException {
final int MAX_BODY_SIZE = 1024;
final StringBuilder bodyStringBuilder = new StringBuilder();
if (!stream.markSupported()) {
stream = new BufferedInputStream(stream);
}

stream.mark(MAX_BODY_SIZE + 1);
final byte[] entity = new byte[MAX_BODY_SIZE + 1];
final int bytesRead = stream.read(entity);

if (bytesRead != -1) {
bodyStringBuilder.append(new String(entity, 0, Math.min(bytesRead, MAX_BODY_SIZE), charset));
if (bytesRead > MAX_BODY_SIZE) {
bodyStringBuilder.append("...");
}
}
stream.reset();

return bodyStringBuilder.toString();
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bodyInStringFormat.getBytes());

return new ServletInputStream() {
private boolean finished = false;

@Override
public boolean isFinished() {
return finished;
}

@Override
public int available() throws IOException {
return byteArrayInputStream.available();
}

@Override
public void close() throws IOException {
super.close();
byteArrayInputStream.close();
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}

public int read () throws IOException {
int data = byteArrayInputStream.read();
if (data == -1) {
finished = true;
}
return data;
}
};
}
}

編寫玩上面的代碼以后,還需要再過濾器中使用,那么后續(xù)過濾器中的ServletRequest實(shí)現(xiàn)類都是CustomHttpRequestWrapper , 就可以再次讀取body的內(nèi)容了,具體代碼如下:

@Component
public class LogRequestFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
if(Arrays.asList("POST", "PUT").contains(httpRequest.getMethod())) {
// 設(shè)置自定義的ServletRequest
CustomHttpRequestWrapper requestWrapper = new CustomHttpRequestWrapper(httpRequest);
filterChain.doFilter(requestWrapper, servletResponse);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}

}

這一下你再次向應(yīng)用程序發(fā)出POST或GET請求時,就不會看到任何報錯了。

責(zé)任編輯:武曉燕 來源: JAVA旭陽
相關(guān)推薦

2018-10-29 10:13:29

Windows 10應(yīng)用程序卸載

2024-01-18 07:53:37

2018-03-28 08:30:01

Linux倉庫應(yīng)用程序

2019-08-13 15:39:27

Linux應(yīng)用程序

2019-05-08 11:30:41

MicrosoftWindows 10后臺應(yīng)用程序

2019-12-06 10:05:28

Windows 10手機(jī)應(yīng)用程序

2018-08-02 11:15:06

應(yīng)用程序Windows 10Windows

2011-01-28 09:12:53

jQuery Mobi

2016-08-02 10:34:17

LinuxWindows雙啟動

2009-08-12 17:36:32

2021-01-30 17:57:23

Python緩存開發(fā)

2011-05-18 10:42:48

2013-03-25 10:38:24

ASP.NETHttpModule

2021-05-07 15:36:50

iOS隱藏應(yīng)用程序

2021-05-10 23:39:31

Python日志記錄

2024-01-15 08:03:10

JVM內(nèi)存工作效率

2011-10-13 16:34:30

MeeGoQt插件

2019-01-04 10:45:31

Windows 10Android應(yīng)用程序

2014-06-26 15:17:17

安卓應(yīng)用保存數(shù)據(jù)

2015-03-30 09:32:15

XcodeiOS應(yīng)用程序
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 久久里面有精品 | 一级大片 | 黄色一级大片在线免费看产 | 日韩一区在线观看视频 | 黄色成人在线网站 | 一区在线视频 | 伊人网在线看 | 精品一区二区av | 91精品国产91久久综合桃花 | 免费一区二区 | 国产在线一区二区三区 | 91在线看| a级片播放 | 日韩成人免费av | 亚洲精品在线看 | 欧美成人精品一区二区男人看 | 91亚洲视频在线 | 日韩欧美在 | 日韩精品视频在线 | 国产成人精品一区二区三区在线观看 | 欧美一区二区三区视频在线观看 | 波多野结衣中文字幕一区二区三区 | 久久精品无码一区二区三区 | 网站国产 | 欧美精品欧美精品系列 | 人人鲁人人莫人人爱精品 | 在线观看视频一区 | 日韩一级不卡 | 97久久久 | 久久99精品久久 | 日本在线观看视频 | 1204国产成人精品视频 | 国产日韩电影 | 成人毛片在线观看 | h视频在线播放 | 亚洲视频在线观看 | 午夜网站视频 | 日韩成人免费视频 | 国产精品一区一区 | 一级特黄在线 | 精品国产乱码久久久久久牛牛 |