65.9K
CodeProject 正在变化。 阅读更多。
Home

REST Web 服务的一切——是什么和如何做——第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (147投票s)

2007年11月4日

CPOL

13分钟阅读

viewsIcon

398344

想设计一个 REST Web 服务吗?想了解 REST 是什么以及如何着手吗?传统 Web 服务和 REST 服务有什么区别?本文从基础讲起,假设您对 REST 一无所知,并指导您如何成为一名 REST 开发者和设计师。

引言

REST 是 **Re**presentational **S**tate **T**ransfer 的缩写。在 Yahoo 或 Google 上搜索“REST Web Services”会得到海量信息。但如果你和几周前的我一样,可能会觉得所有这些信息都令人烦恼而非有益,因为你想尽快创建一个 REST Web 服务。我们已经习惯了从互联网上获取量身定制的指导——而我们最喜欢的就是一步步的操作指南。尽管这有点“邪恶”,但“操作指南”是我们最爱挖掘的。在这次网络搜索中,我却没有得到。

很快我发现,编写 REST Web 服务没有捷径——你必须首先完全理解 REST 是什么。我有一个绝妙的比喻——想象一个 C++、JAVA 或 .NET 新手试图找到一篇关于“如何进行面向对象设计”的“手把手”文章。嗯,祝你好运!REST 就是这样——专家称之为“架构风格”,但我用自己的话来写这篇文章,所以我会避免那些一开始就让我困惑的东西。我认为 REST 是架构和设计融合的地方,两者之间细微的界限消失了。

让我们直奔主题。我将从一个对 REST 一无所知的传统 Web 服务开发者需要了解的内容开始。我不会一开始就抛出术语,那样就像试图从混乱中创造秩序。相反,我将始终保持条理清晰。

从传统 Web 服务过渡到 REST 设计

想想“传统 Web 服务”是什么。它是一个暴露“方法”的接口。客户端知道方法的名称、输入和输出,因此可以调用它们。

现在想象一个**不**暴露“方法”的接口。相反,它暴露“对象”。所以当客户端看到这个接口时,它看到的是一个或多个“对象”。“对象”没有输入和输出——因为“它**什么都不做**”。它是一个名词,而不是一个动词。它是一个“事物”,而不是一个“动作”。

例如,考虑一个提供当前天气条件的传统 Web 服务,你只需提供一个城市。它可能有一个名为 `GetWeatherInfo()` 的 Web 方法,该方法接受一个城市作为输入并提供天气数据作为输出。到目前为止,您很容易理解客户端如何使用这个 Web 服务。

现在想象一下,在上面的 Web 服务的位置,有一个新的服务暴露了**城市**作为对象。所以,当你作为客户端查看它时,你看到的是纽约、达拉斯、洛杉矶、伦敦等等,而不是 `GetWeatherInfo()`。而这些城市本身并没有任何特定的应用程序方法——它们看起来就像惰性气体——它们本身不会产生反应。

你可能在想——嗯,这怎么能帮助你,作为客户端,获取达拉斯的天气呢?我们很快就会讲到。

如果 Web 服务只提供“一组对象”,那么你显然需要一种方式来“对它们进行操作”。对象本身没有方法供你调用,所以你需要一组可以应用于这些对象的动作。换句话说,你需要“将一个动词应用于名词”。如果你看到一个对象,比如**一个苹果**,它是一个“名词”,你可以对它应用“一个动词”,比如**吃**。但并非所有动词都可以应用于所有名词。比如,你可以**驾驶**一辆**汽车**,但不能**驾驶**一台**电视机**。

因此,如果一个 Web 服务只暴露对象,而你被要求——好吧,现在让我们设计一些“所有客户端都可以应用于它们看到的任何对象的”标准**动作**或**动词**,你会选择哪些动词?你会选择**驾驶**吗?不。因为这样的话,这个动作就不能应用于大多数暴露的对象。哪些动词“可以应用于所有名词”?我不知道你,但“获取”(GET)首先出现在我脑海里!

是的,你可以“获取”(get)一个苹果,“获取”(get)一辆汽车,甚至“获取”(get)一个城市!从编程的角度来看,“获取”(get)意味着“检索关于……的信息”。

GET 有一个对应词——PUT。 “放置”(put)意味着“更新关于……的信息”。等等……我们听起来是不是异常地接近 HTTP?

当你.在浏览器中输入一个 URL 时,其底层发生的动作序列与我刚才描述的非常相似!所以你的浏览器应该显示你输入的 URL 的相关信息,但它从哪里获取这些信息呢?如果你把那个 URL 当作“对象”,那么浏览器就需要从远程服务器“获取”“关于那个对象”的信息。因此,它会发送一个 HTTP GET 请求。

