第二章 理解HTTP与REST原则 2.1 软件架构(Software Architecture) 多路技术与流(Multiplexing and streams) 2.7 理查德森完备模型(Richardson maturity Model) REST表示具象状态转移(Representational State Transfer)。REST架构风格是Roy T. Fielding的一篇博士论文,题为《架构风格与基于网络的软件设计》。本文经过6年的研究,于2000年首次发表。我们可以感谢菲尔丁先生的研究工作和发现。 现代的API是围绕REST建模的,你会听到人们说,它不是RESTful的,也有人质疑,你的API是RESTful的吗? 要创建和建模一个定义良好的API,您需要对REST有充分的了解。因此,我们将对Roy T. Fielding的研究进行更深入的探讨。1993年, Roy T. Fielding开始着手解决一些问题。许多作者在网上发表他们的作品,他们想要合作。网络成了一个分享和讨论研究工作的好地方。然而,它刚一流行起来就变成了麻烦。在如何发布文档以及如何编辑文档方面,似乎缺少标准。还有与基础设施和速度有关的问题,编辑和访问文档很慢。 软件架构(Software Architecture)软件体系结构是软件系统在其运行阶段的运行时元素的抽象。一个系统可能由多个抽象层次和多个操作阶段组成,每个阶段都有自己的软件架构。 软件体系结构是通过体系结构元素(组件、连接器和关系中的数据约束)的配置来定义的,以便实现所需的体系结构属性集. 组件(Component): 这是一个软件指令和内部状态的抽象单元,它通过接口提供数据转换 连接器(Connector): 这是一个抽象的机制,用于协调组件之间的通信、协调或协作. 数据(Data): 这是从组件通过其连接器传输或由组件接收的信息元素. REST架构风格是一下几种网络架构的混合: n 数据流风格: l 管道与过滤器 l 统一管道与过滤器 n 复制(Replication)风格: l 复制仓库 l 缓存 n 层次(Hierarchical)风格: l 客户端-服务器 l 系统分层与CS分层 l 客户端-无状态-服务器 l 客户端-缓存-无状态-服务器 l 分层的-客户端-缓存-无状态-服务器 l 远程进程 l 远程数据访问 n 移动代码(Mobile code)风格: l 虚拟机 l 远程计算 l 按需编码 l 分层的-按需编码-客户端-缓存-无状态-服务器 l 移动代理 n 点对点(p2p)风格: l 基于事件的整合 l C2 l 分布式对象 l 被代理的分布式对象 REST原则REST的建模是从零开始,然后添加约束。我们将对软件架构应用约束,您的架构将成为RESTful的。 客户端-服务器(CS)请注意,在Roy T. Fielding的整个工作中,他没有提到必须将REST应用到HTTP协议。在我们的示例中,客户机服务器是浏览器,IIS是服务器. 注意,客户机和服务器的分离允许抽象。这两个组件可以独立构建,也可以独立部署。 无状态下一个要添加的约束是无状态的。服务器不应该包含工作流的任何状态。通过这种方式,客户端就是它想要的信息的驱动程序。当客户机向服务器请求数据时,客户机需要将所有相关信息传递给服务器。这种设计软件的方法创建了一个抽象,其中服务器不知道客户端;它创建了一个松散的耦合设计,这有利于变化。在本章的后面部分,我们将通过扩展幂等概念进一步研究无状态。 客户机必须跟踪它的状态。缺点是客户端必须在每次请求时向服务器发送更多的数据。 使用无状态服务器允许向外扩展,因为服务器不存储任何特定于客户机的数据。 缓存缓存是下一个约束。每当服务器传输不会更改的数据时,我们将这些数据称为静态数据。服务器可以缓存数据。 当发出第一个请求时,服务器将访问数据库以获取数据。然后应该将此数据缓存为应用程序层。对于该数据的每个后续请求都将从缓存中获取,从而将服务器的请求保存到数据库,从而更快地将响应返回到客户机。 统一接口这就是REST不同于其他网络体系结构模式的约束。组件公开的接口是通用的。服务器并不了解它的消费者。它以相同的方式为客户的所有请求提供服务。您得到的是粗粒度数据,因为并不是所有的消费者都需要这么多数据。 为了得到一个统一的接口,必须应用四个约束条件: l 统一资源(Identifying resources) l 操作资源(Manipulating resources) l 自描述消息(self-description messages) l 超媒体作为应用程序状态的引擎(Hypermedia as the engine of the application state) 系统的层结构通过分层组件,我们可以确保每个组件不知道其邻居连接到的层。这促进了良好的安全性,以便有良好的边界墙。它还允许在体系结构中使用遗留系统时保护它们,并允许保护新系统
使用分层方法,它会导致系统之间的许多跳转,但是您有安全边界,并且可以单独更新组件。 按需编码(Code-on-demand)这可能是REST最不受欢迎的特性。它允许服务器通过可由客户端执行的applet或脚本向客户端提供代码。这允许服务器为部署后的客户机提供更多的功能。约束是可选的,我们不会详细讨论它。 REST架构元素如前所述,REST不是一种协议,可以在不实现的情况下讨论它。REST的关键元素是向组件、连接器和数据添加约束的能力。 数据元素当您选择一个需要将数据从服务器传输到客户机的超链接时,客户机需要解释数据并将其呈现为一种格式给用户。REST原则是如何做到这一点的?REST组件将数据和元数据传输到客户机,并提供帮助客户机组合它所请求的资源的指令: |
|
数据元素 |
现代web例子 |
资源 |
超文本参考的预定概念目标 |
资源标识符 |
URL,URN |
数据模型表达 |
HTML文档,jpeg图像 |
模型表达元数据 |
媒体类型,最后修改时间 |
资源元数据 |
资源链接,替换,变化 |
控制数据 |
从何时修改(If-modified-since),缓存控制 |
资源是对希望共享的任何信息的引用。它可以是一张照片或一份文件,你希望与你的朋友分享。Roy T. Fielding非常精确地总结了一种资源。资源是到一组实体的概念映射,而不是在任何特定时间点对应于映射的实体。更准确地说,资源R是一个临时变化的成员函数Mr(r),对于时间t映射到一组等价的实体或值。集合中的值可以是资源表示和/或资源标识符。
当一个资源在组件之间使用时,REST使用一个资源标识符来知道它是哪个资源。
当您的资源在组件之间使用时,它应该有一个资源标识符,REST使用它来标识您的资源。
表示是希望共享的数据和与其关联的元数据的组合。表示的格式称为媒体类型。媒体将在本章后面通过一些具体的例子进行更详细的讨论。当服务器发送一些数据给客户端呈现时,媒体类型很重要;理想情况下,服务器将首先发送媒体类型,它将向客户机描述应该如何呈现数据。当客户机接收到数据时,它可以开始呈现表示形式,从而获得更好的用户体验。这与客户机接收所有数据,然后接收关于如何呈现表示的指令进行比较。
连接器的类型有客户机、服务器、缓存、解析器和隧道。您可以将连接器视为接口。它们抽象组件如何通信。在REST体系结构中,连接器的工作是支持检索资源表示以及公开资源。休息是无状态;每个请求都必须携带服务器处理来自客户机的请求所需的所有信息。
让我们来看看REST用来处理请求的模型。可以将请求与存储过程进行比较:
《架构风格与基于网络的软件架构设计》 Roy T.Fielding 5.2.1.2节 109页
《Architecture Styles and the Design of Network-based Software》
控件数据定义组件之间消息的用途,如被请求的操作或响应的意义。
REST体系结构中的组件是客户机上的web浏览器和服务器上的IIS。
HTTP代表超文本传输协议。第一个版本是0.9;然后是1.0版。
1.0和1.1之间的关键区别是客户端与服务器建立连接,并且该连接被重用(1.1),而在HTTP 1.0中,该连接被丢弃,对于每个请求,都会创建一个新连接。HTTP 1.1也是通过将REST约束应用于1.0而派生出来的。
基本的HTTP消息由头和正文组成。
当客户机与服务器通信时,它通过HTTP进行通信。服务器用消息和代码响应客户机。
状态码的范围很广,它向客户机表明服务器处理的请求发生了什么:
l 2xx:Success成功
l 200:OK
l 201:Created已创建
l 3xx:Redirection重定向
l 4xx:Client error客户端错误
l 400:Bad Request 坏请求
l 401:Unauthorized未授权
l 403:Forbidden禁止访问
l 404:Not found 找不到
l 409:Conflict 冲突
l 5xx:Server error服务端错误
l 500:Internal server error 服务器内部错误
我们将集中讨论最常见的代码,以及在本书后面实现API时将使用的代码。
我使用GitHub API来展示基本的HTTP方法。如果您希望研究这个API,可以注册GitHub并获得一个身份验证令牌。在下一章中,我们将创建自己的API。在这些示例中,我们充当API的消费者。在这些示例中,我使用Fiddler发出请求。你可以使用任何你喜欢的工具;其他常用的工具有Postman,它内置在Chrome浏览器或高级Rest客户机中。幂等性(Idempotent)是一个用于REST api的术语;简单地说,当您调用一个方法时,无论您调用它多少次,它都将返回相同的数据。在下面的例子中,我将列出哪些方法是幂等的。
使用HTTP的POST方法,目标地址为: https://api.apiopen.top/getWangYiNews?page=1&count=5. 我们在请求中也有一个头值(header value).User-Agent是头的键,对应的值为Awesome-Octocat-App. 这是文档中指定的。
POST不是幂等的,所谓的幂等在REST世界中这意味着作为一个客户端,当我多次调用一个端点时,我期望接收到相同的行为,或者期望返回相同的数据。考虑这样一个例子,您必须创建一个具有唯一电子邮件地址的联系人。当第一次调用带有email地址和其他联系信息的POST方法.服务器反馈的状态码为201(Created), 这意味着已经创建了联系人并发布了惟一的资源,您可以在其中获取该数据。
如果你再一次用POST方法推相同的email地址时,服务器会返回状态码409(冲突).说明email在数据库已经存在.因此POST不具备幂等性.
GET是幂等性,当我们第一次请求时,我们得到一个响应。发出相同的请求将返回相同的响应。这也与无状态的REST原则有关。为了实现这个(请求返回相同的数据),服务器应该是无状态的。
我们可以使用以下URL执行对表示的更新。注意HTTP动词PUT。
文档中说过,我们可以在下面的资源中调用PUT方法,并将/ *作为URI的一部分。PUT用于修改我们的表示。一般来说,PUT会有一个body。通常,PUT的概念与POST类似,只是URI包含在调用POST方法时接收到的标识符.
PUT的行为类似于不存在资源时的POST。在我们的示例from POST中,如果您必须在第一次使用POST请求调用PUT来创建联系人,那么您将收到201,通知您已经创建了资源。然后,如果必须再次调用PUT上的请求,则会返回200个具有相同数据的请求。这样,PUT就是幂等的。
DELETE与GET非常相似。我们的HTTP方法是DELETE,我们希望撤消用put创建的星型PUT.DELETE通常没有正文:
删除是幂等;当您调用DELETE时,将返回200,这表示资源已被删除。再次发出此请求将导致404,未找到资源,因为数据已被删除。再次调用将导致404。虽然响应已经从200更改为404,但您仍然返回相同的行为,服务器也没有做任何不同的事情。
HTTP/2是HTTP 1.1的一个优化。许多浏览器已经支持HTTP/2;你的Chrome浏览器已经做到了这一点。
HTTP/2是两个规范的组合:超文本传输协议版本2 (RFC7540)和HTTP2的HPACK头压缩(RFC7541)。
当在传输层安全性(TLS)上使用HTTP/2时,使用“h2”表示协议。
当HTTP/2在明文TCP上使用或HTTP.1.1升级时,将使用“h2c”字符串。
一个使用GET请求的例子为:
RCF 7540 3.2节
GET/HTTP/1.1 Host: server.example.com Connection:Upgrade,HTTP2-Settings Upgrade:h2c HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload> |
这个请求来自一个不知道是否支持HTTP/2的客户端。它发出一个HTTP 1.1请求,但在报头中包含一个升级字段“h2c”,以及至少一个HTTP2-Settings报头字段:
不支持HTTP/2的服务器将按如下方式响应:
HTTP/1.1 200 OK Content-Length:243 Content-Type:text/html |
这看起来像是一个常规的HTTP/1.1响应
如果服务器支持HTTP2,响应将如下:
HTTP/1.1 101 Switching Protocols Connection:Upgrade Upgrade:h2c [….] |
在HTTP2中引入了一个帧作为基单元。你可以把一个帧想象成一个通过电线传输的包。对于请求和响应,头和数据帧用作构建块,对于HTTP/2特性,帧是SETTINGS、WINDOWS_UPDATE和PUSH_PROMISE。
服务器和客户机都可以使用服务器和客户机之间的单个连接来传输多个请求。假设您有一个包含多个组件的页面,所有这些组件都向服务器发出独立的请求—例如,一个将获取今天的天气信息,一个将获取最新的股票价格,一个将获取最新的头条新闻。它们都可以用一个连接来完成,而不是三个单独的连接。对于服务器也是如此。你最终得到的是更少的连接。
服务器可以将数据推送到客户机。当客户端从服务器请求数据时,服务器可以计算出客户端还需要一些其他数据。服务器可以将这些数据推送到客户机。客户端总是可以通过向服务器发送应该禁用推送数据的信号来拒绝数据。服务器发送给客户端的数据被称为一个PUSH_PROMISE框架。如果实现了HTTP缓存,则数据存储在客户机的缓存中。
流就像一个隧道,有许多汽车从两个方向通过,汽车被框架代替,流是独立于客户机和服务器之间的。在HTTP/2中,一个连接可以有多个流,一个请求的帧分布在多个流中,尽管帧的顺序很重要。这是对HTTP 1.1的重大改进,后者使用多个连接来呈现单个页面。
拥有多个流是很好的,但是有时候,您希望在另一个流之前处理一个流。在HTPP/2中,客户端可以在HEADER帧中指定流的优先级。客户端可以使用Priority框架更改流的这个优先级。通过这种方式,客户机可以向它的对等方表明它希望如何处理它的请求。
与文本相比,二进制格式的消息处理速度更快。由于它们是本地二进制格式,所以不需要通过TCP协议将文本转换为二进制。
随着web的发展,越来越多的数据从服务器发送到客户机,又从客户机发送到服务器。HTTP 1.1不压缩报头字段。HTTP通过TCP工作,请求通过这个连接发送,其中头文件很大,包含冗余数据。TCP在慢启动时工作,由网络阻塞避免算法实现,该算法将数据包置于网络之上。如果压缩了头文件,就可以通过网络发送更多的包。HTTP/2解决了报头压缩的问题,它利用了TCP,从而提高了数据的传输速度。
媒体类型通常是指多用途互联网邮件扩展MIME(Multipurpose Internet Mail Extensions)类型,媒体类型在HTTP消息体中被标识.媒体类型的标记遵从{类型/子类型}的格式,例如:
l Text/html
l Image/png
l Audio/mp3
l Video/avi
一个请求可以如下:
GET:
Host:
Accept:application/json,text/JavaScript
客户端正在指定它可以以哪种格式接收数据。
RMM理查德森完备模型由Leonard Richardson发明,它作为标准用来升级你的API.
它是传统的基于soap的web服务或XML-RPC服务。它使用HTTP,但是有一个方法和一个URI。这个方法通常是POST,将返回一个大数据集。我相信我们所有人都使用过这种类型的web服务,或者可能在某个时候遇到过它。整个数据库作为数据集包装在这个输出中。
资源是公开的,但是您仍然有一个HTTP方法。如果您处于0级,那么将web服务更改为返回资源将把您从0级带到1级。您仍然有一个HTTP方法,但是当您的方法被调用时,您的服务将传递回一个资源.
Request:
[Post]Diet/ate {Response: Dite/ate/12789}
Request:
[Post]Diet/ate {Response: Dite/ate/99000}
同样的请求端点,返回值却不一样.
第2级用于使用HTTP谓词。在第1级,我们介绍了资源,第2级介绍了动词.
使用前面的例子,当您在上午10点发布您所吃的东西时。服务器将返回一个资源。使用此资源,您可以执行(对该资源执行GET,并查看您在上午10点所吃食物的详细信息)。
GET:Dite/ate/12789 Response{ Time:10:10, Apple:1, Water:2 } |
你还可以对该资源实施PUT(修改)操作.
PUT:dite/ate/12789 { Time:10:10, Tea:1, Water:2 } |
如果你后来意识到你没有在上午10点吃东西,你也可以删除这个资源。
DEL:dite/ate/12789
对同一个资源实施了不同的动词
当使用L1创建了一个资源后,在此用POST更改资源时会返回201(已创建)错误,当相同资源已经存在时候还会返回409(冲突)错误.
在第3级中,超媒体被引入到我们的响应中,通常称为HATEOAS(超文本作为应用程序状态的引擎 Hypertext As The Engine of Application State)。
以POST为例:
POST:dite/ate Response: { "id":12789, "links": [ {"rel":"self","href":"http://yoursitename/dite/12789"}, {"Rel":"self","href":"http://yoursitename/dite/12789"}, "rel":"rating", "href":"http://yoursitename/dite/12789/rating" ] } |
这个链接的目的是让消费者知道它可以执行什么操作。虽然两个端点看起来是一样的,但消费者会发现其中一个是DELETE另一个是PUT。最后一个链接是评价您所添加的食物的资源。
我们研究了休息的定义以及如何得到RESTful。当您查看REST架构时,您应该能够将其描述为三个类别,正如Roy T. Fielding所解释的那样。一个是流程视图,它描述了数据如何从客户端流向几个组件。第二,连接器视图专门用于在特定于资源和资源标识的组件之间交换消息。第三,我们称为表示的数据如何从服务器传输到客户机的数据视图。很重要的一点是要对REST原则有很好的理解,为了派生出HTTP 1.1, REST被应用到HTTP 1.0。
HTTP是REST原则的一个活生生的例子。像GET和POST这样的操作是无状态的,这是REST原则。示例展示了如何构造HTTP请求以及服务器作为响应发回的内容。有了HTTP/2,我们有了新的特性,这使我们的传输速度更快,应用程序响应更快。
理查森完备模型解释了api是如何分类的;作为一名开发人员,您应该瞄准Level3的模型。如果您是API的消费者,可能需要在几个选项中进行选择。RMM将帮助您做出这个明智的决定。
在本章中,我们没有关注特定的技术;在下一章中,我们将更深入地研究ASP。NET Core以及它作为一个框架提供了什么来构建web api。
咨询电话
0371-68632068