如何多次读取Jetty HttpInput(ServletInputStream)?
How to read Jetty HttpInput (ServletInputStream) many times?
目前我正在使用 RestEasy
和 Jetty
开发 REST API。我对这个 REST API 的计划之一是创建一个挂钩插件,以使用 JAX-RS ContainerRequestFilter
来完成传入请求所需的任何事情。 Jetty
中 ContainerRequestPlugin
的问题是,一旦我在过滤器中调用 requestContext.getEntityStream();
,即使我有,我的端点 Class 也无法再次读取请求再次设置实体流。
以下是我的过滤器代码
@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);
}
}
}
那么这是我的终点class
@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();
}
}
每当我调用此测试方法时,我都可以看到过滤器 class 中发送的名称,但端点 class 中的名称是 NULL
.
后来我发现requestContext返回的getEntityStream是Jetty custom ServletInputStream
即org.eclipse.jetty.server.HttpInput
。我相信在 EndPoint 中无法读取请求,因为我使用 ByteArrayInputStream 设置了实体流。
所以我的问题是,有什么方法可以 build/convert Jetty HttpInput 使用通用的 InputStream 实现吗?还是有其他方法可以解决这种情况?我在哪里可以多次阅读 Jetty HttpInput?
感谢和问候
您肯定已经注意到,Servlet 规范不允许您读取请求正文内容两次。
这是一个有意的决定,因为任何此类功能都需要缓存或缓冲响应正文内容。这导致:
- 针对您的网络应用程序的各种 DoS/拒绝服务攻击。
- 当您的代码第二次从缓冲区读取请求并且没有产生网络流量来重置空闲超时时,请求处理空闲超时。
- 无法受益于或使用 Servlet 异步 I/O 处理。
JAX-RS 端点通常要求 javax.servlet.http.HttpServletRequest
输入流没有被读取,无论出于何种原因 (*)。
您的代码没有尝试限制您分配的字节数组的大小,使用 Zip Bomb 很容易滥用您的服务。 (示例:发送 42 KB 的数据,解压缩后为 3.99 PB)
您可能会找到 JAX-RS 特定实现方式,例如使用 Jersey 内部代码设置实体流,但这种代码很脆弱,可能需要修复您的代码并重新编译更新您的 Jersey 图书馆。
如果您采用自定义路线,请格外小心,不要在您的代码中引入明显的漏洞,限制您的请求大小,限制您可以缓冲的内容等。
通常,需要修改请求输入流内容的 Web 应用程序通过代理 servlet 执行 middle-man 修改 real-time 中的请求,逐个缓冲区地执行。 Jetty有这么一个class,叫起来方便AsyncMiddleManServlet
。这实质上意味着您的客户端与代理对话,代理与您的端点对话,后者尊重网络行为和网络背压需求。 (缓冲过滤器无法正确处理的东西)
(*) 您可能会通过使用请求中要求请求参数或请求部分的东西(这需要为某些特定内容读取主体内容 Content-Types)不小心读取 HttpServletRequest 主体
目前我正在使用 RestEasy
和 Jetty
开发 REST API。我对这个 REST API 的计划之一是创建一个挂钩插件,以使用 JAX-RS ContainerRequestFilter
来完成传入请求所需的任何事情。 Jetty
中 ContainerRequestPlugin
的问题是,一旦我在过滤器中调用 requestContext.getEntityStream();
,即使我有,我的端点 Class 也无法再次读取请求再次设置实体流。
以下是我的过滤器代码
@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);
}
}
}
那么这是我的终点class
@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();
}
}
每当我调用此测试方法时,我都可以看到过滤器 class 中发送的名称,但端点 class 中的名称是 NULL
.
后来我发现requestContext返回的getEntityStream是Jetty custom ServletInputStream
即org.eclipse.jetty.server.HttpInput
。我相信在 EndPoint 中无法读取请求,因为我使用 ByteArrayInputStream 设置了实体流。
所以我的问题是,有什么方法可以 build/convert Jetty HttpInput 使用通用的 InputStream 实现吗?还是有其他方法可以解决这种情况?我在哪里可以多次阅读 Jetty HttpInput?
感谢和问候
您肯定已经注意到,Servlet 规范不允许您读取请求正文内容两次。
这是一个有意的决定,因为任何此类功能都需要缓存或缓冲响应正文内容。这导致:
- 针对您的网络应用程序的各种 DoS/拒绝服务攻击。
- 当您的代码第二次从缓冲区读取请求并且没有产生网络流量来重置空闲超时时,请求处理空闲超时。
- 无法受益于或使用 Servlet 异步 I/O 处理。
JAX-RS 端点通常要求 javax.servlet.http.HttpServletRequest
输入流没有被读取,无论出于何种原因 (*)。
您的代码没有尝试限制您分配的字节数组的大小,使用 Zip Bomb 很容易滥用您的服务。 (示例:发送 42 KB 的数据,解压缩后为 3.99 PB)
您可能会找到 JAX-RS 特定实现方式,例如使用 Jersey 内部代码设置实体流,但这种代码很脆弱,可能需要修复您的代码并重新编译更新您的 Jersey 图书馆。
如果您采用自定义路线,请格外小心,不要在您的代码中引入明显的漏洞,限制您的请求大小,限制您可以缓冲的内容等。
通常,需要修改请求输入流内容的 Web 应用程序通过代理 servlet 执行 middle-man 修改 real-time 中的请求,逐个缓冲区地执行。 Jetty有这么一个class,叫起来方便AsyncMiddleManServlet
。这实质上意味着您的客户端与代理对话,代理与您的端点对话,后者尊重网络行为和网络背压需求。 (缓冲过滤器无法正确处理的东西)
(*) 您可能会通过使用请求中要求请求参数或请求部分的东西(这需要为某些特定内容读取主体内容 Content-Types)不小心读取 HttpServletRequest 主体