15

I'm trying to retrieve the body of a request in a JAX-RS ExceptionMapper. Here is my code so far:

@Provider @Componenet
public class BaseExceptionMapper implements ExceptionMapper<Exception> {

    @Context private HttpServletRequest request;

    @Override
    public Response toResponse(Exception ex) {

        // Trying to retrieve request body for logging throws an error
        String requestBody = IOUtils.toString(request.getInputStream());

    }

}

So my dilemma is I can't get the request body for logging because the servlet API wont allow you to call request.getInputStream() / request.getReader() more than once for a request (and JAX-RS Is obviously calling it to parse the request). Does anyone know if there is a way to do what I'm trying to do?

Brian DiCasa
  • 9,369
  • 18
  • 65
  • 97
  • 2
    I guess you would need to create custom Exception, include already parsed entity into it and reuse that value in your ExceptionMapper.. – Pavel Bucek Nov 12 '11 at 22:20

3 Answers3

12

This question is a bit older, but still the answer may help others. My Example also depends on Commons-Io.

You can create a ContainerRequestFilter and use TeeInputStream to proxy/copy the original InputStream:

@Provider
@Priority(Priorities.ENTITY_CODER)
public class CustomRequestWrapperFilter implements ContainerRequestFilter { 

    @Override
    public void filter(ContainerRequestContext requestContext)
            throws IOException {
        ByteArrayOutputStream proxyOutputStream = new ByteArrayOutputStream();
        requestContext.setEntityStream(new TeeInputStream(requestContext.getEntityStream(), proxyOutputStream));
        requestContext.setProperty("ENTITY_STREAM_COPY", proxyOutputStream);
    }

}

And use @Inject with javax.inject.Provider in your ExceptionMapper to get the ContainerRequest injected.

The ExceptionMapper would look like this:

@Provider
public class BaseExceptionMapper implements ExceptionMapper<Exception> {

    @Inject
    private javax.inject.Provider<ContainerRequest> containerRequestProvider;

    @Override
    public Response toResponse(Exception exception) {
        ByteArrayOutputStream bos = (ByteArrayOutputStream) containerRequestProvider
                .get().getProperty("ENTITY_STREAM_COPY");
        String requestBody = bos.toString();
        ...
    }

}

When I have also used the @Component annotation my ExceptionMapper was not used. I think that @Provider is sufficient.

friedeas
  • 131
  • 1
  • 5
  • `@friedea` This only return request body ,But i need all possible information to send mail with all detail like: 1>request URL 2>request Method 3>request BODY 4>path parameter 5>query parameter 6>IP address etc.etc – HybrisHelp Apr 22 '14 at 06:19
  • 1
    Isn't this leaking the OutputStream? When is it going to close? – Frederico Pantuzza Oct 18 '16 at 15:38
  • `setEntityStream` docs says: "The JAX-RS runtime is responsible for closing the input stream" – Koshinae Apr 03 '19 at 09:08
0

One possible solution is to use a servlet filter and wrap the request, which allows you to intercept read calls to the request input stream. Example pseudo-code (depends on commons-io):

import org.apache.commons.io.output.StringBuilderWriter;
import org.apache.commons.io.input.TeeInputStream;
class MyHttpRequest extends HttpServletRequestWrapper {
    private StringBuilderWriter myString = new StringBuilderWriter();
    private InputStream myIn;
    public MyHttpRequest(HttpServletRequest request) {
        super(request);
        myIn = new TeeInputStream(request.getInputStream(), myString);
    }
    @Override public ServletInputStream getInputStream()
            throws java.io.IOException {
        // this will need an extra wrapper to compile
        return myIn;
    }
    public String getRequestBody() {
        return myString.toString();
    }
}

Filter:

public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
    MyHttpRequest wrapper = new MyHttpRequest((HttpServletRequest) request);
    chain.doFilter(wrapper, response, chain);
}

Mapper:

@Context private HttpServletRequest request;
@Override public Response toResponse(Exception ex) {
    String body = "";
    if (this.request instanceof MyHttpRequest) {
        body = ((MyHttpRequest)request).getRequestBody()
    }
}

You'll need a wrapper class for ServletInputStream, and you can find an example implementation here: Modify HttpServletRequest body

Community
  • 1
  • 1
ngreen
  • 1,559
  • 13
  • 22
  • I've tried your solution but inside the `toResponse` method, `request` is never an instance of `MyHttpRequest`. Instead, `request` is an instance of [`com.sun.proxy.$Proxy`](http://stackoverflow.com/questions/19633534/what-is-com-sun-proxy-proxy) and I could not find a way to cast it to the desired class. Any ideas? – Frederico Pantuzza Oct 18 '16 at 16:56
  • @FredericoPantuzzadeMeira are you certain your filter is running? Proxy classes are typically generated by frameworks, but your filter should be wrapping the generated class. You might also try creating an interface and supplying an implementation in your filter but otherwise referencing only the interface. Also check out http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html – ngreen Oct 18 '16 at 21:15
0

I know this is an old question but I found a workaround that I think it's nice to share.

With the following code you should be able to get the ContainerRequestContext inside the ExceptionMapper, then you can read the body, query params, headers, etc.

@Provider
public class CustomExceptionMapper implements ExceptionMapper<CustomException> {

    @Context
    private ResourceContext resourceContext;

    @Override
    public Response toResponse(CustomException e) {
        ContainerRequestContext requestContext =
                resourceContext.getResource(ContainerRequestContext.class);
    }

}

Hope it can help

dave008
  • 402
  • 3
  • 9