新的RPC协议—— Web服务

新的RPC协议—— Web服务

当Internet迅速成为应用程序运行的平台时,出现了一种真正的独立于语言且独立于平台的创建分布式程序的方法,它成为软件开发行业的“圣杯”。当前,这个圣杯是以Web服务的形式出现的。

Web服务的严格定义是一场永远不会有结果的争论。有的人甚至把对标准网页的一次请求看成是Web服务的一个例子。本书的定义是,一个Web服务是指接受一个请求、返回数据或执行一项处理活动。返回的数据通常都以机器可读的格式,重点不在于内容和显示格式,这不同于标准网页。一个Web服务与一个XML Web服务也有一个明显的区别。后者表示,至少有一方,或请求方,或响应方,必须采用XML格式。在本章,Web服务也包括了XML Web服务,除非另外说明。

Web服务是在Internet上请求数据或执行处理任务的一种方法。正如前文所述,Web 服务通常需要对请求和响应双方进行编码。除了使用Internet标准的传输协议外,这种编码使得传输的信息成为大众通用的格式。这意味着,在Linux平台上运行的一个Perl程序可以调用在Windows.NET平台上运行的.NET程序,没有人会知道!

当然,事情没有这么简单,至少是开始的时候,事情没有这样简单。为了使Web服务能起作用,必须制订相应的标准,使得Internet上的任何人都明白,请求的信息是什么,如何请求,响应的内容采用的格式。

下面几节内容讨论XML-RPC,它是Web服务的一种简单形式。然后把讨论扩展到一些比较重要的协议以及如何把这些协议组合在一起。下一章将详细讨论两个最常用的协议:SOAP和WSDL。

XML Web服务有两种主要的设计方法,它们在如何发出请求上有所不同。第一种方法称为XML-RPC,它模仿传统的函数调用—— 把方法名和参数封装在一个XML格式里。第二种方法使用文档,它只说明Web服务采用XML文档作为其输入参数,XML文档的格式是预先定义好的,通常采用XML Schema模式,然后该服务处理文档并执行请求的任务。

14.3.1  XML-RPC

理解一个Web服务处理过程的最容易的办法是分析XML-RPC协议。由于它最初的设计目标是简单性,因而它提供了调用远程过程的一种办法,只需要说明调用的过程和需要传递的参数。客户端程序向服务器发送一个编码成XML格式的命令,由它执行远程过程调用,并返回响应。响应也编码为XML格式。

协议是简单的,但是过程—— 在Internet上发送一个XML请求,并且获得一个XML响应的过程是Web服务的基础,因此掌握它的工作过程有助于我们理解更加复杂的SOAP等协议。

