Currently im developing a REST API using RestEasy
and Jetty
. One of my plan with this REST API is to create a hook plugin to do anything needed with the incoming request utilizing JAX-RS ContainerRequestFilter
. The thing with ContainerRequestPlugin
in Jetty
here is that once I called requestContext.getEntityStream();
in the Filter then the request wont be able to be read again by my EndPoint Class even if I have set the Entity Stream again.
Following are my Filter code
@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);
}
}
}
Then here is my endpoint 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();
}
}
Whenever I called this test method I can see the name sent in Filter class but in the Endpoint class name is NULL
.
Later then I figured out that the getEntityStream returned from requestContext is Jetty custom ServletInputStream
that is org.eclipse.jetty.server.HttpInput
. I believe the request cannot be read in EndPoint since I set the Entity Stream using ByteArrayInputStream.
So my question will be, is there any way to build/convert Jetty HttpInput using generic InputStream implementation? or is there any other way to work around this case? where I can read Jetty HttpInput many times?
Thanks & Regards
As you have no doubt noticed, the Servlet spec does not allow you to read the Request body contents twice.
This is an intentional decision as any such feature would require caching or buffering the response body content. Which leads to:
JAX-RS endpoints typically require that the javax.servlet.http.HttpServletRequest
input stream has not been read, at all, for any reason (*).
Your code makes no attempt to limit the size of the byte arrays you allocate, it would be easy to abuse your service with a Zip Bomb. (example: sending 42 kilobytes of data that unpacks to 3.99 petabytes)
You may find a JAX-RS implementation specific way, such as using Jersey internal code to set the entity stream, but that kind of code will be fragile and likely result in the need to fix your code and recompile with updates to your Jersey library.
If you go the custom route, please be take extra care to not introduce obvious vulnerabilities in your code, limit your request size, limit what you can buffer, etc.
Typically webapps that need to modify request input stream content do it via proxy servlets that perform middle-man modification of the request in real-time, on a buffer by buffer basis. Jetty has such a class, called conveniently AsyncMiddleManServlet
. This essentially means your client talks to the proxy which talks to your endpoint, which honors network behaviors and network backpressure needs. (something a buffering filter wouldn't be able to handle properly)
(*) You can accidentally read the HttpServletRequest body by using things from the request that ask for the request parameters or the request parts (which require that the body content be read for certain specific Content-Types)