rest开始休息


备注

本节概述了其余部分以及开发人员可能希望使用它的原因。

它还应该提及其中的任何大型主题,并链接到相关主题。由于休息文档是新的,您可能需要创建这些相关主题的初始版本。

通过RESTful HTTP API进行博客管理

以下示例使用HAL表示HATEOAS,并使用:

  • CURIE (压缩URI):用于提供API文档的链接
  • URI模板 :包含在解析URI之前必须替换的参数的URI

获取博客123

请求

GET https://example.com/api/v1.2/blogs/123
headers:
  Accept: application/hal+json
 

响应

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 123,
    "title": "The blog title",
    "description": "The blog description",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123" },
      "doc:articles": { "href": "https://example.com/api/v1.2/blogs/123/articles{?pageIndex,pageSize}", "templated": true }
    }
  }
 

在博客123中创建一篇新文章

请求

POST https://example.com/api/v1.2/blogs/123/articles
headers:
  Content-Type: application/json
  Accept: application/hal+json
  X-Access-Token: XYZ
body:
  {
    "title": "The title 2",
    "content": "The content 2"
  }
 

响应

status: 201 (CREATED)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 789,
    "title": "The title 2",
    "content": "The content 2",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
      "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
    }
  }
 

获取博客123的第789条

请求

GET https://example.com/api/v1.2/blogs/123/articles/789
headers:
  Accept: application/hal+json
 

响应

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 789,
    "title": "The title 2",
    "content": "The content 2",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
      "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
    }
  }
 

获取博客123篇的25篇文章的第4页

请求

GET https://example.com/api/v1.2/blogs/123/articles?pageIndex=4&pageSize=25
headers:
  Accept: application/hal+json
 

响应

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "pageIndex": 4,
    "pageSize": 25,
    "totalPages": 26,
    "totalArticles": 648,
    "_link": {
      "firstPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=1&pageSize=25" },
      "previousPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=3&pageSize=25" },
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=4&pageSize=25" },
      "nextPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=5&pageSize=25" },
      "lastPage": { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=26&pageSize=25" }
    },
    "_embedded": [
      {
        ...
      }, {
        "id": 456,
        "title": "The title 1",
        "content": "The content 1",
        "_links": {
          "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
          "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/456" },
          "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
          "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/456/comments{?pageIndex,pageSize}", "templated": true }
        }
      }, {
        "id": 789,
        "title": "The title 2",
        "content": "The content 2",
        "_links": {
          "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
          "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
          "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
          "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
        }
      }, {
        ...
      }
    ]
  }
 

更新博客123的文章789

请求

PUT https://example.com/api/v1.2/blogs/123/articles/789
headers:
  Content-Type: application/json
  Accept: application/hal+json
  X-Access-Token: XYZ
body:
  {
    "id": 789,
    "title": "The title 2 updated",
    "content": "The content 2 updated"
  }
 

响应

status: 200 (OK)
headers:
  Content-Type: application/hal+json
body:
  {
    "id": 789,
    "title": "The title 2 updated",
    "content": "The content 2 updated",
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "self": { "href": "https://example.com/api/v1.2/blogs/123/articles/789" },
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" },
      "doc:comments": { "href": "https://example.com/api/v1.2/blogs/123/articles/789/comments{?pageIndex,pageSize}", "templated": true }
    }
  }
 

笔记

  • 用于标识要更新的资源的标识符是URL中的标识符:必须以静默方式忽略正文中的标识符(如果有)。
  • PUT 请求更新整个资源时,如果没有content 发送,它应该已从持久资源中删除。

删除博客123的第789条

请求

DELETE https://example.com/api/v1.2/blogs/123/articles/789
headers:
  Accept: application/hal+json
  X-Access-Token: XYZ
 

响应

status: 204 (NO CONTENT)
headers:
  Content-Type: application/hal+json
body:
  {
    "_links": {
      "curies": [{ "name": "doc", "href": "https://example.com/docs/{rel}", "templated": true }],
      "doc:blog": { "href": "https://example.com/api/v1.2/blogs/123", "title": "The blog title" }
    }
  }
 

REST over HTTP

RESTRoy Fielding在他的论文 (第5章是REST的表示)中提出的与协议无关的体系结构,它将Web浏览器的经过验证的概念概括为客户端,以便将分布式系统中的客户端与服务器分离。

为了使服务或API成为RESTful,它必须遵守给定的约束,例如:

  • 客户端服务器
  • 无状态
  • 可缓存
  • 分层系统
  • 统一界面
    • 资源识别
    • 资源代表
    • 自我描述性的信息
    • 超媒体

