ChatGPT解决这个技术问题 Extra ChatGPT

REST API 最佳实践:将参数放在哪里? [关闭]

关闭。这个问题需要更加集中。它目前不接受答案。 7年前关闭。锁定。这个问题及其答案被锁定,因为这个问题离题但具有历史意义。它目前不接受新的答案或交互。

REST API 至少可以通过两种方式获取参数:

作为 URL 路径的一部分(即 /api/resource/parametervalue )作为查询参数(即 /api/resource?parameter=value )

这里的最佳做法是什么?何时使用 1 和何时使用 2 是否有任何一般准则?

现实世界的例子:Twitter 使用查询参数来指定时间间隔。 (http://api.twitter.com/1/statuses/home_timeline.json?since_id=12345&max_id=54321)

将这些参数放在 URL 路径中会被认为是更好的设计吗?


C
Community

如果有记录的最佳实践,我还没有找到它们。但是,在确定将参数放在 url 中的何处时,我使用了一些准则:

可选参数往往更容易放入查询字符串中。

如果您想在参数值与现有资源不对应时返回 404 错误,那么我倾向于使用路径段参数。例如 /customer/232,其中 232 不是有效的客户 ID。

但是,如果您想返回一个空列表,那么当找不到参数时,我建议使用查询字符串参数。例如/contacts?name=dave

如果参数影响 URI 空间的整个子树,则使用路径段。例如语言参数 /en/document/foo.txt/document/foo.txt?language=en

我更喜欢将唯一标识符放在路径段中而不是查询参数中。

URI 的官方规则可在此 RFC 规范 here 中找到。还有另一个非常有用的 RFC 规范 here,它定义了参数化 URI 的规则。


官方规则 URI 和草案 sepc 非常有用且有趣! :-)
404 错误测试帮助我避免将信息放入属于查询参数、标头或请求正文的路径中。感谢您指出这一点!
T
Tor Valamo

迟到的答案,但我将对已共享的内容添加一些额外的见解,即请求有几种类型的“参数”,您应该考虑到这一点。

定位器 - 例如资源标识符,如 ID 或操作/视图过滤器 - 例如,提供搜索、排序或缩小结果集的参数。状态 - 例如会话标识、api 密钥、whatevs。内容 - 例如要存储的数据。

现在让我们看看这些参数可以去哪里。

请求标头和 cookie URL 查询字符串(“GET”变量) URL 路径 正文查询字符串/多部分(“POST”变量)

通常,您希望在标头或 cookie 中设置状态,具体取决于状态信息的类型。我想我们都可以同意这一点。如果需要,请使用自定义 http 标头 (X-My-Header)。

类似地,Content 只有一个归属地,即在请求正文中,可以是查询字符串,也可以是 http 多部分和/或 JSON 内容。这与您在服务器向您发送内容时收到的内容一致。所以你不应该粗鲁并且做不同的事情。

诸如“id=5”或“action=refresh”或“page=2”之类的定位符作为 URL 路径是有意义的,例如 mysite.com/article/5/page=2,其中您部分地知道每个部分的含义(基础知识如因为 article 和 5 显然意味着让我获取 ID 为 5 的 article 类型的数据),并且附加参数被指定为 URI 的一部分。它们可以采用 page=2page/2 的形式,如果您知道在 URI 中的某个点之后“文件夹”是成对的键值对。

过滤器总是放在查询字符串中,因为虽然它们是查找正确数据的一部分,但它们只是为了返回一个子集或修改定位器单独返回的内容。 mysite.com/article/?query=Obama(子集)中的搜索是一个过滤器,/article/5?order=backwards(修改)也是如此。想想它的作用,而不仅仅是它的名字!

如果“视图”决定了输出格式,那么它就是一个过滤器 (mysite.com/article/5?view=pdf),因为它返回的是对找到的资源的修改,而不是我们想要的资源。如果它决定我们可以看到文章的哪个特定部分 (mysite.com/article/5/view=summary),那么它就是一个定位器。

请记住,缩小一组资源的范围就是过滤。在资源中定位特定的东西正在定位......呃。子集过滤可以返回任意数量的结果(甚至 0)。定位将始终找到某物的特定实例(如果存在)。修改过滤将返回与定位器相同的数据,除了修改(如果允许这样的修改)。

希望这有助于给人们一些灵感,如果他们迷失在哪里放东西!


