首页 > 解决方案 > 如何多次读取 Jetty HttpInput (ServletInputStream)?

问题描述

RestEasy目前我正在使用and开发一个 REST API Jetty。我对这个 REST API 的计划之一是创建一个钩子插件来使用 JAX-RS 对传入请求执行任何所需的操作ContainerRequestFilter。这里的问题ContainerRequestPluginJetty,一旦我调用requestContext.getEntityStream();了过滤器,即使我再次设置了实体流,我的端点类也无法再次读取请求。

以下是我的过滤器代码

@Provider
@Priority(2000)
public class DummyRequestFilter implements ContainerRequestFilter{
    static Logger log = Logger.getLogger(DummyRequestFilter .class.getName());
    
    @Context
    private HttpServletRequest servletRequest;
    
    @Override
    public void filter(ContainerRequestContext requestContext) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
    String requestBody = "";
    
    try {           
        IOUtils.copy(requestContext.getEntityStream(), baos);
        
        InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
        
        requestBody = IOUtils.toString(is1);
        
        log.info(requestBody);
        
        requestContext.setEntityStream(is2);
                
    }catch (Exception e) {
        log.log(Level.SEVERE,"Exception Occurred",e);
    }
    }   
}

然后这是我的端点类

@Path("/")
public class DummyService {
    
    Logger log = Logger.getLogger(DummyService .class.getName());
    
    @GET
    @Path("test")
    @Produces(MediaType.APPLICATION_JSON)
    public Response test(@FormParam("name") String name) {
        log.info("Name = "+name);

        return Response.status(200).build();
    }
}

每当我调用这个测试方法时,我都可以看到在 Filter 类中发送的名称,但在 Endpoint 类中的名称是NULL.

后来我发现从 requestContext 返回的 getEntityStream 是 Jetty customServletInputStreamorg.eclipse.jetty.server.HttpInput. 我相信无法在 EndPoint 中读取请求,因为我使用 ByteArrayInputStream 设置了实体流。

所以我的问题是,有没有办法使用通用 InputStream 实现来构建/转换 Jetty HttpInput?或者有没有其他方法可以解决这种情况?我可以在哪里多次阅读 Jetty HttpInput?

感谢和问候

标签: javarestjax-rsjettyresteasy

解决方案


毫无疑问,您已经注意到,Servlet 规范不允许您两次读取请求正文内容。

这是一个有意的决定,因为任何此类功能都需要缓存或缓冲响应正文内容。这导致:

  • 针对您的 webapp 的各种 DoS / 拒绝服务攻击。
  • 当您的代码第二次从缓冲区读取请求并且不产生网络流量来重置空闲超时时,请求处理的空闲超时。
  • 无法受益于或使用 Servlet 异步 I/O 处理。

JAX-RS 端点通常要求javax.servlet.http.HttpServletRequest出于任何原因 (*) 根本没有读取输入流。

您的代码没有尝试限制您分配的字节数组的大小,使用Zip Bomb很容易滥用您的服务。(示例:发送 42 KB 的数据,解压为 3.99 PB)

您可能会找到一种特定于 JAX-RS 实现的方式,例如使用 Jersey 内部代码来设置实体流,但这种代码会很脆弱,并且可能需要修复您的代码并使用对 Jersey 库的更新重新编译。

如果您采用自定义路线,请特别注意不要在您的代码中引入明显的漏洞,限制您的请求大小,限制您可以缓冲的内容等。

通常,需要修改请求输入流内容的 webapps 通过代理 servlet 来完成,这些代理 servlet 在逐个缓冲区的基础上实时执行请求的中间人修改。Jetty 有这样一个类,方便地调用AsyncMiddleManServlet。这实质上意味着您的客户端与与您的端点对话的代理对话,该代理尊重网络行为和网络背压需求。(缓冲过滤器无法正确处理的东西)

(*) 您可能会通过使用请求中要求请求参数或请求部分的内容意外读取 HttpServletRequest 正文(这需要为某些特定的 Content-Types 读取正文内容)


推荐阅读