解决在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、获取请求对象中的数据流,通过解析流信息来获取提交的内容;代码示例如下:

[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();
    }*/
}  
时间: 2024-10-06 13:10:09

解决在Filter中读取Request中的流后,后续controller或restful接口中无法获取流的问题的相关文章

ASP.NET Core 中读取 Request.Body 的正确姿势

ASP.NET Core 中的 Request.Body 虽然是一个 Stream ,但它是一个与众不同的 Stream —— 不允许 Request.Body.Position=0 ,这就意味着只能读取一次,要想多次读取,需要借助 MemoryStream ,详见博问 asp.net core中2次读取Request.Body的问题 using (var buffer = new MemoryStream()) { Request.Body.CopyTo(buffer); buffer.Pos

解决在Filter中读取Request中的流后, 然后再Control中读取不到的做法

我们来看一下核心代码: filter中主要做的事情, 就是来校验请求是否合法, 是否有篡改过值. @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (Boolean.valueOf(authentication)) { HttpServletRequest http

每日踩坑 2018-09-29 .Net Core 控制器中读取 Request.Body

测试代码: 结果: PostMan: 代码: 1 private string GetRequestBodyUTF8String() 2 { 3 this.Request.EnableBuffering(); 4 this.Request.Body.Position = 0; 5 Encoding encoding = System.Text.UTF8Encoding.Default; 6 if (this.Request.ContentLength > 0 && this.Requ

restful接口中指定application/x-www-form-urlencoded的问题

application/x-www-form-urlencoded指定了发送的POST数据,要进行URL编码,但是前面的&,=用在POST报文前面,作为参数的时候,是不需要进行编码的,可以直接跳过.例如: loginusername=admin&loginpassword=admin&param={JSON报文} 对于前面的两个&&都不能进行编码,否则Java后台无法正常解析出POST数据.目前JSON报文里面存在一个uri: http://192.168.0.22

Oracle中读取数据一些原理研究

文章很多摘录了 http://blog.163.com/[email protected]/blog/static/7956964020131069843572/ 同时基于这篇文章的基础上,补充一些学习要点,如有问题,希望指出探讨. 1 ORACLE体系结构 下图描述了oracle的体系结构.SGA(system global area)是各个进程共享的内存块,Buffer cache用来缓存数据文件的数据块(block). 2 如何在data buffer中查找数据块 data buffer存

循环中读取数据库、嵌套循环引起的性能问题

背景说明 K/3 Cloud的代码开发规范,严格禁止在循环中到数据库读取数据,这会引发严重的性能问题: 需在循环外,一次性取回需要的数据. 但对于提前取回的数据,如果没有预先处理,常常需要嵌套一个循环到集合中取数,这也是非常严重的性能问题. 本帖将通过一个案例,编写三套实现方法,演示循环取数,典型的错误方案与推荐方案. 案例说明 需求: 生成销售出库单时,自动检查库存,从有存货的仓库出库. 实现方案: 编写单据转换插件,物料.数量携带完毕后,到数据库取有存货的仓库,填写到仓库字段中: 如果某一个

Mean and Standard Deviation-从文件中读取数据计算其平均数和标准差

Meanand Standard Deviation-从文件中读取数据计算其平均数和标准差 //Meanand Standard Deviation-从文件中读取数据计算其平均数和标准差 #include<iostream> #include<fstream> #include<cstdlib> #include<cmath>   int main() {     usingnamespace std;     ifstream fin;     ofstr

从配置文件或数据库中读取信息并缓存

在项目中将一些常用的不变的信息可以缓存起来,这样可以减少内存的使用率,提高性能.比如说就数据库连接的相关信息等,可以用单例模式第一次进行连接的时候将数据库连接的相关信息缓存再单例对象中. 首先建个.properties文件,存放数据库连接信息,内容如下: #数据库配置 driver_class = oracle.jdbc.driver.OracleDriver url= jdbc:oracle:thin:@192.168.20.188:1521:orcl username = qwszpt pa

教你在Java接口中定义方法

基本上所有的Java教程都会告诉我们Java接口的方法都是public.abstract类型的,没有方法体的. 但是在JDK8里面,你是可以突破这个界限的哦. 假设我们现在有一个接口:TimeClient,其代码结构如下: import java.time.*; public interface TimeClient { void setTime(int hour, int minute, int second); void setDate(int day, int month, int yea