那么为什么 id 不是过滤器呢?它返回资源的子集
@乔纳森。不,它返回特定资源,即文章编号 5。过滤器始终是缩小资源集合中搜索范围的一种方法。如果您只想要那个特定的资源,那么应该有一种指定的方式来获得它。过滤意味着您有可能返回多个资源。 ID 不是过滤器,它是明确的单一资源。如果您有一系列 ID,那么它将是一个过滤器,即使该范围仅包含一个 ID。如果过滤器还包括资源类型,它将返回 ID 为 5 的所有资源,而不仅仅是文章。
@Jonathan .:就像 DarrelMiller 提到的那样,如果 id 未知,您会期望对 object/id 的请求返回 404,而您会期望 object?id=id 返回并清空列表。另外,我认为任何类型的过滤/子集都应该返回一个列表。
页面是一个困难的页面,因为正如您所说,它可以是资源(页面集合)的过滤器,但同时它是该集合中的特定资源。我总是按定位器请求文章页面,而不是过滤器。但是,该页面可以是某项列表的过滤器,例如用户列表。但是页面本质上是一个分隔符,也就是“从第 (page-1)*perpage 项开始并显示 perpage 项”。使用它作为过滤器是正确的,但出于不同的原因。将其称为“页面”在技术上是错误的。在语义上更正确的是称它为“from”或“startAt”
(续)“页面”的语义是,它是一个不会改变的特定资源。它来自物理打印。如果我们从来没有书籍或印刷品,“页面”就不会是一个词。如果你有一个动态的项目列表,分成“页面”,你应该提供一个特定的起点,无论是数字、字母还是特定项目,以及“每页有多少”过滤器。如果我想引用您列表中的某些内容,我需要详细信息。此外,我不想只看到第 5 页才意识到您现在已将内部 perpage 更改为 50 而不是 20。
m
manuel aldana

这取决于设计。 REST over HTTP 中的 URI 没有规则(主要是它们是唯一的)。通常涉及品味和直觉的问题......

我采取以下方法:

url path-element:资源及其路径元素形成目录遍历和子资源(例如/items/{id}、/users/items)。当不确定时问你的同事,如果他们认为遍历并且他们认为在“另一个目录”中最有可能的路径元素是正确的选择

url 参数:当真的没有遍历时(具有多个查询参数的搜索资源就是一个很好的例子)


实际上,对于 URI 的外观有非常明确的规则,并且对于如何将它们应用于 RESTful URI 几乎没有歧义。
P
PeterWong

IMO 参数应该更好地作为查询参数。 url 用于标识资源,而添加的查询参数用于指定您想要的资源的哪一部分,资源应该具有的任何状态等。


实际上,路径和查询是结合使用来识别资源的。这已在 RFC 3986 http://labs.apache.org/webarch/uri/rfc/rfc3986.html#query 中阐明
@DarrelMiller 我知道这是一篇旧帖子,但我有兴趣了解更多关于查询参数也用于识别资源的事实。您提供的链接现在已失效。我看过 RFC3986,但我不明白你是如何推断出这个事实的。此外,根据定义,标识符参数不应是可选的,因此似乎不适合使用查询参数进行标识。
@MickaelMarrache 请参阅第 3.4 节中的第一行 tools.ietf.org/html/rfc3986#section-3.4
@DarrelMiller 谢谢!我的问题来自这样一个事实,即中间 HTTP 组件通常不会缓存包含查询字符串的请求的响应。因此,查询参数似乎更适合根据某些标准搜索资源,而不是唯一标识资源。
B
Brett Caswell

根据 REST 实现,

1) 路径变量用于对资源的直接操作,例如联系人或歌曲 ex.. GET 等 /api/resource/{songid} 或 GET 等 /api/resource/{contactid} 将返回相应的数据。

2) 查询 perms/argument 用于间接资源,例如歌曲的元数据,例如,GET /api/resource/{songid}?metadata=genres 它将返回该特定歌曲的流派数据。


实际上没有 REST 标准。根据 Wikipedia与基于 SOAP 的 Web 服务不同,RESTful Web API 没有“官方”标准。[14]这是因为 REST 是一种架构风格,不像 SOAP,它是一种协议。尽管 REST 不是标准,但像 Web 这样的 RESTful 实现可以使用 HTTP、URI、XML 等标准。
我不喜欢 2 方法。我宁愿喜欢 /api/genres?songid=123 或 /api/songs/{song-id}/genres
@Bart,Satish 指的是路径中的变量,这本质上是您作为偏好引用的内容。但是,如果流派实际上是元数据,而不是歌曲实体/资源的字段。那么我可以看到更多的敏感性在它上面使用查询字符串..
@BrettCaswell 明白了!感谢您指出。真的很感激!
C
Community

