使用Jersey的OPTIONS请求上的CORS标头

时间:2016-06-24 09:14:07

标签: java rest jersey cors

我有一个REST API,我希望某些方法具有特定的CORS标头。我有资源方法的注释,以及添加标题的过滤器:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface CorsHeaders {}

@Path("api")
class MyApi {
  @CorsHeaders
  @GET
  public Response m() {
    return Response.ok().build();
  }
}

@Provider
class CorsFilter implements ContainerResponseFilter {
  @Context private ResourceInfo resourceInfo;

  @Override 
  public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
    if (resourceInfo.getResourceMethod().getAnnotation(CorsHeaders.class) != null) {
      responseContext.getHeaders().add(/* appropriate headers here*/);
    }
  }
}

这适用于所有GET,POST等请求。它不适用于OPTIONS请求,因为资源方法将解析为org.glassfish.jersey.server.wadl.processor.WadlModelProcessor$OptionsHandler而不是我的方法,因此注释将不会出现。

我可以通过向我的API类添加@OPTIONS @CorsHeaders public Response options() { return Response.ok().build(); }方法(在同一个@Path上)来解决这个问题,但我不必为所有方法都这样做。

如何在处理OPTIONS请求时找出实际的(GET / POST)资源方法?

1 个答案:

答案 0 :(得分:0)

我担心你想要完成的事情实际上是不可能以一种不错的方式使用当前版本而不改变泽西本身。

无论如何,根据规范规范,我还不确定使用@Provider是否是正确的请求过滤器。但我是谁,我实际上是自己做的。当然,也可以在ResourceConfig中注册过滤器。 一般来说,我建议看看@NameBinding,但对于这种情况,名称绑定泽西风格是不够的。使用@NameBinding,您不必自己检查注释,因为Jersey已经为您做了。

不幸的是,再次使用@NameBinding,这是为了这种情况而引入的,这就是"自动生成"的问题。 OPTIONS处理程序。我做了一些挖掘(一些最相关的类/方法是OptionsMethodProcessorWadlModelProcessorResourceModelConfigurator#initServerRuntime ApplicationHandler#initialize)但是没有找到一种方法来挂钩进程充分。这对于处理CORS应该足够了:

@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CrossOrigin {
}


@CrossOrigin
public class CrossOriginResponseFilter implements ContainerResponseFilter {
    public void filter(ContainerRequestContext requestContext,  
                       ContainerResponseContext responseContext)
    throws IOException {
        // do Cross Origin stuff
    }
}

@Path("ress")
public class MyResource {
    @CrossOrigin
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response save(DetailsDTO details) {
         // do something with the details
    }
}

虽然这对任何对资源的直接请求都有效,但这对CORS-preflight请求也不起作用,因为Jersey不会将名称绑定注释@CrossOrigin应用于预定义/自动生成OPTIONS处理程序。 您可以看到,在请求上下文中查看资源的运行时表示时(不要让所有文本都激怒您,重要的是nameBindings - 每个ResourceMethod - 属性。 1}}):

[ResourceMethod{
    httpMethod=POST, consumedTypes=[application/json], 
    producedTypes=[application/json], suspended=false, suspendTimeout=0, 
    suspendTimeoutUnit=MILLISECONDS, invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class de.example.MyResource, 
    handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor@2c253414]}, definitionMethod=public javax.ws.rs.core.Response de.example.MyResource.save(de.example.DetailsDTO),
    parameters=[Parameter [type=class de.example.DetailsDTO, source=null, defaultValue=null]],
    responseType=class javax.ws.rs.core.Response},
    nameBindings=[interface de.example.CrossOrigin]},
ResourceMethod{
    httpMethod=OPTIONS, consumedTypes=[*/*], 
    producedTypes=[application/vnd.sun.wadl+xml], suspended=false, 
    suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, 
    invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class org.glassfish.jersey.server.wadl.processor.WadlModelProcessor$OptionsHandler, 
    handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor@949030f]}, 
    definitionMethod=public abstract java.lang.Object org.glassfish.jersey.process.Inflector.apply(java.lang.Object), 
    parameters=[Parameter [type=interface javax.ws.rs.container.ContainerRequestContext, source=null, defaultValue=null]], responseType=class javax.ws.rs.core.Response},
    nameBindings=[]},
ResourceMethod{
    httpMethod=OPTIONS, consumedTypes=[*/*], producedTypes=[text/plain], 
    suspended=false, suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, 
    invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class org.glassfish.jersey.server.wadl.processor.OptionsMethodProcessor$PlainTextOptionsInflector,
    handlerConstructors=[]}, definitionMethod=public abstract java.lang.Object org.glassfish.jersey.process.Inflector.apply(java.lang.Object), 
    parameters=[Parameter [type=interface javax.ws.rs.container.ContainerRequestContext, source=null, defaultValue=null]], 
    responseType=class javax.ws.rs.core.Response}, nameBindings=[]},
ResourceMethod{
    httpMethod=OPTIONS, consumedTypes=[*/*], producedTypes=[*/*], 
    suspended=false, suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, 
    invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class org.glassfish.jersey.server.wadl.processor.OptionsMethodProcessor$GenericOptionsInflector,
    handlerConstructors=[]}, definitionMethod=public abstract java.lang.Object org.glassfish.jersey.process.Inflector.apply(java.lang.Object), 
    parameters=[Parameter [type=interface javax.ws.rs.container.ContainerRequestContext, source=null, defaultValue=null]], responseType=class javax.ws.rs.core.Response}, 
    nameBindings=[]}]

但现在您可以使用名称绑定信息通过创建另一个过滤器来自行处理预检请求:

@Provider
@Priority(1)
public class CrossOriginResponseFilter implements ContainerRequestFilter {
    Resource res = ((ContainerRequest)requestContext)
        .getUriInfo().getMatchedResourceMethod().getParent();

    if (res.getResourceMethods().get(0).getNameBindings().contains(CrossOrigin.class)) {
        // handlePreflightRequest and abort: requestContext.abortWith(builder.build());
    }
}

有趣的是,提取的资源res将只包含与实际请求URI和方法以及自动生成的OPTIONS处理程序匹配的相关资源方法,如上所述,在运行时表示形式中资源方法。示例资源实际上还有其他方法,POST和GET。因此,您可以在此处使用.get(0)来访问所需信息。

但是请注意!我没有检查在任何情况下是否为真,或者只是在您使用单独的路径注释资源方法时。所以也许在我的简单版本中有更多的匹配工作要做。

我自己发现这个解决方案非常难看,最终得到了一个非特定于方法的过滤器,但只是处理对任何资源的所有请求(类似于人员here的解决方案)。但它应该是一个问题的答案,如何在处理OPTIONS请求时找出实际的(GET / POST)资源方法"。