API 是“Application Program Interface”的首字母缩写。WEB API 可用于多种应用程序,并充当两个应用程序之间的一种中介。如果要创建网站、单页应用程序 (SPA) 或移动应用程序,或者需要与服务器端的服务进行通信,或者即使一个应用程序需要与另一个应用程序(例如:客户端到服务器)进行通信,则可以使用 API。还可以将 API 与人工智能、机器学习、云、微服务、大数据和许多其他技术结合使用。为了设计一个好的API,可以遵循一些原则。在本文中,我将解释其中的一些原则。
HTTP 协议
在开始讨论REST本身之前,我将解释一下HTTP协议。HTTP 协议用于通过请求和响应与服务器进行通信。例如,在 WEB 应用程序的情况下,UI 将在后端向服务发出请求,服务将处理此请求并返回响应。这是HTTP工作原理的一个例子:
HTTP 是一种允许获取资源(例如 HTML 文档)的协议。它是 Web 上任何数据交换的基础,它是一种客户端-服务器协议,这意味着请求由接收方(通常是 Web 浏览器)发起。客户端和服务器通过交换单个消息(而不是数据流)进行通信。客户端(通常是 Web 浏览器)发送的消息称为请求,服务器作为应答发送的消息称为响应。
HTTP 请求包含三个部分:
HTTP 响应(文本中的一段数据)包含三个部分:
HTTP 谓词
HTTP 定义了一组请求方法,用于指示要对给定资源执行的所需操作。虽然它们也可以是名词,但这些请求方法有时被称为 HTTP 动词。
基本动词是:
还有其他动词,但这些是最常用的动词。使用它们,可以执行 CRUD(创建、更新、读取、删除)操作。
状态码
在 API 的响应中返回状态代码。这是一个代表成功或失败的数字。当客户端向 API 发出请求时,它将在发出请求后从服务器返回状态代码。一些状态代码是:
如果您想了解更多关于其他代码的信息,可以单击此处阅读 Mozilla 文档。
REST — 再表示状态传输
术语“REST”是“Representational State Transfer”的首字母缩写。它是分布式超媒体系统的一种架构风格,由Roy Fielding于2000年在其著名的论文中首次提出(您可以单击此处阅读此文档)。
REST,即 REpresentational State Transfer,是一种架构风格,用于在 Web 上的计算机系统之间提供标准,使系统更容易相互通信。
REST 有六个指导约束,如果需要将接口称为 RESTfull,则必须满足这些约束。这些原则是:
**REST(**Representational State Transfer)是指一组软件架构设计约束,它们带来了高效、可靠和可扩展的分布式系统。
REST的基本思想是,资源(例如文档)是通过公认的、与语言无关的、可靠的标准化客户端/服务器交互来传输的。当服务遵守这些约束时,它们被视为 RESTful。
通常,HTTP API 有时通俗地称为 RESTful API、RESTful 服务或 REST 服务,尽管它们不一定遵守所有 REST 约束。初学者可以假设 REST API 是指可以使用标准 Web 库和工具调用的 HTTP 服务。
REST API 是使用 HTTP 请求获取或操作数据的应用程序接口 (API)。在REST体系结构中,客户端发送请求以获取或修改资源,服务器发送对这些请求的响应。要执行这些请求,请使用 HTTP 谓词。
术语“Restful”是指使用 REST 的实用方法,这意味着 Web API 应用 REST 原则。不完全 RESTful 的 API 并不少见,原因是一直应用所有原则有点困难,所以分析每种情况总是好的,不要受到太大的限制。确保您正在为应用程序本身构建最佳内容。
资源
REST中信息的关键抽象是一种资源。任何可以命名的信息都可以是资源:文档或图像、时态服务、其他资源的集合、非虚拟对象(例如人)等。REST 使用资源标识符来标识组件之间交互中涉及的特定资源。
每个 URI 都指向一个资源,资源表示表示系统中对象的事物,例如:产品、客户、书籍等。还可以将资源视为域模型/实体。它们是您想要获取、插入、更新和删除的内容。
任何 RESTful API 的基本概念都是资源。资源是具有类型、关联数据、与其他资源的关系以及一组对其进行操作的方法的对象。它类似于面向对象编程语言中的对象实例,但重要的区别是只为资源定义了几个标准方法(对应于标准的 HTTP GET、POST、PUT 和 DELETE 方法),而对象实例通常具有许多方法。
RESTfull API 示例
在本节中,我将展示一个 REST API 示例,以及在考虑 RESTfull 时考虑的一些原则。在此示例中,让我们考虑一个需要处理书籍的场景。我们将有一个端点来创建、获取所有书籍、获取一本书、更新和删除一本书。
URI 中的约定
在 REST 中,URI 是资源的路径。为了演示一些示例,我在本地运行一个 Web API。在本例中,基本 URI 为:。在带有地址的基本 URI 之后,指定对象/资源,在本例中是书籍,因此 URI 是这样的: 。https://localhost:5001/https://localhost:5001/books
想象一下,您有一个 CRUD 操作来处理书籍。以下是它使用 RESTful 约定的方式:
GET /api/books (to get all the books)
GET /api/books/1 (to get a single book searching by id 1)
PUT /api/books/1 (to update a book with id 1)
DELETE /api/books/1 (to delete a book with id 1)
POST /api/books (to create a book)
请注意,每个端点都包含一个 HTTP 谓词,后跟 “” 和 “”。如果我们使用“客户”而不是“预订”,那就是:“”。这是一个约定。此外,对于每个操作,它都使用特定的 HTTP VERB(例如:GET、POST、DELETE 或其他)。/api/books/api/customers/
当我们使用 RESTful API 时**,名词是可取**的。因此,在 API 的设计中,我们应该使用名词,而不是使用动词。例如,它不是像 和 ,而是: ,HTTP Verb 将指定哪种操作。通常,它也将是复数形式(而不是 ),除非在处理单个项目的情况下,例如,在将返回一本书的情况下。/getBooks/deleteBook/booksbooksbookbook/title
在 URI 中,还应使用一些唯一标识符,因为每个 URI 都必须指向特定资源。在上面的端点列表中,书籍的 id 正在某些操作(Get、Put 和 Delete)中使用,但不强制使用 id,也可能是其他一些唯一标识符。
还可以对非资源属性使用查询字符串。它通常用作格式化、排序、搜索等元素。它们是查询字符串,因为它们不是 URI 本身的一部分,它们与这些 URI 或这些资源的可选参数有关。例如,我们可以有类似的东西:或,或其他。/books?sort=title/books?page=2
为了演示 API 请求,我使用 Swagger 在本地运行一个 API(如果您使用的是 .NET 5,则在创建 Web API 项目时会自动配置 Swagger;如果您想了解有关 Swagger 的更多信息,可以单击此处访问 Swagger 的网站)。也可以使用 Postman 等工具向 API 发出请求。这是 Swagger (OpenAPI) 的示例:
特定请求的效果应取决于资源是集合还是单个项。下表总结了使用电子商务示例的大多数 RESTful 实现所采用的常见约定。并非所有这些请求都可能实现 — 这取决于具体方案:
https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design
幂等 API
REST API 必须是幂等的。幂等意味着“可以在不改变结果的情况下多次应用的操作”。这意味着 API 执行的操作必须始终导致相同的副作用。
在 GET、PUT、PATCH 和 DELETE 的情况下,它应该始终执行相同的操作。GET 应始终返回相同的数据(当然,除非系统中的某些内容发生了变化),PUT 和 PATCH 应始终在必要时执行相同的更改,而 DELETE 应删除项目或返回错误。所以,例如。如果您多次执行 PUT,它不应该在第二个或第三个请求中失败,因为没有更改任何内容,但它应该始终有效。幂等的一个例外是 POST,它从来都不是幂等的。每次对 API 进行 POST 时,它始终会返回一个新的对象/资源。
因此,如果您多次发出相同的请求并不重要,结果必须始终相同,当然,除非某些数据在两次调用之间被另一个请求更改。
缓存
当我们考虑 RESTfull API 时,缓存是另一个要求。当然,并非所有 API 都需要缓存,但为了纵向扩展,缓存是您应该考虑的事情。
“如果不能显著提高性能,缓存将毫无用处。HTTP/1.1 中缓存的目标是在许多情况下消除发送请求的需要,并在许多其他情况下消除发送完整响应的需要。(HTTP标准)
缓存是一种存储给定资源副本并在请求时返回的技术。当 Web 缓存的存储中有请求的资源时,它会截获该请求并返回其副本,而不是从原始服务器重新下载。这实现了几个目标:它减轻了服务器的负载,不需要为所有客户端本身提供服务,并且通过更接近客户端来提高性能,即将资源传输回所需的时间更少。对于网站来说,它是实现高性能的主要组成部分。另一方面,它必须正确配置,因为并非所有资源都永远保持相同:重要的是只缓存资源,直到它发生变化,而不是更长时间。
可以有服务器端缓存(这很好),这意味着例如,如果两个或多个客户端请求同一本书,则将其缓存在服务器上以便更快地返回。但是 REST API 中的缓存意味着使用 HTTP 进行缓存机制。例如,当您发出 HTTP 请求以请求某些内容并包含提供数据的最后一个版本时,响应应为 304 — 未修改。因此,在这种情况下,客户端将询问服务器是否有最新版本,而服务器不必找到它,然后将其发回以便您可以进行比较。
通过重用以前获取的资源,可以显著提高网站和应用程序的性能。Web 缓存可减少延迟和网络流量,从而减少显示资源表示形式所需的时间。通过使用 HTTP 缓存,网站的响应速度更快。
如果匹配
HTTP 请求标头使请求成为条件请求。对于 和 方法,仅当请求的资源与列出的资源之一匹配时,服务器才会发回请求的资源。对于和其他不安全的方法,在这种情况下,它只会上传资源。If-MatchGETHEADETagsPUT
执行 HTTP 缓存的另一种方法是在请求上使用名为 If-Match 的标头。因此,在这种情况下,它将被发送一个标识符来指定它在服务器上的版本,然后当客户端尝试更新(PUT)时,例如,以一种并发的方式,它将检查客户端发送的版本是否与服务器上的版本相同,如果为正,则将应用更改, 否则,它将返回 412 — Precondition Failed,因为标头成为前提条件。以下是如何使用 If-Match 的两个示例:
If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
If-Match: "67ab43", "54ed21", "7892dd"
实体标记 (ETag)
处理此缓存的一个好方法是使用实体标记 (Etags)。它支持强缓存和周缓存。强缓存用于支持将存在很长时间的内容,例如,如果您需要缓存某些内容三周。周缓存适用于生存时间非常短的内容。
HTTP 响应标头是资源特定版本的标识符。它使缓存更有效率并节省带宽,因为如果内容未更改,Web 服务器无需重新发送完整响应。此外,etag 有助于防止资源的同时更新相互覆盖(“空中碰撞”)。ETag
这些 ETag 将在响应中返回一些唯一标识符,该标识符表示服务器上资源的版本,并将在标头中返回。例如:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
如果需要返回一周类型,则在 ETAG 中先使用相同的格式,这样就可以向开发人员表明此缓存支持的强度:W/
ETag: W/"33a64df551425fcc55e4d42a148795d9f25f89d4"
此唯一标识符将在标头中返回,也可用于跟踪目的。
避免空中碰撞
使用 ETag 和 If-Match 可以方便地检测空中编辑冲突。例如,当某些内容更新时,将在响应的 Etag 标头中添加唯一标识符:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
当有人更新时,请求将包含包含要检查的值的标头:If-MatchETag
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
如果唯一标识符不匹配,则表示资源已在两者之间编辑,并且将引发 412 — Precondition Failed 错误。
支持并发 — RESTfull
支持并发的一种方法是使用生成 ETag 的组件。这是它的工作原理:
出于演示目的,让我们考虑一个场景,其中我们有两个客户端:客户端 1 和客户端 2。这是您在上图中看到的:
1 — 客户端 1 发送请求以获取 Book,并返回 ETag“123456789”。
2 — 在此期间,客户 2 还:
2.1 – 发送请求以获取书籍,并返回 ETag“123456789”
2.2 — 然后客户端 2 发送一个 PUT 请求来更新在 If-Match 标头中传递 ETag“123456789”的书籍,然后 API 检查此标头,与为该响应保存的 ETag 进行比较,如果它们匹配(在本例中,它们匹配),则将应用更新,此时将为响应生成新的 ETag, ETag “987654321”。
3 — 之后,客户端 1 发送带有包含“1234567989”的 If-Match 标头的更新,这是客户端 1 当前拥有的 ETag,现在将发生的情况是它将到达 API,并且 API 将检查此 ETag 是否与该资源的最新 ETag 不匹配,因此 API 将返回 412 前提条件失败, 并且不会应用来自客户 1 的更新,因为他正在处理该书的旧版本(他应该首先获得该书的新副本以进行处理)。
缓存未更改的资源
标头的另一个典型用途是缓存未更改的资源。如果用户再次访问给定的 URL(具有集),并且该 URL 已_过时_(太旧而无法被视为可用),则客户端将在标头字段中发送其值:ETagETagETagIf-None-Match
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
服务器将客户端的 (发送方式 ) 与其当前版本的资源进行比较,如果两个值都匹配(即资源未更改),则服务器将发回 304 — 未修改状态,不带正文,该状态告诉客户端响应的缓存版本仍然可以使用(全新)。ETagIf-None-MatchETag
提示:使用 Postman 时,默认情况下,配置设置为发送无缓存标头。因此,为了测试缓存,可以在设置中禁用此选项。
对 API 进行版本控制
对 API 进行版本控制非常重要,以防止对客户端进行重大更改。例如,假设一个或多个客户端正在使用您的 API,并且您在 API 中进行了一些更改,这些更改会影响数据的结构方式,并且一旦发布了此新更改,客户端将无法再使用 API,除非他们也更新了。因此,为了避免这种情况,我们可以使用版本控制。
例如,API 的第一个版本将是 v1.0,第二个版本将是 v2.0。当版本 2 发布时,仍然可以在一段时间内使用版本 1,您可以通知您的客户,说在一段时间内版本 1 将不再可用,他们需要更新到版本 2。在此时间段之后,您可以停用 API 的版本 1。这样,当您更新 API 时,即使您更新了可能导致中断更改的内容,这也不会直接影响已经使用它的人,并且您将给您的客户一些时间,以便他们使用最新版本的 API 更新他们的应用程序。
如果您有很多客户端,并且其中一个客户端需要尽快使用特定功能,这也很方便,但此功能可能会导致一些中断更改。在这种情况下,您可以创建一个新版本,并且需要此新功能的客户端已经可以使用它,其他客户端将有一些时间进行更新。
重大更改应始终导致 API 或内容响应类型的主版本号发生更改。重大更改包括:
如果 API 仅由您自己的团队和您自己的应用程序使用,那么为 API 创建版本控制可能太过分了,但是在 API 由内部或外部客户使用的情况下,对 API 进行版本控制非常重要。
对 API 进行版本控制的策略
REST 不提供任何特定的版本控制指南,但可以使用的一些策略包括:
使用 URI 中的 API 版本进行版本控制
对 API 进行版本控制的一种策略是在 URI 路径中包含版本。这是最常用的方法。例如,如果我们的 API 上有两个版本,则版本 2 的 URI 可能如下所示:
https://api.example.com// or using the scenario from the previous example:GET /api/v2/books
GET /api/v2/books/1
PUT /api/v2/books/1
DELETE /api/v2/books/1
POST /api/v2/books
这种版本控制方式的好处是,客户端很清楚哪个是 API 的版本。缺点是每次版本更改时,都需要更改 URI。
使用查询字符串进行版本控制
另一种策略是使用查询字符串进行版本控制,该字符串将请求特定版本的 API。例如:
https://api.example.com?v=2// or using the
scenario from the previous example:GET /api/books?v=2
GET /api/books/1?v=2
PUT /api/books/1?v=2
DELETE /api/books/1?v=2
POST /api/books?v=2
这种版本控制方式的好处是,当未指定版本时,可以使用默认版本的 API。不利的一点是,客户端很容易错过 API 的版本。
使用自定义请求标头进行版本控制
另一种版本控制策略是使用自定义请求标头进行版本控制。在这种情况下,API 的版本将位于标头中。例如:
GET /api/books HTTP/1.1
Host: localhost:5001
Content-Type: application/json
X-Version: 2// or:Accept-version: v1
Accept-version: v2
这种版本控制方式的好处是,这种方法将版本控制与 API 的其余部分分开,因此编写 API 的人不一定需要更改 API 的版本,只需要更新标头中的版本即可。不利的一点是,开发人员处理请求中的标头有点复杂(必须知道如何添加和拦截调用,以便将这些标头添加到其客户端代码中)。
使用 Accept 标头进行版本控制
另一种版本控制策略是使用 Accept Header 进行版本控制。在此方法中,您将使用 accept 标头本身来请求 API 的特定版本,而不是创建自定义标头。例如:
GET /api/books HTTP/1.1
Host: localhost:5001
Content-Type: application/json
Accept: application/json;version=2
这种版本控制方式的好处是,无需创建自己的自定义标头。缺点是,不像在 URI 中使用查询字符串或版本那样清楚,并且还需要更高的使用复杂性。
使用内容类型进行版本控制
另一种版本控制策略是使用内容类型(或 Accept 标头)进行版本控制。这是最复杂的实现方法,但可能非常有用。在这种方法中,应用程序的每个部分都可以处于特定版本中。下面是一个示例:
GET /api/books HTTP/1.1
Host: localhost:5001
Content-Type: application/vnd.applicationname.v1+json
Accept: application/vnd.applicationname.v1+json
这种版本控制方式的好处是可以对有效负载(API 中的有效负载是使用 HTTP 中的 GET 方法发送的实际数据包)以及 API 调用本身进行版本控制。缺点是创建和维护更加复杂。
提示: 使用 ASP.NET Core 时,可以使用名为 的库来对 API 进行版本控制。此外,可用于文档的软件包也利用了版本控制。Microsoft.AspNetCore.Mvc.VersioningMicrosoft.AspNetCore.Mvc.Versioning.ApiExplorer
哈特奥阿斯
HATEOAS 代表 Hypermedia as the Engine of Application State,是 RESTful API 设计中的一项基本原则,它增强了 API 的可发现性和可导航性。它通过为客户端提供动态浏览 API 的机制来增强 RESTful API 的可用性和适应性,并在创建自我描述和动态导航的 API 方面发挥关键作用。
HATEOAS 是一个约束,它需要 RESTful API 在响应中提供超媒体链接,以指导客户端在应用程序的任何给定状态下可以执行哪些操作。简单来说,API 不仅告诉客户端请求的资源,还提供指向相关资源和下一步可以执行的操作的链接。
例如,假设客户端请求有关订单的信息,在这种情况下,服务器不仅应检索订单详细信息,还应检索与此订单相关的相关资源的链接。在下面的 JSON 中,有一个响应示例:
{
"orderId": "123",
"totalAmount": 80.00,
"status": "Shipped",
"customer": {
"customerId": "456",
"name": "Customer Test"
},
"_links": {
"self": { "href": "/api/orders/123" },
"customer": { "href": "/api/customers/456" },
"shipping": { "href": "/api/orders/123/shipping" },
"cancel": { "href": "/api/orders/123/cancel" }
}
}
可以想象,实现 HATEOAS 需要额外的努力,但通过在 API 响应中提供指向相关资源和操作的链接,使 API 更易于使用,从而带来可发现性等好处,因为客户端将知道哪些是下一个可能的可用操作。
保护 API
在以下情况下,您应始终考虑保护 API:
身份验证与授权
简单来说,身份验证就是“你是谁”。身份验证是关于用于确定您的身份的信息,这可能包括用户名和密码或声明等凭据,服务器可以使用此信息来识别您并确保您与您所说的人完全一样。授权是“您可以做什么”,基于身份验证信息,基于身份本身。授权与有关权限、角色、权限的规则有关。
API 的身份验证类型
保护 API 的最常见方法是:
令牌身份验证
代币有很多种,其中最常见和最常用的是 JWT 代币。使用这种方法,令牌将在请求的标头中发送,如下所示:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它是这样工作的:客户端将使用他的凭据(例如用户名和密码)向服务器发送请求,服务器将验证此身份验证并返回一个令牌,该令牌是一系列字符。客户端不需要解码此令牌,它只会使用它来发送请求。例如,在 SPA 应用程序中,浏览器将存储此令牌(在移动应用程序中,您可以将其存储在应用程序的缓存中),并且每次客户端请求某些内容时,该令牌都将在请求的标头中发送。然后,服务器将读取此令牌,并验证用户是否有权执行该操作,并将返回响应:
智威汤逊代币
JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于将信息作为 JSON 对象在各方之间安全地传输。此信息可以验证和信任,因为它是数字签名的。可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对对 JWT 进行签名。
JWT 令牌可以包含:
要了解令牌的结构,并进行一些测试,您可以使用网站 https://jwt.io/。在下图中,有一个 JWT 令牌的示例:
在这张图片的左侧,有 JWT 代币本身。在右侧,有与此令牌相关的信息。
令牌的红色部分是关于标题的。在标头上,有关于此令牌上使用哪种加密算法(在本例中为“HS256”)以及此令牌是哪种令牌(在此示例中是 JWT 令牌,但存在多种类型的令牌)的信息。
令牌的紫色部分是关于这个令牌的数据。在图片的右侧,在“有效载荷”中,有属性“sub”、“name”和“iat”,这些信息使用算法HS256在此令牌上加密。
令牌的蓝色部分是关于签名的,它使用加密密钥。此密钥是应用程序中的密钥。使用此密钥,可以加密或解密令牌。
Extra: SOAP — 简单对象访问协议
在REST之前,另一种非常常用的模式是SOAP。REST和SOAP的主要区别在于,REST使用HTTP传输数据,但也使用文本来传输数据(数据通过文本发送,并通过文本接收),这就是为什么REST非常轻巧的原因。SOAP也使用HTTP,但基于XML,并且包含的信息比REST多得多。这使得信息更加沉重。在某些情况下,SOAP仍在使用,但总的来说,REST的使用要多得多。
如今,REST API在许多应用程序中都非常广泛,它轻巧,快速且运行良好。遵循本文中提到的原则可以让你在 API 中有一个良好的设计,但不要太局限于此,始终考虑你的应用程序真正需要什么,并利用对你的应用程序有益的原则。