Retrieve Request Body in Exception Mapper
Asked Answered
C

3

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?

Colorblind answered 11/11, 2011 at 14:49 Comment(1)
I guess you would need to create custom Exception, include already parsed entity into it and reuse that value in your ExceptionMapper..Blastula
S
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.

Sanmicheli answered 23/9, 2013 at 15:44 Comment(3)
@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.etcNoh
Isn't this leaking the OutputStream? When is it going to close?Jiffy
setEntityStream docs says: "The JAX-RS runtime is responsible for closing the input stream"Gorrono
G
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

Gower answered 4/5, 2013 at 4:55 Comment(2)
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 and I could not find a way to cast it to the desired class. Any ideas?Jiffy
@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 docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.htmlGower
E
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

Exequatur answered 14/6, 2022 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.