根据 Universe-resource-locator 提供的“上下文”“打包”并发布您的数据,这意味着对于定位器来说是#1。

注意#2 的限制。我更喜欢 POST 到 #1。

注意:讨论的限制是

Is there a max size for POST parameter content? 中发布

Is there a limit to the length of a GET request?Max size of URL parameters in _GET 中获取

ps 这些限制基于客户端功能(浏览器)和服务器(配置)。


附加组件:诙谐的路由可以有版本(通过标头区分),因此无需更改使用您在 restify 中编写的完整(api)代码的代码即可提供进化的功能 - >寻找版本化的路由
C
Community

根据URI standard,路径用于分层参数,查询用于非分层参数。办公室。对于您来说,等级划分可能是非常主观的。

在将多个 URI 分配给同一资源的情况下,我喜欢将参数(识别所必需的)放入路径中,并将参数(构建表示所必需的)放入查询中。 (对我来说,这种方式更容易路由。)

例如:

/users/123 和 /users/123?fields="name, age"

/users 和 /users?name="John"&age=30

对于 map reduce,我喜欢使用以下方法:

/users?name="约翰"&age=30

/用户/姓名:约翰/年龄:30

因此,如何构建 URI 完全取决于您(和您的服务器端路由器)。

注意:只是提到这些参数是查询参数。所以你真正在做的是定义一个简单的查询语言。通过复杂的查询(包含和、或、大于等运算符),我建议您使用已经存在的查询语言。 URI templates 的功能非常有限...


J
Joe Plante

作为一个经常在客户端工作的程序员,我更喜欢查询参数。此外,对我来说,它将 URL 路径与参数分开,增加了清晰度,并提供了更多的可扩展性。它还允许我在 URL/URI 构建和参数构建器之间有单独的逻辑。

如果涉及某种树,我确实喜欢 manuel aldana 关于另一种选择的说法。我可以看到用户特定的部分像这样被砍掉。


M
Matt Whipple

没有硬性规定,但从我喜欢使用的纯概念角度来看,经验法则可以简要总结如下:URI 路径(根据定义)表示资源,查询参数本质上是该资源的修饰符.到目前为止,这可能没有帮助...使用 REST API,您可以使用 GETPUTDELETE 对单个资源进行操作的主要方法。因此,是否应该在路径中表示某些东西或作为参数表示可以简化为这些方法是否对所讨论的表示有意义。你会合理地PUT在那条路径上的东西吗?这样做在语义上是否合理?您当然可以在任何地方PUT 一些东西并弯曲后端来处理它,但您应该PUT实际资源的表示,而不是它的一些不必要的上下文化版本。对于集合,同样可以使用 POST 完成。如果您想添加到特定集合中,那么 POST 将是一个有意义的 URL。

这仍然留下一些灰色区域,因为一些路径可能指向多少父资源的子资源,这在某种程度上是自由裁量的并且取决于它们的使用。这画出的一条硬线是任何类型的传递表示都应该使用查询参数来完成,因为它没有底层资源。

作为对原始问题(Twitter 的 API)中给出的真实世界示例的响应,参数表示过滤资源状态(而不是层次结构)的传递查询。在那个特定的例子中,添加到由这些约束表示的集合中是完全不合理的,而且该查询将无法表示为在对象图方面有意义的路径。

采用这种面向资源的视角可以很容易地直接映射到您的领域模型的对象图,并将您的 API 逻辑驱动到一个点,即一切都非常干净,并且一旦清晰起来,就可以以一种相当自我记录的方式。通过远离使用映射到通常不合适的数据模型(即 RDBMS)的传统 URL 路由的系统,也可以使这个概念更加清晰。 Apache Sling 肯定是一个很好的起点。像 Zope 这样的系统中的对象遍历调度的概念也提供了更清晰的类比。


J
Jay

这是我的意见。

查询参数用作请求的元数据。它们充当现有资源调用的过滤器或修改器。

例子:

/calendar/2014-08-08/events

