简介
UDDI(通用说明、发现和集成)通常被称为 Web 服务的“黄页”。虽然黄页这一类比很有用,但它不能完整体现 UDDI 是如何融入基于 Web 服务的软件体系结构中的。黄页类比只说明了 UDDI 的设计时 用法,即通过基于关键字、类别或接口的搜索来查找并使用 Web 服务的功能。从设计时角度来看,黄页类比十分准确:就像黄页将企业及其电话号码分类并编成目录一样,UDDI 将提供商及其 Web 服务分类并编成目录。开发人员可以在 UDDI 中查找 WSDL 文件和访问点,然后将这些 Web 服务并入客户端应用程序。
不过,UDDI 不仅是提供设计时支持。黄页类比并没有说明 UDDI 如何提供运行时 支持。发现过程结束后,UDDI 将扮演一个很重要的角色。在运行时通过编程方式查询 UDDI 这一功能使 UDDI 可以用作基础结构,用于构建可靠有力的 Web 服务应用程序。
UDDI 运行时基础结构
将 Web 服务集成到客户端应用程序中后,需要考虑可能会遇到的问题。其中一个关键问题是无法预测或检测托管 Web 服务的提供商的故障,或者从故障中恢复。如果 Web 服务出现故障,客户端应用程序可以采取什么措施?应用程序如何适当且动态地从失败的 Web 服务调用中恢复?
同样,从 Web 服务提供商的角度看,Web 服务的所有者如何提供关于更改的动态更新呢?我们需要考虑将 Web 服务迁移到新服务器的情况。如何通过有效方式将此更改通知 Web 服务的客户端?所有者如何在运行时分发此信息,而不会导致 Web 服务的所有客户端中断?
在上述情况下,UDDI 在提供基础结构以支持运行时 Web 服务方面能够扮演十分重要的角色。UDDI 通过定义调用规则来解决这些“服务质量”问题,该调用规则包括缓存绑定信息(如 Web 服务的访问点)以及针对此特定实现的其他参数。发生故障时,客户端可以发出运行时 UDDI 查询,用最新信息刷新缓存的信息。
上述规定的模式如下:
• |
在 UDDI 中查找 Web 服务。使用此 Web 服务(以 UDDI tModel 表示)的 WSDL 文件以及实现细节(如访问点和其他配置信息),所有这些信息都包含在 UDDI bindingTemplate 中。 |
• |
为特定的 Web 服务准备一个客户端应用程序。在客户端应用程序中,缓存 Web 服务的唯一 bindingKey,以便在首次使用该应用程序时,从 UDDI 检索所需的所有信息。 |
• |
当应用程序调用远程 Web 服务时,使用从 UDDI Web 注册表获得的缓存数据。 |
• |
如果调用失败,则使用 bindingKey 值和对 UDDI 注册表的 get_bindingTemplate API 调用以获取绑定信息的最新副本。 |
• |
对新旧信息进行比较:如果不同,则重试失败的调用。如果重试成功,则用新数据替换缓存的数据,并存储新数据以便以后调用。不过,如果返回的绑定信息相同,则提供商不进行任何更新,且应用程序将引发错误。同样,如果有新的绑定信息,但调用仍然失败,应用程序也会引发错误。 |
从 Web 服务提供商的角度来说,提供商应当知道何时可以更新该 Web 服务的 UDDI 项。当 Web 服务的提供商需要将流量重定向到新位置或者需要备份系统时,它只需激活备份系统,然后在 UDDI 注册表中更改访问点。此方法称为失败时重试,为客户端提供了一种在运行时从故障中恢复的机制。
示例方案
我们可以看一个示例,以了解此模式的工作原理此示例方案涉及的是一家虚构公司的方案,该公司需要向其内部部门提供实时销售数据。因此,此 Web 服务不能公开,而只在防火墙内部使用。
首先,我们需要一个 Web 服务。在此示例中,我们将提供一个非常简单的 Web 服务,它只支持 GetSalesTotalByRange 一种方法,这种方法使客户端能够获取某一日期范围内实时销售数据的快照。
接下来将创建使用此 Web 服务的客户端。我们将客户端配置为可以缓存访问点和 bindingKey 信息,并为客户端设置一种机制,以便在发生故障时从 UDDI 注册表刷新客户端缓存。
创建 Web 服务:C# .NET .asmx
Microsoft .NET 框架大大简化了 Web 服务的编写工作。在本例中,我们将创建一个简单的 Web 服务,它只包含 GetSalesTotalByRange 一种方法,该方法使用两个日期作为输入参数,并返回两个参数。下面是一个实现了此目的的 .asmx 页,SalesReport.asmx:
<%@ WebService Language="c#" Class="SalesReportUSA.SalesReport" %> using System; using System.Web.Services; namespace SalesReportUSA { [WebService(Namespace="urn:myCompany-com:SalesReport-Interface")] public class SalesReport : System.Web.Services.WebService { [WebMethod] public double GetSalesTotalByRange ( System.DateTime startDate, System.DateTime endDate ) { return 5000.00; } } }
此页应添加到虚拟目录中。要使客户端示例工作,请创建一个名为 SalesReportUSA 的虚拟目录 (http://localhost/SalesReportUSA/SalesReport.asmx)。请注意,此 Web 服务始终返回 5000.00 作为返回值。(如果销售报表有这样的可预见性就好了!)真实的应用程序应当使用数据库调用来检索此信息。对于本示例,只需要一个硬编码的值。
部署此 Web 服务的下一步是在 UDDI 注册表中注册该服务。此 UDDI 注册表是一个内部 UDDI 服务器,公开此 Web 服务没有什么意义。Microsoft 通过 Microsoft? Windows? .NET Server 提供本地 UDDI 服务。(请参看 Windows Server 2003 Web 站点获得此功能的详细信息)。如果没有安装 Microsoft .NET Server,您也可以使用 Microsoft UDDI 软件开发包 (SDK) 在本地计算机上安装 UDD。
可以通过两种方式在 UDDI 中注册 Web 服务:使用 Web 用户界面注册或者使用 UDDI SDK 通过编程方式注册 Web 服务。SDK 使用起来非常方便,您可以参阅 使用 UDDI 的 Web 服务描述和发现 专栏中发布的代码示例。无论使用哪一种方法,都需要先将 Web 服务的 WSDL 文件注册为 tModel。UDDI tModel 是 XML 实体,用于表示接口和抽象的元数据,因此,WSDL 文件表示为 tModel。然后,您需要将 Web 服务的访问点注册为 bindingTemplate。UDDI bindingTemplate 是 XML 结构,用于表示有关给定 Web 服务的实现细节。(有关 UDDI 架构及其与 WSDL 的关系的详细信息,请参阅 http://www.uddi.org UDDI“最佳实践”文档 Using WSDL in a UDDI Registry, Version 1.07)。
以下是使用 UDDI 服务完成上述步骤后得到的 UDDI bindingTemplate 结构的示例。请注意,serviceKey、bindingKey 和 tModelKey 都是由 UDDI 生成的,并且对于我们保存的实体来说是唯一的。由其他 UDDI 注册表生成的关键字会有所不同。
<bindingTemplate serviceKey="ef25102d-2171-454c-ade9-3dd7a4a914ee" bindingKey="f46fced9-2b8a-4817-b957-f8d8aca0a2f9"> <accessPoint URLType="http"> http://localhost/SalesReportUSA/SalesReport.asmx </accessPoint> <tModelInstanceDetails> <tModelInstanceInfo tModelKey= "uuid:b28fe40a-ea62-4657-88d5-752d8a6cdf77" /> </tModelInstanceDetails> </bindingTemplate>
在上述结构中,我们突出显示了此 Web 服务的 accessPoint 和 bindingKey。这对于客户了解这两部分的信息非常重要。此外,如果某个客户端需要获取此 Web 服务的 WSDL,则可以使用 tModelKey 查询 UDDI 以获取该 tModel。
使用 Web Service:C# Windows 窗体 .NET 客户端
现在,我们可以转换一下角色,看看应用程序的客户端部分。在设计时,我们也许可以在 UDDI 中找到此 Web 服务。我们将下载相应的 WSDL 文件并使用 Microsoft Visual Studio? .NET 的 Add Web Reference 或 WSDL.exe 生成一个代理类。(WSDL.exe 是一个命令行工具,属于 Microsoft .NET 框架 SDK 的一部分)。
现在可以开始编写客户端应用程序中的逻辑。在本例中,它是称为 SalesReportClient.exe 的 C# Windows 窗体应用程序,允许用户查询销售报表信息。
首先,需要将 UDDI .NET SDK 类添加到项目中,这些类可供下载。(Microsoft UDDI SDK version 1.5.2 与 Visual Studio .NET Beta 2 兼容;Microsoft UDDI .NET SDK Beta version 1.75 与 Visual Studio .NET Release Candidate 兼容)。using 声明如下:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Configuration; using System.Windows.Forms; using System.Data; using Microsoft.Uddi; using Microsoft.Uddi.Binding;
然后,需要将 UDDI 服务器的访问点存储在此 Web 服务所在的位置(毕竟,UDDI 本身就是一个 Web 服务)。要执行此操作,需要为此 .exe 可执行文件创建一个应用程序配置文件,用于存储 UDDI 服务器的位置。Web 服务的 bindingKey 也将存储在此配置文件中。通过在 .NET 中使用 XML 配置文件,您可以添加任意数量的 appSetting,然后应用程序可以通过集合获取它。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="UDDI_URL" value="http://localhost/uddi/api/inquire.asmx" /> <add key="bindingKey" value="f46fced9-2b8a-4817-b957-f8d8aca0a2f9" /> </appSettings> </configuration>
在此示例中,我们指向驻留在自己的计算机上的 Microsoft UDDI Developer Edition 服务器。UDDI_URL 也可以是公共 UDDI 节点之一,或者是驻留在企业内部的 UDDI 注册表。使用配置文件 app.config 的命名规则来保存此文件:编译应用程序后,配置文件将被放在 /bin 目录中,并使用它自己的 .exe 名称命名。
刚刚完成的这个步骤(添加有关 Web 服务的配置信息),与 Visual Studio .NET 如何在添加到项目的每个 Web 引用上公布 URL Behavior 属性并没有什么不同。通过将该属性更改为 dynamic,Visual Studio .NET 可以创建包含 Web 服务访问点的配置文件。上述操作通过提供在运行时重新查询 UDDI 的功能,进一步扩展了此概念。因此,配置文件包含了 UDDI 节点的访问点和 Web 服务的 bindingKey。
现在,我们可以开始对应用程序本身进行编码。首先需要创建一个文本框、一个标签、一个按钮和两个日期时间选择器。然后建立一些全局变量:
//some variables for the application private string InquiryURL = null; private string bindingKey = null; private string accessPoint = null; private BindingTemplate bt; private double salesFigure = 0;
窗体被实例化以后,我们需要初始化以下变量:
public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); //populate variables from config file InquiryURL = ConfigurationSettings.AppSettings["UDDI_URL"]; bindingKey = ConfigurationSettings.AppSettings["bindingKey"]; bool InitCache = RefreshCacheFromUDDI(); if ( InitCache == true ) accessPoint = bt.AccessPoint.Text; }
RefreshCacheFromUDDI() 函数用于查询 UDDI 服务器以查找访问点。使用 UDDI SDK 执行 UDDI API 调用 (GetBindingDetail),将 bindingKey 作为参数传递。
private bool RefreshCacheFromUDDI() { //using the UDDI SDK, set the UDDI access point Inquire.Url = InquiryURL; //create a get_bindingDetail UDDI API message GetBindingDetail gbd = new GetBindingDetail(); //add the bindingKey gbd.BindingKeys.Add( bindingKey ); try { BindingDetail bd = gbd.Send(); //if we are successful, update our bindingTemplate object //with the first template in the returned collection bt = bd.BindingTemplates[0]; return true; } catch (Exception err) { textBox1.Text += err.Message; return false; } }
在应用程序运行期间,我们将 accessPoint 的位置放在变量中。如果用户要重新启动应用程序,它重新查询 UDDI 以获得accessPoint,因此应用程序始终拥有对 Web 服务的最新更改。如果需要,可以将这些数据缓存在文件系统或数据库中。
接下来,需要创建调用 Web 服务自身的函数:
private bool InvokeWebService() { localhost.SalesReport sr = new localhost.SalesReport(); //set the access point for the proxy class sr.Url = accessPoint; try { salesFigure = sr.GetSalesTotalByRange( dateTimePicker1.Value, dateTimePicker2.Value ); label1.Text = "Sales Figure For Dates Selected: $" + salesFigure.ToString(); textBox1.Text += "Web Service invocation successful!"; return true; } catch (Exception err) { textBox1.Text += err.Message; return false; } }
最后,用户单击按钮时,应用程序将尝试调用 Web 服务。
private void button1_Click(object sender, System.EventArgs e) { Cursor.Current = Cursors.WaitCursor; //try to invoke Web Service bool WebServiceSuccess = InvokeWebService(); //if it fails for some reason, query UDDI if ( WebServiceSuccess == false ) { textBox1.Text += "Web Service failed. Requerying UDDI for new accesspoint.\n\n"; bool UDDISuccess = RefreshCacheFromUDDI(); //we were successful requerying UDDI, if ( UDDISuccess == true ) { //compare the accessPoint with the new accessPoint //to determine if it changed if ( accessPoint.Equals( bt.AccessPoint.Text ) == false) { //because the accessPoint is different, it must be new //we reset our variable accessPoint = bt.AccessPoint.Text; //and attempt to invoke the web service again WebServiceSuccess = InvokeWebService(); //we aren't able to invoke the Web Service with the new info if ( WebServiceSuccess == false ) { textBox1.Text += "Web Service failed again. Updated accesspoint from UDDI didn't help!.\n\n"; } } else { textBox1.Text += "No new information was provided from UDDI.\n\n"; } } else { textBox1.Text += "UDDI refresh failed.\n\n"; } } }
请注意如何在运行时为 Web 服务代理类设置 accessPoint。因为所有代理类都是从 System.Web.Services.Protocols.SoapHttpClientProtocol 中派生出来的,所以代理类会公布一系列属性,.Url 属性就是其中之一。设置此属性使我们能够在运行时指定访问点。然后,我们可以通过线路发送 SOAP 请求。如果没有发生异常,则表示一切正常并且从 Web 服务返回的数据显示在窗体中。但如果确实发生异常,此函数返回 false,调用代码将尝试从 UDDI 刷新访问点,以重新使用 RefreshCacheFromUDDI() 函数。
重新查询 UDDI 之后,我们会将 UDDI 返回的 accessPoint 与原访问点进行比较。如果访问点相同,则提供商尚未使用新信息更新 UDDI,我们所能做的只有尝试与 Web 服务的提供商联系,告诉他们 Web 服务不响应。但是,如果从 UDDI 检索到的访问点不同,则可以尝试再次调用 Web 服务。
为模拟故障,可以更改 Web 服务的名称。尝试运行应用程序。然后,用 Web 服务的新名称更新 UDDI 项。再次运行应用程序。应用程序将在 UDDI 中找到新的访问点,成功查询新服务,然后保存此信息。如果您完全关闭应用程序,再重新打开,则第一次尝试时应该能够再次调用 Web 服务。
其他方案
这个失败时重试 示例讨论的是 UDDI 可在运行时用作 Web 服务客户端的支持基础结构。在以后的专栏中,我们将讨论其他方案,包括:
• |
优化访问点发现 — 可能有多个 Web 服务支持驻留在不同服务器上的公共接口,这些服务器又位于不同的物理位置。使用最近的 Web 服务或许对客户端有意义。通过运行时 UDDI 查找,客户可以根据不同服务的地理分类或与该实现关联的其他元数据来确定最佳访问点。 |
• |
基于公共接口聚合数据 — WSDL 中可能定义了一种用于搜索目录的标准 Web 服务接口。该行业的很多厂商都可以实现 Web 服务接口,并在 UDDI 中发布访问点。在运行时,客户端应用程序可以动态搜索这些访问点并发出查询以收集这种编录数据。通过这种方式,轮询应用程序可以利用运行时 UDDI 数据。 |
此外,我们还将讨论如何优化 WSDL 文件,使其真正充当接口描述文件。
小结
UDDI 提供了重要的运行时功能,可以集成到应用程序中以创建更可靠的动态客户端。通过将 UDDI 用作 Web 服务结构中的基础结构,可以编写更加可靠的应用程序。