Nicholas Chase (nicholas@nicholaschase.com), 開放源代碼開發(fā)和分析人員, Studio B
2004 年 10 月 01 日
Applet
被設(shè)計(jì)成在“沙箱”中運(yùn)行,不會對用戶系統(tǒng)造成任何損害,因此其安全性要高于基于服務(wù)器的應(yīng)用程序?qū)Φ任铩1确秸f,Java?
應(yīng)用程序很容易讓其他服務(wù)器的網(wǎng)絡(luò)連接請求 Web 服務(wù)響應(yīng),applet 也能做到,只不過只能和最初發(fā)起的服務(wù)器對話。但是如果希望
applet 能夠發(fā)出任意的 Web 請求,該怎么辦呢?本文說明如何通過建立基于服務(wù)器的代理來解決這個(gè)問題。它還說明了如何使用
JavaScript 代碼訪問基于 applet 的信息。
本文將介紹如何創(chuàng)建一個(gè)系統(tǒng),從而可以使用瀏覽器請求和交互任意來源的 Web 服務(wù)數(shù)據(jù)。首先創(chuàng)建一個(gè)基本的 applet,然后再創(chuàng)建從 Web 頁面中提取數(shù)據(jù)的 JavaScript 代碼,最后創(chuàng)建一個(gè) servlet 作為非本地請求的代理。
本
文假設(shè)您熟悉 Java 技術(shù)和(初步了解)XML。除了 J2SE 1.4 或更高版本這樣的 Java
開發(fā)環(huán)境之外,本文還需要用到幾個(gè)軟件。為了發(fā)送和接收 SOAP 消息,您需要 SOAP with Attachments
Application Program Interface (API) for Java(SAAJ,關(guān)于如何設(shè)置它,請參閱“ Send and receive SOAP messages with SAAJ”),以及 servlet 引擎,如 IBM? WebSphere? Application Server 或者 Apache Tomcat 來運(yùn)行 servlet。關(guān)于所需各種軟件包的鏈接,請參閱
參考資料。
一個(gè)簡單的請求
首先看一看最終由 applet 發(fā)出的請求。雖然這項(xiàng)技術(shù)適用于任何能夠通過 URL 傳遞的數(shù)據(jù),但本文主要討論 Web 服務(wù),因此我們從一個(gè)簡單的 SOAP 消息開始,如清單 1 所示。
清單 1. 一個(gè)簡單的 SOAP 消息
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="urn:chaosmagnet-quote"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:getQuoteResponse>
<return xsi:type="xsd:string">The early bird gets the worm, but it‘s the
second mouse that gets the cheese...</return>
</ns1:getQuoteResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
該消息允許您創(chuàng)建一個(gè)簡單的 Java 應(yīng)用程序(清單 2)來檢索和解析該 URL。
清單 2. 通過 Java 應(yīng)用程序訪問 URL
import java.net.URLConnection;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class SendRequest {
public static void main(String args[]){
try{
URL url = new URL("http://www.nicholaschase.com/testsoap.php");
URLConnection urlconn = url.openConnection();
Document doc = null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(urlconn.getInputStream());
System.out.println(doc.getDocumentElement()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling().getFirstChild().getNodeValue());
} catch (IOException e) {
System.out.println("can‘t find the file:"+e.getMessage());
} catch (Exception e) {
System.out.print("Problem parsing the XML.");
}
} catch (Exception e){
e.printStackTrace();
}
}
}
|
|
簽名 applet 和未簽名 applet
通常,applet 只能與有限的幾臺服務(wù)器建立連接。為了繞開這一限制,applet 需要進(jìn)行數(shù)字簽名。這樣做雖然避開了限制,但是會造成瀏覽器彈出告警對話框,詢問用戶是否接受該簽名。如果認(rèn)為用戶會對此類警告感到擔(dān)憂,最好像下面這樣來避開這一限制。
|
|
首先,創(chuàng)建真正的
URLConnection 。在此,需要把
InputStream 提供給
DocumentBuilder ,作為構(gòu)造
Document 對象的來源。我發(fā)現(xiàn)輸出語句不是很好,但本文主要討論如何訪問數(shù)據(jù)而不是分析數(shù)據(jù),所以我就采用了直接引用的方法。
編譯該程序然后在命令行中運(yùn)行就可以得到預(yù)期的結(jié)果:
The early bird gets the worm, but it‘s the second mouse that gets the cheese...
|
您可能奇怪我為何寧愿這么麻煩地直接處理 XML,而不去使用(比方說)SAAJ。這是因?yàn)樽罱K要把這些代碼打包成一個(gè) applet,它要在我無法控制的計(jì)算機(jī)上運(yùn)行,因此希望堅(jiān)持使用作為 Java 技術(shù)本身一部分的類。
創(chuàng)建 applet
applet 本身很簡單,如清單 3 所示:
清單 3. 一個(gè)簡單的 applet
import java.applet.*;
import java.awt.*;
public class SendRequest extends Applet {
public void paint(Graphics g) {
g.drawRect(0, 0, 499, 149);
g.drawString("Printing...", 5, 70);
}
}
|
每次打開這個(gè) applet 時(shí),applet 都僅僅繪制一個(gè)矩形,并在其中顯示“Printing...”。保存并編譯這個(gè)類,然后打開第二個(gè)文本文件創(chuàng)建來顯示這個(gè) applet 的 HTML 頁面,如清單 4 所示。
清單 4. 顯示 applet 的 HTML 頁面
<HTML>
<HEAD>
<TITLE>A Simple Program</TITLE>
</HEAD>
<BODY>
<CENTER>
<APPLET CODE="SendRequest.class" WIDTH="500" HEIGHT="150">
</APPLET>
</CENTER>
</BODY>
</HTML>
|
注意,通常 HTML 頁面都包含用來調(diào)用 applet 代碼的
APPLET 標(biāo)簽。將該 HTML 頁面保存到
SendRequest.class 文件所在的目錄中,然后在瀏覽器中打開它。您應(yīng)該看到與圖 1 類似的結(jié)果。
圖 1. 簡單的 applet
現(xiàn)在添加檢索 URL 的代碼。
從 applet 中訪問響應(yīng)
在 applet 中添加檢索 URL 的代碼很簡單,如清單 5 所示。
清單 5. 在 applet 中添加檢索 URL 的代碼
import java.applet.*;
import java.awt.*;
import java.net.URLConnection;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class SendRequest extends Applet {
public void paint(Graphics g) {
g.drawRect(0, 0, 499, 149);
g.drawString(
getResponseText(), 5, 70);
}
public String getResponseText(){
try{
URL url = new URL("http://www.nicholaschase.com/testsoap.php");
URLConnection urlconn = url.openConnection();
Document doc = null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(urlconn.getInputStream());
return (doc.getDocumentElement()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling()
.getFirstChild().getNextSibling()
.getFirstChild().getNodeValue());
} catch (Exception e) {
return "Can‘t get the string.";
}
} catch (Exception e){
return "Problem accessing the response text.";
}
}
}
|
這里包含了大量原來應(yīng)用程序中的代碼,只是將
main() 方法改成了
getResponseText() 方法,并在瀏覽器顯示 applet 時(shí)輸出到頁面中。
一切都準(zhǔn)備就緒,但在刷新頁面時(shí),會看到情況并非如此,如圖 2 所示。(要看到變化,必須在刷新頁面時(shí)按下
Ctrl鍵)。
圖 2. 從本地文件系統(tǒng)中調(diào)用 applet
那
么,問題出在哪兒呢?前面已經(jīng)提到,applet 在設(shè)計(jì)時(shí)有一些安全性限制,其中之一就是不能訪問服務(wù)器,但是不包括最初下載 applet
的服務(wù)器。因此,為了從 www.nicholaschase.com 上請求 URL,只需要把 applet 和 HTML
文件上傳到那臺服務(wù)器上。然后再調(diào)用 applet,就能得到預(yù)期的結(jié)果,如圖 3 所示。
圖 3. 從適當(dāng)?shù)姆?wù)器上訪問 applet
現(xiàn)在已經(jīng)獲得了數(shù)據(jù),可以從 HTML 頁面中訪問了。
通過 JavaScript 訪問 applet 數(shù)據(jù)
這個(gè)過程的最終目標(biāo)是使用 JavaScript 代碼分析檢索的數(shù)據(jù)。其中的關(guān)鍵是將 applet 看作一個(gè)對象,事實(shí)上,
APPLET 標(biāo)簽最后將被替換為
object 標(biāo)簽。為了替換標(biāo)簽,必須為其指定
id 屬性,如清單 6 所示。
清單 6. 作為對象訪問 applet
<HTML>
<HEAD>
<TITLE>A Simple Program</TITLE>
</HEAD>
<BODY>
<CENTER>
<APPLET CODE="SendRequest.class" WIDTH="500" HEIGHT="150" id="TheApplet">
</APPLET>
</CENTER>
<b>The returned data is:</b><br />
<script type="text/javascript">
document.write(TheApplet.getResponseText());
</script>
</BODY>
</HTML>
|
為 applet 指定一個(gè)
id 屬性,從而能夠?qū)⑵渥鳛楹唵蔚膶ο筇幚?,并且可以直接調(diào)用 applet 的方法。如果保存該頁面并刷新它,就會看到從頁面中提取的信息(參見圖 4)。
圖 4: 通過 JavaScript 訪問 applet 數(shù)據(jù)
現(xiàn)在就只剩下能夠訪問任意 URL 的問題了。
創(chuàng)建代理
現(xiàn)在萬事俱備,但是因?yàn)榘踩砸?,您只能訪問下載 applet 的服務(wù)器。如何才能訪問不同的服務(wù)器呢?
比
方說,假設(shè)要從 Quote of the Day service 獲得實(shí)時(shí)報(bào)價(jià)。由于 applet
只能連接到自己的服務(wù)器,所以您就不能直接連接到 applet。但是服務(wù)器可以連接任何事物,就是說除了直接連接到數(shù)據(jù),您還可以連接到檢索數(shù)據(jù)的
servlet。
清單 7 中的代碼創(chuàng)建了一個(gè)從 Quote of the Day service 中檢索響應(yīng)的 servlet。
清單 7. 檢索遠(yuǎn)程信息的 servlet
import javax.servlet.http.*;
import javax.servlet.*;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
public class SendingServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException {
try {
//First create the connection
SOAPConnectionFactory soapConnFactory =
SOAPConnectionFactory.newInstance();
SOAPConnection connection =
soapConnFactory.createConnection();
//Next, create the actual message
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
//Create objects for the message parts
SOAPPart soapPart = message.getSOAPPart();
SOAPEnvelope envelope = soapPart.getEnvelope();
SOAPBody body = envelope.getBody();
//Populate the body
//Create the main element and namespace
SOAPElement bodyElement =
body.addChildElement(envelope.createName("getQuote" ,
"ns1",
"urn:xmethods-qotd"));
//Save the message
message.saveChanges();
//Send the message and get a reply
//Set the destination
String destination =
"http://webservices.codingtheweb.com/bin/qotd";
//Send the message
SOAPMessage reply = connection.call(message, destination);
//Check the output
//Create the transformer
TransformerFactory transformerFactory =
TransformerFactory.newInstance();
Transformer transformer =
transformerFactory.newTransformer();
//Extract the content of the reply
Source sourceContent = reply.getSOAPPart().getContent();
resp.setHeader("Content-Type", "text/plain");
//Set the output for the transformation
StreamResult result = new StreamResult(resp.getWriter());
transformer.transform(sourceContent, result);
//Close the connection
connection.close();
} catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
|
這段腳本看起來又長又復(fù)雜,但實(shí)際上非常簡單。它首先創(chuàng)建
SOAPConnection 和消息對象,然后根據(jù) Quote of the Day service 的要求使用
getQuote 元素填充該對象。
創(chuàng)建完成請求消息之后,將其發(fā)送到服務(wù)并檢索答復(fù)。將答復(fù)作為
SOAPMessage 對象返回,但是您需要把消息的實(shí)際文本傳遞給 servlet 的
Response 對象。為此,只需要使用以響應(yīng)為目標(biāo)的 XSLT 恒等轉(zhuǎn)換。
編譯上述 servlet,并按照一般的 servlet 方式安裝它(如果需要幫助,請參閱
參考資料),然后就可以直接從瀏覽器中調(diào)用它,并看到圖 5 所示的結(jié)果。
圖 5: 本地 servlet 檢索得到的遠(yuǎn)程響應(yīng)
現(xiàn)在,把遠(yuǎn)程信息放到 applet 中就與調(diào)用 servlet 一樣簡單了,如清單 8 所示:
清單 8. 從 applet 中調(diào)用遠(yuǎn)程數(shù)據(jù)
...
public void paint(Graphics g) {
g.drawRect(0, 0, 499, 149);
g.drawString(getResponseText(), 5, 70);
}
public String getResponseText(){
try{
URL url = new URL("
http://localhost:8080/servlet/SendingServlet");
URLConnection urlconn = url.openConnection();
Document doc = null;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(urlconn.getInputStream());
return (doc.getDocumentElement()
.getFirstChild().getFirstChild().getFirstChild()
.getFirstChild().getNodeValue());
} catch (Exception e) {
return "Can‘t get the string:"+e.toString();
}
} catch (Exception e){
return "Problem accessing the response text."+e.toString();
}
}
}
|
注意,這里的代碼基本上是相同的,只有兩個(gè)地方不一樣。首先,這里沒有直接調(diào)用數(shù)據(jù),而是調(diào)用檢索數(shù)據(jù)的 servlet。其次,因?yàn)榉?wù)返回的消息沒有斷行,所以在這里稍微整理了一下。
如需查看結(jié)果,可以將 applet 和 HTML 頁面復(fù)制到安裝 serlevt 的服務(wù)器上,然后就可以訪問 applet 并察看結(jié)果了,如圖 6 所示。
圖 6: 查看結(jié)果
結(jié)束語
本文介紹了如何創(chuàng)建一個(gè)系統(tǒng),可以使瀏覽器訪問任意的 Web 服務(wù)。JavaScript 代碼在 applet 中調(diào)用了一個(gè)方法,而 applet 又調(diào)用了檢索遠(yuǎn)程信息的 servlet,這樣就避開了 applet 訪問能力的限制。
現(xiàn)
在,您可以從幾個(gè)方面來理清整個(gè)過程或者增強(qiáng)其功能。因?yàn)閿?shù)據(jù)是被拖放入 JavaScript 代碼中的,所以您不需要在頁面上顯示真正的
applet。您還可以修改 applet,從服務(wù)中檢索多條信息,或者在單個(gè)請求中傳回信息,而不必在每次用戶更改窗口時(shí)發(fā)送新的請求。
如果想更進(jìn)一步,還可以修改 servlet 的選項(xiàng),使它從 applet 中獲取參數(shù)。這些參數(shù)可以決定 servlet 調(diào)用什么服務(wù),或者傳遞給服務(wù)什么參數(shù)。使用這些方法(如本文中的
getResponseText() ),您甚至可以編寫 JavaScript 代碼,將這些參數(shù)導(dǎo)入 applet,讓用戶決定最終顯示什么信息。
參考資料
|