除了Fielding的论文中提到的限制之外,在他的博客文章中, REST API必须是超文本驱动的 ,Fielding澄清说只是通过HTTP调用服务并不能使它成为RESTful 。因此,服务还应遵守进一步的规则,概述如下:

  • API应遵守并且不违反基础协议。尽管大多数时候都是通过HTTP使用REST,但它并不局限于此协议。

  • 通过媒体类型强烈关注资源及其呈现。

  • 客户不应该对API中的可用资源或其返回状态( “类型化”资源 )有初步了解或假设,而是通过已发出的请求和分析的响应动态学习它们。这使服务器有机会在不破坏客户端实现的情况下轻松移动或重命名资源。

理查森成熟度模型

Richardson成熟度模型是一种通过HTTP应用REST约束以获取RESTful Web服务的方法。

Leonard Richardson将应用程序分为以下4个层次:

  • 0级:使用HTTP进行传输
  • 第1级:使用URL来识别资源
  • 第2级:使用HTTP动词和状态进行交互
  • 3级:使用HATEOAS

由于重点是资源状态的表示,因此鼓励支持同一资源的多个表示。因此,表示可以呈现资源状态的概述,而另一个表示返回相同资源的完整细节。

另请注意,给定Fielding约束, 只有在实现RMM的第3级时,API才有效地进行RESTful


HTTP请求和响应

HTTP请求是:

  • 一个动词(又名方法),大多数时候是GETPOSTPUTDELETEPATCH之一
  • 一个URL
  • 标题(键值对)
  • 可选的身体(又名有效载荷,数据)

HTTP响应是:

HTTP动词特征:

  • 有身体的动词: POSTPUTPATCH
  • 必须安全的动词(即不得修改资源): GET
  • 动词必须是幂等的(即多次运行时不得再次影响资源): GET (nullipotent), PUTDELETE
        body  safe  idempotent
GET      ✗     ✔     ✔
POST     ✔     ✗     ✗
PUT      ✔     ✗     ✔
DELETE   ✗     ✗     ✔
PATCH    ✔     ✗     ✗
 

因此 ,可以将HTTP谓词与CRUD函数进行比较:

请注意, PUT 请求要求客户端使用更新的值发送整个资源 。要部分更新资源,可以使用PATCH 谓词(请参阅如何部分更新资源? )。

通常的HTTP响应状态

成功

重定向

客户错误

服务器错误

笔记

没有什么可以阻止你为错误的反应添加正文,以使客户拒绝更清楚。例如, 422(UNPROCESSABLE ENTITY)有点模糊:响应主体应该提供无法处理实体的原因。


HATEOAS

每个资源必须为其链接的资源提供超媒体。链接至少由以下内容组成:

  • 一个rel (用于关系分析,又名名):描述了主要资源,以及相关的一个(或多个)之间的关系
  • A href :定位链接资源的网址

还可以使用其他属性来帮助弃用,内容协商等。

Cormac Mulhall解释,客户端应根据尝试的内容决定使用什么HTTP动词 。如有疑问,API文档无论如何都应该帮助您了解与所有超媒体的可用交互。


媒体类型

媒体类型有助于提供自我描述性消息。它们扮演客户端和服务器之间的合同的一部分,以便他们可以交换资源和超媒体。

尽管application/jsonapplication/xml 是非常流行的媒体类型,但它们并不包含太多语义。它们只描述了文档中使用的整体语法。应使用(或通过供应商媒体类型扩展)支持HATEOAS要求的更专业的媒体类型 ,例如:

客户端通过向其请求添加Accept 标头来告知服务器它理解哪些媒体类型,例如:

Accept: application/hal+json
 

如果服务器无法以这种表示形式生成所请求的资源,则返回406(不可接受) 。否则,它会在保存所表示资源的响应的Content-Type 标头中添加媒体类型,例如:

Content-Type: application/hal+json
 

无国籍的>有状态的

为什么?

有状态服务器意味着客户端会话存储在服务器实例本地存储中(几乎总是存储在Web服务器会话中)。尝试水平扩展时,这开始出现问题:如果在负载均衡器后面隐藏多个服务器实例,则在登录时首先将一个客户端分派到实例#1 ,然后在获取受保护资源时将其分配给实例#2然后第二个实例将以匿名方式处理请求,因为客户端会话已在本地存储在实例#1中

已经找到解决方案来解决这个问题(例如,通过配置会话复制和/或粘性会话 ),但REST架构提出了另一种方法:只是不要使服务器有状态, 使其成为无状态根据菲尔丁的说法

从客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上任何存储的上下文。 因此,会话状态完全保留在客户端上。

换句话说,无论是否将调度分派给实例#1实例#2 ,都必须以完全相同的方式处理请求。这就是无状态应用程序被认为更容易扩展的原因

