@Path中的正则表达式只匹配了两个指定的路由中的一个,导致了404错误。

huangapple go评论77阅读模式
英文:

Regex in @Path matches only only 1 of 2 two routes specified, resulting in 404

问题

以下是Dropwizard日志中记录的配置资源及其路径:

INFO  [07:07:13.741] i.d.jersey.DropwizardResourceConfig: 对于配置的资源找到以下路径:

    DELETE  /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
    GET     /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
    POST    /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
    GET     /apps/affiliate/v1/generate-url (aff.affiliate.http.AffiliateEndpoint)
    GET     /apps/affiliate/v1/redirect-search-url (aff.affiliate.http.AffiliateEndpoint)
    GET     /openapi.{type:json|yaml} (io.swagger.v3.jaxrs2.integration.resources.OpenApiResource)
    GET     /{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect} (aff.affiliate.http.RedirectEndpoint)

问题出在最后一个路径,它被指定为正则表达式。

我期望它应该对来自 /apps/affiliate/v1/redirect/api/affiliate/v1/redirect 的请求都触发。

然而,访问 /apps/affiliate/v1/redirect 结果是 404,但访问 api/affiliate/v1/redirect 则返回 200。如何让我的资源响应这两个路径呢?

代码很难提供,但以下是基本的框架(值得一提的是,所有方法/接口都有效,我只是在让其中一个方法响应正则表达式时遇到了问题)。

// AffiliateURLTemplatesInternalAPIEndpoint.kt
@Path("/apps/affiliate/internal/v1/templates")
@Produces(MediaType.APPLICATION_JSON)
public class AffiliateURLTemplatesInternalAPIEndpoint() : DropwizardResource() {
    @GET
    @Path("/")
    public fun methodA()

    @POST
    @Path("/")
    public fun methodB()

    @DELETE
    @Path("/")
    public fun methodC()
}
// AffiliateEndpoint.kt
@Path("/apps/affiliate/v1")
class AffiliateEndpoint() : DropwizardResource() {
    @GET
    @Path("generate-url")
    fun methodA()

    @GET
    @Path("redirect-search-url")
    fun methodB()
// RedirectEndpoint.kt
@Path("/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}")
@Produces(MediaType.APPLICATION_JSON)
class RedirectEndpoint() : DropwizardResource() {
    @GET
    fun methodA()
英文:

Here is what dropwizard logs to the console in terms configured resources and their paths:

INFO  [07:07:13.741] i.d.jersey.DropwizardResourceConfig: The following paths were found for the configured resources:

