HttpServletRequestWrapper和HttpServletResponseWrapper使用时的坑

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中的一个。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
HttpServletRequestWrapper 是一个用来增强 HttpServletRequest 的类,它实现了 HttpServletRequest 接口,并且可以通过继承它来对 HttpServletRequest 进行增强。 下面是一个使用 HttpServletRequestWrapper 的示例代码: ```java public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { private Map<String, String[]> customParams; public CustomHttpServletRequestWrapper(HttpServletRequest request) { super(request); customParams = new HashMap<>(); } public void addParameter(String name, String value) { String[] values = customParams.get(name); if (values == null) { values = new String[] { value }; } else { values = Arrays.copyOf(values, values.length + 1); values[values.length - 1] = value; } customParams.put(name, values); } @Override public String getParameter(String name) { String[] values = customParams.get(name); if (values != null && values.length > 0) { return values[0]; } return super.getParameter(name); } @Override public String[] getParameterValues(String name) { String[] values = customParams.get(name); if (values != null && values.length > 0) { return values; } return super.getParameterValues(name); } } ``` 这个类继承了 HttpServletRequestWrapper,并且实现了添加参数和获取参数的方法。在 addParameter 方法中,我们将自定义的参数存储到 customParams 中;在 getParameter 和 getParameterValues 方法中,我们首先从 customParams 中获取参数值,如果没有则调用父类的方法获取。 使用这个类可以很方便地对 HttpServletRequest 进行增强,比如在参数中添加一些自定义参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值