原文地址:http://msdn.microsoft.com/zh-cn/magazine/dd315413.aspx
RESTful 服务(配备 WCF)介绍
Jon Flanders
这是 Windows® Communication Foundation (WCF) Web 服务构建专栏系列的第一篇文章,该服务使用称为具象状态传输 (Representational State Transfer, REST) 的体系结构风格。您可能说在服务站专栏中,2009 年将是 REST 的天下,我认为这已经是迟到了许久的看法。自 2000 年面市后,REST 已在市场上受宠多年。
在本系列的第一个专栏中,我将讨论 REST 的一些基本原则,并演示使用 WCF 实现 RESTful 服务。在后续专栏中,我将深入探讨 REST 基本理念的更多细节,以及使用这一基础构建的技术。
了解 REST
体系结构风格是一组可在执行构建时应用的约束。软件的体系结构风格会对功能做出说明,这些功能用于指导建立软件系统。REST 这一体系结构风格所构建的软件供客户端(用户代理)请求服务(端点)之用。REST 是实现客户端/服务器体系结构风格的一种途径——REST 实际上是明确构建在客户端/服务器体系结构风格之上的。
REST 这一术语最早是由 Roy Thomas Fielding 在他的博士论文(
“体系结构风格和基于网络软件体系的设计”)中提出的。他当时参与制订的规范对于今天的互联网起到了巨大的推进作用,这个规范就是:超文本传输协议 (HTTP)。一般来讲,体系结构风格描述人的背景与风格讨论并无干系,但这一观点在此并不适用,因为我认为能对 REST 产生基本了解的最佳途径之一是思索 Web 及其工作方式。
我必须假定您作为开发人员对 Web 很熟悉,或者可能象我一样,每天都使用 Web 或对其痴迷。事实证明,Web 在任何时候皆可视为最大、伸缩性最强且最流行的分布式应用程序。REST 的约束采用的就是掌控 Web 的基本原则。这些原则是:
- 用户代理与资源交互,任何可命名和表达的事物都可称为资源。每项资源都有一个唯一的统一资源标识符 (URI)。
- 与资源的交互(通过其唯一的 URI 定位)使用 HTTP 标准动词(GET、POST、PUT 和 DELETE)的统一接口完成。交互中声明资源的媒体类型也很重要,它使用 HTTP 内容类型标头指定。(XHTML、XML、JPG、PNG 和 JSON 就是一些广为人知的媒体类型。)
- 资源是自我描述的。处理资源请求所需的全部信息均包含在请求本身内(这样服务可以没有状态)。
- 资源包含到其他资源的链接(超媒体)。
抽象示例
使用 REST 体系结构风格的服务通常称为 RESTful 服务或端点。为简单感受一下此体系结构风格的内部理念,我将提供一个小示例。假定我需要构建一项服务,它要使用《MSDN 杂志》
的相关数据——该服务可以告诉我《MSDN 杂志》所有的发表年份和每期中的每篇文章。对它的要求是杂志的编辑可以使用它新增文章并管理即将发表期刊的数据。
构建 RESTful 服务时,您可通过完成一组非常简单的基本步骤来设计自己的服务。
- 您需要什么样的资源?
- 将使用哪些 URI 表示这些资源?
- 每个 URI 将支持统一接口的哪些部件(HTTP 动词)?
以下是 RESTful 端点的基本构建块:资源及其表示、这些资源的 URI 及每个 URI 响应统一接口的部件。还有您可使用的更高级功能,例如更直接地使用状态代码及使用超链接管理资源状态,但本示例仅讨论基础内容。
现在,我可以使用这些步骤设计我的假想服务。资源是《MSDN 杂志》
所有的发表年份、每年发表的所有期刊及每期杂志中发表的所有文章。出于对此特殊示例目的的考虑,我将使用媒体类型应用程序/xml (XML) 表示这些资源,但请注意,RESTful 的媒体类型不仅限于 XML。
接下来,我需要为每项资源确定 URI。现在,我仅需确定相对 URI,绝对 URI 将由端点的托管位置确定。年份列表将是服务的根 URI (/)。使用此语法,/{year} 将返回每年的所有期刊;/{year}/{issue} 是每期的 URI(我按出版月份标识每期);/{year}/{issue}/{article} 代表每篇文章(我假定每期中的文章编号从 1 到 n)。
然后将 URI 映射到统一接口。由于杂志的历史记录实际上应该是只读状态,因此根资源将仅显示 GET。可以通过对 URI /{year} 执行 PUT 加入新的年份。PUT 用于在客户端知晓新资源的 URI 后新建资源,如本例所示。PUT 还能在知晓 URI 后更新现有资源。POST 用于在客户端不知晓新资源 URI 的情况下创建资源,因此在新增文章资源时会使用 POST,新文章将发给 /{year}/{issue} URI。
我可以继续讲述每项资源和每个动词,但希望您已对确定 RESTful 端点设计的步骤有了大致的了解。
图 1
中显示了资源、URI 和动词的完整列表。
图 1 操作系统支持 |
资源 | URI | 动词 |
所有年份 | "/ " | GET |
特定年份的期刊 | "/{year}" | GET、PUT |
特定期刊 | "/{year}/{issue}" | GET、PUT |
文章 | "/{year}/{issue}/{article}" | GET、POST(由系统指定文章编号),PUT、DELETE(期刊发表后将关闭 Delete) |
如果我想以客户端的身份获取 2006 年 1 月 的文章,我将对 /2006/January 执行 HTTP GET。如果我是编辑,想向 2008 年 12 月刊新增文章,客户端将把文章资源 POST 给 /2008/December,这样就能将新文章加入此期刊。通过反复使用这种模式,我就能以客户端的身份使用此服务。
您为什么应关注 REST?
现在我对 REST 做一些解释,您可能正在想为什么要对它加以关注。作为开发人员,您需要一定的动机来学习和采纳任何风格、技术或模式。如果您阅读此杂志,您可能是常与 Microsoft 技术打交道的开发人员。要实现客户端-服务器应用程序,您可能使用的是另一种体系结构风格:远程过程调用 (RPC)。无论您用过的是专用 RPC 系统(例如 DCOM 或 .NET Remoting),还是可互操作的 RPC 技术(例如,使用 ASMX 或 WCF 的 SOAP),它们都是在 Microsoft 平台上展现的客户端-服务器风格。那么,为什么要学习或使用 REST?
据我所知有两个主要原因。首先,REST 在许多方面所表现出的重要功能和优点都远胜于 RPC 技术。其次,Microsoft 正在将自身许多实现从 RPC 技术(例如 SOAP)转为 REST。这意味着即使您对使用 REST 构建自己的系统尚在犹豫,随着 Microsoft 和其他公司将更多的框架和技术转向 REST,您仍需了解如何与之交互。
以下是一份其他优点的列表(并非全部优点都已列出):
缓存使用 HTTP 向 RESTful 端点申请数据时,用到的 HTTP 动词是 GET。对于 GET 请求响应中返回的资源,可以用多种不同的方式进行缓存。Conditional GET 就是可供选择的一种实现细节,客户端可以向服务验证他的数据是否为最新版本;RESTful 端点可以通过它进一步提高速度和可伸缩性。
扩展 REST 鼓励每项资源包含处理特殊请求所需的所有必要状态。满足这一约束时,RESTful 服务更易于扩展且可以没有状态。
副作用如您使用 GET 请求资源,RESTful 服务应该没有副作用(遗憾的是,与其他一些 REST 约束相比,这一约束更容易被打破)。
幂等统一接口另外两个常用到的主要 HTTP 动词是 PUT 和 DELETE。用户代理想要修改资源时最常使用 PUT,DELETE 可以自我描述。要点(也就是“幂等”一词所强调的)是您可以对特殊资源多次使用这两个动词,效果与首次使用一样——至少不会有任何其他影响。构建可靠的分布式系统时(即错误、网络故障或延迟可能导致多次执行代码),这一优点可提供保障。
互操作性许多人将 SOAP 捧为建立客户端-服务器程序最具互操作性的方法。但一些语言和环境至今仍没有 SOAP 工具包。有一些虽然有工具包,但采用的是旧标准,不能保证与使用更新标准的工具包可靠沟通。对于大多数操作,REST 仅要求有 HTTP 库(当然,XML 库通常也很有帮助),它的互操作性肯定强过任何 RCP 技术(包括 SOAP)。
简易性与其他优点相比,这一优点更主观一些,不同的人可能有不同的感受。对我而言,使用 REST 的简易性涉及到代表资源的 URI 和统一接口。作为一名 Web 冲浪高手,我理解在浏览器中输入不同的 URI 可以得到不同的资源(有时也被称为 URI 或 URL 黑客,但绝无恶意)。由于有多年使用 URI 的经验,所以为资源设计 URI 对我来说得心应手。使用统一接口简化了开发过程,因为我不必为每个需要建立的服务构建接口、约定或 API。接口(客户端与我的服务交互的方式)由体系结构约束设置。
正如我所说的,这并不是一份详尽的列表,您也不能因此认定 REST 是唯一一种经常使用的技术。您应该了解的是它的长处,以便在需要时能够加以利用。
WCF 和 REST
WCF 是 Microsoft 的框架,用于构建使用网络通信的应用程序,无论其风格和协议为何。WCF 是要创建一个可扩展且可嵌入的框架,这样,开发人员就能学习一种编程与配置模型,将这些技能应用于多种不同的分布式系统。
虽然 WCF 的主要作用是配合 RPC(使用 SOAP),但是由于最初是做为 .NET Framework 3.0 的一个部件发布的,它的确可以公开并使用 REST 服务。它所缺乏的是能轻松搭配使用 REST 和 WCF 的编程模型。要将 REST 与 .NET Framework 3.0 配合使用,您还必须构建基础架构的一些部件。在 .NET Framework 3.5 中,WCF 在 System.ServiceModel.Web 组件中新增了编程模型和这些基础架构部件。.NET Framework 3.5 SP1 还多了几项小改进。
编程模型有两个主要的新属性:WebGetAttribute 和 WebInvokeAttribute;还有一个 URI 模板机制,帮助您声明每种方法响应用的 URI 和动词。基础架构的组成是一个绑定 (WebHttpBinding) 和一种行为 (WebHttpBehavior),为使用 REST 提供适宜的连网堆栈。此外,自定义的 ServiceHost (WebServiceHost) 和 ServiceHostFactory (WebServiceHostFactory) 还提供了一些托管基础架构方面的帮助。
WebGetAttribute 和 WebInvokeAttribute
WCF 简化连接系统构建的一种方法是:将网络信息路由给类实例的方法(您将其定义为服务的实现)。这使您可以将精力集中于服务中的代码逻辑上,而不必在意处理网络流量所需的基础结构。
WCF 默认根据操作的概念执行此路由(也称为调度)。为了执行此调度,需要在 WCF 代表您接收的每条消息内包含一个操作。每个唯一的操作均映射为一种特殊的 Action 方法。
Action 的值基于方法的名称(加上您服务的命名空间)或自定义值(通过 OperationContractAttribute.Action 属性设置)。由于默认使用 SOAP 规范的 Action 标头,所以此路由系统与 SOAP 结合得非常紧密。幸运的是,就象 WCF 中的绝大部内容一样,这一默认的调度基础架构也是可替换的。
将 REST 基础结构与 WCF 搭配使用时,会替换默认的调度程序,新程序不是以 Action 为基础,而是以传入请求的 URI 和所使用的 HTTP 动词为基础。这一路由(由 WebHttpDispatchOperationSelector 类完成)让您可以轻松实现 RESTful 端点。此调度程序由称为 WebHttpBehavior 的行为在每个端点上进行配置,您必须将该行为添加到要使用此编程模型的所有端点(稍后您会看到,不必经常手动完成这项工作)。
这项工作主要是为让 WebHttpDispatchOperationSelector 了解如何将各个 URI 和动词映射为您的方法。为此,必须将 WebGetAttribute 和 WebInvokeAttribute 添加至您 WCF ServiceContract 类型的方法中。
WebGetAttribute 告诉调度程序方法应该响应 HTTP GET 请求。WebInvokeAttribute 默认映射为 HTTP POST,但可将 WebInvokeAttribute.Method 属性设置为支持所有其他 HTTP 动词(PUT 和 DELETE 是最常用的两个)。URI 默认由方法的名称确定(添加到端点的基础 URI 之上)。
实际上,这不是非常鲜明的 RESTful,由于方法名称表示为动词,所以它们还不是您想在 REST 中处理的条目。您是希望将 URI 公开为名词。为此,WCF REST 编程模型允许为每个方法自定义 URI,具体操作是使用可在 WebGetAttribute 或 WebInvokeAttribute 上通过 UriTemplate 属性设置的模板。
UriTemplate 和 UriTemplateTable
为能针对每个方法和动词组合自定义 URI,WCF 新增了为每种资源定义 URI 的功能,它使用特殊的模板语法,例如先前我在本专栏中说明《MSDN 杂志》
服务端点所用的语法。您可以使用此语法和可替换的令牌定义您需要的 URI 结构,即结合 HTTP 动词表示每个方法(通过 WebGetAttribute 或 WebInvokeAttribute)。我将在后续专栏中探讨该语法的更多细节。
图 2
显示了《MSDN 杂志》服务的 WCF ServiceContract 定义(使用适合的属性和 UriTemplate 自定义)并应用了我在先前所述的功能。它使用 WebGetAttribute 为那些应响应 GET 的操作扩展了现有的 WCF 约定定义系统。并向操作中添加了 WebInvokeAttribute 以响应任何其他动词。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
[ServiceContract] public interface IMSDNMagazineService { [OperationContract] [WebGet(UriTemplate="/")] IssuesCollection GetAllIssues(); [OperationContract] [WebGet(UriTemplate = "/{year}")] IssuesData GetIssuesByYear(string year); [OperationContract] [WebGet(UriTemplate = "/{year}/{issue}")] Articles GetIssue(string year, string issue); [OperationContract] [WebGet(UriTemplate = "/{year}/{issue}/{article}")] Article GetArticle(string year, string issue, string article); [OperationContract] [WebInvoke(UriTemplate = "/{year}/{issue}",Method="POST")] Article AddArticle(string year, string issue, Article article); }
在本例中,我在 AddArticle 方法内添加了 Method="POST" 来提高可读性,因为 WebInvokeAttribute 的默认动词是 POST。GET 和 POST 方法均有使用 UriTemplate 属性的 URI 自定义。请注意,UriTemplate 语法允许有多个变量路径段,每段均做为参数传递给方法。
WebHttpBinding 和 WebHttpBehavior
在 WCF 中,由绑定确定 WCF 的通信方式。实际上是由绑定这一配置告诉 WCF 如何构建通道堆栈,该堆栈是一组协同工作的对象,用于提供您特殊端点所需的通信。
对于 RESTful 端点,您使用的绑定是 WebHttpBinding。与其他多数绑定不同,WebHttpBinding 相当简单,只包含两个组件:HTTP 传输和文本消息编码器(设置为不期望或使用 SOAP,只有纯 XML)。
如前所述,WebHttpBehavior 是造成使用“URI+动词”调度程序的成因对象,因此常常会组合使用 WebHttpBinding 和 WebHttpBehavior。以下是您自托管 WCF RESTful 端点时创建此类端点的代码:
ServiceHost sh = new ServiceHost(typeof(MSDNMagazineServiceType)); string baseUri = "http://localhost/MagazineService"; ServiceEndpoint se = sh.AddServiceEndpoint(typeof(IMSDNMagazineService), new WebHttpBinding(), baseUri); se.Behaviors.Add(new WebHttpBehavior()); sh.Open();
请注意,向 ServiceHost 添加端点时,我不但必须要指定 WebHttpBinding,还必须明确向端点添加 WebHttpBehavior,以便启用“URI+动词”调度系统。当然,我也可以利用配置完成这一操作(请参见
图 3)。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
<configuration> <system.serviceModel> <services> <service name="MSDNMagazine.MSDNMagazineServiceType"> <endpoint address="http://localhost/MagazineService" binding="webHttpBinding" contract="MSDNMagazine.IMSDNMagazineService" behaviorConfiguration="webby"/> </service> </services> <behaviors> <endpointBehaviors> <behavior name="webby"> <webHttp/> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel> </configuration>
WebServiceHost 和 WebServiceHostFactory
对 WCF 的一个抱怨是有时过于复杂,特别是在配置方面。为了使 RESTful 端点能够在这方面有所改进(再次重申,简易性是 REST 反复宣扬的优点),Microsoft 向 .NET Framework 3.5 中新增了两种类型:WebServiceHost 和 WebServiceHostFactory。
WebServiceHost 是源自 ServiceHost 的类型,它简化了 RESTful 端点的自托管环境。WebServiceHost 的自托管代码如下所示:
string baseUri = "http://localhost/MagazineService"; WebServiceHost sh = new WebServiceHost(typeof(MSDNMagazineServiceType), new Uri(baseUri)); sh.Open();
这是一个相当不错的优化,它没有手动添加 WebHttpBinding 和 WebHttpBehavior 的重复代码。WebServiceHost 类自动创建端点并使用 WebHttpBinding 和 WebHttpBehavior 对其进行配置。
在涉及 WCF 的受控托管环境中,在 Internet 信息服务 (IIS) 的内部,WCF 通常需要指向服务类型的 .svc 文件,以及 web.config 文件中的条目,以便向 WCF 通知端点的情况(其他配置中的绑定和行为)。
为简化受控托管环境,Microsoft 增加了 WebServiceHostFactory,它在受控托管环境中使用开源 WCF 扩展点(采用自定义的 ServiceHostFactory 类型),为诸多 RESTful 服务创建无须配置的体验。.svc 文件类似如下所示:
<%@ ServiceHost Factory= "System.ServiceModel.Activation.WebServiceHostFactory" Service="MSDNMagazine.MSDNMagazineServiceType" %>
WebServiceHostFactory 创建一个 WebServiceHost 的实例,由于 WebServiceHost 将使用 WebHttpBinding 和 WebHttpBehavior 自动配置端点,因此根本无须在 web.config 中对此端点做任何配置。(当然,如果需要自定义绑定,则必须使用配置系统或构建源自 WebServiceHost/WebServiceFactory 的类)。如果我确实需要自定义绑定,我仍可在配置文件中添加合适的条目。
图 4
显示了将在我的服务端点上开启 HTTP 基本身份验证的配置文件。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
<configuration> <system.serviceModel> <services> <service name="MSDNMagazine.MSDNMagazineServiceType"> <endpoint address="http://localhost/MagazineService" binding="webHttpBinding" contract="MSDNMagazine.IMSDNMagazineService" behaviorConfiguration="webby"/> </service> </services> <bindings> <webHttpBinding> <binding name="secure"> <security mode="Transport"> <transport clientCredentialType="Basic"/> </security> </binding> </webHttpBinding> </bindings> <behaviors> <endpointBehaviors> <behavior name="webby"> <webHttp/> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel> </configuration>
使用示例代码
通过联系 REST 说明 WCF 的功能,我已展示了在本专栏开头所提及的服务实现的主要内容。接下来介绍如何与此服务交互。
启动并运行服务后,我可利用任何客户端访问其根 URI,包括 Internet Explorer 这样的 Web 浏览器。许多人都同意,之所以能够使用浏览器快速检验 RESTful 端点,要归功于以 Web 工作方式为基础的 REST 体系结构风格。结果如
图 5
所示。
![](http://i.msdn.microsoft.com/dd315413.fig05_L%28zh-cn%29.gif)
图 5
根资源 URI
在本例中,我在 Visual Studio 2008 Web Development Server 中执行托管,它给出了基本 URI。Issues.svc 文件是受控托管环境中 WCF 的必备文件。如果我想看到特定年份的结果,我只需将它添加到浏览器的地址 (URI) 中即可(请参见
图 6)。
![](http://i.msdn.microsoft.com/dd315413.fig06_L%28zh-cn%29.gif)
图 6
表示 2007 年的资源
如果我请求 2008 年 10 月,URI 是 localhost:1355/Issues.svc/2008/October,此时它是一个空集。如果我想添加文章,我会对该 URI 执行 HTTP POST,将文章的 XML 表示做为 HTTP 请求的主体。
HTTP 另一个相当出色的功能是所有可用工具都能与其交互。我喜欢使用
Fiddler,它让我能“监视”HTTP 请求和响应。还能让我随意执行 HTTP 操作。因此,我可以使用 Fiddler Request Builder 选项卡执行 HTTP POST(请参见图 7)。在 POST 后,您可看到对 2008 年 10 月资源的请求,如图 8 所示。
![](http://i.msdn.microsoft.com/dd315413.fig07_L%28zh-cn%29.gif)
图 7
使用 Fiddler 执行 HTTP POST 请求以新建文章资源
![](http://i.msdn.microsoft.com/dd315413.fig08_L%28zh-cn%29.gif)
图 8
新增的文章资源
虽然对 Internet Explorer 和 Fiddler 是否为真正的客户端仍有争议,但以我刚刚完成的这些简单步骤为基础,即可构建实际的客户端-服务器实现(后续专栏中会介绍构建 RESTful 客户端更为复杂的示例)。客户端需要了解每项资源的 URI,以及对每个 URI. 使用统一接口的哪些部件。客户端可以随后使用这些信息构建自己的功能,与服务进行交互。
我在这一首期专栏中介绍的是 WCF 的一组基本功能,通过它们可以在您的 .NET 应用程序中轻松使用 REST 体系结构风格。这一基础能带来其他有趣的技术,包括 Web 源(RSS 和 Atom)以及支持用 JSON 编码的资源与 AJAX 交互。
在接下来的几期专栏中,我将巩固 REST 和 WCF 的这一基础知识,并深入探讨 Microsoft 平台(建立在此风格和技术上)中的几种其他功能。我还将处理更多 REST 的常见问题,例如,安全性和实现处理端点方面的问题。