1. 程式人生 > >解決在Filter中讀取Request中的流後,後續controller或restful介面中無法獲取流的問題

解決在Filter中讀取Request中的流後,後續controller或restful介面中無法獲取流的問題

首先我們來描述一下在開發中遇到的問題,場景如下:

比如我們要攔截所有請求,獲取請求中的某個引數,進行相應的邏輯處理:比如我要獲取所有請求中的公共引數 token,clientVersion等等;這個時候我們通常有兩種做法

 前提條件是我們實現Filter類,重寫doFilter方法

1、通過getParameter方法獲得

      HttpServletRequest hreq = (HttpServletRequest) req;

      String param = hreq.getParameter("param");

這種方法有缺陷:它只能獲取  POST 提交方式中的  

  Content-Type: application/x-www-form-urlencoded;

    這種提交方式中,key為param ,若提交型別不是這種方式就獲取不到param的值

2、獲取請求物件中的資料流,通過解析流資訊來獲取提交的內容;程式碼示例如下:

	/**
     * 獲取請求Body
     *
     * @param request
     * @return
     */
    public static String getBodyString(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

通過這種簡單的解析方式,將http請求中的body解析成 字串的形式;然後再對字串的格式進行業務解析;

這種處理方式的優點是:能解析 出content-Type 為  application/x-www-form-urlencoded ,application/json , text/xml 這三種提交方式的 資料 (至於另外一種帶檔案的請求:multipart/form-data ,本人沒有親自測試過,等後期遇到此類為題再來解決)。 上述方法返回的可能是key,value(對應第一種content-type),可能是json字串(對應第二種),可能是xml的字串(對應第三種)

但是這種方式有一個很大的缺點

:那就是當從請求中獲取流以後,流被filter中的這個 inputStreamToString(InputStream in) 這個方法處理後就被“消耗”了,這會導致,chain.doFilter(request, res)這個鏈在傳遞 request物件的時候,裡面的請求流為空,導致責任鏈模式下,其他下游的鏈無法獲取請求的body,從而導致程式無法正常執行,這也使得我們的這個filter雖然可以獲取請求資訊,但是它會導致整個應用程式不可用,那麼它也就失去了意義;

    針對第二種方式的缺陷:流一旦被讀取,就無法向下傳遞整個問題,有如下解決方案;

    解決思路如下:將取出來的字串,再次轉換成流,然後把它放入到新request 物件中,在chain.doFiler方法中 傳遞新的request物件;要實現這種思路,需要自定義一個類

繼承HttpServletRequestWrapper,然後重寫public BufferedReader getReader()方法,public  ServletInputStream getInputStream()方法;(這兩個方法的重寫實現邏輯如下:getInputStream()方法中將body體中的字串轉換為位元組流(它實質上返回的是一個ServletInputStream 物件);然後通過getReader()呼叫---->getInputStream()方法;),繼承實現重寫邏輯以後,在自定義分filter(VersionCheckFilter)中,使用自定義的HttpServletRequestWrapper(BodyReaderHttpServletRequestWrapper)將原始的HttpServletRequest物件進行再次封裝;再通過BodyReaderHttpServletRequestWrapper物件去做dofilter(req,res)的req物件;

   涉及的三個類的程式碼如下:

   自定義的HttpServletRequestWrapper

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;



import com.yt.util.HttpHelper;

public class BodyReaderHttpServletRequestWrapper extends
		HttpServletRequestWrapper {
	
	private final byte[] body;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        System.out.println("-------------------------------------------------");  
        Enumeration e = request.getHeaderNames()   ;  
         while(e.hasMoreElements()){  
             String name = (String) e.nextElement();  
             String value = request.getHeader(name);  
             System.out.println(name+" = "+value);  
               
         }  
        body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
    }

    @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();
            }
        };
    }

	@Override
	public String getHeader(String name) {
		return super.getHeader(name);
	}

	@Override
	public Enumeration<String> getHeaderNames() {
		return super.getHeaderNames();
	}

	@Override
	public Enumeration<String> getHeaders(String name) {
		return super.getHeaders(name);
	}
    
}

 2、輔助類

HttpHelper

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

import javax.servlet.ServletRequest;

public class HttpHelper {
	/**
     * 獲取請求Body
     *
     * @param request
     * @return
     */
    public static String getBodyString(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
}

3、自定義的filter
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Map;

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;
import javax.servlet.http.HttpServletResponse;
import javax.xml.crypto.URIDereferencer;

import com.yt.util.HttpHelper;
import com.yt.util.JsonHelper;

public class VersionCheckFilter implements Filter {

	@Override
	public void destroy() {

	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest hreq = (HttpServletRequest) req;
	    String uri = hreq.getRequestURI();
	    if(uri.contains("uploadImageServlet")){
	    	//影象上傳的請求,不做處理
	    	chain.doFilter(req, res);
	    }else{
	    	String reqMethod = hreq.getMethod();
	    	if("POST".equals(reqMethod)){
	    		
	    		PrintWriter out = null; 
	    		HttpServletResponse response = (HttpServletResponse) res;
	    		response.setCharacterEncoding("UTF-8");  
 			    response.setContentType("application/json; charset=utf-8");  
 			    
 			  /* String requestStr = this.inputStreamToString(hreq.getInputStream());

 		        String[] arrs = requestStr.split("&"); 
 		        for (String strs : arrs) {
 		            String[] strs2 = strs.split("=");
 		            for (int i = 0; i < strs2.length; i++) {
 		                if (strs2[0].equals("param")) {
 		                    if (strs2[1] != null &&!strs2[1].equals("")) {
 		                        System.out.println("test=" + strs2[1]);
 		                    }
 		                }
 		            }
 		        }*/
 			    
 			   ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(hreq);
               String body = HttpHelper.getBodyString(requestWrapper);
 			    
	    		//如果是POST請求則需要獲取 param 引數
 		       String param = URLDecoder.decode(body,"utf-8");
	    		//String param = hreq.getParameter("param");
	    		//json串 轉換為Map
 		        if(param!=null&¶m.contains("=")){
 		        	param = param.split("=")[1];
	    		}
	    		Map paramMap = JsonHelper.getGson().fromJson(param, Map.class);
	    		Object obj_clientversion = paramMap.get("clientVersion");
	    		
	    		String clientVersion = null;
	    		if(obj_clientversion != null){
	    			clientVersion = obj_clientversion.toString();
	    			System.out.println(clientVersion);
    			    /*try {  
    			        out = response.getWriter();
    			        Map remap = new HashMap<String, Object>();
    			        remap.put("code", 9);
    			        remap.put("message", Constant.SYSTEM_ERR_DESC);
    			        out.append(JsonHelper.getGson().toJson(remap));  
    			    } catch (IOException e) {  
    			        e.printStackTrace();  
    			    } finally {  
    			        if (out != null) {  
    			            out.close();  
    			        }  
    			    }*/
	    			chain.doFilter(requestWrapper, res);	
    		}else{
    			chain.doFilter(requestWrapper, res);	
    		}
	    	}else{
	    		//get請求直接放行
	    		chain.doFilter(req, res);
	    	}
	    }
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		
	}

	/*public String inputStreamToString(InputStream in) throws IOException {
        StringBuffer out = new StringBuffer();
        byte[] b = new byte[4096];
        for (int n; (n = in.read(b)) != -1;) {
            out.append(new String(b, 0, n));
        }
        return out.toString();
	}*/
}