REST API 设计参数和查询字符串使用的最佳实践
封面图片由 Edwin Young 在 Flickr 上拍摄。
我们设计 API 时,目标是赋予用户一定程度的控制权,让他们能够掌控我们提供的服务。虽然 HTTP 动词和资源 URL 能够实现基本的交互,但通常情况下,提供一些额外的功能也很重要,否则系统可能会变得过于繁琐,难以使用。
其中一个例子就是分页:如果我们的数据库中有数百万篇文章,我们就无法将每篇文章一次性发送给客户。
实现这一目标的一种方法是参数化。
什么是参数化
一般来说,参数化是对请求的某种配置。
在编程语言中,我们可以请求函数的返回值。如果函数不接受任何参数,我们就无法直接影响该返回值。
API 也是如此,尤其是像 REST API 这样的无状态 API,正如 Roy Fielding 所说的那样:
所有 REST 交互都是无状态的。也就是说,每个请求都包含连接器理解该请求所需的所有信息,与之前的任何请求无关。
HTTP 中有很多方法可以向请求添加参数。查询字符串;POST、PUT 和 PATCH 请求的主体以及标头。每种方法都有各自的用例和规则。
因此,在决定将参数放在哪里之前,我们必须提出一些问题,然后我们必须检查如何将其放在正确的位置。
想要获取所有数据且不受太多限制,最简单的方法就是把所有数据都放在 body 中。我见过很多 API 都是这样写的。每个端点都使用 POST 请求,所有参数都放在 body 中。尤其是那些几十年来不断发展、参数越来越多的老旧 API,不得不这样做,因为大量的数据根本无法放在查询字符串中。
虽然这种情况很常见,但我认为这是 API 设计中的一个特殊情况。如果我们事先提出正确的问题,就能及早避免这种情况。
我们要添加什么样的参数?
我们应该问自己的第一个问题是,我们想要添加什么样的参数?
也许它是一个已经在 HTTP 规范中标准化的标头字段的参数。
有很多标准化字段,有时我们会在其他地方添加这些信息,从而重新发明轮子。我并不是说我们不能用不同的方式去做,例如,GraphQL 在很多地方做了一些从 REST 角度来看似乎很疯狂的事情,而且它仍然有效,但有时使用已有的内容会更简单。
例如,有一个标头允许我们定义响应应具有的Accept
格式或媒体类型JSON
。我们可以使用它来告知 API 我们需要什么XML
。我们也可以用它来指定API 响应的版本。
我们还Cache-Control
可以使用一个标头来阻止 API 向我们发送缓存响应,而no-cache
不是使用查询字符串作为缓存破坏器(?cb=<RANDOM_STRING>
)
授权也可以被视为一个参数,因为根据 API 的授权细节,请求授权和未授权的数据时,服务器可能会做出不同的响应。HTTP为此定义了一个标头。Authorization
检查完所有默认标头字段后,下一步就是评估是否应该为参数创建自定义标头字段,或者将其放入 URL 的查询字符串中。
我们什么时候应该使用查询字符串?
如果我们知道我们要添加的参数不属于默认标头字段并且不敏感,那么我们可以检查查询字符串是否适合它。
历史上,查询字符串的用途,顾名思义,就是查询数据。有一个<isindex>
HTML 元素可以用来向服务器发送一些关键词,服务器会以某种方式返回一个与关键词匹配的页面列表。
后来,查询字符串被重新用于 Web 表单,以通过 GET 请求将数据发送到服务器。
因此,查询字符串的主要用途是过滤,以及两种特殊的过滤情况:搜索和分页。这里就不赘述了,因为我们已经在本文中讨论过了。
但正如 Web 表单的重新利用所示,它也可以用于不同类型的参数。RESTful API 会使用带有正文的 POST 或 PUT 请求将表单数据发送到服务器,因此这不是这里的用例,但仍然可以有其他参数。
一个例子是嵌套表示的参数。默认情况下,我们返回一篇文章的简单表示,当将?withComments
查询字符串添加到端点时,我们会以内联方式返回该文章的评论,因此只需要一个请求。
这样的参数是否应该进入自定义标头或查询字符串主要是开发人员经验的问题。
HTTP规范指出标头字段有点像函数参数,因此它们确实被认为是我们想要使用的参数,但是向 URL 添加查询字符串比为此创建客户标头更快、更明显。
这些字段充当请求修饰符,其语义等同于编程语言方法调用中的参数。
所有端点上保持一致的参数更适合用作标头。例如,身份验证令牌会在每次请求时发送。
高度动态的参数,尤其是仅对少数几个或一个端点有效的参数,应该放在查询字符串中。例如,每个端点的过滤器参数都不同。
额外奖励:数组和 Map 参数
经常出现的一个问题是,如何处理查询字符串中的数组参数?
例如,如果我们有多个想要搜索的名称。
一种解决方案是使用方括号。
/authors?name[]=kay&name[]=xing
但 HTTP 规范规定:
由 Internet 协议文字地址(版本 6[RFC3513] 或更高版本)标识的主机通过将 IP 文字括在方括号(“[”和“]”)中来区分。这是 URI 语法中唯一允许使用方括号字符的地方。
许多 HTTP 服务器和客户端的实现并不关心这个事实,但应该牢记这一点。
提供的另一种解决方案是多次使用一个参数名称:
/authors?name=kay&name=xing
这是一个有效的解决方案,但可能会降低开发人员的体验。客户端通常只使用类似 Map 的数据结构,该结构在添加到 URL 之前会进行简单的字符串转换,这可能会导致后续值被覆盖。在发送请求之前,需要进行更复杂的转换。
另一种方法是用 -character 分隔值,
,这些值在 URL 中允许不经过编码。
/authors?name=kay,xing
对于类似地图的数据结构,我们可以使用.
-character,它也允许未编码。
/articles?age.gt=21&age.lt=40
也可以对整个查询字符串进行 URL 编码,这样它就可以使用我们想要的任何字符或格式。需要注意的是,这也会大大降低开发人员的体验。
什么时候我们不应该使用查询字符串?
查询字符串是我们 URL 的一部分,并且客户端和 API 之间的每个人都可以读取我们的 URL,因此我们不应该将密码等敏感数据放入查询字符串中。
此外,如果我们不认真对待 URL 的设计和长度,开发者的体验也会大打折扣。当然,大多数 HTTP 客户端允许 URL 包含五位数的字符,但调试这种长度的字符串确实不太舒服。
由于任何内容都可以定义为资源,因此有时使用 POST 端点来处理大量参数是合理的。这使我们能够将主体中的所有数据发送到 API。
与其向查询字符串中携带大量参数的资源发送 GET 请求,导致 URL 过长且无法调试,不如将其设计为资源的创建,例如搜索资源。根据 API 为满足请求需要执行的操作,我们甚至可以使用它来缓存计算结果。
我们将向我们的/searches
端点发送一个新请求,该请求在正文中保存我们的搜索配置/参数,并返回一个搜索 ID,我们稍后可以使用它来获取我们的搜索结果。
结论
与所有最佳实践一样,作为 API 设计师或架构师,我们的工作不是遵循一种方法作为“最佳解决方案”,而是找出我们的 API 是如何使用的。
最常见的用例应该是最容易实现的,并且很难出错。
因此,从一开始就分析 API 的使用模式至关重要。我们越早获得数据,如果设计出现问题,就越容易进行改进。Moesif的分析服务可以提供帮助。
Moesif是最先进的 API 分析服务,超过 2000 个组织使用它来衡量其客户的使用模式。
如果我们选择一种方式,因为它更容易掌握或更容易实施,我们就必须看看能从中得到什么。
虽然嵌套资源可以用来提升 URL 的可读性,但如果嵌套过多,URL 也会变得过长,难以阅读。参数也是如此。如果我们发现自己创建的端点包含很长的查询字符串,那么最好从中提取另一个资源,并将参数放在正文中发送。
Moesif 是最先进的 API 分析平台,支持 REST、GraphQL 等多种语言。超过 2000 家组织使用 Moesif 来追踪其最忠实的客户如何使用他们的 API。 了解更多
最初发表于www.moesif.com
文章来源:https://dev.to/moesif/rest-api-design-best-practices-for-parameters-and-query-string-usage-31l6