应该给出那天的日历事件。

如果您想要特定类别的事件

/calendar/2014-08-08/events?category=appointments

或者如果您需要超过 30 分钟的活动

/calendar/2014-08-08/events?duration=30

一个试金石测试将检查是否仍然可以在没有查询参数的情况下提供请求。


N
NorthIsUp

我通常倾向于#2,作为查询参数(即 /api/resource?parameter=value )。

第三种选择是在正文中实际发布参数=值。

这是因为它更适用于多参数资源,并且对于未来的使用更具可扩展性。

不管你选哪一个,确保只选一个,不要混搭。这导致了一个令人困惑的 API。


D
Dario Fumagalli

这个主题的一个“维度”被忽略了,但它非常重要:有时“最佳实践”必须与我们正在实施或增强 REST 功能的平台相适应。

实际例子:

现在许多 Web 应用程序都实现了 MVC(模型、视图、控制器)架构。他们假设提供了某个标准路径,当这些 Web 应用程序带有“启用 SEO URL”选项时更是如此。

只提一个相当著名的 Web 应用程序:OpenCart 电子商务商店。当管理员启用“SEO URL”时,它希望所述 URL 采用非常标准的 MVC 格式,例如:

http://www.domain.tld/special-offers/list-all?limit=25

在哪里

special-offers 是处理 URL 的 MVC 控制器(显示特价页面)

list-all 是要调用的控制器的操作或函数名称。 (*)

limit=25 是一个选项,表示每页将显示 25 个项目。

(*) list-all 是我为了清楚起见而使用的虚构函数名称。实际上,OpenCart 和大多数 MVC 框架都有一个默认的、隐含的(通常在 URL 中省略)index 函数,当用户想要执行默认操作时会调用该函数。所以真实世界的 URL 将是:

http://www.domain.tld/special-offers?limit=25

使用与上述类似的现在相当标准的应用程序或框架结构,您通常会得到一个为其优化的 Web 服务器,它会为它重写 URL(真正的“非 SEOed URL”是:http://www.domain.tld/index.php?route=special-offers/list-all&limit=25)。

因此,作为开发人员,您将面临处理现有基础架构并调整您的“最佳实践”,除非您是系统管理员,确切地知道如何调整 Apache / NGinx 重写配置(后者可能很讨厌!)等等上。

因此,遵循引用 Web 应用程序的标准,您的 REST API 通常会更好,既要与它保持一致,又要易于/速度(从而节省预算)。

回到上面的实际例子,一个一致的 REST API 应该是带有如下 URL 的东西:

http://www.domain.tld/api/special-offers-list?from=15&limit=25

或(非 SEO URL)

http://www.domain.tld/index.php?route=api/special-offers-list?from=15&limit=25

混合了“路径形成”参数和“查询形成”参数。


D
Daniel Watrous

我看到很多 REST API 不能很好地处理参数。一个经常出现的例子是当 URI 包含个人身份信息时。

http://software.danielwatrous.com/design-principles-for-rest-apis/

我认为一个必然的问题是一个参数根本不应该是一个参数,而是应该移动到请求的 HEADER 或 BODY 。


j
jfcorugedo

这是一个非常有趣的问题。

你可以同时使用它们,关于这个主题没有任何严格的规则,但是使用 URI 路径变量有一些优点:

缓存:互联网上的大多数 Web 缓存服务在包含查询参数时不会缓存 GET 请求。他们这样做是因为有很多 RPC 系统使用 GET 请求来更改服务器中的数据(失败!!Get 必须是一种安全的方法)

但是如果你使用路径变量,所有这些服务都可以缓存你的 GET 请求。

层次结构:路径变量可以表示层次结构:/City/Street/Place

它为用户提供了有关数据结构的更多信息。

但是,如果您的数据没有任何层次关系,您仍然可以使用路径变量,使用逗号或分号:

/城市/经度、纬度

通常,当参数的顺序很重要时使用逗号,当顺序无关紧要时使用分号:

/IconGenerator/红色;蓝色;绿色

除了这些原因之外,在某些情况下使用查询字符串变量很常见:

当您需要浏览器自动将 HTML 表单变量放入 URI 时

当你处理算法时。例如谷歌引擎使用查询字符串:

http://www.google.com/search?q=rest

总而言之,没有任何充分的理由使用其中一种方法,但只要可以,请使用 URI 变量。