The first sentence of the opening , Have you ever encountered such a problem :
httpServletRequest.getInputStream()
obtain InputStream
after , encounter Required request body is missing
error ?If you answer “ yes ” Words , Then you're right . In this paper , Let's talk about ,
InputStream
Can't read repeatedly ?HttpServletRequest
Medium InputStream
? For the first question ,“ Why? InputStream
Can't read repeatedly ?”, The most direct and rude answer :InputStream
It's designed to be unreadable .
We can have a look InputStream
in read()
Method comments :
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;
Translate it , Its main idea is :
0
To 255
Between int
Type data . If no bytes are available because the stream reaches the end , Then return to -1
. Unless there's input data available 、 Or the end of the stream has been detected 、 Or throw an exception , Otherwise, it will be blocked all the time .According to the note above , We can easily conclude that : Data in the stream , It's not always stored , It will follow the behavior of reading , Be consumed .
Maybe the above explanation is abstract , So we can simply put InputStream
Imagine a pipe filled with water , With the flow of water , The water in the pipe will run out sooner or later .
Think about it ,InputStream
To and NIO Medium Buffer
It's kind of like , But neither InputStream
still OutputStream
It's all one-way , Or you can only go in 、 Or it can only come out of , and NIO Medium Buffer
It's two-way .
Now that we know InputStream
The reason why it can't be read repeatedly , So for the second question ,“ How to read repeatedly HttpServletRequest
Medium InputStream
?”, The solution is simple . We can get HttpServletRequest
Medium InputStream
When , Make a backup at the same time .
for example , First the HttpServletRequest
Medium InputStream
out , To String
object , Then take it. String
Object to byte[]
Save the array back , That's the guarantee HttpServletRequest
in InputStream
The value of does not change , But we get reusable String
object .
Here is an example of the code available , Can guarantee our safe access to HttpServletRequest
Medium InputStream
object :
public class SafeHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final String CHARSET_UTF8 = "UTF-8";
private final byte[] body;
private String bodyString;
public SafeHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.bodyString = StreamUtils.copyToString(request.getInputStream(), Charset.forName(CHARSET_UTF8));
body = bodyString.getBytes(CHARSET_UTF8);
}
public String getBodyString() {
return this.bodyString;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream innerBAIS = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return innerBAIS.read();
}
};
}
}
As shown in the above code ,SafeHttpServletRequestWrapper
It's a safe HttpServletRequest
Packaging . Use mode: :
@Slf4j
public abstract class AbstractFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
SafeHttpServletRequestWrapper requestWrapper = new SafeHttpServletRequestWrapper((HttpServletRequest) servletRequest);
log.info("AbstractFilter servletRequest body is {}", requestWrapper.getBodyString());
filterChain.doFilter(requestWrapper, servletResponse);
} catch (IOException e) {
e.printStackTrace();
}
}
}
As shown in the above code , We will first servletRequest
It turned into HttpServletRequest
, And then it was packaged as SafeHttpServletRequestWrapper
object . stay SafeHttpServletRequestWrapper
In the object , It contains our backup InputStream
object ( actually , It's packaged as ByteArrayInputStream
object ) And the available bodyString
character string .
ad locum , If we want to get the original HttpServletRequest
in InputStream
Object content , We call directly getBodyString()
that will do ; If we want to HttpServletRequest
Pass it on , We deliver the packaged SafeHttpServletRequestWrapper
that will do , Because it already contains the original HttpServletRequest
All the information in , And back up InputStream
Object content .
reference :