ajax訪問Web服務(Yahoo搜索的例子)[轉]

多 年以來一直存在一個軟件工程問題:從一臺機器調用另一臺機器上的服務或方法,即使這些機器使用完全不同的硬件或軟件。對於這個問題,最近提出的解決方案是 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對象非常適合作爲基於RESTWeb服務的客戶。使用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" οnclick="doSearch();"/>
  </form>
 
  <h2>Results:</h2>
  <div id="results"/>
 
</body>
</html>
點擊頁面上的Submit(提交)按鈕將調用doSearch函數。這個函數使用createQuery- String函數來創建目標URL,createQueryString函數負責把搜索項和顯示的最大結果數(即最多顯示多少個結果)放在查詢串中。需要注意,參數名(queryresults)都是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元素的子元素TitleSummaryClickUrlUrl創建內容,並增加到頁面。
可以看到,與基於REST的Web服務結合使用時,Ajax技術相當強大。如果想在你自己的域中訪問Web服務,用JavaScript就可以完成。否則,當訪問其他域的資源時,就要創建外部資源的某種網關,這樣就能避免瀏覽器安全沙箱問題。
圖4-15顯示了結合使用Yahoo! Search Web服務和Ajax的搜索結果。
圖4-15 結合使用Yahoo! Search Web服務和Ajax的搜索結果
Ajax也能與SOAP一同使用嗎?
可以結合使用Ajax和SOAP嗎?答案很簡單——可以。Ajax技術和基於SOAPWeb服務可以一同使用,不過,與使用基於RESTWeb服務相比,這需要做更多的工作。
REST和SOAP都把響應返回爲XML文檔。二者之間最顯著的差異是,REST將請求作爲帶查詢串參數的簡單URL發送,而SOAP請求是具體的XML文檔,通常通過POST而不是GET發送。
結合使用SOAP和Ajax時,要求以某種方式創建SOAP請求的XML,這可能並不容易。一種做法是使用串連接來創建請求XML。儘管概念上講很簡單,但這種方法有些混亂,而且很容易出錯,如很容易這兒忘了雙引號,那兒忘了加號。
還 有一種選擇是使用一個XMLHttpRequest請求從網站加載靜態XML文檔,文檔是SOAP請求的模板。一旦加載了模板,就可以使用 JavaScript DOM方法來修改模板,使之滿足特定的請求。請求準備好後,再用第二個XMLHttpRequest請求發送新創建的SOAP請求。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章