解決HttpServletRequest的輸入流只能讀取一次的問題

背景

一般對安全性有要求的接口都會對請求參數作一些簽名驗證,而咱們通常會把驗籤的邏輯統一放到過濾器或攔截器裏,這樣就不用每一個接口都去重複編寫驗籤的邏輯。java

在一個項目中會有不少的接口,而不一樣的接口可能接收不一樣類型的數據,例如表單數據和json數據,表單數據還好說,調用request的getParameterMap就能所有取出來。而json數據就有些麻煩了,由於json數據放在body中,咱們須要經過request的輸入流去讀取。spring

但問題在於request的輸入流只能讀取一次不能重複讀取,因此咱們在過濾器或攔截器裏讀取了request的輸入流以後,請求走到controller層時就會報錯。而本文的目的就是介紹如何解決在這種場景下遇到HttpServletRequest的輸入流只能讀取一次的問題。json

注:本文代碼基於SpringBoot框架數組


HttpServletRequest的輸入流只能讀取一次的緣由

咱們先來看看爲何HttpServletRequest的輸入流只能讀一次,當咱們調用getInputStream()方法獲取輸入流時獲得的是一個InputStream對象,而實際類型是ServletInputStream,它繼承於InputStream。安全

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

InputStream默認不實現reset(),而且markSupported()默認也是返回false,這一點查看其源碼便知:
解決HttpServletRequest的輸入流只能讀取一次的問題框架

咱們再來看看ServletInputStream,能夠看到該類沒有重寫mark()reset()以及markSupported()方法:
解決HttpServletRequest的輸入流只能讀取一次的問題ide

綜上,InputStream默認不實現reset的相關方法,而ServletInputStream也沒有重寫reset的相關方法,這樣就沒法重複讀取流,這就是咱們從request對象中獲取的輸入流就只能讀取一次的緣由。post


使用HttpServletRequestWrapper + Filter解決輸入流不能重複讀取問題

既然ServletInputStream不支持從新讀寫,那麼爲何不把流讀出來後用容器存儲起來,後面就能夠屢次利用了。那麼問題就來了,要如何存儲這個流呢?code

所幸JavaEE提供了一個 HttpServletRequestWrapper類,從類名也能夠知道它是一個http請求包裝器,其基於裝飾者模式實現了HttpServletRequest界面,部分源碼以下:
解決HttpServletRequest的輸入流只能讀取一次的問題

從上圖中的部分源碼能夠看到,該類並無真正去實現HttpServletRequest的方法,而只是在方法內又去調用HttpServletRequest的方法,因此咱們能夠經過繼承該類並實現想要從新定義的方法以達到包裝原生HttpServletRequest對象的目的。

首先咱們要定義一個容器,將輸入流裏面的數據存儲到這個容器裏,這個容器能夠是數組或集合。而後咱們重寫getInputStream方法,每次都從這個容器裏讀數據,這樣咱們的輸入流就能夠讀取任意次了。

 

實現代碼

package com.eshore.ismp.filter;


import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
 * Created by lihaodi on 2016/4/7.
 */
public class RequestWrapper extends HttpServletRequestWrapper {

    private  byte[] requestBody;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        requestBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
    }

    @Override
    public ServletInputStream getInputStream() {
        if (requestBody == null) {
            requestBody = new byte[0];
        }
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        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 bais.read();
            }
        };
    }
}
相關文章
相關標籤/搜索