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

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

catch java.net 原始的 more from 解析 amp per gson


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

比如我們要攔截所有請求,獲取請求中的某個參數,進行相應的邏輯處理:比如我要獲取所有請求中的公共參數 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、獲取請求對象中的數據流,通過解析流信息來獲取提交的內容;代碼示例如下:

[java] view plain copy
        /** 
         * 獲取請求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

[java] view plain copy
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(); 
    }*/  
}  

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