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

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

開發 前端
我們可以自己定義一個類CustomHttpRequestWrapper?,繼承自HttpServletRequestWrapper?,定義一個成員變量bodyInStringFormat?,存儲body中獲取到的數據,其實字符串底層是字節數組,然后重寫getInputStream?方法,構造一個ByteArrayInputStream?輸入流,而ByteArrayInputStream?實現了ma

前言

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

輸入流只能讀取一次

既然要記錄所有的請求,我們可以創建一個過濾器LogRequestFilter, 統一攔截所有的請求,讀取里面的輸入流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請求體內容
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);
// 讀取輸入流轉為字符串
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();
}

}

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

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

為什么會報錯呢?

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

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

InputStream默認不實現reset(),并且markSupported()默認也是返回false,這一點查看InputStream源碼便知:

圖片

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

圖片

所以InputStream默認不實現reset的相關方法,而ServletInputStream也沒有重寫reset的相關方法,這樣就無法重復讀取流,這就是我們從request對象中獲取的輸入流就只能讀取一次的原因,最后導致再次讀取流的時候報錯。

那該如何解決呢?

改寫ServeltRequest

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

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

圖片

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

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

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;
}
};
}
}

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

@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())) {
// 設置自定義的ServletRequest
CustomHttpRequestWrapper requestWrapper = new CustomHttpRequestWrapper(httpRequest);
filterChain.doFilter(requestWrapper, servletResponse);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}

}

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

責任編輯:武曉燕 來源: JAVA旭陽
相關推薦

2018-10-29 10:13:29

Windows 10應用程序卸載

2019-08-13 15:39:27

Linux應用程序

2024-01-18 07:53:37

2019-05-08 11:30:41

MicrosoftWindows 10后臺應用程序

2019-12-06 10:05:28

Windows 10手機應用程序

2018-08-02 11:15:06

應用程序Windows 10Windows

2018-03-28 08:30:01

Linux倉庫應用程序

2009-08-12 17:36:32

2021-05-07 15:36:50

iOS隱藏應用程序

2021-05-10 23:39:31

Python日志記錄

2024-01-15 08:03:10

JVM內存工作效率

2011-01-28 09:12:53

jQuery Mobi

2016-08-02 10:34:17

LinuxWindows雙啟動

2019-01-04 10:45:31

Windows 10Android應用程序

2014-06-26 15:17:17

安卓應用保存數據

2011-05-18 10:42:48

2013-03-25 10:38:24

ASP.NETHttpModule

2021-01-30 17:57:23

Python緩存開發

2022-04-27 08:55:01

Spring外部化配置

2019-07-17 15:23:23

Windows 10應用程序Windows
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品久久久久久久 | 久久久做 | 亚洲一级av毛片 | 国产成人在线观看免费 | 欧美日韩中文字幕在线 | 视频一区在线观看 | 久久精品网 | 成年人黄色免费视频 | 九九成人 | 国产精品美女久久久 | 国产免费一区二区三区免费视频 | 亚洲网站在线播放 | 日本一区二区三区精品视频 | 国产99视频精品免费视频7 | 久久激情视频 | 狠狠艹 | 91精品国产色综合久久不卡蜜臀 | 青青久久 | 精品美女 | 青青草av网站 | 欧美激情亚洲激情 | 午夜影院 | 国产精品久久久久久久久 | 污片在线观看 | 国产视频黄色 | 国产成人精品一区二区三区视频 | 日韩激情视频一区 | 国产不卡视频在线 | 在线播放中文字幕 | 色永久 | 亚洲一区精品在线 | 亚洲精品乱码久久久久久久久 | 欧美日韩在线视频一区 | 免费久久精品视频 | 狠狠干综合视频 | 中文字幕一二三区 | 亚洲综合一区二区三区 | 免费三级网站 | 97精品超碰一区二区三区 | 日日干日日操 | 中文字幕二区三区 |