11

I have a Jersey REST API and am using a ContainerRequestFilter to handle authorization. I'm also using @ManagedAsync on all endpoints so that my API can serve thousands of concurrent requests.

My authorization filter hits a remote service, but when the filter is run, Jersey hasn't yet added the current thread to it's internal ExecutorService, so I'm completely losing the async benefits.

Can I tell Jersey that I want this ContainerRequestFilter to be asynchronous?

@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter
{
    @Inject
    private AuthorizationService authSvc;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException
    {
        String authToken = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

        // HITS A REMOTE SERVER
        AuthorizationResponse authResponse = authSvc.authorize(authToken);

        if (!authResponse.isAuthorized())
        {
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                    .entity("unauthorized!")
                    .build());
        }
    }
}

And here's an example resource:

@Path("/stuff")
@Produces(MediaType.APPLICATION_JSON)
public class StuffResource
{
    @GET
    @Path("/{id}")
    @ManagedAsync
    public void getById(@PathParam("id") long id, @Suspended final AsyncResponse ar)
    {
        Stuff s;

        // HIT THE DATABASE FOR STUFF

        ar.resume(s);
    }
}

UPDATE Just heard back from the Jersey guys, and this is not possible as of 2.7. Only the resource method itself is invoked asynchronously, not filters. Any suggestions for proceeding still welcome.

Alden
  • 6,293
  • 2
  • 32
  • 46
  • Did the Jersey guys say whether async filters would be supported in the future? – Paul Bellora Dec 18 '13 at 20:47
  • 1
    They said to add a feature request, and that it would take some refactoring :(. In the short term I'm just calling a method for authorization at the top of each resource endpoint, which is really ugly but gets the job done. I'm considering using Guice and AOP, but would obviously like to be able to do this in Jersey somehow. I'm surprised this hasn't come up before, basically `@ManagedAsync` is useless if you have any filters... – Alden Dec 18 '13 at 22:53
  • @Alden is `ContainerRequestFilter` still not working nicely with `@ManagedAsync`? I've searched for this all over and can't find any information related to that. I guess HK2 is still the best approach, right? 5 years later... – igracia Apr 12 '18 at 06:15
  • @igracia I am not sure. About a year after posting this question, we were able to significantly improve API performance by reworking our Guice setup, and decided to stop using `@ManagedAsync` entirely. It's been almost 4 years since then, and we have not looked back - we now have many Jersey APIs with high throughput running smoothly in production. – Alden Aug 03 '18 at 11:55

3 Answers3

4

This is not built in to Jersey as of 2.7.

@ManagedAsync is useless if you have any filters or interceptors that do any serious work (like hit a remote authorization service). They may add the ability to run filters asynchronously in the future, but for now you're on your own.

UPDATE - there are other ways...

After a long and perilous journey, I have found a very hacky solution that I'm using in the short term. Here is a rundown of what I tried and why it failed/worked.

Guice AOP - failed

I use Guice for DI (getting Guice injection to work with Jersey is a feat in itself!), so I figured I could use Guice AOP to get around the issue. Though Guice injection works, it is impossible to get Guice to create resource classes with Jersey 2, so Guice AOP cannot work with resource class methods. If you are trying desperately to get Guice to create resource classes with Jersey 2, don't waste your time because it will not work. This is a well-known problem.

HK2 AOP - RECOMMENDED SOLUTION

HK2 just recently released an AOP feature, see this question for details on how to get it working.

Monitoring - also worked

This is not for the faint of heart, and it is completely discouraged in the Jersey docs. You can register and ApplicationEventListener and override onRequest to return a RequestEventListener that listens for RESOURCE_METHOD_START and calls an authentication/authorization service. This event is triggered from the @ManagedAsync thread, which is the whole goal here. One caveat, the abortWith method is a no-op, so this won't work quite like a normal ContainerRequestFilter. Instead, you can throw an exception if auth fails instead, and register an ExceptionMapper to handle your exception. If someone is bold enough to give this a try, let me know and I'll post code.

Community
  • 1
  • 1
Alden
  • 6,293
  • 2
  • 32
  • 46
1

I am not sure if this is what you were looking for but, have you looked into Spring's OncePerRequestFilter? I am currently using it for my authorization layer where each request goes through some filter that extends this OncePerRequestFilter depending on how my filters are mapped to the URLs. Here's a quick overview of how I am using it:

Authentication/Authorization of a resource in Dropwizard

I am not very clear on the async dispatch parts of these filters but I hope this link atleast sheds some light to what you are trying to achieve!

Vasil Lukach
  • 3,383
  • 3
  • 27
  • 36
shahshi15
  • 2,332
  • 2
  • 17
  • 23
  • 1
    This doesn't look asynchronous to me. Have you ever used `OncePerRequestFilter` asynchronously with Spring? – Alden Dec 20 '13 at 03:22
  • I did some more reading and I was wondering if you can clarify some things? "Jersey hasn't yet added the current thread to it's internal ExecutorService", agreed. But by default, each HTTP request is already handled on a separate thread (from a predetermined threadpool ofcourse) by the Web Server itself. Take a look here: [link](https://jersey.java.net/documentation/latest/jaxrs-resources.html#d0e2229). Unless you want jax-rs to start each request on a separate thread (from it's own threadpool), I don't see a straight up need for it. Tweaking your webserver should be good enough I think. – shahshi15 Dec 20 '13 at 07:22
  • And why would you ever want your authorization filter to be async? What would you return back as a response before the authorization has actually finished ? Call to the filter has to be sync. You have to wait till the time the auth service returns Accepted or Denied and based on that you can make forward an async resource call or return 401 Unauthorized. In short, call to resource being async makes sense (if the execution of that call is time consuming), but call to the filter being async doesn't make sense. Just my 2 cents. – shahshi15 Dec 20 '13 at 07:26
  • you are correct that the web sever serves each request on a different thread from its thread pool. the standard problem is your web server has lets say 20 threads at its disposal. if a request comes in, use thread from the pool to serve it (now 19 left), and when its done add the thread back to the pool. But if that request takes a long time, and a bunch of other requests take a long time, your web server is now spending lots of resources maintaining those and has a limited thread pool. the point of the separate ExecutorService is specifically so that the web server is freed up to serve many – Alden Dec 20 '13 at 07:34
  • requests. I'm talking more than 1,000 concurrent requests. I don't want to get into the details of how an async auth might work, but basically you tell the web server that it can go ahead and serve more requests, but watch out because at some later time I'm going to be serving the response to this particular request. TLDR; there are very good reasons for doing this, read up on it if you're curious. – Alden Dec 20 '13 at 07:37
0

We use Spring security for authentication/authorization. I worked around the problem using a sub-resource locator with empty path as shown below:

@Path("/customers")
public class CustomerResource {
    @Inject
    private CustomerService customerService;

    @Path("")
    public CustomerSubResource delegate() {
        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return new CustomerSubResource(auth);
    }

    public class CustomerSubResource {
        private final Authentication auth;           

        public CustomerSubResource(final Authentication auth) {
            this.auth = auth;
        }

        @POST
        @Path("")
        @Produces(MediaType.APPLICATION_JSON)
        @Consumes(MediaType.APPLICATION_JSON)
        @ManagedAsync
        public void createCustomer(final Customer customer, @Suspended final AsyncResponse response) {
            // Stash the Spring security context into the Jersey-managed thread
            SecurityContextHolder.getContext().setAuthentication(this.auth);

            // Invoke service method requiring pre-authorization
            final Customer newCustomer = customerService.createCustomer(customer);

            // Resume the response
            response.resume(newCustomer);
        }
    }
}