Search code examples
restjerseydropwizardbean-validation

Dumping bad requests


I have a service implemented with Dropwizard and I need to dump incorrect requests somewhere.

I saw that there is a possibility to customise the error message by registering ExceptionMapper<JerseyViolationException>. But I need to have the complete request (headers, body) and not only ConstraintViolations.


Solution

  • You can inject ContainerRequest into the ExceptionMapper. You need to inject it as a javax.inject.Provider though, so that you can lazily retrieve it. Otherwise you will run into scoping problems.

    @Provider
    public class Mapper implements ExceptionMapper<ConstraintViolationException> {
        
        @Inject
        private javax.inject.Provider<ContainerRequest> requestProvider;
    
        @Override
        public Response toResponse(ConstraintViolationException ex) {
            ContainerRequest request = requestProvider.get();
        }
    }
    

    (This also works with constructor argument injection instead of field injection.)

    In the ContainerRequest, you can get headers with getHeaderString() or getHeaders(). If you want to get the body, you need to do a little hack because the entity stream is already read by Jersey by the time the mapper is reached. So we need to implement a ContainerRequestFilter to buffer the entity.

    public class EntityBufferingFilter implements ContainerRequestFilter {
    
        @Override
        public void filter(ContainerRequestContext containerRequestContext) throws IOException {
            ContainerRequest request = (ContainerRequest) containerRequestContext;
            request.bufferEntity();
        }
    }
    

    You might not want this filter to be called for all requests (for performance reasons), so you might want to use a DynamicFeature to register the filter just on methods that use bean validation (or use Name Binding).

    Once you have this filter registered, you can read the body using ContainerRequest#readEntity(Class). You use this method just like you would on the client side with Response#readEntity(). So for the class, if you want to keep it generic, you can use String.class or InputStream.class and convert the InputStream to a String.

    ContainerRequest request = requestProvider.get();
    String body = request.readEntity(String.class);