服务站

构建基于 REST 的客户端

Jon Flanders

下载示例代码

在本部分的 RESTful 服务机器中我将讨论生成客户端对 RESTful 服务。 生成客户端察觉到为困难 (主要是由于缺乏的自动客户端生成的元数据,à la 从 SOAP 和 WSDL),但在实际情况,就像您编写的代码的任何其他类型:首先有是一些向上斜坡的时间获取用于一个特定的编程范例。 后在挂起的范例的, 编写针对它的代码将更容易、 更容易。 我发现这对于编写 RESTful 的服务对客户端具有许多我已经交互其他开发人员。

客户端基础

我将向客户端和使用 REST 服务之间的交互的基础知识。 只需快速概述有关 REST 之前我们的讨论。 REST 的体系结构风格相同原则提供了在万维网的基础上生成的。 客户端与服务交互,从而 HTTP 的请求,并通常包括客户端代码可以使用驱动器的应用程序的资源的表示形式的 HTTP 响应的响应的服务。

假设有一个公开信息和与 Hyper-V (虚拟化技术,内置 Windows Server 2008) 相关的功能的服务。 如果"项"此服务的 URI 是资源的 http://localhost/HyperVServices/VMs.svc/,为我进行检索标识此 URI 的表示形式的 HTTP GET 请求客户端。 在这种情况下该资源的格式为 XML 的并且代表一个特定计算机上安装的所有虚拟机 (VM) s 的列表。


图 1 一个简单的 HTTP GET 请求

