RESTful Web 服务:快速入门指南 - 第二部分
编写客户端以从面向响应的 RESTful 服务检索数据
《RESTful Web Services:快速入门指南》第2部分探讨了编写RESTful操作的基础性问题,包括基本身份验证和异常处理。在此过程中,我将与您分享多个实际编码技巧和解决方法。
引言
在我十几岁的时候,我最喜欢的电视节目之一是“狂野西部”。该节目的描述是这样的:“……CBS电视台一部60分钟的西部动作剧,独一无二。特工詹姆斯·韦斯特和阿尔捷摩斯·戈登在内战后不久就成为了尤利西斯·S·格兰特总统的间谍。在其他方面,他们很容易与20世纪的詹姆斯·邦德混淆。他们拥有一辆‘高科技’(就当时而言)的火车车厢,里面装满了先进的武器。詹姆斯·韦斯特似乎特别会赢得他遇到的每一位美丽女性的芳心。特工们典型的任务是拯救美国免受某种灾难或被某个邪恶天才的控制。”如果您好奇或者像我一样是它的粉丝,这里是YouTube上完整一集的第一部分的链接。
RESTful服务与狂野西部有什么关系?在我看来,关系很大。首先是“狂野”的部分。正如服务提供商API之间的简单比较所显示的那样,并非所有RESTful服务都是平等的。每个服务提供商在松散地遵循状态表示范式时,都会创建适合其个人风格和偏好的请求和响应格式。实现服务客户端的责任落在了开发人员身上。然后是创新的方面。就像特工阿尔捷摩斯·戈登一样,RESTful客户端开发人员需要工具、创造力和持续的意识,因为他们正在处理非正式的(敌人)规范。此外,在PowerBuilder方面,尚不支持与DataWindow技术直接集成,因此您必须想方设法将数据输入和输出到显示屏。还需要通过将调用包装在适当的try catch块中来处理可能的HTTP异常,以保护您的应用程序免受外部问题的困扰,从而保持应用程序的响应性和稳定性。
在我看来,如果没有正式的模式和元数据描述支持,配置RESTful状态服务客户端仍然是一门艺术。我必须承认,在编写本文的实验过程中,我曾多次摔倒。我尝试调用通信格式与一般预期格式不完全匹配的服务。一个服务提供商要求将参数放在消息正文中进行传递并返回结果,而不是将数据格式化为XML或JSON。经过大量的摸索和调试,很明显PowerBuilder只能生成和解释格式为JSON和XML的消息正文内容。没有办法从任何其他格式进行转换。所有底层细节都是内部处理的,无法进入消息内部更改格式。另一个服务提供商以JSON数据集的形式展示了他们的API,但在数小时的实验和工程部门的一些帮助后,我们发现该服务需要multipart/form-data,并将JSON作为表单参数发送。这不是支持的格式。因此,请注意‘买者自慎’,确保服务提供商拥有‘传统’的RESTful API形式。幸运的是,我相信大多数拥有广泛使用的API的‘大公司’都有。
哦,那些项目对象!
在深入细节之前,先谈谈RESTful项目对象和生成的代码。RESTful项目对象被设计成类似于一次性的Derringer手枪;开一枪,然后你需要重新装弹。每个项目对象只能生成一个代理对象用于一个方法,仅此而已。这意味着如果您需要与多个Web方法(GET、POST、PUT和DELETE)进行交互,您将需要多个代理对象。此外,每个操作,根据其类型,最多可以有两个引用的.NET值对象,一个用于请求,一个用于响应。我注意到的一件好事是,如果多个服务依赖于相同的参数集,您可以重用引用的值对象,而不是生成多余的副本。图1显示了如何配置项目设计器来引用已生成的程序集。每个引用对象都放置在自己独立的程序集中,位于同名的命名空间中。不幸的是,似乎您不能在一个程序集或命名空间中拥有多个值对象。图2显示了引用对象程序集在解决方案资源管理器中的外观。图3显示了在选择现有值对象程序集时的向导视图。
以下是我在构建应用程序时发现的一系列技巧
技巧1:项目对象拥有其生成的代理对象。这意味着您对其进行的任何修改都将在重新生成代理时丢失。因为我需要修改生成的代码,所以我采用了将项目对象生成作为起点的方法。一旦代理对象生成符合我的要求,我就删除了项目对象,以避免意外地删除我的代码。
技巧2:如果您有多个服务使用相同的请求对象或返回相同的响应结构,您可以重用您的值对象定义。一次生成,多次使用。只需在向导或项目对象中选择它们即可。
技巧3:进入新项目对象的唯一途径是通过向导。这与经典版本不同,经典版本有两个进入新项目的方法(向导或空项目设计器页面)。
技巧4:这里有几个不错的XML样本数据解析功能:首先,如果样本数据集中有一个XML元素包含属性,则属性值将作为生成的值对象的一部分返回。图4显示了一个包含属性的样本响应数据集;图5显示了相应的生成值对象。其次,正如您从图6中看到的,嵌套的XML结构在生成的程序集中变成了单独的对象。
在了解了这些项目对象细节后,我们现在可以着手处理两个重要的运行时维护需求:返回状态检查和异常处理。
检查返回状态
某些服务通过返回特定且恒定的格式的数据来指示其结果。其他服务则通过返回HTTP状态码而不是XML或JSON嵌入的状态消息来指示其结果。还有一些服务将状态条件包装在特制的XML或JSON结构中。如果您需要检查HTTP返回状态码的结果,您将需要从Response对象中获取状态码。方法如下:响应对象以PBWebHttp.WebMessage对象的形式返回给代理。此对象有一个MessageStatus类型的StatusCode属性,其中包含响应结果。您可以将StatusCode与WebResponseStatusCode类中的枚举值进行比较,并相应地进行分支。图7显示了MessageStatus代码及其含义的部分列表。有关HTTP 1.1代码的完整列表,请参阅http://www.w3.org/Protocols/HTTP/HTRESP.html。列表1显示了测试代理方法调用状态码的示例。
PBWebHttp.WebMessage msg //generated code
PBWebHttp.MessageStatus l_msg_status //my custom addition
Try
msg = m_service.GetMessage()
//an exception was not thrown - check the status of the outcome
l_msg_status= msg.Status
if l_msg_status.StatusCode = PBWebHttp.WebResponseStatusCode.OK! then
MessageBox('Success! ','call returned OK')
end if
.....
异常处理
向导生成的代理代码不包含任何异常处理逻辑。发生的任何异常都会被抛给调用者进行处理。未处理的异常将成为系统错误。在Web世界中,400范围内的响应状态码表示客户端错误;500范围内的表示服务器错误。在WCF中,400和500代码会引发异常。
最佳编码实践要求进行健壮的异常处理。根据您需要报告的详细程度,您可以在代理方法调用者级别或在代理方法调用本身中处理异常。
基于HTTP的RESTful服务方法通常会抛出System.Net.WebException。虽然通用的System.Exception祖先属性Message包含错误描述,但直接测试返回的状态码值更有用。幸运的是,WebException包含一个对返回的Response对象的引用,该对象包含状态码。列表2显示了在调用失败时测试状态码的示例。
catch (System.Net.WebException webex)
System.Net.HttpWebResponse l_resp
l_resp = webex.Response //Get the response object
if l_resp.StatusCode = System.Net.HttpStatusCode.Unauthorized! then //check its status code
MessageBox('Your Credentials Are Invalid', 'Uid=' + uid + '~r~nPwd=' + pwd )
return Result
end if
现在您已经了解了维护问题,让我们来看看安全问题。
基本身份验证
WCF支持各种形式的身份验证,从None到数字证书。使用基本身份验证时,您将在用户ID和密码中放入您的凭据到请求头字段中。如果使用HTTP,凭据将以纯文本头的形式进行加密(然而,像Fidder这样的免费程序可以解密SSL)。如果使用HTTPS,则所有传输的数据包都将应用SSL加密。客户端/服务器协商算法如下:(1)客户端发出请求;(2)服务器响应401未经授权的响应;(3)客户端响应请求,包括一个包含用户ID和密码的登录头;(4)在整个通信过程中,客户端会自动包含该头信息。图8显示了Fiddler揭示的对话。有关更详细的讨论,请参阅http://www.4guysfromrolla.com/articles/031204-1.aspx
从PB WCF的角度来看,您的编程任务是设置通信的凭据。低级运行时交换过程由基础结构内部处理。一个小挑战是,从PowerBuilder编码的角度来看,您正在进入部分未知的领域。关于Sybase.PowerBuilder.WCF.Runtime程序集的成员没有PowerBuilder文档。然而,掌握了WCF的基本知识并在检查引用程序集的成员时花费几分钟,我想您就能弄清楚该怎么做。这是基本算法:
- 在您的代理类的构造函数中,实例化一个WebClientCredential对象,并将您的凭据和身份验证类型设置进去。图9显示了WebClientCredential类及其成员(它只是一个值对象)。a.使用AuthenticationMode枚举中的值设置AuthenticationMode。在这种情况下,我们将值设置为Basic!b.提供您的用户ID和密码。
- 将您的WebClientCredential对象分配给您的WebConnection对象。WebConnection已经实例化,并具有标识符名称restConnectionObject。图9显示了此对象及其重要属性。
- 像往常一样调用您的服务。请务必包含测试401 HTTP错误的异常处理代码。
列表3显示了一个配置为使用基本身份验证的示例RESTful服务。
// vvvvv----Project Generated Code----vvvv
m_service = create PBWebHttp.RestService("http://phprestsql.sourceforge.net/tutorial/user?firstname={p_first}&surname={p_last}&email={p_email}&company_uid={p_id}", PBWebHttp.WebMessageFormat.Xml!, PBWebHttp.WebMessageFormat.Xml!)
restConnectionObject = create PBWebHttp.WebConnection
restConnectionObject.Endpoint = http://phprestsql.sourceforge.net/tutorial/user?firstname={p_first}&surname={p_last}&email={p_email}&company_uid={p_id}
restConnectionObject.RequestMessageFormat = PBWebHttp.WebMessageFormat.Xml!
restConnectionObject.ResponseMessageFormat = PBWebHttp.WebMessageFormat.Xml!
// vvvvv----Custom Authentication Code----vvvv
PBWebHttp.WebClientCredential lsCredential //configure credentials
lsCredential = create PBWebHttp.WebClientCredential
lsCredential.AccessAuthentication = PBWebHttp.AuthenticationMode.Basic!
lsCredential.Password='demo'
lsCredential.Username='p126371rw'
restConnectionObject.ClientCredential = lsCredential //add credentials to connection
安全套接字层通信
指定和处理SSL传输上的加密通信对应用程序开发人员来说相当透明。正如您在图10中所看到的,您只需要在项目设计器的服务URL中指定HTTPS协议。握手和加密由底层基础结构处理,如图11所示。
结论
除了调用服务方法之外,PowerBuilder客户端开发人员还负责在调用服务之前配置身份验证和授权属性,以及编写代码来处理服务器端错误响应。稍加考虑,这段代码就可以以可重用的方式编写,以便与项目设计器生成的代理类结合使用。通过添加RESTful Web服务客户端基础结构,PowerBuilder .NET客户端现在可以与其他.NET语言一样享受面向服务的互操作性。
PowerBuilder 万岁!