Jersey Async ContainerRequestFilter

时间:2013-12-12 23:13:30

标签: java rest asynchronous jersey jersey-2.0

我有一个Jersey REST API并使用ContainerRequestFilter来处理授权。我还在所有端点上使用@ManagedAsync,以便我的API可以处理数千个并发请求。

我的授权过滤器命中远程服务,但是当运行过滤器时,Jersey还没有将当前线程添加到它的内部ExecutorService,所以我完全失去了异步的好处。

我可以告诉Jersey我希望这个ContainerRequestFilter是异步的吗?

@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());
        }
    }
}

这是一个示例资源:

@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);
    }
}

更新刚刚从泽西岛球员那里听到,从2.7开始这是不可能的。仅异步调用资源方法本身,而不是过滤器。任何有关进行的建议仍然欢迎。

3 个答案:

答案 0 :(得分:4)

自2.7以来,这并不是内置于泽西岛。

如果你有任何过滤器或拦截器做任何认真的工作(如点击远程授权服务),

@ManagedAsync是没用的。他们可能会在将来添加异步运行过滤器的功能,但现在你可以自己动手了。

更新 - 还有其他方法......

经过漫长而危险的旅程,我发现了一个非常讨厌的解决方案,我在短期内使用它。以下是我尝试过的内容及其失败/工作原因的概述。

Guice AOP - 失败

我使用Guice进行DI(使用Guice注入与Jersey一起工作是feat in itself!),所以我想我可以使用Guice AOP来解决这个问题。虽然Guice注入工作,但是不可能让Guice用Jersey 2创建资源类,因此Guice AOP无法使用资源类方法。如果你拼命想让Guice用Jersey 2创建资源类,那么不要浪费你的时间,因为它不会起作用。这是well-known problem

HK2 AOP - 推荐解决方案

HK2刚刚发布了AOP功能,请参阅this question了解如何使其正常运行。

监控 - 也有效

这不适合胆小的人,Jersey docs完全不鼓励。您可以注册ApplicationEventListener并覆盖onRequest以返回侦听RequestEventListener的{​​{1}}并调用身份验证/授权服务。此事件由RESOURCE_METHOD_START线程触发,这是此处的整个目标。有一点需要注意,@ManagedAsync方法是无操作方法,所以这不会像普通的abortWith那样有效。相反,如果auth失败则可以抛出异常,并注册ContainerRequestFilter来处理异常。如果有人大胆尝试一下,请告诉我,我会发布代码。

答案 1 :(得分:1)

我不确定这是不是你想要的,但是,你看过Spring的OncePerRequestFilter吗?我目前正在将其用于我的授权层,其中每个请求都会通过一些扩展此OncePerRequestFilter的过滤器,具体取决于我的过滤器如何映射到URL。以下是我如何使用它的快速概述:

Authentication/Authorization of a resource in Dropwizard

我对这些过滤器的异步调度部分不是很清楚,但我希望这个链接至少可以解释你想要实现的目标!

答案 2 :(得分:0)

我们使用Spring安全性进行身份验证/授权。我使用具有空路径的子资源定位器解决了这个问题,如下所示:

@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);
        }
    }
}