4.8 访问Web服务
多年以来一直存在一个软件工程问题:从一台机器调用另一台机器上的服务或方法,即使这些机器使用完全不同的硬件或软件。对于这个问题,最近提出的解决方案是Web服务。几年前,Web服务大受吹捧,它的头上围绕着耀眼的光环,有些人认为Web服务就是分布式软件开发的“圣杯”。后来,它的光芒逐渐黯淡下来,Web服务最终找到了自己合适的位置,它是支持异构计算机系统相互操作的一种有用的工具。
Web服务通常用作为计算机系统之间的通信管道,这与CORBA(公共对象请求代理体系结构)、RMI(远程方法调用)或DCOM(分布式组件对象模型)很相似。区别在于,Web服务独立于具体的开发商,可以采用大量编程工具和平台来实现。为了支持更高层次的互操作性,Web服务是基于文本的协议,通常在HTTP之上实现。由于Web服务是基于文本的协议,所以几乎总能使用某种XML。
最著名的Web服务实现是SOAP(简单对象访问协议)。SOAP是由W3C管理的规约,它是XML协议,对于如何调用远程过程给出了定义。
WSDL(Web服务描述语言)文档也是XML文档,描述了如何创建Web服务的客户。通过提供WSDL文档,Web服务提供者就能很轻松地为可能的客户创建客户端代码。WSDL和SOAP通常一同使用,不过不一定非得这样,因为这两个规约是分开维护的。
尽管人们在简化SOAP实现上做出了很大努力,但SOAP还是一个很难使用的技术,因此很受“排挤”,只有在跨平台互操作性确实是一个很重要的需求时才会使用SOAP。实现Web服务还有一种更简单的方法,称为REST(代表状态传输),它在开发人员中享有越来越高的知名度,这些开发人员一方面希望得到SOAP好处的80%,另一方面只希望付出SOAP代价的20%。
Yahoo!选择REST作为其公共Web服务的协议。Yahoo!认为基于REST的服务很容易理解,而且很推崇REST的“平易近人”,因为当前大多数编程语言都可以访问REST。实际上,Yahoo!相信,与SOAP相比,REST的门槛更低,使用也更容易。
通过使用REST,建立请求时可以先指定一个服务入口URL,再向查询串追加搜索参数。服务将结果返回为XML文档。这个模式听上去是不是很熟悉?你说对了,它与本书中你见过的Ajax例子是一样的。
XMLHttpRequest对象非常适合作为基于REST的Web服务的客户。使用XMLHttpRequest对象,可以向Web服务异步地发出请求,并解析得到的XML响应。对于Yahoo!
Web服务,XMLHttpRequest对象可以向Yahoo!发出请求,搜索指定的项。一旦Yahoo!返回响应,则使用JavaScript
DOM方法解析响应,并向页面动态地提供结果数据。
代码清单4-15展示了如何使用Ajax技术访问Yahoo! Web服务,并向页面提供结果。页面上的文本字段允许用户指定搜索项。用户可以使用选择框来指定需要显示多少个结果。点击Submit(提交)按钮就能启动搜索。
不过,先等等!第2章我们曾经说过,XMLHttpRequest对象只能访问发起文档(即调用脚本)所在域中的资源。如果试图访问其他域的资源,可能因为浏览器的安全限制而失败。怎么解决呢?
解决办法有好几个。在第2章已经了解到,浏览器实现安全沙箱的方式各有不同。IE会询问用户是否允许访问另一个域中的资源。Firefox则会报告错误,自动失败,虽然可以用专用于Firefox的JavaScript代码避免这种行为。
还有一个选择,这也是本例中要采用的方法,就是建立Yahoo!的网关,它与XMLHttp-
Request脚本在同一个域中。由网关接收来自XMLHttpRequest对象的请求,并把它转发到Yahoo!
Web服务。Yahoo!做出响应返回结果时,网关再把结果路由传送到浏览器。通过使用这种方法,就能避免使用浏览器特定的JavaScript。另外,这种方法也更加健壮,因为你还可以扩展网关,让它支持其他的Web服务提供者。
代码清单4-15 yahooSearch.html
<!DOCTYPE html PUBLIC "-//W3C//DTD
XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Yahoo! Search Web
Services</title>
<script type="text/javascript">
var xmlHttp;
function createXMLHttpRequest()
{
if (window.ActiveXObject)
{
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest)
{
xmlHttp = new XMLHttpRequest();
}
}
function doSearch() {
var url =
"YahooSearchGateway?" + createQueryString()
+ "&ts=" + new Date().getTime();
createXMLHttpRequest();
xmlHttp.onreadystatechange
= handleStateChange;
xmlHttp.open("GET",
url, true);
xmlHttp.send(null);
}
function createQueryString()
{
var searchString
= document.getElementById("searchString").value;
searchString
= escape(searchString);
var maxResultsCount
= document.getElementById("maxResultCount").value;
var queryString
= "query=" + searchString + "&results="
+ maxResultsCount;
return queryString;
}
function handleStateChange()
{
if(xmlHttp.readyState
== 4) {
if(xmlHttp.status == 200) {
parseSearchResults();
}
else {
alert("Error accessing Yahoo! search");
}
}
}
function parseSearchResults()
{
var resultsDiv
= document.getElementById("results");
while(resultsDiv.childNodes.length
> 0) {
resultsDiv.removeChild(resultsDiv.childNodes[0]);
}
var allResults
= xmlHttp.responseXML.getElementsByTagName("Result");
var result
= null;
for(var i
= 0; i < allResults.length; i++) {
result = allResults[i];
parseResult(result);
}
}
function parseResult(result)
{
var resultDiv
= document.createElement("div");
var title
= document.createElement("h3");
title.appendChild(document.createTextNode(
getChildElementText(result, "Title")));
resultDiv.appendChild(title);
var summary = document.createTextNode(getChildElementText(result, "Summary"));
resultDiv.appendChild(summary);
resultDiv.appendChild(document.createElement("br"));
var clickHere
= document.createElement("a");
clickHere.setAttribute("href",
getChildElementText(result, "ClickUrl"));
clickHere.appendChild(document.createTextNode
(getChildElementText(result, "Url")));
resultDiv.appendChild(clickHere);
document.getElementById("results").appendChild(resultDiv);
}
function getChildElementText(parentNode,
childTagName) {
var childTag
= parentNode.getElementsByTagName(childTagName);
return childTag[0].firstChild.nodeValue;
}
</script>
</head>
<body>
<h1>Web Search Using
Yahoo! Search Web Services</h1>
<form action="#">
Search String:
<input type="text" id="searchString"/>
<br/><br/>
Max Number
of Results:
<select
id="maxResultCount">
<option value="1">1</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</select>
<br/><br/>
<input
type="button" value="Submit" onclick="doSearch();"/>
</form>
<h2>Results:</h2>
<div id="results"/>
</body>
</html>
点击页面上的Submit(提交)按钮将调用doSearch函数。这个函数使用createQuery- String函数来创建目标URL,createQueryString函数负责把搜索项和显示的最大结果数(即最多显示多少个结果)放在查询串中。需要注意,参数名(query和results)都是Yahoo!
Search API定义的。
createQueryString函数创建的查询串发送给Yahoo! Search网关。在这个例子中,网关实现为名为YahooSearchGatewayServlet的Java
servlet(见代码清单4-16)。这个servlet的目的很简单,就是转发对Yahoo! Search URL的所有请求,并把结果传给浏览器。当然,这个网关也可以用其他语言(而不是Java)来实现。这个网关很简单,在XMLHttpRequest对象需要访问其他域中的资源时,这个网关确实能解决问题。
代码清单4-16 YahooSearchGatewayServlet.java
package ajaxbook.chap4;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.servlet.*;
import javax.servlet.http.*;
public class YahooSearchGatewayServlet
extends HttpServlet {
private static
final String YAHOO_SEARCH_URL =
"http://api.search.yahoo.com/WebSearchService/V1/webSearch?"
+ "appid=your_app_id" + "&type=all";
protected
void processRequest(HttpServletRequest request
, HttpServletResponse response)
throws ServletException,
IOException {
String url = YAHOO_SEARCH_URL + "&" + request.getQueryString();
HttpURLConnection con = (HttpURLConnection)new URL(url).openConnection();
con.setDoInput(true);
con.setDoOutput(true);
con.setRequestMethod("GET");
//Send back the response to the browser
response.setStatus(con.getResponseCode());
response.setContentType("text/xml");
BufferedReader reader =
new BufferedReader(new InputStreamReader(con.getInputStream()));
String input = null;
OutputStream responseOutput = response.getOutputStream();
while((input = reader.readLine()) != null) {
responseOutput.write(input.getBytes());
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException,
IOException {
processRequest(request, response);
}
}
Yahoo! Search把结果返回给网关,而且网关将结果再转发给浏览器后,会调用parse-
SearchResults函数。这个函数从XMLHttpRequest对象获取得到的XML文档,并查找所有标记名为Result的元素。
各Result元素传给parseResult函数。这个函数使用Result元素的子元素Title、Summary、ClickUrl和Url创建内容,并增加到页面。
可以看到,与基于REST的Web服务结合使用时,Ajax技术相当强大。如果想在你自己的域中访问Web服务,用JavaScript就可以完成。否则,当访问其他域的资源时,就要创建外部资源的某种网关,这样就能避免浏览器安全沙箱问题。
图4-15显示了结合使用Yahoo! Search Web服务和Ajax的搜索结果。
图4-15 结合使用Yahoo! Search Web服务和Ajax的搜索结果
Ajax也能与SOAP一同使用吗?
可以结合使用Ajax和SOAP吗?答案很简单——可以。Ajax技术和基于SOAP的Web服务可以一同使用,不过,与使用基于REST的Web服务相比,这需要做更多的工作。
REST和SOAP都把响应返回为XML文档。二者之间最显著的差异是,REST将请求作为带查询串参数的简单URL发送,而SOAP请求是具体的XML文档,通常通过POST而不是GET发送。
结合使用SOAP和Ajax时,要求以某种方式创建SOAP请求的XML,这可能并不容易。一种做法是使用串连接来创建请求XML。尽管概念上讲很简单,但这种方法有些混乱,而且很容易出错,如很容易这儿忘了双引号,那儿忘了加号。
还有一种选择是使用一个XMLHttpRequest请求从网站加载静态XML文档,文档是SOAP请求的模板。一旦加载了模板,就可以使用JavaScript
DOM方法来修改模板,使之满足特定的请求。请求准备好后,再用第二个XMLHttpRequest请求发送新创建的SOAP请求。
上一页 首页
下一页 |