问题描述
当前,我正在使用RestEasy
和Jetty
开发REST API。我使用此REST API的计划之一是创建一个hook插件,以利用JAX-RS ContainerRequestFilter
处理传入请求中的任何事情。这里ContainerRequestPlugin
中带有Jetty
的地方是,一旦我在过滤器中调用了requestContext.getEntityStream();
,即使我已经设置了实体流,我的EndPoint类也无法再次读取该请求再次。
以下是我的过滤器代码
@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定制ServletInputStream
,即org.eclipse.jetty.server.HttpInput
。我相信该请求无法在EndPoint中读取,因为我使用ByteArrayInputStream设置了实体流。
所以我的问题是,有什么方法可以使用通用InputStream实现来构建/转换Jetty HttpInput吗?还是有其他方法可以解决这种情况?在哪里可以多次阅读Jetty HttpInput?
感谢与问候
解决方法
毫无疑问,Servlet规范不允许您两次读取Request正文内容。
这是一个有意的决定,因为任何此类功能都需要缓存或缓冲响应正文内容。导致:
- 针对您的Web应用的各种DoS /拒绝服务攻击。
- 当代码第二次从缓冲区读取请求并且不产生任何网络流量来重置空闲超时时,请求处理中的空闲超时。
- 无法从Servlet异步I / O处理中受益或无法使用。
JAX-RS端点通常要求出于任何原因(*)根本不读取javax.servlet.http.HttpServletRequest
输入流。
您的代码没有尝试限制您分配的字节数组的大小,使用Zip Bomb很容易滥用服务。 (例如:发送42千字节的数据并将其解压缩为3.99 PB)
您可能会发现一种JAX-RS实现特定的方式,例如使用Jersey内部代码来设置实体流,但是这种代码将是脆弱的,并且可能导致需要修复代码并使用对您的更新进行重新编译泽西图书馆。
如果您使用自定义路线,请格外小心,不要在代码中引入明显的漏洞,限制请求的大小,限制可以缓冲的内容,等等。
通常,需要修改请求输入流内容的web应用程序通过代理servlet来实现,代理servlet逐个缓冲区实时执行请求的中间人修改。 Jetty有这样的类,方便地称为AsyncMiddleManServlet
。从本质上讲,这意味着您的客户端与代理进行对话,而代理与您的终结点进行对话,这符合网络行为和网络背压需求。 (某些缓冲过滤器将无法正确处理)
(*)您可能会不小心使用请求中要求请求参数或请求部分的内容(要求读取某些特定Content-Type的内容)来读取HttpServletRequest正文