HTTP 是一个“协议”。它规定:客户端(在这个例子中是浏览器)可以向服务器发送一个请求,该请求可以是 `GET`、`PUT`、`POST` 或 `DELETE`(也有其他动词——HTTP 1.1 支持 8 个动词,但我们先只关注这 4 个)。所以如果你停下来想一下,整个互联网这个错综复杂的网络实际上是基于这些动词工作的,你就会突然意识到这些动词有多么强大和伟大——突然你对一组**通用动作**的搜索就结束了。答案就在你眼前:HTTP 动词。你在网上做的几乎所有事情都是通过使用这些动词之一来完成的——这足以证明这些动词应该是“穷尽的”。这意味着,如果一个 Web 服务只暴露对象,并支持对这些对象的这四种操作,**任何客户端都应该能够用该 Web 服务做任何事情**!

恭喜您,您已经过渡到了 REST Web 服务!REST Web 服务是指只暴露对象,并支持在其上执行一组动词的服务。所以回到我们的天气示例,您的暴露城市信息的 Web 服务也将支持对这些城市执行 `GET` 操作。因此,当客户端发送一个类似“GET LOS ANGELES”的请求时,响应可能是洛杉矶的天气数据。响应也可能是关于洛杉矶的任何其他信息——比如它的人口、一系列图片或它的历史。但您的 Web 服务可能不会做所有这些。

所以为您的 Web 服务选择一个好的域名——比如 weatherinfo。因此,一个客户端请求,比如

GET http://weatherinfo.com/45327 HTTP/1.1 

其中 45327 是洛杉矶的城市 ID,是您的 Web 服务的一个有效请求。您的 Web 服务可能响应:

1 HTTP/1.1 200 Ok
2 Date: Sat, 03 Nov 2007 07:35:58 GMT
3 Content-Type: text/xml
4 Content-length: 139
5
6 <City name="Los Angeles" datetime="2007-11-03 07:35:58 GMT" >
7 <Condition>Overcast</Condition>
8 <Temp>69.5</Temp>
9 <Humidity>80</Humidity>
10 </City> 

第一行是初始行,第二至第四行是 HTTP 头(可以有很多头,这里只显示了 3 个),第五行是头部和正文之间必需的空行,第六至第十行构成“HTTP 正文(或内容)”——这部分是响应携带的数据,可以是任何格式,不一定是 XML。事实上,网络上最常用的格式是 HTML——这是 Web 服务器用来将数据发送回浏览器的一种格式。“Content-type”头通常会指定它。但如果你正在编写一个 Web 服务,XML 是一个更好的选择,但那只是我个人的看法。如果您的 Web 服务不返回复杂或复合数据,格式就不需要是 XML——它可以是 text/plain,在这种情况下,正文将只是一个字符字符串。

这就是您第一个 REST Web 服务的输入和输出!

REST 不仅仅是关于 Web 服务

HTTP 创建者有同样的目标——一个用于客户端-服务器通信的通用而简单的协议。所以我们并没有真正地重新发明轮子——编写一个 REST“Web 服务”与编写一个“Web 服务器应用程序”并没有太大区别。难道不应该是这样吗?因为这一切都是关于 Web。但也有显著的区别——不是技术上的差异,而是使用上的差异。让我们来处理这些差异一段时间。

首先,当浏览器(想象一下客户端)发出 HTTP 请求时

GET http://www.yahoo.com HTTP/1.0 

浏览器期望返回“大量数据”——文本、标题、图片和链接——就像你在 Yahoo 主页上看到的那样。所有这些数据都以“HTML 格式”返回到您的浏览器(作为 HTTP 正文)。但是,当客户端调用一个“Web 服务”来获取某些信息时,通常是获取某些特定的信息。因此,Web 应用程序和 Web 服务之间的第一个区别是,响应 `GET` 的 HTTP 正文或内容在内容和格式上差异很大。

其次,浏览器除了发出 `GET` 命令外,几乎不需要发出任何其他命令,所以大多数 Web 应用程序都没有编码来响应,比如 `PUT` 或 `DELETE`。然而,`POST` 被许多服务器处理,但这主要是因为 `POST` 是被滥用最多的 HTTP 动词——很多时候,Web 应用程序要求客户端发送 `POST` 请求不是因为 `POST` 是正确的,而是因为 `POST` 可以灵活地用于任何通用的请求,这些请求会带一些传入数据并返回一些传出数据。

然而,一个设计良好的 REST Web 服务应该能够处理 `GET`、`POST`、`PUT`、`DELETE` 和 `HEAD` 操作。我们将在本系列第二部分的“设计 REST Web 服务的步骤”一节中详细介绍这些操作。