怎么样?

常见的方法是基于令牌的身份验证 ,尤其是时尚的JSON Web令牌 。请注意,JWT仍然存在一些问题,特别是关于失效自动延长到期时间 (即记住我的功能)。

旁注

使用cookie或标头(或其他任何东西)与服务器是有状态还是无状态无关:这些只是用于传输令牌的媒体(有状态服务器的会话标识符,JWT等),仅此而已。

当RESTful API仅供浏览器使用时,( HttpOnly安全 )cookie可以非常方便,因为浏览器会自动将它们附加到传出请求。值得一提的是, 如果您选择cookie,请注意CSRF (一种防止它的好方法是让客户端生成并在cookie和自定义HTTP头中发送相同的唯一秘密值 )。


具有条件请求的可缓存API

使用Last-Modified 标头

服务器可以为包含可缓存资源的响应提供Last-Modified 日期标头 。然后,客户端应将此日期与资源一起存储。

现在,每次客户端请求API读取资源时,他们都可以向其请求添加包含他们收到和存储的最新Last-Modified 日期的If-Modified-Since 标头 。然后,服务器将比较请求的标头和资源的实际上次修改日期。如果它们相等,则服务器返回304(未修改) ,空体:请求客户端应使用它具有的当前缓存资源。

此外,当客户端请求API更新资源时(即使用不安全的动词),他们可以添加If-Unmodified-Since 标头 。这有助于处理竞争条件:如果标题和实际的最后修改日期不同,则服务器返回412(PRECONDITION FAILED) 。然后,客户端应在重试修改资源之前读取资源的新状态。

使用ETag 标头

ETag (实体标签)是资源的特定状态的标识符。它可以是用于强验证的资源的MD5哈希,或用于弱验证的域特定标识符。

基本上,该过程与Last-Modified 标头相同:服务器为包含可缓存资源的响应提供ETag 标头 ,然后客户端应将此标识符与资源一起存储。

然后,客户端在想要读取资源时提供If-None-Match 标头 ,其中包含接收和存储的最新ETag。如果标头与资源的实际ETag匹配,则服务器现在可以返回304(NOT MODIFIED)

同样,客户端可以在他们想要修改资源时提供If-Match 标头 ,并且如果提供的ETag与实际ETag不匹配,则服务器必须返回412(PRECONDITION FAILED)

补充说明

ETag>约会

如果客户在其请求中同时提供日期和ETag,则必须忽略该日期。来自RFC 7232( 此处此处 ):

收件人必须忽略If-Modified-Since / If-Unmodified-Since 如果请求包含If-None-Match / If-Match 头字段; If-None-Match / If-Match 被认为是If-Modified-Since / If-Unmodified-Since 的条件的更准确的替代,并且这两者仅为了与较旧的中间人互操作而组合可能无法实现If-None-Match / If-Match

浅ETags

此外,虽然很明显上次修改日期与资源服务器端一起保留,但ETag可以使用多种方法

通常的方法是实现浅ETag:服务器处理请求,好像没有给出条件头,但仅在最后,它生成它将要返回的响应的ETag(例如通过散列),并比较它与提供的一个。这相对容易实现,因为只需要一个HTTP拦截器(并且许多实现已经存在,具体取决于服务器)。话虽这么说,值得一提的是这种方法可以节省带宽而不是服务器性能

ETag机制的更深层实现可能提供更大的好处 - 例如从缓存提供一些请求而根本不需要执行计算 - 但实现绝对不会像浅层方法那样简单,也不可插入这里描述。


常见的陷阱

我为什么不把动词放在URL中?

HTTP不是RPC使HTTP与RPC显着不同的原因是请求被定向到资源 。毕竟,URL代表统一资源定位器,URL代表URI :统一资源 Idenfitier。 URL 以您要处理的资源为目标,HTTP方法指示您要对其执行的操作 HTTP方法也称为动词 :URL中的动词使得没有意义。请注意,HATEOAS关系也不应包含动词,因为链接也是针对资源的。

如何部分更新资源?

由于PUT 请求要求客户端使用更新的值发送整个资源 ,因此PUT /users/123 不能用于简单地更新用户的电子邮件。正如William Durand在Please中所解释的那样。不要像白痴一样修补。 ,提供了几种符合REST的解决方案:

  • 公开资源的属性并使用PUT 方法发送更新的值,因为PUT 规范 通过将具有与较大资源的一部分重叠的状态的单独标识的资源作为目标来指示部分内容更新是可能的
PUT https://example.com/api/v1.2/users/123/email
body:
  new.email@example.com
 
  • 使用PATCH 请求,该请求包含一组描述资源必须如何修改的指令(例如,遵循JSON修补程序 ):
