【问题标题】:Using Jersey/JAX-RS should a prefix match on a Resource Class prevent Subresources on another Resource matching?使用 Jersey/JAX-RS 资源类上的前缀匹配是否应该阻止另一个资源上的子资源匹配?
【发布时间】:2017-02-24 04:04:25
【问题描述】:

我正在尝试创建一个资源类,以处理各种其他前缀下的类似功能,即“/bar/{id}/foo”“/fizz/{id}/foo”,因为以这种方式对方法进行分组对我来说更有意义。

所以我有一个 Resource 类,它本质上是:

@Path("/")
public class FooResource {
    @GET
    @Path("/foo")
    public String foo()
    {
        return "Hello Foo";
    }


    @GET
    @Path("/bar/stuff/foo")
    public String bar()
    {
        return ("Hello nested foo.");
    }
}

当它在 Dropwizard 应用程序中自行注册时,它工作得很好(即按预期给我响应)。但是,如果另一个 Resource 类注册了一个公共前缀,例如

@Path("/bar")
public class BarResource
{
    @GET
    @Path("/{id}")
    public String bar(@PathParam("id") String id)
    {
        return "Hello:" + id;
    }
}

然后 FooResource 中的子资源永远不会匹配导致 404。然而,在这两种情况下,Dropwizard 都将 FooResource 子资源列为找到的路径。

GET     /bar/stuff/foo (FooResource)
GET     /bar/{id} (BarResource)
GET     /foo (FooResource)

阅读 JAX-RS 规范的第 3.7.2 节,

JSR-339 Java™ API for RESTful Web Services(“规范”)

版本:2.0

状态:最终发布

发布:2013 年 5 月 22 日

看起来不像它应该的工作方式。由于 bar 资源没有匹配 /bar/stuff/foo 的子资源。我是否遗漏了一些关于它应该如何工作的东西?显然我可以重构我的代码,但我确实更喜欢这种组织方案。

