REST API最佳实践:参数应该放在哪里?

346

REST API可以以至少两种方式具有参数:

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

这里有什么最佳实践吗?何时使用1和何时使用2是否有任何一般性指导方针?

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

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

14个回答

253

如果有文件记录的最佳实践,我还没有找到。然而,以下是我在确定URL参数放置位置时使用的一些准则:

可选参数通常更容易放在查询字符串中。

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

但是,如果要返回空列表,则建议在未找到参数时使用查询字符串参数。例如:/contacts?name=dave

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

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

有关URI的官方规则可以在此RFC规范找到。 还有另一个非常有用的RFC规范在这里定义了URI参数化的规则。


5
官方规则URI和草案规范非常有用和有趣! :-) - KajMagnus
1
404错误测试对我很有帮助,可以避免将信息放入路径中,而应该放在查询参数、头部或请求体中。感谢指出这一点! - Kevin Condon

152
晚了一些,但是我会在之前分享的内容上添加一些额外的见解,即请求中有几种类型的“参数”,你应该考虑到这一点。
  1. 定位器 - 如资源标识符(如 ID 或操作/视图)
  2. 过滤器 - 如提供搜索、排序或缩小结果集的参数。
  3. 状态 - 如会话标识、API 密钥等。
  4. 内容 - 如要存储的数据。

现在让我们看看这些参数可能放置的不同位置。

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

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

类似地,内容只有一个归属地,即在请求体中,可以作为查询字符串或 HTTP 多部分和/或 JSON 内容。这与服务器发送内容时收到的内容一致。所以你不应该做得不礼貌。

定位器,如“id=5”或“action=refresh”或“page=2”可以作为 URL 路径,例如mysite.com/article/5/page=2,其中你部分知道每个部分应该意味着什么(基本的如文章和 5 显然意味着获取 ID 为 5 的文章数据),而其他参数则指定为 URI 的一部分。它们可以采用page=2的形式,或者如果你知道在 URI 的某个点之后,“文件夹”是成对的键值,则采用page/2的形式。

筛选器始终放在查询字符串中,因为尽管它们是找到正确数据的一部分,但它们只是返回定位器单独返回的子集或修改内容。在 mysite.com/article/?query=Obama 中搜索(子集)是一个筛选器,/article/5?order=backwards 也是 (修改)。要考虑它的作用,而不仅仅是它的名称!

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

记住,缩小“资源集”是筛选。在资源内部定位具体内容是定位... 显然。子集筛选可能会返回任意数量的结果(甚至可能是0)。定位将总是找到具体的某个实例(如果存在的话)。修改筛选将返回与定位器相同的数据,除非进行了修改(如果允许这样的修改)。

希望这能帮助那些对如何放置内容感到困惑的人们!


2
为什么id不是一个过滤器呢?它返回资源的子集。 - Jonathan.
13
@Jonathan,它返回特定的资源,即文章编号5。筛选器始终是在资源集合中缩小搜索范围的一种方式。如果您只想要特定的资源,则应该有指定的方法来获取它。筛选意味着您可以返回多个资源。ID不是筛选器,而是明确的单个资源。如果您有一系列ID,那么它将成为筛选器,即使该范围仅包括一个ID。如果筛选器还包括资源类型,则会返回所有ID为5的资源,而不仅仅是文章。 - Tor Valamo
1
页面是一个比较困难的概念,因为它既可以作为资源(页面集合)的筛选器,又可以作为该集合内的具体资源。我总是通过定位器请求文章页面,而不是使用筛选器。然而,页面可以作为某些东西的列表的筛选器,例如用户列表。但是,页面本质上是一个分界符,也就是“从第(page-1)*perpage项开始显示perpage项”。在这种情况下将其用作筛选器是正确的,但出于不同的原因。技术上讲,称之为“页面”是错误的。更语义化正确的叫法应该是“从”或“startAt”。 - Tor Valamo
1
“页面”这个词的语义意义是它是一个不变的特定资源,它来自于实体印刷物。如果我们从未有过书籍或印刷品,“页面”就不会真正成为一个词。如果您有一个动态的项目列表,分成“页面”,您应该提供一个特定的起始点,无论是数字、字母或甚至是特定项目,以及一个“每页多少”筛选器。如果我想引用您列表中的某些内容,我需要具体的信息。此外,我不想去第5页,只发现您现在已经将内部的“perpage”更改为50而不是20。 - Tor Valamo
1
如果我想要的话,我也应该能够一次检索1000个项目。毕竟这是一个API,所以计算机会去完成这项工作。强制我一次只能加载20个是浪费时间和带宽。因此,总结一下:如果页面是静态的,那么“page”就是定位符;如果是动态的,你应该使用“from”和“size”或类似的筛选器,而不是“page”过滤器。 - Tor Valamo
显示剩余4条评论

21

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

我采用以下方法:

  • url路径元素:资源及其路径元素形成目录遍历和子资源(例如/items/{id},/users/items)。当不确定时,请询问同事,如果他们认为是遍历并且他们认为在“另一个目录”中,那么路径元素很可能是正确的选择。
  • url参数:当实际上没有遍历时(带有多个查询参数的搜索资源是一个非常好的例子)

1
实际上,URI 应该具有相当明确的规则,并且在将它们应用于 RESTful URI 方面几乎没有歧义。 - DanMan

18

在我看来,参数最好作为查询参数。 URL 用于标识资源,而添加的查询参数用于指定您想要的资源部分,任何状态资源应该具有的等等。


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