为我显示在第一个在 2009 年 1 月月刊的 MSDN 杂志 》 (msdn.microsoft.com/magazine/2009.01.servicestation.aspx),REST,我服务站文章中的一个小好处的使用 REST 是可以使用 HTTP 请求的工具以使服务对您的初始测试的请求。 (对于调试问题使用这些工具是还非常有用。在这种情况下我使用称为 Fiddler (fiddlertool.com) 一个免费工具使我的所有虚拟机表示资源的 HTTP GET 请求。 请参阅图 1。 当然,Fiddler 之类的工具有价值的但要构建的应用程序需要对该服务编写代码。 我将开始进行 HTTP 请求,然后到实际读取资源的问题的基础知识。

.NET 框架具有始终提供一个基本的 HTTP API 可用于与 RESTful 服务交互。 此 API 的中心是 HttpWebRequest 和 HttpWebResponse 类型。 若要将 HTTP 请求创建和配置 HttpWebRequest 实例,然后索取该 HttpWebResponse。 图 2 显示了一个示例。 到 RESTful 服务发出请求是一个简单的设置的步骤操作:

  1. 请确保您使用正确的 URI。
  2. 请确保您使用正确的方法 (HTTP 谓词)。
  3. 如果状态代码是 200 确定,处理响应。
  4. 处理其他状态代码为相应 (多对此更高版本)。

因为很容易步骤 1 和 2,一般来说第 3 步 (和有时第 4 步) 结束更难的步骤。

第 3 步的工作大部分一些合理的方式处理资源表示形式。 由于 XML 仍是最典型的资源响应,我将在本文中介绍的。 (其他最有可能媒体类型将是 JSON)后资源 XML,您需要分析的资源,并编写代码,可以从该 resource.Using.NET 中提取所需的数据,有几个选项可用于分析 XML。 没有尝试和 $ 真 XmlReader 类。 如下类还可能,以及使用这些类型之一,可以手动分析也可以使用 XPath 定位您周围 XML 的方法。 与.NET 3.0 的出现,您也获得,XDocument 类的组合在一起使用 LINQ to XML,已变得准业界选择用于处理 XML。 图 3 显示处理 VM 的列表中的代码的示例使用 XDocument。

图 2 的简单 HttpWebRequest 代码

string uri = "http://localhost/HyperVServices/VMs.svc/";
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
//this is the default method/verb, but it's here for clarity
webRequest.Method = "GET";
var webResponse = (HttpWebResponse)webRequest.GetResponse();
Console.WriteLine("Response returned with status code of 0}",
webResponse.StatusCode);
if (webResponse.StatusCode == HttpStatusCode.OK)
ProcessOKResponse(webResponse);
else
ProcessNotOKResponse(webResponse);

LINQ 到一起的匿名类型的 XML 地很好,可以轻松处理将 XML 转换为对象的应用程序可以处理,并且当然,您还可以而不是匿名类型使用一个预定义的类型。

导致响应以自动反序列化到.NET 类型对基于 SOAP 的和基于 REST 的服务进行编程时常用的另一种方法。 在 SOAP 的情况下这通常发生在 WSDL 生成代理中。 与 Windows 通信基础 (WCF) REST,这可以在两种方法中完成。 一个方式 (我确实不建议,但我一提的完整性) 是在客户端上使用 WCF 服务约定定义使用对称 WCF 的性质。 实际上,REST WCF 支持包括名为可用于创建一个对服务约定定义的客户端通道 WebChannelFactory 类型。 建议不要在此类型的原因有两个编程的客户端。 第一次,创建客户端将成为一个非常手动和容易出错的操作。 第二个,使用强类型的服务约定,将创建一个您的客户端和服务之间的紧密耦合。 避免紧密耦合是主要原因,网站成功我们想要继续的趋势时以编程方式使用 Web。

使用 XML 序列化另一种方法是使用 HttpWebResponse.GetResponseStream 方法并反序列化 XML 到对象手动。 您可以这样使用该 XmlSerializer 或 WCF DataContract 序列化程序。 在大多数情况下,该 XmlSerializer 是最好的方法,因为它处理 XML 文档 (渚嬪的方式  属性和非限定的元素) 中的多个变体比 DataContract 序列化程序。

图 3 处理使用 XDocument 一个 RESTful 响应

var stream = webResponse.GetResponseStream();
var xr = XmlReader.Create(stream);
var xdoc = XDocument.Load(xr);
var vms = from v in xdoc.Root.Elements("VM")
select new { Name = v.Element("Name").Value};
foreach (var item in vms)
{
Console.WriteLine(item.Name);
}

问题仍看起来要圈回到事实的 RESTful 服务一般不公开元数据,可以使用自动生成全部或部分此代码。 尽管许多 RESTful 的 camp 中看不到此作为问题 (再次,任何服务定义都可以看到为紧密耦合的恶意代理的 autogenerates),肯定有的时间不使用这些工具对 REST 采用一个 hindrance。

WCF REST 初学者工具包 (asp.net/downloads/starter-kits/wcf-rest/),它是 Microsoft 的编程模型在.NET 3.5,在 REST 的增强的一个有趣的方法是提供部分 autogeneration 功能。 如果您安装该初学者工具包,您需要新的菜单项在 Visual Studio 2008 中编辑菜单下可以看到在图 4 的。


图 4 粘贴 XML 作为类型的菜单项的

使用此命令的模型就非常简单。 您将复制到剪贴板上的 XML 资源表示形式 (从一些人类可读的文档服务公开或进行请求与 Fiddler 类似工具)。 这样做后创建一组 XmlSerializable 类型是,然后您可以使用流从该 HttpWebResponse 转变的对象。 请参阅 (生成类型的正文不显示为简洁起见的起见) 图 5

图 5 粘贴使用 XML 作为类型从生成的代码

var stream = webResponse.GetResponseStream();
//in a real app you'd want to cache this object
var xs = new XmlSerializer(typeof(VMs));
var vms = xs.Deserialize(stream) as VMs;
foreach (VMsVM vm in vms.VM)
{
Console.WriteLine(vm.Name);
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "",
IsNullable = false)]
public partial class VMs
{
private VMsVM[] vmField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("VM")]
public VMsVM[] VM
{
get
{
return this.vmField;
}
set
{
this.vmField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.4918")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class VMsVM
{
//omitted
}

REST 初学者工具包不仅简化了在 XmlSerializer 的使用,但它还提供了一个非常好的 API HttpWebRequest/WebResponse API 的顶部。 下面的代码中重写使用 REST 初学者工具包的 HttpClient API 的图 1 显示了简单的 GET 请求:

 

string uri = "http://localhost/HyperVServices/VMs.svc/";
var client = new HttpClient();
var message = client.Get(uri);
ProcessOKResponse(message.Content.ReadAsStream());

HttpClient 类极大地简化了 (和使显式您正在使用哪些谓词) 使用的.NET HTTP API。 如您所见下面的代码还简化了粘贴 XML 为类型生成功能的使用:

var vms = XmlSerializerContent.ReadAsXmlSerializable<VMs>(
message.Content);

尽管 REST 初学者工具包不正式支持 Microsoft 还,它不会说明了非常重要的一点:编程的 REST 客户端可以简化,并且部分自动不具有完整的元数据 à la RESTful 服务 WSDL 文件。

使用 HTTP

客户端和服务可以充分利用 HTTP 的一种方法是将正常状态代码和标题。 在 w3.org/Protocols/rfc2616/rfc2616-sec10.html 找不到状态代码的详细信息。

我已经消失讲述有关 REST 在世界各地,通常指出错误的 RESTful 服务的优点之一就是 GET 请求可以被缓存。 没有出现错误可伸缩性,成功的网站很大程度上由于 HTTP 缓存。

一种方法利用 HTTP 缓存是使用条件 GET。 条件 GET 使客户端 ("用户代理"在 HTTP 外语) 若要进行 GET 请求用户代理已有的一个副本的资源。 如果没有更改该资源,服务器将通知用户代理该资源是完全相同为已持有用户代理的版本。 条件 GET 的效率好处是释放用于请求新创建或修改的资源,带宽用户代理程序在服务器之间网络的带宽的使用减少。 鍙 ﹀ 的方式  它保存其他所需序列化资源,但未处理的时间生成或检索资源 (因为您需要一份当前资源以将其与用户代理与该条件 GET 发送该信息进行比较) 的处理时间。

图 6 服务器端实现的条件 GET

[OperationContract]
[WebGet(UriTemplate = "/{name}")]
public VMData GetOne(string name)
{
VMManager.Connect();
var v = VMManager.GetVirtualMachine(name);
var newVM = FromVM(v);
string etag = GenerateETag(newVM);
if (CheckETag(etag))
return null;
if (newVM == null)
{
OutgoingWebResponseContext ctx =
WebOperationContext.Current.OutgoingResponse;
ctx.SetStatusAsNotFound();
ctx.SuppressEntityBody = true;
}
SetETag(etag);
return newVM;
}
private bool CheckETag(string currentETag)
{
IncomingWebRequestContext ctx =
WebOperationContext.Current.IncomingRequest;
string incomingEtag =
ctx.Headers[HttpRequestHeader.IfNoneMatch];
if (incomingEtag != null)
{
if (currentETag == incomingEtag)
{
SetNotModified();
return true;
}
}
return false;
}
string GenerateETag(VMData vm)
{
byte[] bytes = Encoding.UTF8.GetBytes(vm.ID +
vm.LastChanged.ToString());
byte[] hash = MD5.Create().ComputeHash(bytes);
string etag = Convert.ToBase64String(hash);
return string.Format("\"{0}\"", etag);
}
void SetETag(string etag)
{
OutgoingWebResponseContext ctx =
WebOperationContext.Current.OutgoingResponse;
ctx.ETag = etag;
}

像大多数其他的"高级"HTTP 的概念 WCF 不支持条件 GET 自动条件 GET 的实现是高度变量中服务实现。 但是,与为其他"高级"HTTP 的概念 WCF 提供了该工具来实现条件 GET。 有两个方法实现此目的:使用上次修改资源时,或者使用一个称为一个 ETag 的唯一标识符。

ETags 已经变得更常用方式来实现条件 GET。 在的图 6,您可以看到代码以实现 VM 服务上的基于 ETag 条件 GET。 请注意这是服务器端实现 ;将得到向客户端在一段时间。

基本流是在 ETag 由客户端,如果-无-匹配 HTTP 标头中查找并尝试匹配它生成资源在当前 ETag 服务器。 在这种情况下我正在使用每个 VM 加上上次修改时间戳 (转换为字节,然后进行到 MD5 哈希,这是一个非常常见的实现) 的唯一的 ID。 如果两个值匹配,服务器发送回一个 304 未修改的 HTTP 标头有一个空的正文,保存序列化时间,以及带宽。 客户端获取一个多快的响应,并知道它可以使用已有相同的资源。


图 7 的简单的 WPF VM 客户端

假设一个客户端应用程序,如中所示图 7.此应用程序中显示的每个 VM,名称加上 VM 的当前状态的图像。 现在假设您要更新此应用程序以匹配每个 VM 的当前状态。 要实现此目的,您将不得不计时器中编写一些代码,并更新每个记录,如果它不同于您的本地记录。 并且您需要更新时只是为了简化在的应用程序每次迭代的所有内容,但这是浪费资源的可能。

如果您的客户端上而是使用条件 GET,您可以通过发送将使用 If-无-匹配 HTTP 标头指示该资源的 ETag 一个条件 GET 请求轮询更改该服务。 VM 的收藏集,该服务可以使用大多数最近已更改的 VM 生成该 ETag,和客户端将更新才在一个或多个 VM 已更改其状态。 (如果您有大量的 VM,您可能需要为每个 VM 执行此操作。 但由于我们是数据绑定到此应用程序中集合,我确定更新整个集合)。

现在,实现此逻辑不是很难但 REST 初学者工具包实现的功能之一。 包括在代码示例中是文件 PollingAgent.cs,具有自动的条件 GET 客户端的轮询一个 RESTful 端点上名为您定义的时间间隔。 在 PollingAgent 确定资源的已更改 (因为该服务不再返回一个 302) 时, 激发回调。

因此我简单的 WPF 应用程序中我只需执行的回调 (即 HttpResponseMessage 对象) 的结果并重新绑定到新数据我控件。 图 8 显示了代码以实现使用该 PollingAgent 此应用程序。

下面的超文本

之前保留的客户端主题,要按另一重要 REST 的约束:使用该引擎的应用程序状态 (称为 HATEOAS) hypermedia。

我发现 HATEOAS 易于理解的人的 Web 上下文中。 使用人为网站时, 有时我们需要书签我们知道我们想要访问的站点。 这些的站点内但是,我们通常遵循基于我们需要一天中的超链接。 可能将 www.amazon.com,书签,但当我想购买内容时, 我按照项目添加到我的车每个页面 (资源) 上的链接。 然后我会在处理我的订单的每个后续页面上按照超链接 (在窗体)。 在订单处理的每个阶段,页中的链接表示应用程序的当前状态 (换而言哪些可以作为该用户代理程序?)。

一个重要的考虑因素时 (尽管 RESTful 服务实施者以及在现有的这一部分) 构建 RESTful 客户端保留"书签"最小。 这意味着实际上是客户端应具有很少了解可能为您的表示形式的 URI 结构但尽可能他们应该知道如何在"导航"在"超链接"在这些资源。 我将"超链接"用引号括起来,因为您的资源中的数据的任一条用于生成下一个"链接"可以相对 URI为客户端。

图 8 条件 GET 使用轮询代理 REST 初学者工具包

public Window1()
{
InitializeComponent();
string uri = "http://localhost/HyperVServices/VMs.svc/";
var client = new HttpClient();
var message = client.Get(uri);
var vms = XmlSerializerContent.ReadAsXmlSerializable<VMs>(
message.Content);
_vmList.DataContext = vms.VM;
var pa = new PollingAgent();
pa.HttpClient = client;
pa.ResourceChanged += new EventHandler<ConditionalGetEventArgs>(
pa_ResourceChanged);
pa.PollingInterval = new TimeSpan(0, 0, 5);
pa.StartPolling(new Uri(uri), message.Headers.ETag, null);
}
void pa_ResourceChanged(object sender, ConditionalGetEventArgs e)
{
var vms = XmlSerializerContent.ReadAsXmlSerializable<VMs>(
e.Response.Content);
_vmList.DataContext = vms.VM;
}

在我的示例每个 VM 的名称确实是一种链接因为客户端可以请求特定 VM 的数据的名称请求 URI 的一部分。 因此 http://localhost/HyperVServices/VMs.svc/MyDesktop (其中 MyDesktop 是名称元素在集合内的 VM 的元素中的值) 是 MyDesktop VM 资源的 URI。

假设此 RESTful 服务可以设置并启动 VM。 这会使 VM 可以将被放在某一时刻,而不是让客户端启动的未被设置正确但 VM 嵌入到不同的状态的每个 VM 资源超链接内的有意义。

使用 HATEOAS 有助于验证该的应用程序的当前状态,并将它也鼓励更松散耦合的客户端 (因为客户端不必自己那样了解服务的 URI 结构)。

比您想像的容易

没有毫无疑问构建 RESTful 客户端是努力开始这样做比构建基于 SOAP 的客户端支持的 WSDL 元数据。 为我编写了在我以前的专栏中,"REST 是简单,但简单并不一定意味着变得容易。 SOAP 是容易 (由于 WSDL),但并轻松不总是意味着简单"好的编码做法和 REST 初学者工具包类似工具中,使用构建 RESTful 客户端可以比您想象的更容易。 最后我相信您会发现将多个构成您从使用该体系结构风格获得的优点构建客户端中的任何临时延迟的。

Jon Flanders 是一种独立的顾问、 扬声器和培训师 Pluralsight 的。 他专门负责 BizTalk Server,Windows 流基础 Windows 通信的基础。 您可以在 masteringbiztalk.com/blogs/jon 与他联系。