我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Request的包装类HttpServletRequestWrapper的使用说明

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Request的包装类HttpServletRequestWrapper的使用说明

Request的包装类HttpServletRequestWrapper使用

在使用zuul进行鉴权的时候,我们希望从请求Request中获取输入流,解析里面的内容,奈何InputStream只能被读取一次。为啥呢?源码里是这样说的:

public int read(byte[] b,int off, int len)

   Reads up to len bytes of data into an array of bytes from this input stream. Ifpos equals count, then -1 is returned to indicate end of file. Otherwise, the number k of bytes read is equal to the smaller of len and count-pos.If k is positive, then bytes buf[pos] through buf[pos+k-1] are copied into b[off] through b[off+k-1] in the manner performed by System.arraycopy. The value k is added into pos and k is returned.  

大致的意思是:

在InputStream读取的时候,会有一个pos指针,它指示每次读取之后下一次要读取的起始位置。在每次读取后会更新pos的值,当你下次再来读取的时候是从pos的位置开始的,而不是从头开始,所以第二次获取String中的值的时候是不全的,API中提供了一个解决办法:reset()。但我发现在inputStream和servlet中根本不起作用。提示 mark/reset not supported 。意思是只有重写过markSupported()方法的IO流才可以用。所以一般我们使用inputStream,最好在一次内处理完所有逻辑。

那么就没法在中途获取请求流中的数据么?当然有办法了,我可是PPZ,只需要重写Request缓存一下流中的数据就好了,实现代码如下:

BodyReaderHttpServletRequestWrapper.java


package com.neo.authUtils;
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 java.util.NoSuchElementException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;  
public class BodyReaderHttpServletRequestWrapper extends  
        HttpServletRequestWrapper {  
   // private final byte[] body;  
     -----》private 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"));  
    }
    
    public String getJsessionidFromHeader(HttpServletRequest request) {
        String jsessionid = null;//识别用户身份的id;
        Enumeration<?> e = request.getHeaderNames()   ;    
        while(e.hasMoreElements()){    
            String name = (String) e.nextElement();    
            String value = request.getHeader(name);
            //cookie = JSESSIONID=B926F6024438D4C693A5E5881595160C; SESSION=458e80dc-e354-4af3-a501-74504a873e70
            if("cookie".equals(name)) {
                jsessionid = value.split(";")[0].split("=")[1];
            }
            System.out.println(name+"="+value);
        }
       // System.out.println("======jsessionid========>"+jsessionid);
        return jsessionid;
    }
    @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() {  
            public int read() throws IOException {  
                return bais.read();  
            }
            @Override
            public boolean isFinished() {
                // TODO Auto-generated method stub
                return false;
            }
            @Override
            public boolean isReady() {
                // TODO Auto-generated method stub
                return false;
            }
            @Override
            public void setReadListener(ReadListener listener) {
                // TODO Auto-generated method stub
            }  
        };  
    }  
    @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) {  
        if ((null != name && name.equals("Content-Type"))||(null != name && name.equals("content-type"))) {  
            return new Enumeration<String>() {  
                private boolean hasGetted = false;  
                @Override  
                public String nextElement() {  
                    if (hasGetted) {  
                        throw new NoSuchElementException();  
                    } else {  
                        hasGetted = true;
                        return "application/json;charset=utf-8";  
                    }  
                }  
                @Override  
                public boolean hasMoreElements() {  
                    return !hasGetted;  
                }  
            };  
        }  
        return super.getHeaders(name);  
    }  
    
    public void appendCustomMsgToReqBody(String customMsg) {
        String oldBodyString = HttpHelper.getBodyString(this);//oldBodyString一定是通过当前对象的输入流解析得来的,否则接收时会报EOFException;
        String appendMsg = HttpHelper.appendCustomMsgToReqBody(customMsg);
        String requestBodyAfterAppend = appendMsg + "," +oldBodyString;
        //this.body = HttpHelper.appendCustomMsgToReqBody(HttpHelper.appendCustomMsgToReqBody(customMsg)+(HttpHelper.getBodyString(this))).getBytes(Charset.forName("UTF-8"));
        //this.body = HttpHelper.appendCustomMsgToReqBody((HttpHelper.getBodyString(this))).getBytes(Charset.forName("UTF-8"));
        this.body = HttpHelper.appendCustomMsgToReqBody(requestBodyAfterAppend).getBytes(Charset.forName("UTF-8"));
    } 
}  

HttpHelper.java


