Solution for Repeated Reading of Request Body Content over SpringBoot v 2.2
- 2021-12-04 10:18:31
- OfStack
Repeat reading Request Body contents above SpringBoot v2.2
1. Requirements
There are two scenarios in the project that will use reading content from Body of Request.
Print request log The Api interface is provided. Before the api method is executed, the parameters are read from the Request Body for verification. After the verification is passed, the api method is executed
2. Solutions
2.1 Custom RequestWrapper
public class MyRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = RequestReadUtils.read(request);
}
public String getBody() {
return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
... Slightly
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
RequestReadUtils (copied online)
private static final int BUFFER_SIZE = 1024 * 8;
public static String read(HttpServletRequest request) throws IOException {
BufferedReader bufferedReader = request.getReader();
for (Enumeration<String> iterator = request.getHeaderNames(); iterator.hasMoreElements();) {
String type = iterator.nextElement();
System.out.println(type+" = "+request.getHeader(type));
}
System.out.println();
StringWriter writer = new StringWriter();
write(bufferedReader,writer);
return writer.getBuffer().toString();
}
public static long write(Reader reader,Writer writer) throws IOException {
return write(reader, writer, BUFFER_SIZE);
}
public static long write(Reader reader, Writer writer, int bufferSize) throws IOException
{
int read;
long total = 0;
char[] buf = new char[bufferSize];
while( ( read = reader.read(buf) ) != -1 ) {
writer.write(buf, 0, read);
total += read;
}
return total;
}
2.2 Defining Filter
@WebFilter
public class TestFilter implements Filter{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
MyRequestWrapper wrapper = WebUtils.getNativeRequest(request, MyRequestWrapper.class);
chain.doFilter(wrapper == null ? new MyRequestWrapper(request) :wrapper,servletRequest);
}
}
STEP 3 Encounter problems
SpringBoot v 2.1. x version used
Form Commit No Problem No problem getting RequestBodyUse SpringBoot v 2.2. 0 and above (including v 2.3. x)
Form Submit could not get parameters No problem getting RequestBody
4. Problem troubleshooting
After investigation, the difference between v2.2. x and v2.1. x lies in the code difference under 1:
BufferedReader bufferedReader = request.getReader();
-----------------
char[] buf = new char[bufferSize];
while( ( read = reader.read(buf) ) != -1 ) {
writer.write(buf, 0, read);
total += read;
}
When the form is submitted
v2.1. x Unable to read to content, read result is-1 v2.2. x, v2.3. x can read contentWhen the form is submitted (x-www-form-urlencoded), getInputStream operation of wrapper will not be triggered after inputStream is read once, so Controller cannot get parameters.
Solutions
Transformation of MyRequestWrapper
public class MyRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = getBodyString(request);
}
public String getBody() {
return body;
}
public String getBodyString(final HttpServletRequest request) throws IOException {
String contentType = request.getContentType();
String bodyString = "";
StringBuilder sb = new StringBuilder();
if (StringUtils.isNotBlank(contentType) && (contentType.contains("multipart/form-data") || contentType.contains("x-www-form-urlencoded"))) {
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> next : parameterMap.entrySet()) {
String[] values = next.getValue();
String value = null;
if (values != null) {
if (values.length == 1) {
value = values[0];
} else {
value = Arrays.toString(values);
}
}
sb.append(next.getKey()).append("=").append(value).append("&");
}
if (sb.length() > 0) {
bodyString = sb.toString().substring(0, sb.toString().length() - 1);
}
return bodyString;
} else {
return IOUtils.toString(request.getInputStream());
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public int read() {
return bais.read();
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
Pit for SpringBoot to read Request parameters
Backend takes parameter correlation
When configured by default,
Errors will be reported when getInputStream () and getReader () are used
Use getInputStream () twice, and the second pass will be empty
When there are annotations such as @ RequestBody, springMVC has read the stream once, and either getInputStream () alone or getReader () is empty by default.
Resolution: Write filter to inherit HttpServletRequestWrapper, cache InputStream, override getInputStream () and getReader () methods, and use ByteArrayInputStream is = new ByteArrayInputStream (body. getBytes ()); Read InputStream.
Note: In springboot, the filter only needs @ Component to take effect, and the path and priority can be configured in FilterRegistrationBean.
For interceptors, the addInterceptor method must be called in InterceptorRegistry. (Path can be added in a chain)
About flow
It can only be read once, similar to a tube.
It only undertakes transmission responsibilities, and has nothing to do with processing and storage.
For byte streams, repeated reads are easy to implement, but the pointer is not reset, which should be to keep 1 with the InputStream interface definition.