大多数现代的Web应用程序都公开了客户端可以用来与应用程序交互的API。精心设计的Web API应该旨在支持:
-
平台独立性。无论内部如何实现API,任何客户端都应该能够调用API。这需要使用标准协议,并具有一种机制,使客户端和Web服务可以就要交换的数据格式达成一致。
-
服务演进。Web API应该能够独立于客户端应用程序进行演化和添加功能。随着API的发展,现有的客户端应用程序应继续运行而无需修改。所有功能都应该是可发现的,以便客户端应用程序可以充分使用它。
本文介绍了设计Web API时应考虑的问题。
REST简介
在2000年,罗伊·菲尔丁(Roy Fielding)提出了代表性状态转移(REST)作为设计Web服务的体系结构方法。REST是一种用于构建基于超媒体的分布式系统的体系结构样式。REST独立于任何底层协议,不一定与HTTP绑定。但是,大多数常见的REST实现使用HTTP作为应用程序协议,并且本指南重点介绍为HTTP设计REST API。
REST相对于HTTP的主要优势在于它使用开放标准,并且不会将API或客户端应用程序的实现绑定到任何特定的实现。例如,REST Web服务可以用ASP.NET编写,并且客户端应用程序可以使用可以生成HTTP请求并解析HTTP响应的任何语言或工具集。
以下是使用HTTP的RESTful API的一些主要设计原则:
-
REST API是围绕资源设计的,资源是客户端可以访问的任何类型的对象,数据或服务。
-
资源具有标识符,它是唯一标识该资源的URI。例如,特定客户订单的URI可能是: https://www.abc.com/orders/1
-
客户端通过交换资源表示与服务交互。许多Web API使用JSON作为交换格式。例如,对上面列出的URI的GET请求可能返回此响应主体(json):
{"orderId":1,"orderValue":99.90,"productId":1,"quantity":1} -
REST API使用统一的接口,这有助于使客户端和服务实现脱钩。对于基于HTTP构建的REST API,统一接口包括使用标准HTTP动词对资源执行操作。最常见的操作是GET,POST,PUT,PATCH和DELETE。
-
REST API使用无状态请求模型。HTTP请求应该是独立的,并且可以以任何顺序发生,因此在请求之间保留瞬态信息是不可行的。信息存储的唯一位置是资源本身,每个请求都应该是原子操作。此约束使Web服务具有高度可伸缩性,因为不需要在客户端和特定服务器之间保留任何亲缘关系。任何服务器都可以处理来自任何客户端的任何请求。也就是说,其他因素可能会限制可伸缩性。例如,许多Web服务写入后端数据存储,这可能很难扩展。
-
REST API由表示形式中包含的超媒体链接驱动。例如,以下显示了订单的JSON表示形式。它包含获取或更新与订单关联的客户的链接。
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links": [
{"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" },
{"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" }
]
}
2008年,伦纳德·理查森(Leonard Richardson)为Web API 提出了以下成熟度模型:
-
级别0:定义一个URI,并且所有操作都是对此URI的POST请求。
-
级别1:为单个资源创建单独的URI。
-
级别2:使用HTTP方法定义对资源的操作。
-
级别3:使用超媒体(HATEOAS,如下所述)。
根据Fielding的定义,级别3对应于真正的RESTful API。实际上,许多已发布的Web API都属于2级左右。
围绕资源组织API
着重于Web API公开的业务实体。例如,在电子商务系统中,主要实体可能是客户和订单。创建订单可以通过发送包含订单信息的HTTP POST请求来实现。HTTP响应指示订单是否成功下达。如果可能,资源URI应基于名词(资源)而不是动词(对资源的操作)。
https://www.abc.com/orders // Good
https://www.abc.com/create-order // Avoid
资源不必基于单个物理数据项。例如,订单资源可能在内部实现为关系数据库中的多个表,但作为单个实体呈现给客户。避免创建仅反映数据库内部结构的API。REST的目的是为实体以及应用程序可以在这些实体上执行的操作建模。客户不应接触内部实现。
实体通常被组合到集合中(订单,客户)。集合是与集合中项目无关的资源,并且应具有自己的URI。例如,以下URI可能代表订单的集合:
https://www.abc.com/orders
向集合URI发送HTTP GET请求可检索集合中的项目列表。集合中的每个项目都有自己的唯一URI。对商品URI的HTTP GET请求返回该商品的详细信息。
在URI中采用一致的命名约定。通常,对引用集合的URI使用复数名词会有所帮助。将集合和项目的URI组织到层次结构中是一个好习惯。例如,/customers
是通往客户集合/customers/5
的路径,是通往ID等于5的顾客的路径。这种方法有助于保持Web API的直观性。另外,许多Web API框架都可以基于参数化URI路径来路由请求,因此您可以为path定义路由/customers/{id}
。
还请考虑不同类型的资源之间的关系以及如何暴露这些关联。例如,/customers/5/orders
可能代表客户5的所有订单。您也可以朝另一个方向前进,并使用URI之类的URI表示从订单到客户的关联/orders/99/customer
。但是,将模型扩展得太远可能难以实施。更好的解决方案是在HTTP响应消息的正文中提供指向关联资源的可导航链接。
在更复杂的系统中,提供URI使客户端能够浏览多个级别的关系(例如)可能很诱人/customers/1/orders/99/products
。但是,如果将来资源之间的关系发生变化,则这种复杂性级别可能难以维护,并且很难保持灵活性。相反,请尝试使URI保持相对简单。一旦应用程序引用了资源,就应该可以使用该引用来查找与该资源有关的项目。可以将前面的查询替换为URI,/customers/1/orders
以查找客户1的所有订单,然后/orders/99/products
查找此订单中的产品。
另一个因素是,所有Web请求都会对Web服务器施加负载。请求越多,负载越大。因此,请尝试避免使用暴露大量小资源的“聊天” Web API。这样的API可能要求客户端应用程序发送多个请求以查找其所需的所有数据。取而代之的是,您可能想对数据进行非规范化并将相关信息合并为更大的资源,这些资源可以通过单个请求检索。但是,您需要在此方法与获取客户端不需要的数据的开销之间取得平衡。检索大对象可能会增加请求的延迟,并导致额外的带宽成本。
避免在Web API和基础数据源之间引入依赖关系。例如,如果您的数据存储在关系数据库中,则Web API不需要将每个表都显示为资源的集合。实际上,这可能是一个糟糕的设计。相反,可以将Web API视为数据库的抽象。如有必要,请在数据库和Web API之间引入一个映射层。这样,客户端应用程序就不会与基础数据库方案的更改保持隔离。
最后,可能无法将Web API实施的每个操作映射到特定资源。您可以通过调用功能的HTTP请求来处理此类非资源方案,并将结果作为HTTP响应消息返回。例如,实现简单的计算器操作(例如加法和减法)的Web API可以提供将这些操作公开为伪资源的URI,并使用查询字符串来指定所需的参数。例如,对URI / add?operand1 = 99&operand2 = 1的GET请求将返回带有包含值100的正文的响应消息。但是,仅保留使用这些形式的URI。
根据HTTP方法定义操作
HTTP协议定义了许多将语义分配给请求的方法。大多数RESTful Web API使用的常见HTTP方法是:
-
GET以指定的URI检索资源的表示形式。响应消息的正文包含所请求资源的详细信息。
-
POST在指定的URI处创建一个新资源。请求消息的正文提供了新资源的详细信息。请注意,POST也可以用于触发实际上并不创建资源的操作。
-
PUT可以创建或替换指定URI处的资源。请求消息的正文指定要创建或更新的资源。
-
PATCH执行资源的部分更新。请求主体指定要应用于资源的一组更改。
-
DELETE删除指定URI处的资源。
特定请求的效果应取决于资源是集合还是单个项目。下表使用电子商务示例总结了大多数RESTful实现所采用的通用约定。并非所有这些请求都可以实现-这取决于特定的方案。
资源资源 | 开机自检 | 得到 | 放 | 删除 |
---|---|---|---|---|
/顾客 | 建立新客户 | 检索所有客户 | 批量更新客户 | 删除所有 客户 |
/客户/ 1 | 错误 | 检索客户1的详细信息 | 更新客户1的详细信息(如果存在) | 删除客 户1 |
/ customers / 1 /订单 | 为客户1创建新订单 | 检索客户1的所有订单 | 批量更新客户1的订单 | 删除客户1的所有订单 |
POST,PUT和PATCH之间的差异可能令人困惑。
-
POST请求创建资源。服务器为新资源分配一个URI,并将该URI返回给客户端。在REST模型中,您经常将POST请求应用于集合。新资源将添加到集合中。POST请求也可以用于提交数据以处理现有资源,而无需创建任何新资源。
-
PUT请求创建资源或更新现有资源。客户端指定资源的URI。请求主体包含资源的完整表示。如果具有此URI的资源已存在,则将其替换。否则,如果服务器支持,则创建一个新资源。PUT请求最常应用于单个项目(例如特定客户)而不是集合的资源。服务器可能支持更新,但不支持通过PUT创建。是否支持通过PUT创建取决于客户端是否可以在资源存在之前为资源有意义地分配URI。如果不是,则使用POST创建资源,并使用PUT或PATCH更新。
-
PATCH请求对现有资源执行部分更新。客户端指定资源的URI。请求主体指定一组更改以应用于资源。这可能比使用PUT更有效,因为客户端仅发送更改,而不发送资源的完整表示。从技术上讲,如果服务器支持,PATCH也可以创建新资源(通过为“空”资源指定一组更新)。
PUT请求必须是幂等的。如果客户端多次提交相同的PUT请求,则结果应始终相同(将使用相同的值修改相同的资源)。POST和PATCH请求不保证是幂等的。
符合HTTP语义
本节描述了设计符合HTTP规范的API的一些典型注意事项。但是,它没有涵盖所有可能的细节或场景。如有疑问,请查阅HTTP规范。
媒体类型
如前所述,客户端和服务器交换资源的表示形式。例如,在POST请求中,请求主体包含要创建的资源的表示形式。在GET请求中,响应主体包含获取的资源的表示形式。
在HTTP协议中,通过使用媒体类型(也称为MIME类型)来指定格式。对于非二进制数据,大多数Web API支持JSON(媒体类型= application / xml),可能还支持XML(媒体类型= application / xml)。
请求或响应中的Content-Type标头指定表示的格式。这是一个包含JSON数据的POST请求示例:
POSThttps://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
如果服务器不支持媒体类型,则应返回HTTP状态码415(不支持的媒体类型)。
客户端请求可以包含一个Accept标头,该标头包含客户端将在响应消息中从服务器接受的媒体类型列表。例如:
GEThttps://adventure-works.com/orders/2 HTTP/1.1 Accept: application/json
如果服务器无法匹配列出的任何媒体类型,则服务器应返回HTTP状态代码406(不可接受)。
GET方法
成功的GET方法通常返回HTTP状态代码200(确定)。如果找不到资源,则该方法应返回404(未找到)。
POST方法
如果POST方法创建新资源,它将返回HTTP状态代码201(已创建)。新资源的URI包含在响应的Location标头中。响应主体包含资源的表示形式。
如果该方法进行了一些处理但未创建新资源,则该方法可以返回HTTP状态代码200,并将操作结果包括在响应主体中。或者,如果没有要返回的结果,则该方法可以返回没有响应正文的HTTP状态代码204(无内容)。
如果客户端将无效数据放入请求中,则服务器应返回HTTP状态代码400(错误请求)。响应主体可以包含有关错误的其他信息,也可以包含提供更多详细信息的URI链接。
PUT方法
如果PUT方法创建新资源,则与POST方法一样,它返回HTTP状态代码201(已创建)。如果该方法更新了现有资源,则返回200(确定)或204(无内容)。在某些情况下,可能无法更新现有资源。在这种情况下,请考虑返回HTTP状态代码409(冲突)。
考虑实现批量HTTP PUT操作,该操作可以批量更新集合中的多个资源。PUT请求应指定集合的URI,请求主体应指定要修改的资源的详细信息。这种方法可以帮助减少聊天情况并提高性能。
Delete方法
如果删除操作成功,则Web服务器应使用HTTP状态代码204进行响应,指示该过程已成功处理,但是响应主体不包含其他信息。如果资源不存在,则Web服务器可以返回HTTP 404(未找到)。
异步操作
有时POST,PUT,PATCH或DELETE操作可能需要一些处理才能完成。如果在发送响应到客户端之前等待完成,则可能会导致无法接受的延迟。如果是这样,请考虑使操作异步。返回HTTP状态码202(已接受)以指示请求已接受处理,但未完成。
您应该公开一个返回异步请求状态的端点,以便客户端可以通过轮询状态端点来监视状态。在202响应的Location标头中包含状态终结点的URI。例如:
HTTP/1.1 202 Accepted
Location: /api/status/12345
如果客户端将GET请求发送到此端点,则响应应包含请求的当前状态。可选地,它还可以包括估计的完成时间或取消操作的链接。
HTTP/1.1 200 OK
Content-Type: application/json
{
"status":"In progress", "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" } }
如果异步操作创建了新资源,则该操作完成后,状态端点应返回状态代码303。在303响
应中,包括一个Location标头,该标头提供了新资源的URI:
HTTP/1.1 303 See Other
Location: /api/orders/12345
筛选和分页数据
当仅需要一部分信息时,通过单个URI公开资源集合可能导致应用程序获取大量数据。例如,假设客户端应用程序需要查找成本超过特定值的所有订单。它可能会从/ orders URI中检索所有订单,然后在客户端过滤这些订单。显然,此过程效率很低。它浪费了网络带宽和托管Web API的服务器上的处理能力。
相反,API可以允许在URI的查询字符串中传递过滤器,例如/ orders?minCost = n。然后,Web API负责解析和处理minCost
查询字符串中的参数,并在服务器端返回过滤后的结果。
收集资源上的GET请求可能会返回大量项目。您应该设计一个Web API来限制任何单个请求返回的数据量。考虑支持查询字符串,这些字符串指定要检索的最大项目数以及集合中的起始偏移量。例如:
/orders?limit=25&offset=50
还应考虑对返回的项目数施加上限,以帮助防止拒绝服务攻击。为了帮助客户端应用程序,返回分页数据的GET请求还应该包括某种形式的元数据,以指示集合中可用资源的总数。
通过提供将字段名作为值的排序参数,例如/ orders?sort = ProductID,可以使用类似的策略对数据进行排序。但是,此方法可能会对缓存产生负面影响,因为查询字符串参数形成了资源标识符的一部分,该标识符被许多缓存实现用作缓存数据的键。
如果每个项目包含大量数据,则可以扩展此方法以限制为每个项目返回的字段。例如,您可以使用查询字符串参数,该参数接受以逗号分隔的字段列表,例如/ orders?fields = ProductID,Quantity。
在查询字符串中为所有可选参数提供有意义的默认值。例如,如果实现分页,则将参数设置limit
为10,将offset
参数设置为0,如果实现排序,则将sort参数设置为资源的键,fields
如果支持投影,则将参数设置为资源中的所有字段。
支持对大型二进制资源的部分响应
资源可能包含较大的二进制字段,例如文件或图像。为了克服由不可靠和间歇性连接引起的问题并改善响应时间,请考虑使此类资源能够分块检索。为此,Web API应该支持用于大资源的GET请求的Accept-Ranges标头。此标头表示GET操作支持部分请求。客户端应用程序可以提交GET请求,该请求返回指定为字节范围的资源子集。
另外,请考虑为这些资源实现HTTP HEAD请求。HEAD请求与GET请求相似,不同之处在于,它仅返回描述资源的HTTP标头,且消息正文为空。客户端应用程序可以发出HEAD请求,以确定是否通过使用部分GET请求来获取资源。例如:
HEADhttps://adventure-works.com/products/10?fields=productImage HTTP/1.1
这是示例响应消息:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580
Content-Length标头提供资源的总大小,Accept-Ranges标头指示相应的GET操作支持部分结果。客户端应用程序可以使用此信息以较小的块来检索图像。第一个请求通过使用Range标头获取前2500个字节:
HTTP/1.1 303 See Other
Location: /api/orders/12345
筛选和分页数据
当仅需要一部分信息时,通过单个URI公开资源集合可能导致应用程序获取大量数据。例如,假设客户端应用程序需要查找成本超过特定值的所有订单。它可能会从/ orders URI中检索所有订单,然后在客户端过滤这些订单。显然,此过程效率很低。它浪费了网络带宽和托管Web API的服务器上的处理能力。
相反,API可以允许在URI的查询字符串中传递过滤器,例如/ orders?minCost = n。然后,Web API负责解析和处理minCost
查询字符串中的参数,并在服务器端返回过滤后的结果。
收集资源上的GET请求可能会返回大量项目。您应该设计一个Web API来限制任何单个请求返回的数据量。考虑支持查询字符串,这些字符串指定要检索的最大项目数以及集合中的起始偏移量。例如:
/orders?limit=25&offset=50
还应考虑对返回的项目数施加上限,以帮助防止拒绝服务攻击。为了帮助客户端应用程序,返回分页数据的GET请求还应该包括某种形式的元数据,以指示集合中可用资源的总数。
通过提供将字段名作为值的排序参数,例如/ orders?sort = ProductID,可以使用类似的策略对数据进行排序。但是,此方法可能会对缓存产生负面影响,因为查询字符串参数形成了资源标识符的一部分,该标识符被许多缓存实现用作缓存数据的键。
如果每个项目包含大量数据,则可以扩展此方法以限制为每个项目返回的字段。例如,您可以使用查询字符串参数,该参数接受以逗号分隔的字段列表,例如/ orders?fields = ProductID,Quantity。
在查询字符串中为所有可选参数提供有意义的默认值。例如,如果实现分页,则将参数设置limit
为10,将offset
参数设置为0,如果实现排序,则将sort参数设置为资源的键,fields
如果支持投影,则将参数设置为资源中的所有字段。
支持对大型二进制资源的部分响应
资源可能包含较大的二进制字段,例如文件或图像。为了克服由不可靠和间歇性连接引起的问题并改善响应时间,请考虑使此类资源能够分块检索。为此,Web API应该支持用于大资源的GET请求的Accept-Ranges标头。此标头表示GET操作支持部分请求。客户端应用程序可以提交GET请求,该请求返回指定为字节范围的资源子集。
另外,请考虑为这些资源实现HTTP HEAD请求。HEAD请求与GET请求相似,不同之处在于,它仅返回描述资源的HTTP标头,且消息正文为空。客户端应用程序可以发出HEAD请求,以确定是否通过使用部分GET请求来获取资源。例如:
HEADhttps://adventure-works.com/products/10?fields=productImage HTTP/1.1
这是示例响应消息:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580
Content-Length标头提供资源的总大小,Accept-Ranges标头指示相应的GET操作支持部分结果。客户端应用程序可以使用此信息以较小的块来检索图像。第一个请求通过使用Range标头获取前2500个字节:
GEThttps://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499
响应消息通过返回HTTP状态代码206指示这是部分响应。Content-Length标头指定了消息正文中返回的实际字节数(不是资源的大小),而Content-Range标头指示了哪个响应。这是资源的一部分(4580中的字节0-2499):
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580
来自客户端应用程序的后续请求可以检索资源的其余部分。
使用HATEOAS启用对相关资源的导航
REST背后的主要动机之一是,无需事先了解URI方案,就应该可以浏览整个资源集。每个HTTP GET请求应通过响应中包含的超链接返回查找与请求的对象直接相关的资源所必需的信息,并且还应向其提供描述这些资源中的每一个可用操作的信息。此原理称为HATEOAS,或称为应用程序状态引擎的超文本。该系统实际上是一个有限状态机,对每个请求的响应都包含从一种状态转移到另一种状态所需的信息。不需要其他信息。
对RESTful Web API进行版本控制
Web API保持静态的可能性很小。随着业务需求的变化,可能会添加新的资源集合,资源之间的关系可能会更改,并且资源中的数据结构可能会被修改。尽管更新Web API以处理新的或不同的要求是一个相对简单的过程,但是您必须考虑此类更改将对使用Web API的客户端应用程序产生的影响。问题在于,尽管设计和实现Web API的开发人员可以完全控制该API,但是开发人员对客户端应用程序的控制程度不同,该客户端应用程序可以由远程运行的第三方组织构建。
通过版本控制,Web API可以指示其公开的功能和资源,并且客户端应用程序可以提交针对功能或资源的特定版本的请求。以下各节描述了几种不同的方法,每种方法都有其自身的优点和取舍。
没有版本控制
这是最简单的方法,对于某些内部API可能是可接受的。重大更改可以表示为新资源或新链接。向现有资源添加内容可能不会带来重大变化,因为不希望看到此内容的客户端应用程序将忽略它。
例如,到URI的请求https://www.abc.com/customers/3
应该返回单个客户的含有细节id
,name
以及address
由所述客户端应用程序预期字段:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8 {"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
如果将该DateCreated
字段添加到客户资源的架构,则响应将如下所示:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}
如果现有的客户端应用程序能够忽略无法识别的字段,则它们可能会继续正常运行,而新的客户端应用程序可以设计为处理此新字段。但是,如果对资源的架构进行了更根本的更改(例如,删除或重命名字段),或者资源之间的关系发生了更改,则这些更改可能构成重大更改,从而阻止现有客户端应用程序正常运行。在这些情况下,您应该考虑使用以下方法之一。
URI版本控制
每次您修改Web API或更改资源架构时,您都会为每个资源的URI添加一个版本号。先前存在的URI应该继续像以前一样操作,返回符合其原始架构的资源。
延伸的前面的例子,如果该address
字段被重组为包含地址的每个组成部分(如子场streetAddress
,city
,state
,和zipCode
),这个版本的资源的可通过URI暴露包含一个版本号,如 https://adventure-works.com/v2/customers/3
:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}
这种版本控制机制非常简单,但是取决于服务器将请求路由到适当的端点。但是,随着Web API通过多次迭代成熟,并且服务器必须支持许多不同的版本,它可能变得笨拙。同样,从纯粹主义者的角度来看,在所有情况下,客户端应用程序都在提取相同的数据(客户3),因此URI不应真正取决于版本。此方案还使HATEOAS的实现复杂化,因为所有链接都需要在其URI中包括版本号。
查询字符串版本控制
您可以使用附加到HTTP请求的查询字符串中的参数(例如)来指定资源的版本,而不是提供多个URI https://www.abc.com/customers/3?version=2
。如果较旧的客户端应用程序省略了版本参数,则该版本参数应默认为有意义的值,例如1。
这种方法具有语义优势,即始终从相同的URI中检索相同的资源,但是它取决于处理请求的代码以解析查询字符串并发送回适当的HTTP响应。这种方法还遭受与URI版本控制机制相同的实现HATEOAS的复杂性。
标头版本控制
您可以实现一个指示资源版本的自定义标头,而不是将版本号附加为查询字符串参数。这种方法要求客户端应用程序将适当的标头添加到任何请求,尽管如果省略了版本标头,则处理客户端请求的代码可以使用默认值(版本1)。以下示例使用名为Custom-Header的自定义标头。此标头的值指示Web API的版本。
版本1:
GEThttps://www.abc.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
版本2:
GEThttps://www.abc.com/customers/3 HTTP/1.1
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
与前两种方法一样,实现HATEOAS要求在任何链接中包括适当的自定义标头。
媒体类型版本控制
当客户端应用程序将HTTP GET请求发送到Web服务器时,应按照本指南前面所述,指定它可以使用Accept标头处理的内容格式。通常,Accept标头的目的是允许客户端应用程序指定响应的主体是XML,JSON还是客户端可以解析的其他常见格式。但是,可以定义包括信息的自定义媒体类型,该信息使客户端应用程序能够指示期望的资源版本。以下示例显示了一个请求,该请求使用值application / vnd.adventure-works.v1 + json指定一个Accept报头。该vnd.adventure-works.v1元素向Web服务器指示应返回资源的版本1,而json元素则指定响应主体的格式应为JSON:
GEThttps://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json
处理请求的代码负责处理Accept报头并尽可能地兑现它(客户端应用程序可以在
Accept报头中指定多种格式,在这种情况下,Web服务器可以为响应正文选择最合适
的格式)。Web服务器通过使用Content-Type标头确认响应正文中的数据格式:
HTTP/1.1 200 OK
Content-Type: application/vnd.adventure-works.v1+json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
如果Accept标头未指定任何已知的媒体类型,则Web服务器可以生成HTTP 406(不可接受)响应消息或返回具有默认媒体类型的消息。
这种方法可以说是最纯粹的版本控制机制,并且很自然地适合HATEOAS,后者可以在资源链接中包括相关数据的MIME类型。
注意:
选择版本控制策略时,还应考虑对性能的影响,尤其是Web服务器上的缓存。URI版本控制和查询字符串版本控制方案是缓存友好的,因为相 同的URI /查询字符串组合每次都引用相同的数据。标头版本控制和媒体类型版本控制机制通常需要其他逻辑来检查自定义标头或“接受”标头 中的值。在大规模环境中,许多使用不同版本的Web API的客户端可能会在服务器端缓存中导致大量重复数据。如果客户端应用程序通过实现 缓存的代理与Web服务器通信,并且仅在当前不将请求数据的副本保存在其缓存中的情况下将请求转发到Web服务器,则此问题可能变得很严 重。
感谢阅读!
喜欢本文的朋友,欢迎关注“isevena”