阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

修改响应

27次阅读
没有评论

共计 2827 个字符,预计需要花费 8 分钟才能阅读完成。

既然我们能通过 Filter 修改HttpServletRequest,自然也能修改HttpServletResponse,因为这两者都是接口。

我们来看一下在什么情况下我们需要修改HttpServletResponse

假设我们编写了一个 Servlet,但由于业务逻辑比较复杂,处理该请求需要耗费很长的时间:

@WebServlet(urlPatterns = "/slow/hello")
public class HelloServlet extends HttpServlet {protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html");
        // 模拟耗时 1 秒:
        try {Thread.sleep(1000);
        } catch (InterruptedException e) { }
        PrintWriter pw = resp.getWriter();
        pw.write("<h1>Hello, world!</h1>");
        pw.flush();}
}

好消息是每次返回的响应内容是固定的,因此,如果我们能使用缓存将结果缓存起来,就可以大大提高 Web 应用程序的运行效率。

缓存逻辑最好不要在 Servlet 内部实现,因为我们希望能复用缓存逻辑,所以,编写一个 CacheFilter 最合适:

@WebFilter("/slow/*")
public class CacheFilter implements Filter {// Path 到 byte[]的缓存:
    private Map<String, byte[]> cache = new ConcurrentHashMap<>();

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        // 获取 Path:
        String url = req.getRequestURI();
        // 获取缓存内容:
        byte[] data = this.cache.get(url);
        resp.setHeader("X-Cache-Hit", data == null ? "No" : "Yes");
        if (data == null) {// 缓存未找到, 构造一个伪造的 Response:
            CachedHttpServletResponse wrapper = new CachedHttpServletResponse(resp);
            // 让下游组件写入数据到伪造的 Response:
            chain.doFilter(request, wrapper);
            // 从伪造的 Response 中读取写入的内容并放入缓存:
            data = wrapper.getContent();
            cache.put(url, data);
        }
        // 写入到原始的 Response:
        ServletOutputStream output = resp.getOutputStream();
        output.write(data);
        output.flush();}
}

实现缓存的关键在于,调用 doFilter() 时,我们不能传入原始的 HttpServletResponse,因为这样就会写入 Socket,我们也就无法获取下游组件写入的内容。如果我们传入的是“伪造”的HttpServletResponse,让下游组件写入到我们预设的ByteArrayOutputStream,我们就“截获”了下游组件写入的内容,于是,就可以把内容缓存起来,再通过原始的HttpServletResponse 实例写入到网络。

这个 CachedHttpServletResponse 实现如下:

class CachedHttpServletResponse extends HttpServletResponseWrapper {private boolean open = false;
    private ByteArrayOutputStream output = new ByteArrayOutputStream();

    public CachedHttpServletResponse(HttpServletResponse response) {super(response);
    }

    // 获取 Writer:
    public PrintWriter getWriter() throws IOException {if (open) {throw new IllegalStateException("Cannot re-open writer!");
        }
        open = true;
        return new PrintWriter(output, false, StandardCharsets.UTF_8);
    }

    // 获取 OutputStream:
    public ServletOutputStream getOutputStream() throws IOException {if (open) {throw new IllegalStateException("Cannot re-open output stream!");
        }
        open = true;
        return new ServletOutputStream() {public boolean isReady() {return true;
            }

            public void setWriteListener(WriteListener listener) { }

            // 实际写入 ByteArrayOutputStream:
            public void write(int b) throws IOException {output.write(b);
            }
        };
    }

    // 返回写入的 byte[]:
    public byte[] getContent() {return output.toByteArray();}
}

可见,如果我们想要修改响应,就可以通过 HttpServletResponseWrapper 构造一个“伪造”的HttpServletResponse,这样就能拦截到写入的数据。

修改响应时,最后不要忘记把数据写入原始的 HttpServletResponse 实例。

这个 CacheFilter 同样是一个“可插拔”组件,它是否启用不影响 Web 应用程序的其他组件(Filter、Servlet)。

练习

通过 Filter 修改响应。

下载练习

小结

借助 HttpServletResponseWrapper,我们可以在 Filter 中实现对原始HttpServletResponse 的修改。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计2827字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中