我有一个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之前是不可能的。只有资源方法本身被异步调用,而不是过滤器。任何关于继续进行的建议仍然欢迎。
自2.7起,泽西岛没有内置此功能。
如果您有任何过滤器或拦截器来做任何严肃的工作(比如访问远程授权服务),那么@ManagedAsync
是无用的。它们可能会在未来增加异步运行过滤器的功能,但目前您只能靠自己了。
更新-还有其他方法。。。
经过漫长而危险的旅程,我找到了一个短期内使用的非常棘手的解决方案。以下是我尝试的内容以及失败/成功的原因。
Guice AOP-失败
我使用Guice进行DI(让Guice注入与Jersey一起工作本身就是一项壮举!),所以我想我可以使用Guice AOP来解决这个问题。尽管Guice注入有效,但不可能让Guice使用Jersey 2创建资源类,因此Guice AOP无法使用资源类方法。如果你拼命想让Guice用Jersey 2创建资源类,不要浪费时间,因为它不起作用。这是一个众所周知的问题。
HK2 AOP-推荐解决方案
HK2最近刚刚发布了一个AOP功能,有关如何使其工作的详细信息,请参阅此问题。
监控-也适用于
这不适合胆小的人,泽西岛的医生们完全不鼓励这样做。您可以注册和ApplicationEventListener
并覆盖onRequest
以返回一个侦听RESOURCE_METHOD_START
并调用身份验证/授权服务的RequestEventListener
。这个事件是由@ManagedAsync
线程触发的,这就是这里的全部目标。需要注意的是,abortWith
方法是无操作的,因此它的工作方式与普通的ContainerRequestFilter
不太一样。相反,如果auth失败,您可以抛出一个异常,并注册一个ExceptionMapper
来处理您的异常。如果有人大胆尝试一下,请告诉我,我会发布代码。
我不确定这是否是您想要的,但是,您研究过Spring的OncePerRequestFilter
吗?我目前正在将其用于我的授权层,在授权层中,每个请求都会经过一些过滤器,这些过滤器根据我的过滤器如何映射到URL来扩展这个OncePerRequestFilter
。以下是我如何使用它的快速概述:
Dropwizard 中资源的身份验证/授权
我不太清楚这些过滤器的异步调度部分,但我希望这个链接至少能为你想要实现的目标提供一些启示!
我们使用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);
}
}
}