18

REST API设计优秀实践之参数与查询的使用

 4 years ago
source link: http://developer.51cto.com/art/202004/614845.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

aq2uQnU.jpg!web

【51CTO.com快译】

众所周知,我们设计API的目标往往是要通过我们的服务,为用户提供一定的功能。虽然HTTP和URL资源都允许数据流进行一定程度的基本交互,但是它们在面对其他特定需求时,往往会让您的API显得力不从心。在此,我们以分页为例,即:如果某个数据库中存放着上百万篇的文章,那么我们很可能无法在单次响应中,将每一篇文章都发送给客户。针对此类需求,业界提出了参数化(parametrization)的解决方法。

什么是参数化?

通常而言,参数化是一种请求配置。在编程语言中,我们可以通过某个函数来请求对应的返回值。如果一个函数不带任何参数,那么我们将无法直接去影响它的返回值。

API也是如此,尤其是那些无状态的REST API。毕竟,所有REST的交互都是无状态的。也就是说,每个请求都会包含那些方便连接器理解该请求的所有信息,而且它们与之前的任何请求都无关。

在实际应用中,我们有许多种方法可以向HTTP请求中添加参数,其中包括:查询字符串,POST、PUT报文,各种PATCH请求、以及标头(header)。它们各自都有自己的用例和规则。

那么,添加所有参数数据的最简单方法,就是将所有内容放入报文内。在具体应用中,每个端点都会用到POST方法,而许多API也会把所有的参数放置在报文中。长此以往,传统的API里积累了越来越多的参数,以致于它们不再适合查询字符串等操作。显然,我们应当避免在设计API时此类极端情况的发生。

添加何种参数?

就REST而言,作为API查询语言的GraphQL,为用户提供了满足数据查询的运行时。那么,我们是否应当添加那些在HTTP规范中已经标准化的标头字段类来作为参数(请参见:http://www.rfcreader.com/#rfc2616_line4589)呢?

在我们了解了各种默认标头字段后,下面我们来讨论是否应该为自己的参数创建一个自定义的标头字段,或者将其放入URL的查询字符串中。

何时该使用查询字符串?

如果我们已获悉待添加的参数不属于默认的标头字段,且并不敏感,那么我们就应当通过查看查询字符串,以确认是否合适。在查询字符串的时候,有个 <isindex> 的HTML元素,可被用于向服务器发送一些关键字。而服务器则会据此做出响应,并列出与关键字相匹配的页面列表。接着,查询字符串会被重新用于Web表单,以通过GET的请求方式,将数据发送到服务器处。

因此,查询字符串的主要用例便是过滤,它会着重过滤搜索和分页这两种特殊的情况。有关此方面的详细讨论,请参见: https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sorting-and-Pagination/?utm_source=dzone&utm_medium=paid&utm_campaign=placed%20article&utm_term=rest%20api%20design%20best%20practices%20for%20parameters%20and%20query%20string%20usage 。不过,正如针对Web表单的目的所示,它可以被用于不同类型的参数。而RESTful API可以将POST或PUT请求与报文一起使用,以实现将表单数据发送给服务器。

我们来看一个有关嵌套表示(nested representation)的参数示例。默认情况下,我们需要返回文章的普通表示形式(plain representation)。当我们将?withComments查询字符串添加到端点时,?withComments会以普通表示形式返回某篇文章的评论,而且仅需要一个请求即可。至于此类参数是应该被放在自定义的标头中,还是查询字符串里,则主要取决于开发人员的个人经验与偏好。

根据HTTP的规范陈述(请参见: http://www.rfcreader.com/#rfc2616_line1761 ):标头字段可以被视为函数的参数。不过,将查询字符串添加到URL中会明显比创建客户端标头,要更快、更显著。实际上,这些字段充当了请求修饰符,其语义等同于编程语言在方法调用中的参数。

在实际应用中,您会发现:在所有端点上保持相同的参数更适合于标头。例如:身份验证令牌可以由每个请求所发送。而那些高度动态的参数(尤其是仅对少数几个端点有效的参数)则应该被放在查询字符串中。例如:每个端点的过滤器参数都会有所不同。

数组和映射参数

对于开发人员而言,他们可能会经常碰到的一个问题是:如何处理查询字符串中的数组参数?例如,我们可能碰到需要搜索多个名称的需求场景。而通常的解决方案是:使用方括号。如下列代码所示:

67ZvYfv.png!web

不过,HTTP的规范指出:由IPv6 [RFC3513]或更高版本标识的主机通过将IP方括号(“[”和“]”)来区分。这是URI语法中唯一允许使用方括号字符的地方。

我们在许多HTTP服务器和客户端的实现场景中,都应当牢记上述规范。当然,为了简便起见,我们可以采用“多次使用一个参数名称”的另一种解决方案。如下列代码所示:

UfiIF3j.png!web

该方法虽然有效,但是它会导致开发人员体验上的下降。通常情况下,客户端只会使用类似于地图(map-like)的数据结构。而该结构在被添加到URL中之前,会进行简单的字符串转换。而这恰恰会导致后续的数值被覆盖。可见,在发送请求之前,我们需要进行更加复杂的转换。

与此同时,也有人会采用另一种方法:使用“,”字符来分隔数值,直接在URL中出现未经编码的字符。如下列代码所示:

qQze2mv.png!web

而对于那些类似地图的数据结构,我们也可以使用无需编码的“.”字符。如下列代码所示:

Fr22aaQ.png!web

另外,您还可以对整个查询字符串进行URL编码(请参见: https://en.wikipedia.org/wiki/Percent-encoding ),以便直接使用任何想要的字符或格式。不过值得一提的是,这同样也会大幅降低开发人员的使用体验。

何时不该使用查询字符串?

在实际应用中,由于查询字符串往往是URL的一部分。而那些隐藏在客户端和API之间的攻击者(如:中间人,MIM)则可以轻而易举地读取到我们的URL,因此我们不应该简单地将诸如密码之类的敏感数据,直接放入查询字符串之中。

虽然大多数HTTP客户端在URL中都会允许使用五位数(five-figure)长度的字符,但是如果我们未能全面设计和考虑URL的整体长度,那么开发人员在调试此类字符串时,往往也会经历各种繁琐和不便。

当然,由于我们能够将任何内容都定义为资源,因此我们有时候完全可以使用POST端点,来进行大量参数的调用与传递,进而将报文中的所有数据都发送给API。

可见,为了避免向查询字符串中那些具有多个参数的资源发送GET请求,进而导致冗长且不可识别的URL产生,我们可以设计出诸如搜索类型的资源,根据API的需求,来缓存各种计算的结果。同时,我们还可以向/searches端点发布新的请求。而该请求会将我们的搜索相关配置与参数保存在报文之中。通过返回一个搜索ID,以便我们使用它来获取搜索的结果。

总结

作为API设计人员或架构师,我们所追求的优秀实践,实际上就是要找出API的最理想使用方式,以及最简单的实现用例。综上所述,我们建议您注意如下两个方面:

  • 从一开始就能够借助Moesif(人工智能API服务平台)之类的服务,着手分析自身API的使用模式。
  • 嵌套式资源虽然可以提高URL的可读性,但是如果我们嵌套得太多的话,则会导致URL参数的冗长。因此,如果您发现自己创建了一个具有超大的查询字符串端点,那么我建议您最好从其中提取一部分子资源,放置到报文中作为参数发送出去。

原文标题:REST API Design Best Practices for Parameters and Query String Usage,作者:Kay Ploesser

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

【责任编辑:庞桂玉 TEL:(010)68476606】


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK