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

Jor*_*orn 5 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*/);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Fen*_*cer 4

恐怕您想要完成的事情实际上不可能在不更改 Jersey 本身的情况下使用当前版本以良好的方式实现。

无论如何,我也不确定@Provider根据规范规范,使用特定于请求的过滤器是否是正确的方法。但我能说谁呢?我实际上是自己做的。当然,也可以将过滤器注册到ResourceConfig. 一般来说,我建议看一下@NameBinding,但对于这种情况,名称绑定 Jersey-style 是不够的。您不必@NameBinding自己检查注释,因为 Jersey 已经为您完成了这项工作。

不幸的是,再次使用 using @NameBinding,它是专门针对这种情况引入的,存在“自动生成的”选项处理程序的问题。我做了相当多的挖掘(一些最相关的类/方法是OptionsMethodProcessor、、和) WadlModelProcessor,但没有找到一种方法来充分挂钩该过程。以下内容足以处理 CORS:ResourceModelConfigurator#initServerRuntime ApplicationHandler#initialize

@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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

[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=[]}]
Run Code Online (Sandbox Code Playgroud)

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

@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());
    }
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,提取的 Resourceres将仅包含与实际请求 URI 和方法相匹配的相关资源方法以及自动生成的 OPTIONS 处理程序,正如您在上面的资源方法的运行时表示中看到的那样。示例资源实际上还有更多方法,POST 和 GET。因此,您可以使用.get(0)此处访问所需的信息。

但要小心!我没有检查在任何情况下这是否正确,或者只是当您使用单独的路径注释资源方法时。因此,与我这里的简单版本相比,也许还有更多的匹配工作要做。

我自己发现这个解决方案非常丑陋,最终得到的过滤器不是特定于方法的,而是简单地处理对任何资源的所有请求(类似于这里的解决方案)。但这应该是如何“在处理 OPTIONS 请求时找出实际的(GET/POST)资源方法”这个问题的答案。