package com.neo.authUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import javax.servlet.ServletRequest;
public class HttpHelper {
    
    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();
    }
    //添加自定义的信息到请求体中;
    public static String appendCustomMsgToReqBody(String newReqBodyStr) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        String newReqBody = null;
        try {
            //通过字符串构造输入流;
            inputStream = String2InputStream(newReqBodyStr);
            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();
                }
            }
        }
        //返回字符串;
        newReqBody = sb.toString();
        return newReqBody;
    }
    //将字符串转化为输入流;
    public static InputStream String2InputStream(String str) {
        ByteArrayInputStream stream = null;
        try {
            stream = new ByteArrayInputStream(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return stream;
    }
}

上述方案解决了

使用request.getInpuStream()方法读取流中的数据只能读取一次的问题,其实当我们在使用第三方接口时,如果请求头信息和我们的服务所需不一致,例如第三方接口中头部信息为:content-type=text/plain;charset=UTF-8

而我们需要的是:”application/json;charset=utf-8”时,我们也是可以通过重写对应的方法对请求的头部信息进行修改的,代码如下:



    @Override  
    public Enumeration<String> getHeaders(String name) {  
        if ((null != name && name.equals("Content-Type"))||(null != name && name.equals("content-type"))) {  
            return new Enumeration<String>() {  
                private boolean hasGetted = false;  
                @Override  
                public String nextElement() {  
                    if (hasGetted) {  
                        throw new NoSuchElementException();  
                    } else {  
                        hasGetted = true;
                        return "application/json;charset=utf-8";  
                    }  
                }  
                @Override  
                public boolean hasMoreElements() {  
                    return !hasGetted;  
                }  
            };  
        }  
        return super.getHeaders(name);  
    }  

当我们在后端设置了头部信息后,如果不出意外,前端发送的请求将变为简单请求,这样,服务器的处理机制将简单很多。

HttpServletRequestWrapper和HttpServletResponseWrapper使用时的坑

WrapperRequest和WrapperResponse的使用

在做JavaWeb开发过程中如果想拿到请求参数和返回数据的话我们就会使用到这两个类,从类名上就可以看出是包装类,通过这两个类的包装我们可以使用移花接木的方式获取到对应的参数数据。

这里涉及到的坑

坑1

如果请求参数在Body内时取出参数后,后端程序就无法再次取出数据

这个和InputStream不能重复读有关 ,这里需要将Request中的数据自己保存一份然后在使用的时候给出新的InputStream,这样就可以避免使用同一个InputStream读取完数据后无法重新读取数据


@Override
    public ServletInputStream getInputStream() throws IOException {
        //这里每次都重新创建了一个InputStream
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(bodyData);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }

坑2

使用HttpServletResponseWrapper包装Response后无法返回数据或者无法返回html,css等数据

这个跟网上的教程有关,大多网上的教程是这样的如下代码:


//先定义一个Filter类包装对应的request和response
public class WrapperFilter extends OncePerRequestFilter {
    private static Logger logger = LoggerFactory.getLogger(WrapperFilter.class);
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            logger.debug(" request mapping {} {}", request.getRequestURL(), request.getRequestURI());
            RequestWrapper requestWrapper = new RequestWrapper(request);
            ResponseWrapper responseWrapper = new ResponseWrapper(response);
            filterChain.doFilter(requestWrapper, responseWrapper);
    }
//response的包装类
public class ResponseWrapper extends HttpServletResponseWrapper {
    private static Logger logger = LoggerFactory.getLogger(ResponseWrapper.class);
    private final ByteArrayOutputStream buffer;
    private final ServletOutputStream out;
    private final PrintWriter writer;
    
    public ResponseWrapper(HttpServletResponse response) throws IOException {
        super(response);
        buffer = new ByteArrayOutputStream(2048);
        out = new WrapperOutputStream(buffer);
        writer = new PrintWriter(buffer);
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return out;
    }
    
    @Override
    public PrintWriter getWriter() {
        return writer;
    }
   
    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }
    public String getContent() throws IOException {
        flushBuffer();
        return buffer.toString();
    }
}

上面的代码虽然是可以获取到数据的但是,数据就无法返回到前端页面了,那么为什么会出现这样的问题呢,咱们来分析一下。

1、包装类对response进行了包装,并且重写了getWriter和getOutputStream 这样就可以保证后端使用response向前端写数据时写到我们定义的buffer中

2、这个包装类是不范围的,也就是只在WrapperFilter 之后有效,但是浏览器从response读取数据明显是在WrapperFilter的范围之外的

