Analysis of Abnormal Problem of Interceptor Operation Data Flow

【background】

The need at that time was to perform signature authentication for a specific request, and I chose to use an interceptor to handle this. At first, I put the signed information in the Body, so I need to get the interceptor through HttpServletRequest, and Body data is stored in the stream. At that time, the stream information was read through request.getReader(). In addition, I used @RequestBody in the controller to receive the JSON body. As a result, the following problems occurred when the program was running:

Error one: getInputStream() has already been called for this request
Error two: nested exception is java.lang.IllegalStateException: getWriter() has already been called for this respons
Note: I encountered error one in the program, but the above two The principle of the problem is the same, summarized here

[Error code case]

Question 1: request
Insert picture description here
Question 2: HttpServletResponse
Insert picture description here

【analysis】

A stream cannot read two exceptions. This exception usually occurs when the frame or interceptor reads the data of the stream in the request. We read it again in the business code (such as @requestBody), because the data in the stream is no longer available. , So an exception will be thrown the second time it is read.

【solution】

Solution 1: This solution is also the most common solution I found on the Internet.
First save the Request Body, and then overwrite the getReader() and
getInputStream() methods through the HttpServletRequestWrapper class that comes with the Servlet , so that the stream can be read from the saved body. Then replace ServletRequest with AuthenticationRequestWrapper in Filter.

public class MyRequestWrapper extends HttpServletRequestWrapper {
    
    
    private byte[] body;
 
    public MyRequestWrapper(HttpServletRequest request) throws IOException {
    
    
        super(request);
 
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader reader = request.getReader();
        while ((line = reader.readLine()) != null) {
    
    
            sb.append(line);
        }
        String body = sb.toString();
        this.body = body.getBytes(StandardCharsets.UTF_8);
    }
 
 
    public String getBody() {
    
    
        return new String(body, StandardCharsets.UTF_8);
    }
}
//使用
MyRequestWrapper myRequestWrapper = new MyRequestWrapper(request);
      myRequestWrapper.getBody();

Solution two:

 public static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal();

Use ThreadLocal, take advantage of its thread-private characteristics, allocate a ThreadLocal for each request processing thread, and then store the stream data in the ThreadLocal container when the interceptor uses the stream, and then read it directly from this later.
For example, the interceptor reads the body content, then converts it into a Map, and then stores it to the controller behind the static ThreadLocal to get the container data.

Solution 3: The
above two methods are both intrusive. In fact, there are many similar solutions. Depending on which container you choose for storage, in addition to the above methods, you can also store data in request.setAttribute Medium and so on.
But if you write it like this, you need to add some special processes for other people or yourself to add new code in the future, such as getting the body through MyRequestWrapper, or getting data through ThreadLocal.get(). On the one hand, additional memory storage is required, and on the other hand, code is added. But if it is unavoidable, you can only choose this way. In my practice, since I only need to sign and authenticate the request, in order to avoid the above-mentioned troubles, I separate the signature information from the business data. The signature information is placed in the header, and the business data is still placed in the body, then in the interceptor At this stage, I only need request.getHeader. There is no need to manipulate streaming data anymore. Of course, this method requires the caller to modify the parameters carried in the request, and the specific implementation plan still needs to be based on actual needs and its own thinking.

Guess you like

Origin blog.csdn.net/Octopus21/article/details/110732774