Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jersey InputStream is modified in filter. Unable to figure out how to access modified inputStream in Jersey Resource

As discussed in How to use Jersey interceptors to get request body, I am modifying the EntityInputStream in a ContainerRequestFilter.

public filter(ContainerRequest request){
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     InputStream in = request.getEntityInputStream(); 
     try{
           Readerwriter.writeTo(in, out);
           byte[] requestEntity = out.toByteArray();

           // DO SOMETHING WITH BYTES HERE

           request.setEntityInputStream(new ByteArrayInputStream(requestEntity));

      }/// error handling code here  
}

However, later on I can't figure out how to access the modified InputStream. I can get the ServletContext in the resource, but I can't figure out how to get ahold of the object I actually modified in the filter, the ContainerRequest.

Can I do something like this? Jersey can't start up out when I try this:

@Post
@Path("/test")
public Response test(@Context ContainerRequest cr){
     // blah blah
     return....
}

Jersey error:

Missing dependecy for method public javax.ws.rs.core.Response example.TestController.test(com.sun.jersey.spi.container.ContainerRequest), annotated with POST of resource, class example.TestController, is not recognized as a valid resource method.

I am stuck on an old version of jersey, 1.8, so I'm not sure if that's part of the problem.

like image 297
MeowCode Avatar asked Sep 17 '15 14:09

MeowCode


1 Answers

All you need to do is accept an InputStream as the entity body in your resource method. If you want the ByteArrayInputStream just cast it.

@POST
public Response post(InputStream in) {
    ByteArrayInputStream bin = (ByteArrayInputStream)in;
}

If you don't already know, how Jersey converts the request stream (for the request body) into Java types (for instance JSON to POJO) is through MessageBodyReaders. You can read more about them at JAX-RS Entity Providers.

Jersey already comes with some standard readers for easily convertible types, for instance String. Most content-types can be converted to String. Likewise, it has a reader to handle InputStream. This is probably the easiest conversion, as the request is already coming in as an InputStream, so really all the reader would need to do is return the original stream, and that's what would get passed to our method.

If we look at the implementation InputStreamProvider, we can see that that's what actually happens. The original stream is simply returned. And since the filter happens before the readers, the reader simply returns the stream that we set.

Here is a complete example using Jersey Test Framework

public class StreamFilterTest extends JerseyTest {

    public static class InputStreamFilter implements ContainerRequestFilter {

        @Override
        public ContainerRequest filter(ContainerRequest request) {
            try {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                InputStream in = request.getEntityInputStream();
                ReaderWriter.writeTo(in, out);

                byte[] requestBytes = out.toByteArray();
                byte[] worldBytes = " World".getBytes(StandardCharsets.UTF_8);
                byte[] newBytes = new byte[requestBytes.length + worldBytes.length];
                System.arraycopy(requestBytes, 0, newBytes, 0, requestBytes.length);
                System.arraycopy(worldBytes, 0, newBytes, requestBytes.length, worldBytes.length);

                request.setEntityInputStream(new ByteArrayInputStream(newBytes));
            } catch (IOException ex) {
                Logger.getLogger(InputStreamFilter.class.getName()).log(Level.SEVERE, null, ex);
                throw new RuntimeException(ex);
            }

            return request;
        }
    }

    @Path("stream")
    public static class StreamResource {

        @POST
        public String post(InputStream in) throws Exception {
            ByteArrayInputStream bin = (ByteArrayInputStream) in;
            StringWriter writer = new StringWriter();
            ReaderWriter.writeTo(new InputStreamReader(bin), writer);
            return writer.toString();
        }
    }

    public static class AppConfig extends DefaultResourceConfig {
        public AppConfig() {
            super(StreamResource.class);
            getContainerRequestFilters().add(new InputStreamFilter());
        }
    }

    @Override
    public WebAppDescriptor configure() {
        return new WebAppDescriptor.Builder()
                .initParam(WebComponent.RESOURCE_CONFIG_CLASS,
                        AppConfig.class.getName())
                .build();
    }

    @Test
    public void should_return_hello_world() {
        String response = resource().path("stream").post(String.class, "Hello");
        assertEquals("Hello World", response);
    }
}

Here's the test dependency

<dependency>
    <groupId>com.sun.jersey.jersey-test-framework</groupId>
    <artifactId>jersey-test-framework-grizzly2</artifactId>
    <version>1.17.1</version>
    <scope>test</scope>
</dependency>
like image 103
Paul Samsotha Avatar answered Sep 29 '22 13:09

Paul Samsotha