Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture Response Payload in JAX-RS filter

I want to capture and log the response payload in JAX-RS filter. Here is the code snippet of the filter method that I'm using to intercept the response. (FYI - I'm using RestEasy for implementation)

@Override
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext         responseContext) throws IOException {
    ...
      final OutputStream out = responseContext.getEntityStream();
       try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
               out.write(baos.toByteArray());
        ....
   }
}

However, the ByteArrayOutputStream turns out be empty. Looking at the RestEasy code, it's using DeferredOutputStream, but not sure how it would matter here in pulling the response payload. I have tried writing to byte[] directly, but that doesn't help either. Am I missing anything here ? Thanks.

like image 326
codehammer Avatar asked Aug 15 '14 18:08

codehammer


2 Answers

If you don't want to write more data to the response you don't need to deal with the OutputStream. Just use the response entity:

@Provider
public class SomeFilter implements ContainerResponseFilter {

    private Logger LOG = LoggerFactory.getLogger(SomeFilter.class);

    @Override
    public void filter(ContainerRequestContext requestContext,
            ContainerResponseContext responseContext) throws IOException {
        LOG.info("response entity: " + responseContext.getEntity());
    }

}

The OutputStream is empty at the time the Filter is called because the JAX-RS runtime has not written to it. After your Filter the runtime will choose the correct MessageBodyWriter which will serialize the entity to the OutputStream.

You could also intercept all MessageBodyWriters with a WriterInterceptor. Following example passes a ByteArrayOutputStream to the MessageBodyWriter and restores the original OutputStream afterwards:

@Provider
public class ResponseInterceptor implements WriterInterceptor {

    private Logger LOG = LoggerFactory.getLogger(ResponseInterceptor.class);

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
        OutputStream originalStream = context.getOutputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        context.setOutputStream(baos);
        try {
            context.proceed();
        } finally {
            LOG.info("response body: " + baos.toString("UTF-8"));
            baos.writeTo(originalStream);
            baos.close();
            context.setOutputStream(originalStream);
        }
    }

}
like image 193
lefloh Avatar answered Oct 25 '22 17:10

lefloh


I had the same problem and solved differently, so I leave here my response as well although the question is already marked as correctly answered.

I implemented a ContainerResponseFilter and injected Providers through which I retrieved the MessageBodyWriter for the particular entity of the response and the particular MediaType; then I used it to write the entity to an accessible OutputStream which I used to log the entity.

This approach allows you to capture the exact payload of the response, not just the entity attached to the Response, i.e. if the entity is going to be serialized as JSON, then you will log the JSON, if it is serialized as XML, you will log the XML. If using the toString() method of the attached entity is enough, this approach is just a useless computational cost.

Here is the code (the trick is done in the function CustomResponseLogger.payloadMessage):

@Provider
public class CustomResponseLogger implements ContainerResponseFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomResponseLogger.class);

    @Context private Providers providers;

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        String message = new String("Outgoing message").concat(System.lineSeparator());
        if (responseContext.getMediaType() != null) 
            message = message.concat("Content-Type: ").concat(responseContext.getMediaType().toString()).concat(System.lineSeparator());
        message = message.concat("Payload: ").concat(payloadMessage(responseContext)).concat(System.lineSeparator());
        LOGGER.info(message);
    }

    private String payloadMessage(ContainerResponseContext responseContext) throws IOException {
        String message = new String();
        if (responseContext.hasEntity()) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();           
            Class<?> entityClass = responseContext.getEntityClass();
            Type entityType = responseContext.getEntityType();
            Annotation[] entityAnnotations = responseContext.getEntityAnnotations();
            MediaType mediaType = responseContext.getMediaType();
            @SuppressWarnings("unchecked")
            MessageBodyWriter<Object> bodyWriter = (MessageBodyWriter<Object>) providers.getMessageBodyWriter(entityClass, 
                    entityType, 
                    entityAnnotations, 
                    mediaType); // I retrieve the bodywriter
            bodyWriter.writeTo(responseContext.getEntity(), 
                    entityClass, 
                    entityType, 
                    entityAnnotations, 
                    mediaType, 
                    responseContext.getHeaders(), 
                    baos); // I use the bodywriter to write to an accessible outputStream
            message = message.concat(new String(baos.toByteArray())); // I convert the stream to a String
        }
        return message;
    }
}

Hope this helps!

like image 28
kekolab Avatar answered Oct 25 '22 17:10

kekolab