Retrieve Request Body in Exception Mapper - java

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?

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.

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

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

Related

How to access incoming HTTP requests in X-Ray SegmentListener?

Issue
I use AWS X-Ray SDK for Java to enable X-Ray tracing for my Spring Boot micro services.
With following snippet I am able to attach a custom SegmentListener:
final AWSXRayRecorder recorder = AWSXRayRecorderBuilder
.standard()
.withPlugin(new EcsPlugin())
.withSegmentListener(new SLF4JSegmentListener())
.withSegmentListener(new MyHttpHeaderSegementListener())
.build();
AWSXRay.setGlobalRecorder(recorder);
In MyHttpHeaderSegementListener I try to inject a X-Ray annotation based on an incoming HTTP request header (from the frontend):
public class MyHttpHeaderSegementListener implements SegmentListener {
// snippet source: https://stackoverflow.com/a/54349178/6489012
public static Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
public MyHttpHeaderSegementListener() {}
#Override
public void onBeginSegment(final Segment segment) {
final var httpContext = MyHttpHeaderSegementListener.getCurrentHttpRequest();
httpContext.ifPresent(context -> segment.putAnnotation("Origin", context.getHeader("Origin")));
}
}
The segment listener is triggered as expected onBeginSegment segment but MyHttpHeaderSegementListener.getCurrentHttpRequest() always returns an Optional.empty.
Questions
Is there a possibility to inspect incoming HTTP requests (as they
were received by a Controller) within a SegmentListener?
Does aws-xray-sdk-java maybe even support a native way to do so?
Why is the request retrieved from RequestContextHolder always empty?
(A bit off-topic but: 4. Is it even a good practice to set an annotation based on a HTTP header)
I have no answer for the 2. and 3. question but I found an answer for 1. question.
For incoming requests you need to add a Spring Filter to configure AWS X-Ray. As filters have access to the HTTP request I just wrapped my own filter around the com.amazonaws.xray.javax.servlet.AWSXRayServletFilter of AWS:
public class XRayServletFilter extends AWSXRayServletFilter {
public XRayServletFilter(String fixedSegmentName) {
super(fixedSegmentName);
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
this.addHttpRequestToContext(request);
super.doFilter(request, response, chain);
}
private void addHttpRequestToContext(final ServletRequest request){
final Optional<HttpServletRequest> httpServletRequest = HttpRequestUtils.castToHttpRequest(request);
if (httpServletRequest.isPresent()) {
final ServletRequestAttributes requestAttributes = new ServletRequestAttributes(httpServletRequest.get());
RequestContextHolder.setRequestAttributes(requestAttributes);
}
}
}
Which uses a static class that I wrote:
public final class HttpRequestUtils {
public static Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
public static Optional<HttpServletRequest> castToHttpRequest(ServletRequest request) {
try {
return Optional.of((HttpServletRequest) request);
} catch (ClassCastException classCastException) {
return Optional.empty();
}
}
}
This custom filter basically sets the HTTP requests in the RequestContextHolder. After that you can use it in your segment listeners:
public class MyHttpHeaderSegementListener implements SegmentListener {
public MyHttpHeaderSegementListener() {}
#Override
public void onBeginSegment(final Segment segment) {
final Optional<HttpServletRequest> request = HttpRequestUtils.getCurrentHttpRequest();
request.map(req -> req.getHeader("Origin")).ifPresent(origin -> segment.putAnnotation("client_origin", origin));;
}
}

Injecting HttpRequest in RESTEasy Reactive / Quarkus fails