PATCH https://example.com/api/v1.2/users/123
body:
  [
    { "op": "replace", "path": "/email", "value": "new.email@example.com" }
  ]
 
PATCH https://example.com/api/v1.2/users/123
body:
  {
    "email": "new.email@example.com"
  }
 

那些不适合CRUD操作世界的行为呢?

引用Vinay Sahni 设计实用RESTful API的最佳实践

这是事情变得模糊的地方。有很多方法:

  1. 将操作重组为显示为资源字段。如果操作不采用参数,则此方法有效。例如, 激活动作可以映射到布尔activated 字段,并通过PATCH更新到资源。

  2. 将其视为具有RESTful原则的子资源。例如,GitHub的API可以让你出演一个要点PUT /gists/:id/star取消星号标记DELETE /gists/:id/star

  3. 有时你真的无法将动作映射到合理的RESTful结构。例如,多资源搜索实际上没有意义应用于特定资源的端点。在这种情况下,即使它不是资源, /search 也会最有意义。这没关系 - 只需从API使用者的角度做正确的事情,并确保明确记录以避免混淆。


常见做法

https://example.com/api/v1.2/blogs/123/articles
                        ^^^^
 
https://example.com/api/v1.2/blogs/123/articles
                             ^^^^^     ^^^^^^^^
 
  • 网址使用kebab-case (单词是小写的,以破折号分隔):
https://example.com/api/v1.2/quotation-requests
                             ^^^^^^^^^^^^^^^^^^
 
{
  ...,
  _links: {
    ...,
    self: { href: "https://example.com/api/v1.2/blogs/123/articles/789" }
    ^^^^
  }
}
 
  • HATEOAS关系使用lowerCamelCase(单词是小写的,然后大写,除了第一个,省略空格),以允许JavaScript客户端在访问链接时尊重JavaScript命名约定时使用点表示法
{
  ...,
  _links: {
    ...,
    firstPage: { "href": "https://example.com/api/v1.2/blogs/123/articles?pageIndex=1&pageSize=25" }
    ^^^^^^^^^
  }
}
 

REST概述

REST表示RE表象小号大老贸易交接,并在他的博士论文是由Roy Fielding创造的建筑风格和基于网络的软件架构的设计 。在其中,他确定了具体的建筑原则,如:

  • 可寻址资源:REST中信息和数据的关键抽象是一种资源,每个资源必须通过URI进行寻址。

  • 统一的,受约束的界面:使用一小组明确定义的方法来操纵我们的资源。

  • 面向表示:一个URI引用的资源可以有不同的格式,不同的平台需要不同的格式,例如浏览器需要HTML,JavaScript需要JSON和Java应用程序可能需要XML,JSON,CSV,文本等。所以我们与服务交互使用该服务的表示。

  • 无状态沟通:无状态应用程序更容易扩展。

  • 超媒体作为应用程序状态引擎:让我们的数据格式驱动应用程序中的状态转换。

这些架构原则的集合称为REST。 REST的概念受到HTTP的启发。向我们提供REST的Roy Fielding也是HTTP规范的作者之一。

Web服务和RESTful Web服务是暴露于Internet以进行编程访问的服务。它们是在线api,我们可以从我们的代码中调用它。有两种类型的“大”Web服务 SOAP和REST Web服务。

RESTful Web服务 :通过应用REST体系结构概念编写的Web服务称为RESTful Web服务,它关注系统资源以及如何通过HTTP协议将资源状态传输到不同的客户端。

本文档仅关注RESTful Web服务,因此我们不会深入了解SOAP WS的细节。

设计RESTful Web服务时没有严格的规则

  • 没有协议标准
  • 没有通信渠道标准
  • 没有服务定义标准

但SOAP对所有这些都有严格的规则。所有SOAP Web服务都遵循SOAP规范,该规范规定了每个SOAP Web服务应该是什么。此规范由委员会开发和管理,如果SOAP WS甚至不遵循单个规则,那么根据定义,它不是SOAP。

RESTful Web服务的概念

在设计/开发RESTful api时,需要考虑的指南很少:

  1. 基于资源的位置/ URI
  2. 正确使用HTTP方法
  3. HATEOAS(超媒体作为应用程序状态的引擎)

开发RESTful API时的主要方法应该是使API“尽可能地保持REST”。

违反REST

<stock>
    <add>
        <item>
            <name>Milk</name>
            <quantity>2</quantity>
        </item>
    </add>
</stock>
 

将这个主体放到像/stocks/123 这样的资源上会违反REST背后的想法。当这个主体被put 并且它包含所有必要的信息时,它还伴随着一个方法调用来在处理主体时add 某个地方。在REST之后,可以将item/stocks/123/items/