A series of problems caused by reading ServletInputStream multiple times
- 2021-12-04 10:08:59
- OfStack
Problems caused by reading ServletInputStream multiple times
Because the transmission mode between the server and app is JSON
The format is as follows
{
head:null
body:null
token:xxxxxxxxxxxxxxxxxxxxx
}
Therefore, I want to write an interceptor on the server or read token first by filter to verify my identity. However, if the interceptor is pre-intercepted, the parameter controller in springMVC will not read it, resulting in the annotated parameter @ RequestBody not working.
The reason is that ServletInputStream has been read once in the front interceptor, and it can't be read again when it is read in the next ArgumentHandler, resulting in no data and no assignment.
So I wrote the following filter to have ServletInputStream read it many times
Write an request first
public class MyHttpRequest extends HttpServletRequestWrapper {
private static Logger log=Logger.getLogger(MyHttpRequest.class);
private byte[] bytes;
/**
* @param request {@link javax.servlet.http.HttpServletRequest} object.
* @throws IOException
*/
public MyHttpRequest(HttpServletRequest request) throws IOException {
super(request);
bytes= IOUtils.toByteArray( request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new DelegatingServletInputStream(byteArrayInputStream);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
The above contents are read out first, and then put into an byte [], and then you can create a new stream about Byte [] every time you take the stream.
Then write another filter
public class MyFilter implements Filter{
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request=(HttpServletRequest) req;
MyHttpRequest myrequest=new MyHttpRequest(request);
chain.doFilter(myrequest, res);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
In web. xml configuration 1, try the effect, very good. Can read many times,
But, I'll go
At the request of post, things like request. getParamter don't work again.
The reason is that the request we rewrote is the super. getparamter method that calls the parent class by default, and super. getparamter depends on super. getInputStream. However, in the construction method in our rewritten request, the old request is injected into super in the first sentence, and then the stream of the old request is read in the next sentence, resulting in an empty stream when we call super. getparameter.
Solution: Solve it yourself
package com.hrhs.jyj.filter;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.mock.web.DelegatingServletInputStream;
public class MyHttpRequest extends HttpServletRequestWrapper {
private static Logger log = Logger.getLogger(MyHttpRequest.class);
private byte[] bytes;
private String body;
private Map<String, List<String>> map;
private int readMap=0;
private String queryString;
/**
* @param request
* {@link javax.servlet.http.HttpServletRequest} object.
* @throws IOException
*/
public MyHttpRequest(HttpServletRequest request) throws IOException {
super(request);
bytes = IOUtils.toByteArray(request.getInputStream());
queryString = request.getQueryString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new DelegatingServletInputStream(byteArrayInputStream);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public String getParameter(String name) {
log.info(" Go away getParameter");
return super.getParameter(name);
}
@Override
public Map<String, String[]> getParameterMap() {
log.info(" Go away getParameterMap");
return super.getParameterMap();
}
@Override
public Enumeration<String> getParameterNames() {
log.info(" Go away getParameterNames");
return super.getParameterNames();
}
// Rewrite this for the time being 1 A , Others can also be modified
@Override
public String[] getParameterValues(String name) {
log.info(" Go away getParameterValues");
try {
Map<String, List<String>> nameVals = doParameter();
List<String> list = nameVals.get(name);
if(list!=null&&list.size()>0){
return list.toArray(new String[]{});
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new String[]{};
}
// Object for all parameter values map
public Map<String, List<String>> doParameter() throws UnsupportedEncodingException {
if(readMap==0){
// Put it here post Parameters in and address bar parameters are combined with 1 Rise , Then parse
body = new String(bytes, getCharacterEncoding())+"&"+queryString;
String[] nameVals = body.split("&");
map = new HashMap<String, List<String>>();
for (String nameVal : nameVals) {
String name = nameVal.split("=")[0];
String val = nameVal.split("=")[1];
if (map.containsKey(name)) {
List<String> vals = map.get(name);
vals.add(val);
map.put(name, vals);
} else {
List<String> vals = new ArrayList<String>();
vals.add(val);
map.put(name, vals);
}
}
readMap=1;
}
return map;
}
}
ServletInputStream Repeat Read Problem
acess_log, which was intended to implement tomcat, prints post request parameters. Under Tucao 1, the function of tomcat is several blocks different from that of nginx. I found a method on the Internet and realized it with filter of tomcat.
However, when writing filter, the problem of repeated reading of ServletInputStream was found.
Find several methods on the Internet, but they can't be used directly. Based on the comprehensive information on the Internet, according to my own understanding, it can finally run perfectly.
The code is posted directly, and the pro-test can be used
First, write an BufferHttpServletRequestWrapper class that replicates HttpServletRequest request.
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class BufferHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public BufferHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = request.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte buff[] = new byte[ 1024 ];
int read;
while( ( read = is.read( buff ) ) > 0 ) {
baos.write( buff, 0, read );
}
body = baos.toByteArray();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
};
}
}
Then filter is implemented as follows:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* Servlet Filter implementation class PostDataDumperFilter
*/
public class PostDataDumperFilter implements Filter {
private FilterConfig filterConfig = null;
/**
* Default constructor.
*/
public PostDataDumperFilter() {
// TODO Auto-generated constructor stub
}
/**
* @see Filter#destroy()
*/
public void destroy() {
this.filterConfig = null;
}
/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (filterConfig == null)
return;
// Backup HttpServletRequest
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new BufferHttpServletRequestWrapper((HttpServletRequest) request);
}
// Using streams
InputStream reader = requestWrapper.getInputStream();
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);
int i =0;
byte [] b = new byte[100];
while((i = reader.read(b))!= -1){
byteOutput.write(b, 0, i);
}
request.setAttribute("post", new String(byteOutput.toByteArray()));
// pass the request along the filter chain
if(null == requestWrapper){
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
this.filterConfig = fConfig;
}
}