【问题讨论】:

    标签: jersey jax-rs dropwizard


    【解决方案1】:

    我确实在混淆时误读了规范中的算法(JaxRS 规范的第 3.7.2 节)。显示我哪里出错的算法部分的摘要是:

    1. 为资源找到最佳匹配(或匹配)。仅考虑这些资源类。 “根据定义,C' 中的所有根资源类都必须使用相同的 URI 路径模板模变量名称进行注释。”
    2. 如果在 C' 中找不到匹配项,则抛出 404。

    我原以为它会返回到第 1 步并找到下一个最佳匹配。

    最接近我能够以这种方式组织代码的方式是为每个子路径拥有一个单独的资源以获得所需的优先级。

    【讨论】:

      【解决方案2】:

      建立在你自己的答案和陈述之上:

      看起来我最接近于组织我的代码 这种方式是为每个子路径获取一个单独的资源 所需的优先级。

      我想添加子资源定位器作为完整性选项。你可以这样组织你的代码:

      public class Application extends io.dropwizard.Application<Configuration>{
      
          @Override
          public void run(Configuration configuration, Environment environment) throws Exception {
              environment.jersey().register(SubResourceTest.class);
          }
      
          public static void main(String[] args) throws Exception {
              new Application().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
          }
      
          public static class MyResourceBean {
              String test;
          }
      
          @Path("/bar")
          public static class SubResourceTest {
      
              @Path("bar")
              public SubR test() {
                  return new SubR();
              }
          }
      
          @Path("")
          public static class SubR {
      
              @GET
              @Path("foo/stuff")
              public String fooStuff() {
                  return "fooStuff";
              }
      
              @GET
              @Path("{id}")
              public String generic(@PathParam("id") String id) {
                  return id;
              }
      
          }
      }
      

      现在SubResourceTest 返回一个子资源,为您想要的两个路径提供服务。

      或者,为了让它更加古怪 :),如果您绝对必须拆分资源类,这个资源示例也适用于我(并不是说这很漂亮,只是有点玩弄):

      @Path("/bar")
          public static class SubResourceTest {
      
              @Path("bar/{string}")
              public Object test(@PathParam("string") String s) {
                  if(s.equals("foo")) {
                      return new SubRStuff();
                  }
                  return new SubRGeneric(s);
              }
          }
      
          public static class SubRStuff {
      
              @GET
              @Path("stuff")
              public String fooStuff() {
                  return "fooStuff";
              }
      
          }
      
          public static class SubRGeneric {
      
              private String id;
      
              public SubRGeneric(String id) {
                  this.id = id;
              }
      
              @GET
              public String generic() {
                  return id;
              }
          }
      

      本质上,bar 资源以编程方式检查路径(是foo?),然后返回处理问题的适当子资源。如果它是通用的,它只是传递字符串。这对我有用:

      artur@pandaadb:~$ curl localhost:9085/api/bar/bar/HelloWorld
      HelloWorld
      artur@pandaadb:~$ curl localhost:9085/api/bar/bar/foo/stuff
      fooStuff
      curl localhost:9085/api/bar/bar/myNameIsArtur
      myNameIsArtur
      

      显然这可能不是您想要的,但它是一种替代方案,也许它适合您的用例:)

      【讨论】:

        【解决方案3】:

        因为我误解了你的问题,我又给了它一次调试。我阅读了您指出的相关部分。首先,我将尝试概述应该发生的事情,然后是球衣内部发生的事情:

        1. 类级别的所有资源都会获得一个用于匹配路径的正则表达式,这对您来说意味着:

        @Path("/") 变为 (/.*)?

        @Path("bar") 变为 /bar(/.*)?

        3.7.3 中描述了如何执行路径。我知道这些是正确的,因为我是从调试器中取出它们的 :)

        这就是我们真正需要看看这里发生了什么。现在我们有了正则表达式,JSR 中描述的算法在 3.7.2.1.(e) 中规定了这种行为:

        使用每个成员中的文字字符数对 E 进行排序 主键(降序),捕获组的数量作为 辅助键(降序)和捕获组的数量 使用非默认正则表达式(即不是'([^/]+?)')作为 第三键(降序)

        文字字符在哪里:

        这里,文字字符是指那些不是由模板产生的 变量替换。

        现在,查看PathMatchingRouter,我们可以发现在apply 方法中,我们正在逐步检查可能的Routes 并选择第一个匹配的。这也是您在此处找到的描述:2.7.2.1.(f)

        将 Rmatch 设置为 E 的第一个成员,并将 U 设置为 当与 U 匹配时,Rmatch 的最终捕获组。令 C 0 为 类 Z 的集合,使得 R(TZ) = Rmatch。根据定义,所有根 C 0 中的资源类必须使用相同的 URI 路径进行注释 模板模变量名称。

        问题是,正如您正确指出的那样:

        根据定义,所有根 C 0 中的资源类必须使用相同的 URI 路径进行注释 模板模变量名称。

        我们通过两个不同的根路径打破了这一点。这自然会始终匹配第一个,因此只能匹配一个。

        问题(从我的角度来看):我们是否没有匹配错误的资源?从JSR来看,我认为匹配的资源应该是反过来排序的,每次都匹配你的/bar/stuff/foo,因为根资源路径比另一个短。 (完全不确定)

        我已经尝试过使用调试器并取消匹配第一个(较短的)资源,这有我指出的结果,每次都匹配另一个资源。

        但是,简而言之,这似乎是正确的(除了上面的问题)

        泽西岛发生了什么(作为附加信息):

        Jersey 的资源模型将资源和方法分解为路由阶段。

        Stage1 -> 匹配所有路由与路径

        Stage2 -> 基于 Stage1 匹配所有可以匹配 Stage1 的方法

        这意味着 Stage1 不是匹配的资源类,而只是一个高级路由阶段,作为路由子级,它具有所有可能匹配根的路径(这就是为什么如果类级别变量相同)。

        如果您想自己调试(这实际上也很有趣),您可以查看这些内部类(应用中的断点):

        • MatchResultInitializerRouter
        • SubResourceLocatorRouter
        • 方法选择路由器
        • 路径匹配路由器

        我也发现这是附加信息:

        https://abhirockzz.wordpress.com/2015/03/02/quick-peek-at-jax-rs-request-to-method-matching/

        为了完整起见,这里是有问题的 JSR:

        http://download.oracle.com/otn-pub/jcp/jaxrs-2_0_rev_A-mrel-eval-spec/jsr339-jaxrs-2.0-final-spec.pdf

        我希望这能更详细地回答(有点自我回答的)问题。再次,我很抱歉一开始就弄错了问题。

        【讨论】:

        • 我重读了算法,发现了我的错误,但你的解释似乎不太正确。可以匹配两个资源,但前提是它们“使用相同的 URI 路径模板模变量名称进行注释”,而不是从算法的第 1 步移至下一个资源,而是在 2(e) 处终止
        • @cacsar 感谢您指出这一点。我没有时间一直调试,但认为它可能会有所帮助:) 很高兴你想通了。我会在星期一尝试纠正我的答案:)
        • 你有来源吗,我也在查找一些信息,但我读到你的声明和我读到的一样:You can bypass that by making both resources sub-resources. That means, move the path annotation from the class level to the method level and it should work. 通过在同一路径上拥有资源,它们成为根资源(如在一个定义为根的对象中,可以在“/bar”上匹配),而两种资源上的所有方法都成为相对于根的子资源。但我可能错了
        • 正如我在问题中提到的,它是 JSR-339。我们所拥有的差异可能只是阅读方式的差异。我一直都知道如何 让它工作,但不知道它为什么不工作的why。关于您的回答,我仍然认为可以匹配和搜索多个“根资源类”(规范术语)。
        • @cacsar 你是对的。我误读了这个问题。我又试了一次,但你自己已经回答了这个问题:)
        【解决方案4】:
        @Path("/")
        public class FooResource {
            @GET
            @Path("/foo")
            public String foo()
            {
                return "Hello Foo";
            }
        
        
            @GET
            @Path("/bar/stuff/foo")
            public String bar()
            {
                return ("Hello nested foo.");
            }
        }
        
        @Path("/")
        public class BarResource
        {
            @GET
            @Path("/bar/{id}")
            public String bar(@PathParam("id") String id)
            {
                return "Hello:" + id;
            }
        }
        

        如果你像这样改变你的代码,它会像我尝试的那样工作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-12-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-21
          • 2017-04-25
          • 1970-01-01
          相关资源
          最近更新 更多