Jersey Async ContainerRequestFilter

2022-01-21 00:00:00 rest asynchronous java jersey jersey-2.0

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

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.

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

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.

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

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

这是一个示例资源:

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

更新刚刚收到来自 Jersey 家伙的回复,从 2.7 开始这是不可能的.只有资源方法本身被异步调用,而不是过滤器.任何关于继续的建议仍然欢迎.

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.

推荐答案

自 2.7 起,Jersey 未内置此功能.

This is not built in to Jersey as of 2.7.

@ManagedAsync 如果你有任何过滤器或拦截器来做任何严肃的工作(比如点击远程授权服务),那么它是无用的.他们可能会在未来添加异步运行过滤器的功能,但现在你只能靠自己了.

@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 - 失败

我使用 Guice 进行 DI(让 Guice 注入与 Jersey 一起工作是 壮举本身!),所以我想我可以使用 Guice AOP 来解决这个问题.虽然 Guice 注入有效,但不可能让 Guice 使用 Jersey 2 创建资源类,因此 Guice AOP 无法使用资源类方法.如果您拼命想让 Guice 使用 Jersey 2 创建资源类,不要浪费您的时间,因为它不起作用.这是众所周知的问题.

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 - 推荐的解决方案

HK2 AOP - RECOMMENDED SOLUTION

HK2 最近刚刚发布了一个 AOP 功能,请参阅这个问题了解如何让它工作的详细信息.

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

监控 - 也有效

这不适合胆小的人,在 泽西岛文档.您可以注册和 ApplicationEventListener 并覆盖 onRequest 以返回一个 RequestEventListener 侦听 RESOURCE_METHOD_START 并调用身份验证/授权服务.此事件由 @ManagedAsync 线程触发,这是这里的全部目标.需要注意的是,abortWith 方法是无操作的,因此它不会像普通的 ContainerRequestFilter 那样工作.相反,如果身份验证失败,您可以抛出异常,并注册一个 ExceptionMapper 来处理您的异常.如果有人足够大胆尝试一下,请告诉我,我会发布代码.

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.

相关文章