17
根据REST实现,
1)路径变量用于直接操作资源,例如联系人或歌曲 例如..
GET等/api/resource/{songid} 或
GET等/api/resource/{contactid} 将返回相应的数据。
2)查询参数/参数用于间接资源,例如歌曲的元数据 例如.., GET /api/resource/{songid}?metadata=genres 它将返回该特定歌曲的流派数据。

5
实际上,并没有一种REST“标准”。根据维基百科(http://en.wikipedia.org/wiki/REST#RESTful_web_APIs)的说法:与基于SOAP的Web服务不同,RESTful Web API没有“官方”标准。这是因为REST是一种架构风格,而SOAP是一种协议。虽然REST不是一个标准,但像HTTP、URI、XML等标准可以用于RESTful实现,比如Web。 - DavidRR
我不喜欢第二种方法。我更倾向于使用/api/genres?songid=123或/api/songs/{song-id}/genres。 - Bart Calixto
1
@Bart,Satish提到的是路径中的变量,这本质上就是你所说的首选项...但是,如果genres实际上是元数据,而不是歌曲实体/资源的字段...那么我可以看到在它上面使用查询字符串更有意义。 - Brett Caswell
@BrettCaswell明白了!感谢您指出。非常感激! - Bart Calixto

16

对于提供的宇宙资源定位器,需要“打包”并使用POST方式提交您的数据到“上下文”,这意味着为了定位器而进行第一步。

请注意#2的限制。我更喜欢针对#1使用POST。

注:限制已在以下讨论中讨论:

POST在是否存在POST参数内容的最大限制?中讨论。

GET在GET请求的长度是否有限制?_GET中URL参数的最大大小中讨论。

p.s. 这些限制基于客户端(浏览器)和服务器(配置)的能力。


附加功能:机智路由可以有版本(通过标头区分),因此提供了进化的功能,无需更改消耗您编写的 RESTful(API)代码的代码,如 restify -> 寻找版本化路由。 - dgm

5
根据URI标准,路径用于层次参数,查询用于非层次参数。当然,什么对你来说是层次的可能非常主观。
在多个URI分配给同一资源的情况下,我喜欢将用于标识的参数放入路径中,将用于构建表示的参数放入查询中。(对我来说,这样更容易路由。)
例如:
- /users/123 和 /users/123?fields="name, age" - /users 和 /users?name="John"&age=30
对于map reduce,我喜欢使用以下方法:
- /users?name="John"&age=30 - /users/name:John/age:30
因此,如何构造URI取决于您(和您的服务器端路由器)。
注意:这些参数是查询参数。因此,您实际上正在定义一个简单的查询语言。对于包含 and、or、greater than 等运算符的复杂查询,建议使用已有的查询语言。URI 模板 的功能非常有限...

4
这是我的意见。
查询参数被用作请求的元数据。它们作为筛选器或修改器来调用现有资源。例如:
/calendar/2014-08-08/events
应该返回当天的日历事件。
如果您想要特定类别的事件,
/calendar/2014-08-08/events?category=appointments
或者如果您需要超过 30 分钟的事件,
/calendar/2014-08-08/events?duration=30
一个检验标准是检查是否可以在没有查询参数的情况下仍然提供服务。

4
作为客户端的程序员,我更喜欢使用查询参数。对于我来说,这种方式将URL路径与参数分开,增加了清晰度,并提供了更好的可扩展性。它还使我能够在URL / URI构建和参数构建之间拥有单独的逻辑。
我很喜欢曼努埃尔·阿尔达纳所说的另一种选项,如果涉及到某种树形结构,那么我可以看到用户特定部分被像那样分离出来。

4

没有硬性规定,但从纯概念角度来看,我喜欢使用的经验法则可以简要地总结如下:URI路径(根据定义)表示资源,查询参数本质上是对该资源的修改器。到目前为止,这可能并没有帮助...使用REST API时,您可以使用主要方法GETPUTDELETE来操作单个资源。因此,是否应将某些内容表示为路径还是作为参数可以归结为这些方法是否适用于所讨论的表示形式。您是否可以合理地在该路径上PUT某些内容,并且这样做在语义上是否合理?当然,您可以在几乎任何地方PUT一些内容并弯曲后端以处理它,但您应该PUT代表实际资源而不是其无需上下文化版本的内容。对于集合,也可以使用POST完成相同的操作。如果您想要添加到特定集合中,那么哪个URL最合适用于POST呢。

这仍然存在一些灰色地带,因为有些路径可能指向父资源的子级,这在某种程度上是任意的,并取决于它们的用途。唯一的硬性规定是应使用查询参数执行任何类型的传递性表示,因为它没有底层资源。
针对原始问题中给出的现实世界示例(Twitter的API),这些参数表示过渡性查询,该查询会对资源的状态进行过滤(而不是层次结构)。在那个特定的例子中,将在这些约束条件所代表的集合中添加内容完全不合理,而且进一步的查询也无法表示为对象图的术语中有任何意义的路径。
采用这种资源导向的视角可以轻松地直接映射到您的领域模型的对象图,并驱动API的逻辑,使一切变得非常干净,在理解清晰后以相当自我记录的方式工作。通过远离使用传统URL路由映射到通常不适合的数据模型(即RDBMS)的系统,这个概念也可以更清晰地阐述。Apache Sling肯定是一个很好的开始。像Zope这样的系统中的对象遍历分派概念也提供了更清晰的类比。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接