本节的要点是:*“Web 服务与网站的区别仅在于使用方式。”* 其他一切都相同——实际上,请求/响应协议也可以相同。HTTP 动词足以被任何 Web 服务使用,因为它们已经成功地模拟了所有在线通信。那么,为什么我们要编写一个 Web 服务来创建自己的动作或动词呢?创建一个像 `GetWeatherInfo()` 这样的 Web 方法只不过是创建你自己的动词。这种风格,可以理解,是我们桌面编程风格的自然延伸——类和接口有方法,所以 Web 服务也必须有方法。那么,为什么我们称之为**Web**服务呢?

这些不仅仅是挑战长期以来行之有效的成功传统方式的问题。这些问题实际上帮助我们理解为什么 SOAP 被发明出来。你知道 SOAP 是为什么被发明的吗?SOAP 是为了结合这些不同的风格——**Web** 的风格和**服务**的风格(服务=接口或类)。Web 请求使用 HTTP 协议,但不知何故,它必须传达所需动作的名称——`GetWeatherInfo`。不仅如此,它还必须携带参数,并传出返回数据。所有这些都是通过一个称为 SOAP 的复杂混合体实现的。

但我们之所以必须发明这种混合体,仅仅是因为我们懒得放弃传统的“方法调用思维方式”。

像 Web 一样思考的两个巨大挑战

你可能会问——用简单的 HTTP 动作来替代传统的 Web 方法调用以实现所有目标,可能并不容易。你说得对——这并不容易。但付出的努力是值得的。让我们来分析一下这两个巨大的挑战。

  • 第一——有效地用少数简单动词替换所有操作需要勇气
  • 第二——类似 Web 的请求/响应机制意味着“你必须拥抱无状态性”

我们一个一个来看。我们已经明白了如何替换 `GetWeatherInfo`。但是,我们如何替换 `GetLoanApprovalDecision` 呢?或者在电子商务的情况下,我们如何替换 `Buy` 呢?

设计 REST Web 服务,正如你将学到的,基本上包括两个步骤:决定要暴露哪些对象,然后,决定如何响应这些对象上的 `GET`、`PUT`、`POST` 和 `DELETE` 操作?这些就是你武器库中的武器。它们有限制或有约束吗?在某种程度上,是的,因为你无法设计一个像 `GetLoanApprovalDecision` 这样的 Web 方法。但这种限制是好的——它迫使我们所有人以标准化的方式设计 Web 服务,强制执行一些简单而强大的规则,这些规则绝非不足。同样,这就像利用面向对象方法论的力量——给定一个冗长复杂的 C 或 VB6 程序,你能将其转换为面向对象的设计并仍然做同样的事情吗?当然可以。如果你不能,你可能需要回到教室去;你不能说 OOD 对于这个场景来说不够好。在我看来,REST 设计就是这样的——如果你实在想不出一种以 REST 式方式模拟 `GetLoanApprovalDecision` 的方法,那就去买一本 REST 的教科书吧。REST 可以做到,只是你还没有准备好去构思。

我的下一篇关于这个主题的文章(第二部分)将从“设计 REST Web 服务的步骤”开始——我将在其中尝试回顾我们所需的思维过程,以便能够做到这一点。

转向第二个挑战,无状态性:无状态性的本质是任何调用都不需要引用任何先前的调用。所有调用都是独立的。服务器在处理某个调用时,不需要知道先前调用发生了什么。你想要一个例子吗?一个客户端必须先调用 `Login()` 然后才能调用其余内容的 Web 服务怎么样?这有状态吗?不。因为在处理第二个调用时,Web 服务必须记住该用户已经在之前的调用中登录了。该“登录状态”在 Web 服务中得以保留。客户端在第二次或第三次调用时无需再次传递凭据。这不是无状态性,而是“有状态性”。REST**不允许**这样做,因为一个真正可扩展且可扩展的 Web 服务(或网站)不应该是 [有状态的]——它应该是无状态的。状态使事情变得如此复杂,以至于根本不值得。允许状态在调用之间蔓延,会让你无法在不同的机器上处理后续的客户端请求。我将不深入讨论有状态的优缺点——这篇文章不是关于这个的。但关键是,我们经常在传统 Web 服务中内置状态。当我们转向 REST 时,我们就不能再这样做了,因为 Roy Fielding 明确禁止在 REST 中存在状态。 Roy Fielding 是谁?他的名字出现在 HTTP 规范委员会的页面上。他是第一个使用“Representational State Transfer”这个词的人。

在本系列的下一部分,我将详细介绍设计 REST Web 服务的具体细节,并触及其实施。我们将了解为什么选择 REST(支持 REST 的论据)、如何构建 REST 服务和 REST 客户端。

© . All rights reserved.