Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 528
shonky linux user Avatar asked Jul 08 '13 05:07

shonky linux user


2 Answers

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
like image 62
shonky linux user Avatar answered Sep 29 '22 16:09

shonky linux user


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();
    }
    ...
}
like image 45
Wilfredo Pomier Avatar answered Sep 29 '22 14:09

Wilfredo Pomier