I'm currently trying to inject and read out the HttpRequest in Quarkus 1.13 but without any success. I'm using RESTEasy-Reactive for my endpoint.
This is how I'm currently including it
#Path("/users/{id}")
class UserController(
#Inject val service: UserService,
#Context val httpRequest: io.vertx.core.http.HttpServerRequest,
)
...
The build process succeeds but when I try to access a property like httpRequest.absoluteURI() I am getting an NPE
java.lang.NullPointerException: Cannot invoke "org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext.serverRequest()" because the return value of "org.jboss.resteasy.reactive.server.core.CurrentRequestManager.get()" is null
at io.quarkus.resteasy.reactive.server.runtime.QuarkusContextProducers.httpServerRequest(QuarkusContextProducers.java:26)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusContextProducers_Subclass.httpServerRequest$$superaccessor3(QuarkusContextProducers_Subclass.zig:451)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusContextProducers_Subclass$$function$$3.apply(QuarkusContextProducers_Subclass$$function$$3.zig:29)
...
I also tried other classes like io.vertx.mutiny.core.http.HttpServerRequest or java.net.http.HttpRequest but still without success. Injecting it with #Inject didn't even build. I'm missing the HttpServletRequest class :/
Anybody got an idea?
You have a few options:
Using HttpFilter: https://javaee.github.io/javaee-spec/javadocs/javax/servlet/http/HttpFilter.html
#WebFilter(urlPatterns = "/*")
public class FilterEverything extends HttpFilter {
#Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
//Do something with HttpServletRequest
}
}
Using ContainerRequestFilter: https://docs.oracle.com/javaee/7/api/javax/ws/rs/container/ContainerRequestFilter.html
As Quarkus Documentation Showcases:
#Provider
public class LoggingFilter implements ContainerRequestFilter {
private static final Logger LOG = Logger.getLogger(LoggingFilter.class);
#Context
UriInfo info;
#Context
HttpServerRequest request;
#Override
public void filter(ContainerRequestContext context) {
//Do whatever you want
}
}
As part of the method signature:
#GET
#Path("/someEndPoint")
#Produces("application/json")
public JsonObject getData(#PathParam("owner") String owner, #Context HttpServletRequest request) {
//Do something here
}

How can i custom filter for 302 Status Code in spring mvc java and response.redirect to custom page?

Hi all I want to create RedirectionFilter at java spring. The main purpose is when status code 302 is detected then send redirect and replacing the response content by custom text content. The first problem is don't know how to catch the response code 302. This is my current thinking .
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) (request);
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String redirectURL="www.google.com";
if(statuscode == 302 ){
httpServletResponse.sendRedirect(redirectURL);
}
Something like that. I have out of ideas. Thanks for helping.
You can use HandlerInterceptorAdapter to get the response after each call and can validate the response code and do necessary things
#Component
public class TestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, java.lang.Object object) throws Exception {
System.out.println("test");
return true;
}
#Override
public void afterCompletion(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, java.lang.Object handler, #org.springframework.lang.Nullable java.lang.Exception ex) throws java.lang.Exception {
if(response.getStatus()==302){
// your code
}
}
}
After creating a interceptor need to register it in InterceptorRegistry
#Component
public class InterceptorsConfig implements WebMvcConfigurer {
#Autowired
private TestInterceptor testInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(testInterceptor);
}
}
Once triggering a request, the control will come to interceptor first.
Restcontroller sample
#RestController
#RequestMapping("/test")
public class Restcontroller {
#GetMapping
public ModelAndView redirectWithUsingRedirectPrefix(ModelMap model) {
model.addAttribute("attribute", "redirectWithRedirectPrefix");
return new ModelAndView("redirect:/redirectedUrl", model);
}
}

how to inject headers in a `#context HttpServletRequest`?

Let's say I have this code:
#ApplicationPath("...")
public class MyApp extends ResourceConfig {
public SalesLayerApplication() {
this.register(HeaderInjecterFilter.class);
this.register(Test.class);
}
}
#PreMatching
public class HeaderInjecterFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext crc) throws IOException {
crc.getHeaders().add("foo", "bar");
}
}
#Path("/test")
public class Test {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String dump(#Context final HttpServletRequest request) {
return request.getHeader("foo");
}
}
I was expecting to call the rest entry point /test and to retrieve the string bar.
But all I see is null
If I use #HeaderParam("foo") I correctly retrieve the variable, but I need to access throug the #Context HttpServletRequest.
Why would you expect that adding headers to the ContainerRequestContext would also add it to the HttpServletRequest? These are completely unrelated entities. Try injecting HttpHeaders or you can also inject the ContainerRequestContext directly.

How to Read the JSON Payload in a JAX-RS Filter

I'm trying to do a personalised authZ process prior to each api-request.
I'm using JAX-RS/Jersey.
public class AuthorizationRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext request) throws IOException {
// How to see JSON payload here?
...
}
}
request.getEntityStream() will contain an InputStream that represents the body. You can read it there.

Categories