    DELETE  /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
    GET     /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
    POST    /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
    GET     /apps/affiliate/v1/generate-url (aff.affiliate.http.AffiliateEndpoint)
    GET     /apps/affiliate/v1/redirect-search-url (aff.affiliate.http.AffiliateEndpoint)
    GET     /openapi.{type:json|yaml} (io.swagger.v3.jaxrs2.integration.resources.OpenApiResource)
    GET     /{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect} (aff.affiliate.http.RedirectEndpoint)

The problem is with the last path, specified as a regular expression.

My expectation is that it should trigger for incoming requests to both /apps/affiliate/v1/redirect and /api/affiliate/v1/redirect.

However, visiting /apps/affiliate/v1/redirect results in a 404, but visiting api/affiliate/v1/redirect results in a 200. How can I get my resource to respond to either of those paths?

The code is hard to provide but this is essentially the scaffolding (fwiw, all methods work/api works, I'm just having trouble having one of the methods respond to the regex (my actual problem)).

// AffiliateURLTemplatesInternalAPIEndpoint.kt
@Path("/apps/affiliate/internal/v1/templates")
@Produces(MediaType.APPLICATION_JSON)
public class AffiliateURLTemplatesInternalAPIEndpoint() : DropwizardResource() {
    @GET
    @Path("/")
    public fun methodA()

    @POST
    @Path("/")
    public fun methodB()

    @DELETE
    @Path("/")
    public fun methodC()
}
// AffiliateEndpoint.kt
@Path("/apps/affiliate/v1")
class AffiliateEndpoint() : DropwizardResource() {
    @GET
    @Path("generate-url")
    fun methodA()

    @GET
    @Path("redirect-search-url")
    fun methodB()
// RedirectEndpoint.kt
@Path("/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}")
@Produces(MediaType.APPLICATION_JSON)
class RedirectEndpoint() : DropwizardResource() {
    @GET
    fun methodA()

答案1

得分: 1

404 确实被正确返回。为什么?是因为 JAX-RS 的 URL 匹配算法。直到 @Paul Samsotha 要求我粘贴我的代码,我才最终意识到 404 的原因。Dropwizard/Jersey 输出显示了它找到的所有路由,但遗漏了关于代码中路径结构的关键上下文。由于 JAX-RS 实现了路由匹配排序和优先级规则,代码结构对于确定哪些路由将被触发至关重要。所以在这种情况下,有用的输出最终大部分是误导性的。

如果你敢尝试,可以阅读 JAX-RS Spec 3.0 的第 3.7 节 - 匹配请求到资源方法,其中有答案。

另外,Bill Burke 的《RESTful Java with JAX-RS 2.0》第 4 章对路由匹配行为提供了很好的洞察。不幸的是,它没有澄清一个重要的区别(我陷入的确切情况),即当应用 JAX-RS URL 匹配规则时,你不能简单地合并资源和其方法的路径(就像输出中的情况)。实际上,我查阅了大量关于 JAX-RS 的资料,但没有一篇提到这个实际区别。

相反,你首先要尝试找到一个根资源类的匹配,然后再查看资源方法。如果在根级别或方法级别找不到匹配,你必须返回 404。

尽管如此,我发现它对于理解规范非常有帮助,比规范本身要少令人望而生畏。

现在,来解释一下 404 的实际原因。

Jersey(实现 JAX-RS 规范)首先收集与根资源相关的所有路径:

/apps/affiliate/internal/v1/templates
/apps/affiliate/v1
/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}

然后,它根据规范应用其排序和优先级逻辑(从 Burke 的书中改编而来):

排序的主要键是完整 URI 匹配模式中的文字字符数。排序按降序进行。
如果两个或更多模式在第一个方面排名相似,那么排序的次要键是模式中嵌入的模板表达式数。此排序按降序进行。
最后,如果两个或更多模式在第二个方面排名相似,那么排序的第三个键是非默认模板表达式的数量。默认模板表达式是不定义正则表达式的表达式。

当一个到达 /apps/affiliate/v1/redirect 的 GET 请求到达时,两个模式都匹配,但第一个模式具有最多匹配的文字字符数(18 对 1),因此第一个模式具有优先权。

现在,选择了一个根资源后,它查看根资源的方法,并编译了匹配传入请求的可用路径/HTTP 方法的列表。在方法级别的模式匹配方面,根资源的路径将与资源方法的路径连接起来。

以下模式可供选择:

GET /apps/affiliate/v1/generate-url
GET /apps/affiliate/v1/redirect-search-url

由于请求是对 /apps/affiliate/v1/redirect 的 GET 请求,上述两个路由都不匹配,因此出现了 404。

这完全合理,但我陷入这个兔子洞中,因为我对路由规则和优先级的假设与我的其他路由库的经验不符合实际的 JAX-RS 规范。我希望库会为每个可用的方法维护一个主列表(就像 Dropwizard/Jersey 的初始输出一样),然后每个请求都会在该主列表上运行排序和优先级规则。不幸的是,情况并非如此。

英文:

The 404 is indeed being correctly returned.

Why? JAX-RS’ URL Matching Algorithm.

It was only after @Paul Samsotha asked me to paste my code that I finally realized the reason for the 404. 🤦

The Dropwizard/Jersey output I was relying on shows all the routes it found, but leaves out critical context about how paths have been structured in the code. Due to the way JAX-RS has implemented route matching sorting and precedence roles, code structure is essential in determining which routes will be triggered. So in this case the helpful output ended up being mostly misleading.

Read Section 3.7 - Matching Requests to Resource Methods of JAX-RS Spec 3.0 if you dare, but the answers are there.

Also, Chapter 4 of Bill Burke's RESTful Java with JAX-RS 2.0 gives great insight into route matching behavior. Unfortunately it doesn't go into clarifying an important distinction (the exact situation I got into) which is that you can't simply combine a resource and its methods paths (like the output) when applying JAX-RS url matching rules. Actually, I went through a bunch of JAX-RS write ups and none of them mentioned this actual distinction.

Instead you first try to find a match a root resource class, then look at resource methods. If you don't find a match either at the root or method level, you must return a 404.

Still, I found it to be a great resource at shining light on the spec and is much less intimidating that the spec.

Now to the actual explanation of the 404.

Jersey (which implements the JAX-RS spec), first collects all the paths associated with root resources:

/apps/affiliate/internal/v1/templates
/apps/affiliate/v1
/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}

It then applies its sorting and precedence logic according the spec (paraphrased from Burke's book):

> The primary key of the sort is the number of literal characters in the full URI matching pattern. The sort is in descending order.
>
> If two or more patterns rank similar in the first, then the secondary key of the sort is the number of template expressions embedded within the pattern. This sort is in descending order.
>
> Finally, if two or more patterns rank similar in the second, then the tertiary key of the sort is the number of non-default template expressions. A default template expression is one that does not define a regular expression.

When an incoming GET request to /apps/affiliate/v1/redirect arrives, both

/apps/affiliate/v1
/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}

match, but the first pattern takes precedence because it has the greatest number of literal characters that match (18 vs 1).

Now that a root resource is selected, it looks at root resource's methods and compiles a list of available paths/http methods that match the incoming request. A bit of an extra detail, but for pattern matching purposes at the method level, the root resource's path will be concatenated to the resource method's path.

The following patterns are available to select from:

GET /apps/affiliate/v1/generate-url
GET /apps/affiliate/v1/redirect-search-url")

Since the request was a GET to /apps/affiliate/v1/redirect neither of the above routes match. Hence my 404 :(.

It makes complete sense, but I got into this rabbit hole because my assumptions about routing rules and precedence from experience working with other routing libraries did not align with the actual JAX-RS specs. I expected the library to have a master list for each and every method available (much like the initial output from Dropwizard/Jersey) and for each request to run through sorting and precedence rules on that master list. Alas, that is not the case.

huangapple
  • 本文由 发表于 2023年2月18日 00:23:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/75486823.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定