大多数现代Web应用程序都公开了客户端可用于与应用程序交互的API。精心设计的Web API应旨在支持:
- 平台独立性。无论API如何在内部实施,任何客户端都应该能够调用API。这需要使用标准协议,并且具有客户端和Web服务可以就要交换的数据格式达成一致的机制。
- 服务发展。Web API应该能够独立于客户端应用程序进化和添加功能。随着API的发展,现有的客户端应用程序应该继续运行而无需修改。应该可以发现所有功能,以便客户端应用程序可以完全使用它。
本指南介绍了在设计Web API时应考虑的问题。
REST简介
2000年,Roy Fielding提出了Representational State Transfer(REST)作为设计Web服务的架构方法。REST是一种用于构建基于超媒体的分布式系统的架构风格。REST独立于任何底层协议,不一定与HTTP绑定。但是,大多数常见的REST实现都使用HTTP作为应用程序协议,本指南重点介绍如何为HTTP设计REST API。
REST over HTTP的主要优点是它使用开放标准,并且不会将API或客户端应用程序的实现绑定到任何特定实现。例如,REST Web服务可以用ASP.NET编写,客户端应用程序可以使用任何可以生成HTTP请求和解析HTTP响应的语言或工具集。
以下是使用HTTP的RESTful API的一些主要设计原则:
- REST API是围绕资源设计的,资源是客户端可以访问的任何类型的对象,数据或服务。
- 资源具有标识符,该标识符是唯一标识该资源的URI。例如,特定客户订单的URI可能是:
HTTP
复制https://adventure-works.com/orders/1
- 客户端通过交换资源表示来与服务进行交互。许多Web API使用JSON作为交换格式。例如,对上面列出的URI的GET请求可能会返回此响应正文:
{"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表示。它包含用于获取或更新与订单关联的客户的链接。
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的定义,Level 3对应于真正的RESTful API。实际上,许多已发布的Web API都在2级左右。
围绕资源组织API
关注Web API公开的业务实体。例如,在电子商务系统中,主要实体可能是客户和订单。可以通过发送包含订单信息的HTTP POST请求来创建订单。HTTP响应指示订单是否成功放置。如果可能,资源URI应基于名词(资源)而不是动词(资源上的操作)。
复制
https://adventure-works.com/orders // Good
https://adventure-works.com/create-order // Avoid
资源不必基于单个物理数据项。例如,订单资源可能在内部实现为关系数据库中的多个表,但作为单个实体呈现给客户端。避免创建仅仅镜像数据库内部结构的API。REST的目的是为实体和应用程序可以对这些实体执行的操作建模。客户不应该接触内部实现。
实体通常组合在一起成为集合(订单,客户)。集合是与集合中的项目分开的资源,应具有自己的URI。例如,以下URI可能表示订单集合:
复制
https://adventure-works.com/orders
向集合URI发送HTTP GET请求将检索集合中的项目列表。集合中的每个项目也都有自己唯一的URI。对项目URI的HTTP GET请求返回该项目的详细信息。
在URI中采用一致的命名约定。通常,对引用集合的URI使用复数名词是有帮助的。将集合和项目的URI组织到层次结构中是一种很好的做法。例如,/customers
是客户集合/customers/5
的路径,是ID等于5的客户的路径。此方法有助于保持Web API的直观性。此外,许多Web API框架可以根据参数化的URI路径路由请求,因此您可以为路径定义路由/customers/{id}
。
还要考虑不同类型资源之间的关系以及如何公开这些关联。例如,/customers/5/orders
may可能代表客户5的所有订单。您也可以转向另一个方向,并使用URI等表示从订单返回给客户的关联/orders/99/customer
。但是,将此模型扩展太远可能会变得很麻烦。更好的解决方案是提供HTTP响应消息正文中相关资源的可导航链接。“ 使用HATEOAS ”一节中将更详细地介绍此机制,以启用对相关资源的导航。
在更复杂的系统中,提供允许客户端在多个级别的关系中导航的URI很有诱惑力,例如/customers/1/orders/99/products
。然而,如果资源之间的关系在未来发生变化,则这种复杂程度可能难以维护并且不灵活。相反,尝试保持URI相对简单。一旦应用程序引用了资源,就应该可以使用此引用来查找与该资源相关的项目。可以使用URI替换上述查询/customers/1/orders
以查找客户1的所有订单,然后/orders/99/products
按此顺序查找产品。
小费
避免要求资源URI比集合/项目/集合更复杂。
另一个因素是所有Web请求都会对Web服务器施加负载。请求越多,负载越大。因此,尽量避免暴露大量小资源的“聊天”Web API。这样的API可能需要客户端应用程序发送多个请求以查找它所需的所有数据。相反,您可能希望对数据进行非规范化,并将相关信息组合到可以使用单个请求检索的更大资源中。但是,您需要平衡此方法与获取客户端不需要的数据的开销。检索大对象会增加请求的延迟并导致额外的带宽成本。有关这些性能反模式的更多信息,请参阅Chatty I / O和Extraneous Fetching。
避免在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实现所采用的常见约定。并非所有这些请求都可以实现 - 这取决于具体方案。
资源 | POST | 得到 | 放 | 删除 |
---|---|---|---|---|
/顾客 | 创建一个新客户 | 检索所有客户 | 批量更新客户 | 删除所有客户 |
/客户/ 1 | 错误 | 检索客户1的详细信息 | 更新客户1的详细信息(如果存在) | 删除客户1 |
/客户/ 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还可以创建新资源(通过指定“null”资源的一组更新)。
PUT请求必须是幂等的。如果客户端多次提交相同的PUT请求,则结果应始终相同(将使用相同的值修改相同的资源)。POST和PATCH请求不保证是幂等的。
符合HTTP语义
本节介绍了设计符合HTTP规范的API的一些典型注意事项。但是,它并未涵盖所有可能的细节或方案。如有疑问,请参阅HTTP规范。
媒体类型
如前所述,客户端和服务器交换资源的表示。例如,在POST请求中,请求正文包含要创建的资源的表示。在GET请求中,响应主体包含所获取资源的表示。
在HTTP协议中,通过使用媒体类型(也称为MIME类型)指定格式。对于非二进制数据,大多数Web API支持JSON(媒体类型= application / json)和可能的XML(媒体类型= application / xml)。
请求或响应中的Content-Type标头指定表示的格式。以下是包含JSON数据的POST请求示例:
复制
POST https://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头,该头包含客户端将在响应消息中从服务器接受的媒体类型列表。例如:
复制
GET https://adventure-works.com/orders/2 HTTP/1.1
Accept: application/json
如果服务器无法匹配列出的任何媒体类型,则应返回HTTP状态代码406(不可接受)。
GET方法
成功的GET方法通常返回HTTP状态代码200(OK)。如果找不到资源,则该方法应返回404(Not Found)。
POST方法
如果POST方法创建新资源,则返回HTTP状态代码201(已创建)。新资源的URI包含在响应的Location标头中。响应正文包含资源的表示。
如果该方法执行某些处理但不创建新资源,则该方法可以返回HTTP状态代码200并将操作的结果包括在响应主体中。或者,如果没有返回结果,则该方法可以返回没有响应主体的HTTP状态代码204(无内容)。
如果客户端将无效数据放入请求中,则服务器应返回HTTP状态代码400(错误请求)。响应正文可以包含有关错误的其他信息或指向提供更多详细信息的URI的链接。
PUT方法
如果PUT方法创建新资源,它将返回HTTP状态代码201(已创建),与POST方法一样。如果方法更新现有资源,则返回200(OK)或204(No Content)。在某些情况下,可能无法更新现有资源。在这种情况下,请考虑返回HTTP状态代码409(冲突)。
考虑实施批量HTTP PUT操作,该操作可以批量更新集合中的多个资源。PUT请求应指定集合的URI,请求正文应指定要修改的资源的详细信息。这种方法可以帮助减少干扰和提高性能。
PATCH方法
通过PATCH请求,客户端以补丁文档的形式向现有资源发送一组更新。服务器处理修补程序文档以执行更新。补丁文档不描述整个资源,只应用一组更改。PATCH方法(RFC 5789)的规范没有定义补丁文档的特定格式。必须从请求中的媒体类型推断出格式。
JSON可能是Web API最常用的数据格式。有两种主要的基于JSON的补丁格式,称为JSON补丁和JSON合并补丁。
JSON合并补丁更简单一些。修补程序文档与原始JSON资源具有相同的结构,但仅包括应更改或添加的字段子集。此外,可以通过null
在修补文档中指定字段值来删除字段。(这意味着如果原始资源可以具有显式空值,则合并补丁不适用。)
例如,假设原始资源具有以下JSON表示:
复制
{
"name":"gizmo",
"category":"widgets",
"color":"blue",
"price":10
}
以下是此资源的可能JSON合并修补程序:
复制
{
"price":12,
"color":null,
"size":"small"
}
这告诉服务器更新price
,删除color
和添加size
,同时name
和category
不修改。有关JSON合并修补程序的确切详细信息,请参阅RFC 7396。JSON合并补丁的媒体类型是application/merge-patch+json
。
如果原始资源可以包含显式空值,则由于null
修补文档中的特殊含义,合并修补程序不适用。此外,修补程序文档未指定服务器应应用更新的顺序。根据数据和域,这可能或不重要。RFC 6902中定义的JSON补丁更灵活。它将更改指定为要应用的操作序列。操作包括添加,删除,替换,复制和测试(以验证值)。JSON补丁的媒体类型是application/json-patch+json
。
以下是处理PATCH请求时可能遇到的一些典型错误情况,以及相应的HTTP状态代码。
错误情况 | HTTP状态代码 |
---|---|
不支持修补程序文档格式。 | 415(不支持的媒体类型) |
格式错误的补丁文档。 | 400(不良请求) |
修补程序文档有效,但更改无法应用于当前状态的资源。 | 409(冲突) |
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
有关更多信息,请参阅REST中的异步操作。
过滤和分页数据
通过单个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请求来获取资源。例如:
复制
HEAD https://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个字节:
复制
GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499
响应消息通过返回HTTP状态代码206指示这是部分响应.Intent-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,或称为超文本作为应用程序状态的引擎。该系统实际上是一个有限状态机,对每个请求的响应包含从一个状态移动到另一个状态所需的信息; 不需要其他信息。
注意
目前,没有标准或规范定义如何建模HATEOAS原则。本节中显示的示例说明了一种可能的解决方案。
例如,为了处理订单和客户之间的关系,订单的表示可以包括标识订单客户的可用操作的链接。这是一个可能的表示:
复制
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links":[
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"DELETE",
"types":[]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"DELETE",
"types":[]
}]
}
在此示例中,links
阵列具有一组链接。每个链接表示对相关实体的操作。每个链接的数据包括关系(“customer”),URI(https://adventure-works.com/customers/3
),HTTP方法和支持的MIME类型。这是客户端应用程序需要能够调用操作的所有信息。
该links
阵列还包括有关已检索的资源本身的自引用信息。这些有关系的自我。
返回的链接集可能会更改,具体取决于资源的状态。这就是超文本是“应用程序状态引擎”的含义。
版本化RESTful Web API
Web API极不可能保持静态。随着业务需求的变化,可能会添加新的资源集合,资源之间的关系可能会发生变化,资源中的数据结构可能会被修改。虽然更新Web API以处理新的或不同的需求是一个相对简单的过程,但您必须考虑此类更改将对使用Web API的客户端应用程序产生的影响。问题在于,虽然设计和实现Web API的开发人员可以完全控制该API,但开发人员对客户端应用程序的控制程度不同,客户端应用程序可能由远程操作的第三方组织构建。
版本控制使Web API能够指示其公开的功能和资源,并且客户端应用程序可以提交定向到特定版本的功能或资源的请求。以下部分描述了几种不同的方法,每种方法都有其自身的优点和权衡。
没有版本控制
这是最简单的方法,对于某些内部API可能是可接受的。重大变化可以表示为新资源或新链接。将内容添加到现有资源可能不会产生重大变化,因为不期望看到此内容的客户端应用程序将忽略它。
例如,到URI的请求https://adventure-works.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"}
注意
为简单起见,本节中显示的示例响应不包括HATEOAS链接。
如果该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://adventure-works.com/customers/3?version=2
。如果较旧的客户端应用程序省略了version参数,那么它应该默认为有意义的值,例如1。
此方法具有语义优势,即始终从同一URI检索相同的资源,但它取决于处理请求以解析查询字符串并发回适当的HTTP响应的代码。这种方法也存在与实现HATEOAS作为URI版本控制机制相同的复杂性。
注意
某些较旧的Web浏览器和Web代理不会缓存对URI中包含查询字符串的请求的响应。这会降低使用Web API并在此类Web浏览器中运行的Web应用程序的性能。
标题版本控制
您可以实现一个指示资源版本的自定义标头,而不是将版本号附加为查询字符串参数。此方法要求客户端应用程序向任何请求添加适当的标头,但是如果省略版本标头,则处理客户端请求的代码可以使用默认值(版本1)。以下示例使用名为Custom-Header的自定义标头。此标头的值表示Web API的版本。
版本1:
复制
GET https://adventure-works.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:
复制
GET https://adventure-works.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需要在任何链接中包含适当的自定义标头。
媒体类型版本控制
当客户端应用程序向Web服务器发送HTTP GET请求时,它应该使用Accept标头规定它可以处理的内容的格式,如本指南前面所述。Accept标头的目的通常是允许客户端应用程序指定响应的主体是否应该是XML,JSON或客户端可以解析的其他一些常见格式。但是,可以定义包含信息的自定义媒体类型,使客户端应用程序能够指示它所期望的资源版本。以下示例显示了一个请求,该请求指定了一个值为application / vnd.adventure-works.v1 + json的Accept标头。该vnd.adventure-works.v1element向Web服务器指示它应该返回资源的版本1,而json元素指定响应主体的格式应该是JSON:
复制
GET https://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 /查询字符串组合每次引用相同的数据。
标头版本控制和媒体类型版本控制机制通常需要额外的逻辑来检查自定义标头或Accept标头中的值。在大规模环境中,许多使用不同版本的Web API的客户端可能会在服务器端缓存中产生大量重复数据。如果客户端应用程序通过实现缓存的代理与Web服务器通信,并且仅在Web服务器当前未在其缓存中保存所请求数据的副本时将请求转发给Web服务器,则此问题可能会变得严重。
开放API倡议
该开放API计划是由一个行业协会创建于不同厂商规范REST API描述。作为该计划的一部分,Swagger 2.0规范更名为OpenAPI规范(OAS),并归入Open API Initiative。
您可能希望为Web API采用OpenAPI。有些要考虑的要点:
- OpenAPI规范附带了一套关于如何设计REST API的固定指导原则。这对于互操作性具有优势,但在设计API以符合规范时需要更加小心。
- OpenAPI促进了契约优先方法,而不是实现优先方法。契约优先意味着您首先设计API契约(接口),然后编写实现契约的代码。
- 像Swagger这样的工具可以从API契约中生成客户端库或文档。例如,请参阅使用Swagger的ASP.NET Web API帮助页面。
更多信息
- Microsoft REST API指南。有关设计公共REST API的详细建议。
- Web API清单。设计和实现Web API时要考虑的有用项目列表。
- 开放API倡议。Open API的文档和实现细节。
via https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design