这里讨论的服务是属于Internet主题交换(Internet Topic Exchange,见http://topicexchange. com/)的范畴,它是一组“频道”,列出某个专题的帖子。例如,当Wiley出版社在网站发布一个新版本时,它们就在book频道里添加一条记录。

   现在就开始讨论将要调用的API接口程序。

1. 目标API

Internet主题交换只有三个方法。第一个方法是

struct topicExchange.getChannels()

该方法的名字已清楚地说明了它的功能:它返回一个现有频道的列表(不必为struct感到不安)。它不需要参数,因此调用方法非常简单。

   接着介绍第二个方法:

struct topicExchange.ping(string topicName, struct details)

   该方法把一个新记录添加到某个主题里,这个主题由参数topicName确定。

   第三个方法是

struct topicExchange.getChannelInfo(string topicName)

   该方法取回某个频道的信息。它的参数是一个字符,频道的名字,返回一个结构体,包含相关信息,如主题的URL地址、它的内容描述。下面马上就要进行一个实际请求,但是我们先分析如何构建一个XML Web服务消息。

2. 一个简单的请求

一个最简单的XML-RPC请求是执行一个不需要参数的方法。在本例中,topicExchange. getChannels()就是这样的方法。如下所示:

<methodCall>

<methodName>topicExchange.getChannels</methodName>

</methodCall>

在这个例子里,调用很简单。由于只需要调用 topicExchange.getChannels()方法,因此就把它插入到methodName元素里。当我们把这小段XML程序发送到Web服务时,Web服务就会返回一个XML文档,文档里是现有频道的列表。我们马上就要讨论响应,先让我们分析一个比较复杂的请求。

3. 传递参数的请求

在一般情形中,调用的方法需要参数,因此XML-RPC包含了在XML请求里说明参数的方法。例如,topicExchange.ping()方法要求一个string参数和一个struct参数。字符串参数比较容易定义,如下所示:

<methodCall>

<methodName>topicExchange.ping</methodName>

<params>

<param>

<value><string>books</string></value>

</param>

</params>

</methodCall>

   这个例子先定义一组params元素,然后定义param单个元素。在param元素里定义第一个参数,即频道名称,需要注意的是,它也是一个字符串类型。实际上,XML-RPC定义了7种标题类型:<i4>(或<int>,表示四字节的带符号整数)、<boolean>(0代表假,1代表真)、<string>、<double>、<dateTime.iso8601>(日期/时间值,如20040422T16:12:04)和<base64>(采用base64编码的二进制数)。在某些情形下,参数不是单值标量,而是一个结构。

4. 使用结构体

一个struct(结构体)是一组命名值,可以像一个对象一样传递。例如,Internet主题交换想得到某个发布的信息,可以用单个struct连接到这个发布信息:

<methodCall>

<methodName>topicExchange.ping</methodName>

<params>

<param>

<value><string>books</string></value>

</param>

<param>

<value>

<struct>

<member>

<name>blog_name</name>

<value><string>Wiley Today</string></value>

</member>

<member>

<name>title</name>

<value><string>Latest Publications</string></value>

</member>

<member>

<name>url</name>

<value>

<string>http://www.wiley.com/WileyCDA/Section/index.html </string>

</value>

</member>

<member>

<name>excerpt</name>

<value><string>Wiley latest publications have something for

everyone. Beginning XML is proving to be this year’s hottest selling item.

</string></value>

</member>

</struct>

</value>

</param>

</params>

</methodCall>

在这个例子里,details参数由单值组成,但是它的值是一个struct。struct有4个成员,每个都有一个name和一个value。与前面一样,value是7类型之一的标量值,实际上这并非必要的。一个struct可以有一个成员,也可以有多个成员。正如下面来自getChannels()方法返回的响应值:

<methodResponse>

<params>

<param>

<value>

<struct>

<member>

<name>channels</name>

<value>

<struct>

<member>

<name>books</name>

<value>

<struct>

<member>

<name>url</name>

<value>

<string>http://topicexchange.com/t/books/</string>

</value>

</member>

</struct>

</value>

</member>

<member>

<name>logic</name>

<value>

<struct>

<member>

<name>url</name>

<value>

<string>http://topicexchange.com/t/logic/</string>

</value>

</member>

</struct>

</value>

</member>

<!-- more member elements -->

</struct>

</value>

</member>

</struct>

</value>

</param>

</params>

</methodResponse>

   为了简单起见,已从这个响应的内容里删除了某些无关的内容,但是结构还是与几十个频道的结构一样。就XML-RPC语法而言,就这些内容。现在我们只需要讨论如何把一个实际的请求发送出去。

14.3.2  网络传输

通常Web服务的规范允许我们利用网络传输发送和接收消息。例如,可以用IBM MQSeries或微软的消息队列(Microsoft Message Queue, MSMQ)异步发送SOAP消息。或者利用SMTP,通过电子邮件发送SOAP消息。然而,最常使用的发送协议还是HTTP。事实上,它是XML-RPC规范里不可缺少的协议。因此,它是本章讨论的重点。

1. HTTP协议

许多读者可能已经对HTTP协议比较熟悉,因为每当我们用浏览器请求一个Web页面时,就使用HTTP协议。大多数实现Web服务的软件都以HTTP为底层协议,因此我们从底层分析它的工作过程。

超文本传输协议(Hypertext Transfer Protocol,HTTP)是一种请求/响应协议。这意味着当我们采用最基本的形式发出一个HTTP请求时,必须经历以下4个步骤:

(1) 客户端(大多数情形,它就是浏览器)建立到HTTP服务器的连接;

(2) 客户端向服务器发送一个请求;

(3) 服务器执行处理;

(4) 服务器发回一个响应;

(5) 关闭连接。

HTTP消息包含两部分内容:一组消息头,后面紧跟一个可选的消息主体。消息头都是文本字符,各个消息头之间用换行符分隔。消息主体可以是文本内容也可以是二进制内容。主体与消息头之间用两个换行符分隔。

例如,假设想把一个位于http://www.wiley.com/WileyCDA/Section/index.html的一个HTML页面装入本地的浏览器,假设这个浏览器是IE 7.0。该浏览器向www.wrox.com服务器发送如下所示的请求:

GET /WileyCDA/Section/index.html HTTP/1.1

Accept: */*

Accept-Language: en-us

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Win32)

Host: www.wiley.com

本请求信息的第一行规定了HTTP服务器使用的方法。HTTP协议规定了几类请求,本请求采用GET方法,它告诉服务器我们要请求某个资源,即/WileyCDA/Section/index.html(另一个常用的方法是POST,稍后介绍)。此外,这一行也规定了,使用HTTP/1.0版协议。还有其他几个消息头,它们向Web服务器详细说明了浏览器的一些信息,如可能接收的信息类型。具体有:

●       Accept告诉服务器本浏览器要求接收什么类型的MIME,在这个例子里,*/*表示可以接收任何类型的MIME。

●       Accept-Language告诉服务器本浏览器使用的语言。此后服务器可能利用这些信息对返回的内容进行个性化处理。在本例里,浏览器说明了采用美国英语(us,en)。

●       Accept-Encoding告诉服务器,返回给浏览器的内容是否需要编码。在本例里,浏览器说明,它可以接受采用gzip或deflate方法压缩的文档。它们是一些数据压缩方法,需要在客户端解压。

对于GET请求,HTTP消息没有消息主体。服务器在响应时,向浏览器发送类似于如下所示的内容:

HTTP/1.1 200 OK

Server: Microsoft-IIS/5.0

Date: Fri, 09 Mar 2007 15:30:52 GMT

Content-Type: text/html

Last-Modified: Thu, 06 Mar 2007 12:19:57 GMT

Content-Length: 98

<html>

<head><title>Hello world</title></head>

<body>

<p>Hello world</p>

</body>

</html>

显然,Wiley出版社的实际主页要比这个例子复杂许多。同样也有一组HTTP消息头,但是后面有消息主体。在本例,HTTP服务器发回的一些消息头如下:

●       一个状态码(200)。它表示浏览器的请求成功。HTTP规范(ftp://ftp.isi.edu/in-notes/ rfc2616.txt)定义了在HTTP的响应中可以使用的几个状态码。如大家比较熟悉的404代码,它表示请求的资源不存在。

●       Content-Type消息头,它表示消息主体里内容的类型。客户端程序(如Web浏览器)利用这个消息头决定对消息主体采取哪种处理方法。例如,如果内容类型为.wav文件,浏览器就运行一个外部的声音播放程序,播放这个声音文件。或者让用户把这个文件保存到硬盘上。

●       Content-Length消息头。它表示消息体的长度。

GET方法是HTTP协议中最常用的方法,在我们每天的网上冲浪中,都要用到它。第二个常用的方法是POST。当我们使用POST方法时,浏览器以消息体的形式把消息传送给HTTP服务器。例如,当我们在Web上填好一个表格,并单击Submit(提交)按钮时,Web浏览器通常用POST方法把信息传送给Web服务器,服务器对它进行处理,然后把处理结果发送回浏览器。假设我们已创建了一个HTML页面,它有以下的一个表单:

<html>

<head>

<title>Test form</title>

</head>

<body>

<form action="acceptform.asp" method="POST">

Enter your first name: <input name="txtFirstName" /><br />

Enter your last name: <input name="txtLastName" /><br />

<input type="submit" />

</form>

</body>

</html>

该表单用POST方法把其中的数据传送给acceptform.asp页面。它保存在HTML文件的同一个位置,它的内容如下:

POST /acceptform.asp HTTP/1.1

Accept: */*

Referer: http://www.wiley.com/myform.htm

Accept-Language: en-us

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Win32)

Host: www.wiley.com

Content-Length: 36

txtFirstName=Joe&txtLastName=Fawcett

GET方法是为网上冲浪而设计的,而POST方法是为电子商务等活动而设计的,后者可以在服务器与浏览器之间来回传送信息。

在本章的后面,读者将会明白,GET方法也可以传递消息体,方法是把消息体插入到URL里,但是通常情况下,尽量使用POST方法。

2. Web服务使用HTTP协议的理由

前面曾提到,大多数实现Web服务的应用程序都采用HTTP作为其传输协议。下面是其中几个理由:

●       HTTP是一个已普遍使用而且容易理解的协议;

●       请求/响应模式本身非常适合于RPC协议;

●       大多数的防火墙已按HTTP的要求进行配置;

●       使用安全套接层(Secure Socket Layer,SSL),HTTP容易实现安全保护。

3. 普遍性

Internet爆炸式增长的一个主要原因是万维网的出现(World Wide Web),后者也是基于HTTP协议的。全世界有数以千计的服务器在运行,向我们提供HTML文档和其他内容。很多公司利用HTTP协议进行电子商务活动。

HTTP是一个相对容易实现的协议。这也是为什么万维网运行比较平稳的原因之一。如果HTTP协议很难实现,那么,实现HTTP协议的一些程序就会容易出故障,这意味着,一些Web浏览器不能访问另一些Web服务器。

因此,比起网络协议,把HTTP协议应用到Web服务比较容易实现。由于实现Web服务的应用程序可以利用现有的服务器,情况尤其如此。这意味着,我们根本无须担心HTTP程序。

4. 请求与响应

通常当客户程序发出一个RPC调用时,它需要收到某种响应。例如,当我们调用getChannels()方法时,需要取回一个频道列表,否则执行这个远程调用毫无意义。在其他情形下,如向主题栏里添加一个新记录时,RPC调用返回的数据可能对我们没有用,但是即使这样,也要证实这个远程过程已成功执行。例如,向后台终端的数据库提交一个订单并不需要返回数据,但是我们需要知道这次提交是否成功。

HTTP的请求/响应模式很容易解决这种类型的问题。对于前面的“加法运算”远程过程,必须执行以下步骤;

(1) 与提供XML-RPC服务的服务器建立一个连接;

(2) 发送需要相加的两个数;

(3) 执行加法运算;

(4) 返回运算结果,如果不能执行加法运算,返回一个错误代码;如果运算成功,返回一个ping标识符;

(5) 关闭连接。

在有些情形中,如SOAP规范,消息只能单向传递,不能双向传递。这是指,需要传递两个独立的消息。一个消息从客户端到服务器,如加法运算的两个数;另一个消息是从服务器到客户端,把运算结果传送给客户端。但是在大多数情形中,如果一个规范需要使用两个单向消息,则它肯定也同时规定可以使用HTTP请求/响应模式,把这两个消息插入到请求/响应协议里。

5. 现成的防火墙

大多数公司为了保护自己的计算机免受外面的黑客侵扰,总是在内部系统与外部网络之间放置一道防火墙。防火墙是通过控制某些类型的网络流量而采取的一种保护措施。大多数防火墙允许HTTP流量(它就是在使用浏览器时产生的流量),而阻止另外一些网络流量。

防火墙可以保护公司内部的数据,但是它们也使得向外部用户提供本公司内部的基于Web的服务更加困难。例如,假如一个公司建立了网上商店,推销它的产品。这种基于Web的服务需要某些信息,如库存的商品信息,这些信息需要从公司的内部系统里读取。为了提供此项服务,公司可能需要建立如图14-3的网络平台。

 

图 14‑3

这种配置非常普遍。它把Web服务器放置在两个防火墙之间(两个防火墙之间的区域常称为隔离区(Demilitarized Zone,DMZ)。防火墙1保护了公司系统,因此必须进行精心设置,它允许Web服务器与内部系统之间正常的通信,而又不允许其他外部网络访问进入。防火墙2的配置要允许Web服务器与Internet之间的正常通信,但是不允许其他网络流量。

这种安排保护了公司的内部系统,但是由于防火墙带来的复杂性—— 特别是Web服务与后端服务器之间的通信尤为复杂,因此开发人员很难建立这种类型的Web服务。然而,由于防火墙的配置允许HTTP流量进入系统,因此,如果Web服务器与其他服务器之间的通信都使用这种协议,则还是比较容易提供必要的功能。

6. 安全

由于HTTP协议已经有一个安全模型—— 安全套接层(Secure Socket Layer,SSL),因此采用HTTP安全措施进行商务处理是比较容易的。SSL把需要传送的数据进行加密,使得外人无法知道其中的秘密,因此非常适合于网上商务交易,如采用信用卡购物等。事实上,由于SSL经常使用,因此现在有硬件加速卡,以加快SSL处理。

7. 在XML-RPC中使用HTTP

把HTTP应用于XML-RPC是一件非常容易的事。只需要在客户端做两件事:

●       对于HTTP方法,使用POST模式;

●       对于消息体,插入由XML-RPC请求组成的XML文档。

例如,分析下面的代码:

POST /RPC2 HTTP/1.1

Accept: */* Accept-Language: en-us

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)

Host: www.wiley.com

Content-Length: 79

<methodCall>

<methodName>topicExchange.getChannels</methodName>

</methodCall>

消息头定义了请求内容,消息体由XML-RPC请求组成。服务器知道如何读取并处理信息体。在下一章,我们将介绍实际请求的处理过程,但是目前暂且发送一个XML-RPC请求,并处理其响应。

试一试  利用HTTP POST调用RPC

   现在我们可以编写一个简单的HTTP网页来测试请求/响应。该网页利用MSXML组件把信息发送到互联网主题交换中心,它需要IE 5.0或更高版本的支持(下一节将介绍其他浏览器的其他方法)。

1. 输入以下代码(或者从网站下载PostTester-IE.html)。注意其中几行重要的代码采用灰色背景,它们是JavaScript代码:

<html><head><title>POST Tester - IE</title>

<script language="JavaScript">

var xhHTTP;

function doPost()

..{

var xdDoc, sXML;

sXML = "<methodCall>"

+ "<methodName>topicExchange.ping</methodName>"

+ "<params><param><value><string>test</string></value></param>"

+ "<param><value><struct>"

+ "<member><name>blog_name</name>"

+ "<value><string>" + pingForm.blog_name.value +

"</string></value></member>"

+ "<member><name>title</name>"

+ "<value><string>" + pingForm.title.value +

"</string></value></member>"

+ "<member><name>url</name>"

+ "<value><string>" + pingForm.url.value +

"</string></value></member>"

+ "<member><name>excerpt</name>"

+ "<value><string>" + pingForm.excerpt.value +

"</string></value></member>"

+ "</struct></value>"

+ "</param></params>"

+ "</methodCall>";

xdDoc = new ActiveXObject("MSXML2.DOMDocument.3.0");

xdDoc.async = false;

xdDoc.loadXML(sXML);

xhHTTP = new ActiveXObject("MSXML2.XMLHTTP.3.0");

xhHTTP.onreadystatechange = handleReadyStateChange;

xhHTTP.open("POST", "http://topicexchange.com/RPC2", true);

xhHTTP.send(xdDoc);

}

function handleReadyStateChange()

{

if (xhHTTP.readyState == 4)

{

var xdDoc = xhHTTP.responseXML;

xdDoc.setProperty("SelectionLanguage", "XPath");

var oErrorNode = xdDoc.selectSingleNode("//member[name=’flError’]/value");

if(oErrorNode && oErrorNode.text == "1")

{

var oMessageNode =

xdDoc.selectSingleNode("//member[name=’message’]/value")

var msg = "Error:/n" + oMessageNode.text;

}

else

{

var oPingNode = xdDoc.selectSingleNode("//member[name=’pingid’]/value");

var oTopicNode =

xdDoc.selectSingleNode("//member[name=’topicUrl’]/value");

var msg = "Success! Ping "

+ oPingNode.text

+ " successfully added to URL "

+ oTopicNode.text;

}

alert(msg);

}

}

</script>

</head>

<body>

<form name="pingForm" id="pingForm">

<table width="100%">

<tr><td>Blog name:</td>

<td><input id="blog_name" name="blog_name" size="45"></td></tr>

<tr><td>Post title:</td>

<td><input id="title" name="title" size="45" ></td></tr>

<tr><td>Post url:</td>

<td><input id="url" name="url" size="45"></td></tr>

<tr><td>Post excerpt:</td>

<td><textarea rows="6" cols="34" id="excerpt"

name="excerpt"></textarea></td></tr>

</table>

<input type="button" value="Send The Ping" id="btnPost" name="btnPost"

οnclick="doPost()">

</form>

</body>

</html>

2. 把这段代码保存为posttester.html,然后在IE浏览器中打开这个文件。在文本框里输入示例信息—— 实际内容并不重要,因为这只是一个“测试”频道。然后单击Send the Ping按钮,执行POST方法,并显示返回结果。图14-4显示一次成功操作的结果。

 

图 14‑4

   如果发送的Ping标识符已经存在,则会返回一个错误消息,如图14-5所示。

 

图 14‑5

工作过程

本示例中使用了一个标准的HTML表单。它提供了三个文本框(textbox)和一个文本区域(text area),用来存放需要发送的详细内容。变量xhHTTP声明在两个函数之外,因此它是一个全局变量,允许这两个函数使用。实际发送数据总是使用Submit按钮,但是这里使用一个标准的按钮,单击它就调用doPost()函数:

function doPost()

..{

var xdDoc, sXML;

sXML = "<methodCall>"

+ "<methodName>topicExchange.ping</methodName>"

+ "<params><param><value><string>test</string></value></param>"

+ "<param><value><struct>"

+ "<member><name>blog_name</name>"

+ "<value><string>" + pingForm.blog_name.value +

"</string></value></member>"

+ "<member><name>title</name>"

+ "<value><string>" + pingForm.title.value +

"</string></value></member>"

+ "<member><name>url</name>"

+ "<value><string>" + pingForm.url.value +

"</string></value></member>"

+ "<member><name>excerpt</name>"

+ "<value><string>" + pingForm.excerpt.value +

"</string></value></member>"

+ "</struct></value>"

+ "</param></params>"

+ "</methodCall>";

函数的前面部分建立一个字符串,字符串的内容采用需要的XML格式,并把来自表单的4个值插入到这个字符串。这4个值分别代表:网络日志名、标题、URL和内容摘要。

根据本例子的方法创建XML字符串对于很小的程序和演示程序是可行的,但是很容易出错。例如,有些字符需要用转义符表示,如用户输入的&符号,它们会影响文档的良构性。一种更健壮的创建文档的方法已在第11章里介绍过,即使用XML 文档对象模型。

xdDoc = new ActiveXObject("MSXML2.DOMDocument.3.0");

xdDoc.async = false;

xdDoc.loadXML(sXML);

接着把这个字符串装入到一个XML文档里。先调用ActiveXObject()方法,该方法的参数是程序标识符,此处是MSXML2.DOMDocument.3.0。MSXML组件有很多版本,在一些比较新的系统里,默认安装了3.0版本。下一行把async属性设置为false,它表示当装入XML文档时,程序必须处于等待状态(后面将介绍,当采用异步调用模式时,程序不会暂停,继续执行)。最后,用loadXML()方法把含有Web服务请求的XML文档装入到内存。

在使用IE MSXML时,Windows XP和后来的版本以及大部分的Windows 2000版本都安装了3.0版本。MSXML的最新版 6.0可以从www.microsoft.com/上下载。

xhHTTP = new ActiveXObject("MSXML2.XMLHTTP.3.0");

xhHTTP.onreadystatechange = handleReadyStateChange;

xhHTTP.open("POST", "http://topicexchange.com/RPC2", true);

xhHTTP.send(xdDoc);

装入了XML 文档之后,接着建立第二个ActiveX 对象。MSXML2.XMLHTTP这个类的名字很容易引起误解,因为实际上它可以用任何格式发送请求,并不只限于XML格式。与前面一样,3.0版本是性能与通用性之间的一个折中。

onreadystatechange属性表示,当请求的状态发生变化时,将采取什么样的处理方法。处理的方法共有5级,从0开始,最后级是4。0表示没有初始化,4表示完全初始化。状态4是我们最感兴趣的,它的属性值是一个函数指针,即函数名,不需要引号或括号。

接着用Open方法对HTTP请求进行初始化。它需要三个参数:GET或POST方法、实际的URL地址和是否采用异步方式。在本例里,最后一个参数设置为true。采用异步方式执行请求有许多优点,其中之一是,在服务器处理请求时,用户可以执行其他任务。这个功能成为Ajax编程的支柱(有关Ajax将在第16章介绍)。

调用了Open()方法之后,就用send()方法把XML数据发送给服务器。现在把程序的控制权交给了用户,直到执行handleReadyState()函数为止:

function handleReadyStateChange()

{

if (xhHTTP.readyState == 4)

{

var xdDoc = xhHTTP.responseXML;

xdDoc.setProperty("SelectionLanguage", "XPath");

前面曾提到,每当xhHTTP的状态值发生变化时,就调用handleReadyStateChange()函数。我们关心的是,响应什么时候结束,这要用readyState的值4来表示。当响应结束后,把返回的XML文档赋值给xdDoc变量。然后用setProperty()方法指令xdDoc使用XPath作为选取语言(这一步是必须的,因为在微软最初发布这些库时,XPath还没有全部完成,因此采用一个类似的但现在已过时的语法即XSLPattern作为搜索和选取语言)。

var oErrorNode = xdDoc.selectSingleNode("//member[name='flError']/value");

if(oErrorNode && oErrorNode.text == "1")

{

var oMessageNode = xdDoc.selectSingleNode("//member[name='message']/value")

var msg = "Error:/n" + oMessageNode.text;

}

SelectSingleNode()方法首先搜索子元素为<name>的<member>元素,当name的值为flError时,就把<value>子元素赋给oErrorNode变量。当这个结点的text值为1时,表示出现了错误,必须使用一个类似的XPath搜索路径读取实际的错误消息:

else

{

var oPingNode =

xdDoc.selectSingleNode("//member[name='pingid']/value");

var oTopicNode =

xdDoc.selectSingleNode("//member[name='topicUrl']/value");

var msg = "Success! Ping "

+ oPingNode.text

+ " successfully added to URL "

+ oTopicNode.text;

}

alert(msg);

}

}

如果一切都正常,则选取两个结点元素,并从它们那里得到新帖子的ID和标题URL地址。它必须与HTTP请求中发送的内容相匹配。最后,把执行失败或成功的消息显示给用户。发送给服务器的信息采用如下的格式:

POST /RPC2 HTTP/1.1

Accept:*/*

Accept-Language: en-us

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Win32)

Host: www.wiley.com

Content-Length: 591

<methodCall><methodName>topicExchange.ping</methodName><params><param><value>

<string>test</string></value></param><param><value><struct><member>

<name>blog_name</name><value><string>Wiley Books</string></value>

</member><member><name>title</name><value><string>Latest Editions</string>

</value></member><member><name>url</name><value><string>

http://www.wiley.com/CDASection/index.html

</string></value></member><member><name>excerpt</name>

<value><string> There is something for everyone with these latest editions from

Wiley.</string></value>

</member></struct></value></param></params></methodCall>

同时,服务器返回的信息采用如下的格式:

HTTP/1.1 200 OK

Server: Microsoft-IIS/5.0

Date: Fri, 06 Jul 2001 17:48:34 GMT

Content-Length: 48

Content-Type: text/xml

<methodResponse>

<params>

<param>

<value>

<struct>

<member>

<name>topicUrl</name>

<value><string>http://topicexchange.com/t/test/</string></value>

</member>

<member>

<name>flError</name>

<value><boolean>0</boolean></value>

</member>

<member>

<name>editkey</name>

<value><string>cqvq9v20805830945mv0a9w4850239185932850</string>

</value>

</member>

<member>

<name>errorCode</name>

<value><int>0</int></value>

</member>

<member>

<name>pingid</name>

<value><string>28044</string></value>

</member>

<member>

<name>message</name>

<value><string>New ping added.</string></value>

</member>

<member>

<name>topicName</name>

<value><string>test</string></value>

</member>

</struct>

</value>

</param>

</params>

</methodResponse>

现在已建立了一个用HTTP POST来回传送消息的系统。在这个系统里,所有数据都采用XML格式。这实现起来确实很容易,但这还只是初显身手!

8. 用Firefox和Netscape发布帖子

前面的例子只适用于Windows系统的IE浏览器。对于基于Gecko引擎的浏览器,如Firefox和Netscape等,必须采用另外一种方法。对于这些浏览器,它们内部已嵌入了HTTP请求的功能,不需要外部的ActiveX组件。建立一个HttpRequest请求,要用下面的命令:

var xhHTTP = new XmlHttpRequest();

与微软一样,它也有open()和send()等方法。如果每次开发一个网页都创建两个版本的代码,那将是烦不胜烦,通常的方法是用JavaScript库构建界面,这些库会自动创建所需要的组件。此外,库函数也会把一些方法添加到XmlHttpRequest类上,这样它就像一个微软的程序。

一个最受人们欢迎的跨浏览器组件库由Nicholas C .Zakas开发。它是Professional Ajax一书(Wrox出版,2006)的作者。这个组件库已包含在本书的下载文件里,在作者的个人网站上(www.nczonline.net)还有其他一些有用的工具程序。它有两个版本:zxml.js和zxml.src.js,前者已包含在本书的下载文件里(为了快速下载,已压缩);后者的代码内容与前者一样,但是出于教学目的,经过了格式处理,并且加上了注解。

这个库以及其他库的基础是需要检测浏览器的类型,并返回合适的对象。

试一试  用Firefox和Netscape发帖子

库函数需要执行的一项例行任务是读取XMLHttpRequest的正确版本,是IE 7.0以前版本的ActiveX控件,还是Firefox和Netscape内置的控件,或者是IE 7.0的控件。基本的代码如下所示:

var zXml /*:Object*/ = {

useActiveX: (typeof ActiveXObject != "undefined"),

useDom: document.implementation && document.implementation.createDocument,

useXmlHttp: (typeof XMLHttpRequest != "undefined")

};

zXml.ARR_XMLHTTP_VERS /*:Array*/ = ["MSXML2.XmlHttp.6.0","MSXML2.XmlHttp.3.0"];

zXml.ARR_DOM_VERS /*:Array*/ = ["MSXML2.DOMDocument.6.0","MSXML2.DOMDocument.3.0"];

/**

* Static class for handling XMLHttp creation.

* @class

*/

function zXmlHttp() {

}

/**

* Creates an XMLHttp object.

* @return An XMLHttp object.

*/

zXmlHttp.createRequest = function ()/*:XMLHttp*/ {

//if it natively supports XMLHttpRequest object

if (zXml.useXmlHttp) {

return new XMLHttpRequest();

} else if (zXml.useActiveX) { //IE < 7.0 = use ActiveX

if (!zXml.XMLHTTP_VER) {

for (var i=0; i < zXml.ARR_XMLHTTP_VERS.length; i++) {

try {

new ActiveXObject(zXml.ARR_XMLHTTP_VERS[i]);

zXml.XMLHTTP_VER = zXml.ARR_XMLHTTP_VERS[i];

break;

} catch (oError) {

}

}

}

if (zXml.XMLHTTP_VER) {

return new ActiveXObject(zXml.XMLHTTP_VER);

} else {

throw new Error("Could not create XML HTTP Request.");

}

} else {

throw new Error("Your browser doesn't support an XML HTTP Request.");

}

};

第一步需要决定,采用微软的XmlHttp类还是Firefox等的内置XmlHttpRequest控件:

var zXml /*:Object*/ = {

useActiveX: (typeof ActiveXObject != "undefined"),

useDom: document.implementation && document.implementation.createDocument,

useXmlHttp: (typeof XMLHttpRequest != "undefined")

}

首先说明zXml变量并插入三个属性。这三个属性分别表示是否使用ActiveX控件、内置的DOM和内置的XmlHttpRequest。

微软的XML类有很多版本,但是经常使用的是6.0版本和3.0版本,因此接着需要定义一个数组,表示这些版本:

zXml.ARR_XMLHTTP_VERS = ["MSXML2.XmlHttp.6.0", "MSXML2.XmlHttp.3.0"];

这个程序的后面部分将要循环访问这个数组里的每个元素,并初始化最新的版本:

zXmlHttp.createRequest = function ()

{

if (zXml.useXmlHttp) {

return new XMLHttpRequest();

}

函数的第一部分只是返回XmlHttpRequest对象,假设有这个对象。

else if (zXml.useActiveX) {

if (!zXml.XMLHTTP_VER) {

for (var i=0; i < zXml.ARR_XMLHTTP_VERS.length; i++) {

try {

new ActiveXObject(zXml.ARR_XMLHTTP_VERS[i]);

zXml.XMLHTTP_VER = zXml.ARR_XMLHTTP_VERS[i];

break;

} catch (oError) {

}

}

}

如果使用的是微软的浏览器,则执行上面这段程序。如果程序以前已经运行过,则把最新版本保存在zXml.XMLHTTP_VER变量里。否则,循环语句从最新的版本开始逐个进行测试,如果测试正常,则返回一个有效的实例,并把这个版本保存在zXml.XMLHTTP_VER变量里。其余代码只是进行异常处理。这些异常现象包括不能生成XML请求,浏览器的版本太老,或者用户的安全配置禁用ActiveX控件等。

下面的程序文件PostTester-CrossBrowser.html是PostTester-IE.html文件的跨浏览器版。HTML的主体部分内容还是与原来的一样,只是脚本程序不同。注意,代码中包含了zXml.js库:

<script type="text/javascript" src="zXML.src.js"></script>

<script type="text/javascript">

var xhHTTP;

function doPost()

{

var xdDoc, sXML;

sXML = "<methodCall>"

+ "<methodName>topicExchange.ping</methodName>"

+ "<params><param><value><string>test</string></value></param>"

+ "<param><value><struct>"

+ "<member><name>blog_name</name>"

+ "<value><string>" + pingForm.blog_name.value +

"</string></value></member>"

+ "<member><name>title</name>"

+ "<value><string>" + pingForm.title.value + "</string></value></member>"

+ "<member><name>url</name>"

+ "<value><string>" + pingForm.url.value + "</string></value></member>"

+ "<member><name>excerpt</name>"

+ "<value><string>" + pingForm.excerpt.value +

"</string></value></member>"

+ "</struct></value>"

+ "</param></params>"

+ "</methodCall>";

if (zXml.useXmlHttp && !zXml.useActiveX)

{

try

{

netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");

}

catch (e)

{

alert("Permission UniversalBrowserRead denied.");

}

}

xdDoc = zXmlDom.createDocument();

xdDoc.loadXML(sXML);

xhHTTP = zXmlHttp.createRequest();

xhHTTP.onreadystatechange = handleReadyStateChange;

xhHTTP.open("POST", "http://topicexchange.com/RPC2", true);

xhHTTP.send(xdDoc);

}

function handleReadyStateChange()

{

if (xhHTTP.readyState == 4)

{

if (zXml.useXmlHttp && !zXml.useActiveX)

{

try

{

netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");

}

catch (e)

{

alert("Permission UniversalBrowserRead denied.");

}

}

var xdDoc = xhHTTP.responseXML;

var oErrorNode = zXPath.selectSingleNode(xdDoc.documentElement,

"//member[name='flError']/value");

if (oErrorNode && oErrorNode.text == 1)

{

var oMessageNode = zXPath.selectSingleNode(xdDoc.documentElement,

"//member[name='message']/value")

var msg = "Error:/n" + oMessageNode.text;

}

else

{

var oPingNode = zXPath.selectSingleNode(xdDoc.documentElement,

"//member[name='pingid']/value");

var oTopicNode = zXPath.selectSingleNode(xdDoc.documentElement,

"//member[name='topicUrl']/value");

var msg = "Success! Ping "

+ oPingNode.text

+ " successfully added to URL "

+ oTopicNode.text

}

alert(msg);

}

}

</script>

工作过程

   这个程序主要有两点比较重要。第一是用zXml、zXmlHttp和zXpath建立XML文档和XML请求,并从响应中读取信息。这样安排是为了使它可以适用于三个不同的浏览器,并且通过检测浏览器的类型,选用合适的类和方法。

第二是下面这段代码:

if (zXml.useXmlHttp)

{

try

{

netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");

}

catch (e)

{

alert("Permission UniversalBrowserRead denied.");

}

}

   虽然可以从本地的驱动里寻找并运行IE 版本的PostTester-IE.html,但是对于Mozilla版本,不允许这样。一项称为跨域发帖的安全措施禁止这样的操作。即使是IE 浏览器,如果从Web服务器上运行这个文件,也会运行失败。跨域发帖表示,要访问的服务必须与发送请求的页面不在同一个域里。

   由于发帖请求有可能来自于带有恶意目的的网页,后者可能发送对第三方有害的数据,因此默认时,跨域发帖是被禁止的。用户只有提高浏览器的权限级别才可以。前面的程序会出现如图14-6所示的对话框。

图 14‑6

   用户可以把IE游览器的跨域操作模式设置为允许、禁止和提示。显然,这样的情形并不是我们理想中的。第16章的Ajax将推出一种更好的方法—— 服务器端代理,它解决了这个问题,又不会给用户带来任何不利的影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值