JEE6 REST Service @AroundInvoke Interceptor is injecting a null HttpServletRequest object
Asked Answered
A

3

6

I have an @AroundInvoke REST Web Service interceptor that I would like to use for logging common data such as the class and method, the remote IP address and the response time.

Getting the class and method name is simple using the InvocationContext, and the remote IP is available via the HttpServletRequest, as long as the Rest Service being intercepted includes a @Context HttpServletRequest in its parameter list.

However some REST methods do not have a HttpServletRequest in their parameters, and I can not figure out how to get a HttpServletRequest object in these cases.

For example, the following REST web service does not have the @Context HttpServletRequest parameter

@Inject
@Default
private MemberManager memberManager;

@POST
@Path("/add")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Member add(NewMember member) throws MemberInvalidException {
    return memberManager.add(member);
}

I have tried injecting it directly into my Interceptor, but (on JBoss 6.1) it is always null...

public class RestLoggedInterceptorImpl implements Serializable {
    @Context
    HttpServletRequest req;

    @AroundInvoke
    public Object aroundInvoke(InvocationContext ic) throws Exception {

        logger.info(req.getRemoteAddr());  // <- this throws NPE as req is always null
        ...
        return ic.proceed();

I would like advice of a reliable way to access the HttpServletRequest object - or even just the Http Headers ... regardless of whether a REST service includes the parameter.

Alcaeus answered 8/7, 2013 at 5:47 Comment(0)
A
5

After researching the Interceptor Lifecycle in the Javadoc http://docs.oracle.com/javaee/6/api/javax/interceptor/package-summary.html I don't think its possible to access any servlet context information other than that in InvocationContext (which is defined by the parameters in the underlying REST definition.) This is because the Interceptor instance has the same lifecycle as the underlying bean, and the Servlet Request @Context must be injected into a method rather than the instance. However the Interceptor containing @AroundInvoke will not deploy if there is anything other than InvocationContext in the method signature; it does not accept additional @Context parameters.

So the only answer I can come up with to allow an Interceptor to obtain the HttpServletRequest is to modify the underlying REST method definitons to include a @Context HttpServletRequest parameter (and HttpServletResponse if required).

@Inject
@Default
private MemberManager memberManager;

@POST
@Path("/add")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Member add(NewMember member, @Context HttpServletRequest request, @Context HttpServletResponse response) throws MemberInvalidException {
    ...
}

The interceptor can then iterate through the parameters in the InvocationContext to obtain the HttpServletRequest

@AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
    HttpServletRequest req = getHttpServletRequest(ic);
    ...
    return ic.proceed();
}

private HttpServletRequest getHttpServletRequest(InvocationContext ic) {
    for (Object parameter : ic.getParameters()) {
        if (parameter instanceof HttpServletRequest) {
            return (HttpServletRequest) parameter;
        }
    }
    // ... handle no HttpRequest object.. e.g. log an error, throw an Exception or whatever
Alcaeus answered 8/7, 2013 at 7:23 Comment(3)
Does this means that you have to include a @Context HttpServletRequest parameter in every REST method of your application? It has to be a better way. I am fairly new to Java EE6 and I am struggling with a very similar, if not the same, problem. I've wrote a question here, it is not asking the same but it comes down to this: I need to check session on the interceptor in order to perform authorisation. #19454057Gerhardine
@nosuchnick I also thought "there has to be a better way", that is why I asked the question. This seems to be one of those JEE6 little annoyances where adding an interceptor requires boilerplate changes to the intercepted code.Alcaeus
Sure. I ended up doing all the session management in a @SessionScoped bean and injecting it to the interceptor. I can then use its methods there.Gerhardine
S
5

Another work around to avoid creating additional parameters in every REST method is creating a super class for all REST services that use that kind of interceptors:

public abstract class RestService {
    @Context
    private HttpServletRequest httpRequest;

    // Add here any other @Context fields & associated getters 

    public HttpServletRequest getHttpRequest() {
        return httpRequest;
    }
}

So the original REST service can extend it without alter any method signature:

public class AddService extends RestService{
    @POST
    @Path("/add")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Member add(NewMember member) throws MemberInvalidException {
        return memberManager.add(member);
    }
    ...
}

And finally in the interceptor to recover the httpRequest:

public class RestLoggedInterceptorImpl implements Serializable {
    @AroundInvoke
    public Object aroundInvoke(InvocationContext ic) throws Exception {

        // Recover the context field(s) from superclass:
        HttpServletRequest req = ((RestService) ctx.getTarget()).getHttpRequest();

        logger.info(req.getRemoteAddr());  // <- this will work now
        ...
        return ic.proceed();
    }
    ...
}
Scheelite answered 11/2, 2016 at 22:45 Comment(0)
D
0

I'm using Glassfish 3.1.2.2 Jersey

For http header this works for me:

@Inject
@HeaderParam("Accept")
private String acceptHeader;

To get UriInfo you can do this:

@Inject
@Context
private UriInfo uriInfo;
Dedededen answered 29/7, 2014 at 13:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.