也就是说浏览器从reponse读取数据时无法使用ResponseWrapper而只能使用response 这个默认是ResponseFacade

3、那么问题来了咱们上面有往response中写入数据吗,显然是没有的也就是写数据只在ResponseWrapper中有而ResponseFacade 是没有数据的所以浏览器了就无法读取到返回的数据啦。

清楚以上问题后问题就变得简单得多了,那么我们只需要往ResponseFacade 中也定入一份数据就可以了

解决问题

Filter的内容不变

ResponseWrapper中的代码如下修改


public class ResponseWrapper extends HttpServletResponseWrapper {
    private static Logger logger = LoggerFactory.getLogger(ResponseWrapper.class);
    private final ByteArrayOutputStream buffer;
    private final ServletOutputStream out;
    private final PrintWriter writer;
   
    public ResponseWrapper(HttpServletResponse response) throws IOException {
        super(response);
        buffer = new ByteArrayOutputStream(2048);
        //这里将response也传入了WrapperOutputStream 和 WrapperWriter 
        out = new WrapperOutputStream(buffer,  response);
        writer = new WrapperWriter(buffer, response);
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return out;
    }
    
    @Override
    public PrintWriter getWriter() {
        return writer;
    }
    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }
    public String getContent() throws IOException {
        flushBuffer();
        return buffer.toString();
    }
}

这里将response也传入了WrapperOutputStream 和 WrapperWriter 这样我们在做数据写入的时候就可以同时向reponse中写入数据了

这两个类的实现如下:


public class WrapperOutputStream extends ServletOutputStream {
    private OutputStream innerOut;
    private HttpServletResponse response;
    public WrapperOutputStream(OutputStream innerOut, HttpServletResponse response) {
        super();
        this.response = response;
        this.innerOut = innerOut;
    }
    @Override
    public boolean isReady() {
        if(response == null){
            return false;
        }
        try {
            return response.getOutputStream().isReady();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
    @Override
    public void setWriteListener(WriteListener listener) {
        if(response != null){
            try {
                ((CoyoteOutputStream)response.getOutputStream()).setWriteListener(listener);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //关键在这里
    @Override
    public void write(int b) throws IOException {
        if(response != null){
            response.getOutputStream().write(b);
        }
        innerOut.write(b);
    }
}
//另一个类
public class WrapperWriter extends PrintWriter {
    private HttpServletResponse response;
    ByteArrayOutputStream output;
    public WrapperWriter(ByteArrayOutputStream out, HttpServletResponse response) {
        super(out);
        this.response = response;
        this.output = out;
    }
    //关键在这里
    @Override
    public void write(int b){
        super.write(b);
        try {
            response.getWriter().write(b);
        } catch (IOException e) {
            e.printStackTrace();
            this.setError();
        }
    }
    //关键在这里
    @Override
    public void write(String s, int off, int len) {
        super.write(s,off,len);
        try {
            response.getWriter().write(s,off,len);
        } catch (IOException e) {
            e.printStackTrace();
            this.setError();
        }
    }
}

以上可以看到数据的写入变成了写两份一份写到了自定义的对象中一份写到了response中这样返回到前端的responnse就不会没有数据了

到此问题完全解决,这里还需要注意的就是PrintWriter 有多个writer重载需要都进行重写才行

问题延伸

有人会问能不能直接将response中的OutputStream和Writer获取到分配给对应的WrapperOutputStream 和WrapperWriter而不是直接传入response本身,答案是不可以的,response是不能同时获取OutputStream和Writer的因为他们操作的是同一个数据,所以ResponseFacade 实现时对其进行了保护——同时只能获取OutputStream和Writer中的一个。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Request的包装类HttpServletRequestWrapper的使用说明

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

RCMD的使用说明

这篇文章主要介绍“RCMD的使用说明”,在日常操作中,相信很多人在RCMD的使用说明问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”RCMD的使用说明”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!*****
2023-06-08

使用golang-unsafe包的注意事项及说明

这篇文章主要介绍了使用golang-unsafe包的注意事项及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-02-10

ORACLE event的使用说明

这篇文章给大家介绍ORACLE event的使用说明,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。eventEvent Reference 10013 - Monitor Transaction Recovery102
2023-06-06

vue-jsonp的使用及说明

这篇文章主要介绍了vue-jsonp的使用及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-12-08

基于@JsonProperty的使用说明

这篇文章主要介绍了基于@JsonProperty的使用说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录