2006 年 11 月 23 日
對(duì)于絕大多數(shù) Web 開發(fā)人員來(lái)說(shuō),輸出 Html、XHTML 乃至 xml 是一項(xiàng)微不足道的任務(wù),它只不過(guò)是創(chuàng)建和部署 Web 應(yīng)用程序的常規(guī)流程。從顯示 Internet 頁(yè)面到應(yīng)答電話呼叫看似一個(gè)巨大的飛躍,但實(shí)際并非如此。在這篇文章中,您將看到相同的技術(shù)如何幫助您動(dòng)態(tài)創(chuàng)建 Web 頁(yè)面以便接聽(虛擬的)電話并應(yīng)答呼叫。
在過(guò)去五年中,Web 得到了比以往任何時(shí)候都長(zhǎng)足的發(fā)展。一度主要以文本為基礎(chǔ)的軟件程序媒介 “Web 瀏覽器” 現(xiàn)已發(fā)展成為一種可供任何具有連通性的設(shè)備使用的信息源。最早列入可訪問(wèn) Web 頁(yè)面的設(shè)備列表的是移動(dòng)電話,隨后是尋呼機(jī)、手持設(shè)備、個(gè)人電子助理和其他任何可通過(guò)無(wú)線連接連入 Web 的設(shè)備。在最近幾年,電話也加入了這一陣營(yíng),通過(guò)普通電話線路使用 Web 程序的呼聲也越來(lái)越高。
這種最新型的應(yīng)用程序(用戶通過(guò)電話訪問(wèn)在線服務(wù))的更恰當(dāng)?shù)拿Q是電話應(yīng)用程序(telephone application)。顯而易見,由于電話無(wú)法用來(lái) “單擊一個(gè)鏈接”,應(yīng)用程序交互幾乎全部是通過(guò)語(yǔ)音處理的。用戶不是采用單擊鏈接的方式,而是說(shuō)出 “賬戶信息” 或使用鍵盤按預(yù)先錄制好的指令進(jìn)行操作。
通過(guò)現(xiàn)有(或略加修改的)Web 應(yīng)用程序?yàn)殡娫捥峁┓?wù)的能力是一種強(qiáng)大的想法,也是許多 Web 開發(fā)人員都渴望探索的領(lǐng)域之一。關(guān)于 Web 應(yīng)用程序與電話應(yīng)用程序,要了解的最重要的一件事就是您實(shí)際上可以使用相同的技術(shù)組合來(lái)創(chuàng)建這兩種應(yīng)用程序。HTML、XHTML 和 XML 是 Web 界面之下最常用的三種底層技術(shù),VoiceXML(或簡(jiǎn)稱為 VXML)則是一種密切相關(guān)的技術(shù),它使得電話客戶機(jī)可以利用 Web 交互。javaServer Pages 與 servlet、php 腳本以及 Ruby 應(yīng)用程序均可響應(yīng)電話請(qǐng)求,就像響應(yīng)那些進(jìn)入手持設(shè)備或 Web 瀏覽器的請(qǐng)求一樣輕松。在這篇文章中,我們主要關(guān)注使用 Java 平臺(tái)為簡(jiǎn)單的 VoiceXML 應(yīng)用程序提供服務(wù),但您可將本文介紹的方法同樣地應(yīng)用到 PHP、Perl 或您選擇的任何編程語(yǔ)言。
VoiceXML、CCXML 還是 CallXML?
您需要首先建立一個(gè)免費(fèi)的 Voxeo 開發(fā)者賬戶,這樣才能在學(xué)習(xí)本文過(guò)程中配合練習(xí)。盡管 Voxeo 并不是任何 VoiceXML 的服務(wù)所必需的,但它確實(shí)提供了一套出色的工具,更有數(shù)千份 VoiceXML、CallXML 和 CCXML 文檔頁(yè)面。本文同時(shí)介紹 Voxeo 與 VioiceXML 編程,后續(xù) developerWorks 還將為您提供更多有關(guān) Voxeo 的內(nèi)容。
構(gòu)建語(yǔ)音應(yīng)用程序最常用的標(biāo)準(zhǔn)就是 VoiceXML。絕大多數(shù) VXML 瀏覽器都支持 VoiceXML 2.0,本文通篇將使用這個(gè)版本的 VXML。VXML 符合 W3C 規(guī)范且發(fā)展迅速,目前的版本依然是 v2.1。VXML 3.0 即將推出。
CCXML 是 Call Control XML 的縮寫,也是電話標(biāo)記方面符合 W3C 規(guī)范的最新一員。CCXML 比大多數(shù) VoiceXML 實(shí)現(xiàn)更為高級(jí),提供了對(duì)回叫、事件偵聽器和多路及多方會(huì)話的支持。但除非您特別需要這些屬性,否則最好的選擇或許是繼續(xù)使用 VoiceXML,VoiceXML 更加穩(wěn)定,應(yīng)用也更廣泛。
CallXML 是特定于 Voxeo 的一種平臺(tái)。CallXML 學(xué)習(xí)起來(lái)非常輕松,并提供了對(duì)按鍵電話(toUChtone)輸入的支持(請(qǐng)注意,它并不支持語(yǔ)音識(shí)別)。CallXML 的最大缺陷就是特定于廠商。Voxeo 是一個(gè)非常出色的站點(diǎn),具有無(wú)數(shù)資源,但被一家廠商鎖定絕對(duì)不是個(gè)好主意。此時(shí),大多數(shù)開發(fā)人員會(huì)再次發(fā)現(xiàn),VoiceXML 更適合他們的需求。
VoiceXML 101
在接觸 VoiceXML 世界的 Java 方面之前,您應(yīng)大致理解 VoiceXML 應(yīng)用程序的工作原理。之后,我將為您介紹一個(gè)非常簡(jiǎn)單的 VoiceXML 應(yīng)用程序。示例應(yīng)用程序?qū)⑹鼓軌虿榭?VXML 文件,同時(shí)確保您有權(quán)訪問(wèn)(且可使用)Voxeo 的 call-assignment 服務(wù),這個(gè)服務(wù)對(duì)本文后面的內(nèi)容非常關(guān)鍵。
一個(gè)簡(jiǎn)單的 VXML 頁(yè)面
VoiceXML 最少要以一個(gè) VXML 文件開始,使用 VoiceXML 風(fēng)格的 XML 版本告訴電話應(yīng)用程序它們應(yīng)該以及能夠作些什么。清單 1 給出了一個(gè)非常簡(jiǎn)單的 VXML 文件。將這個(gè)文件保存到您的本機(jī)上。
清單 1. 一個(gè)非常簡(jiǎn)單的 VXML 文件
<?xml version="1.0" encoding="UTF-8"?><vxml version="2.1"> <form> <block> <PRompt> Things are working correctly! Congratulations. </prompt> </block> </form></vxml>
對(duì)于 VoiceXML 來(lái)說(shuō),這非常基礎(chǔ),如果您對(duì)語(yǔ)法的了解還不夠清楚,請(qǐng)查看 參考資料 中列出的其他 VoiceXML 文章。清單 1 中的 VXML 文件只包含一條提示信息,未提供任何交互功能,在處理 Java 代碼的一節(jié)中,您將看到更高級(jí)的用法。但目前,使用這個(gè)簡(jiǎn)單的測(cè)試用例來(lái)確保您的環(huán)境工作正常。
上傳應(yīng)用程序
接下來(lái),將您的 VXML 文件放在某個(gè)可以訪問(wèn)的位置。如果您有 ISP,只需將 VXML 文件上傳到您的 Web 站點(diǎn),您可能也會(huì)希望在 Web 根目錄下為您的 VoiceXML 文件創(chuàng)建一個(gè)目錄,例如 /voicexml 或 /voice。確保這些目錄和文件可通過(guò) Web 訪問(wèn)(如果您不清楚如何進(jìn)行這些操作,請(qǐng)咨詢您的系統(tǒng)管理員或 ISP)。
如果您沒(méi)有 ISP,那么可以在 Voxeo 注冊(cè),以便使用該站點(diǎn)的 File Manager。您應(yīng)已建立了一個(gè) Voxeo 賬戶,它附帶 10 MB 的主機(jī)空間,因此這是個(gè)不錯(cuò)的免費(fèi)選擇。(10 MB 可以容納大量 VXML 文件!)
使 VXML 應(yīng)用程序聯(lián)機(jī)之后,我們還想確定能夠通過(guò)在 Web 瀏覽器中輸入 URL 來(lái)訪問(wèn)它。根據(jù)您所使用的具體瀏覽器不同,可能會(huì)要求您下載 XML 文件,也可能會(huì)在您的瀏覽器中以某種形式呈現(xiàn)它。這只是一個(gè)測(cè)試,確保您的 VXML 可用,因此即使您的計(jì)算機(jī)沒(méi)有開始跟您交談,也不要太過(guò)憂慮。VXML 聯(lián)機(jī)后,也就作好了將其與一個(gè)電話號(hào)碼連接的準(zhǔn)備。
為您的應(yīng)用程序分配一個(gè)電話號(hào)碼
最后一次呼吁您使用 Voxeo!
如果您尚未注冊(cè)獲得一個(gè) Voxeo 賬戶,那么現(xiàn)在就去注冊(cè)吧!從這里開始,以下的示例都需要您使用 Voxeo 工具。建立賬戶是免費(fèi)的,沒(méi)有任何責(zé)任,您更會(huì)獲得杰出的工具與支持。現(xiàn)在就去 注冊(cè)獲得一個(gè)開發(fā)者賬戶吧!
與傳統(tǒng)的 Web 應(yīng)用程序不同,您無(wú)法直接打開 Web 瀏覽器然后瀏覽您的 VXML 文件,至少在您希望獲得語(yǔ)音應(yīng)答時(shí)不能這樣做。為了測(cè)試基于電話的應(yīng)用程序,您顯然需要一臺(tái)電話,這就意味著一個(gè)呼叫號(hào)碼。有許多高成本的方法可以將號(hào)碼映射到 VoiceXML 應(yīng)用程序,但對(duì)于測(cè)試、登臺(tái)(staging)和開發(fā)而言,Voxeo 提供了一種出色的免費(fèi)映射服務(wù)。
導(dǎo)航到 Voxeo.com,登錄(使用頁(yè)面左上角的字段)。在 Account 菜單中選擇 Application Manager,如 圖 1 所示。
圖 1. 使用 Voxeo Application Manager
選擇 Add Application,然后選擇 VoiceXML 2.0 作為部署平臺(tái)。
接下來(lái),提供您的 VXML 文件的 URL,另外還有您的應(yīng)用程序的名稱,您可以按照自己的偏好任選名稱。圖 2 展示了訪問(wèn)我的 VXML 文件的設(shè)置。從 Application Phone Number 下拉菜單中選擇 Staging 選項(xiàng)。這將為應(yīng)用程序分配一個(gè)臨時(shí)登臺(tái)電話號(hào)碼(temporary staging phone number),以使您可以真正地用您自己的電話呼叫這個(gè)號(hào)碼。
圖 2. 將一個(gè) VXML 文件映射到一個(gè)電話號(hào)碼
單擊 Create Application,Voxeo 將為您的應(yīng)用程序分配一些電話號(hào)碼。圖 3 展示了最終屏幕(略微向下滾動(dòng)了一點(diǎn)),以及 VXML 文件的所有訪問(wèn)點(diǎn)。
圖 3. 成功映射!
這一功能值得您花時(shí)間去注冊(cè) Voxeo,您現(xiàn)在可以通過(guò)長(zhǎng)途電話號(hào)碼、800 免費(fèi)電話號(hào)碼和 Skype 訪問(wèn)您的 VXML 文件,而這些方法還只是其中的一小部分。這非常好,因?yàn)槟槐厥褂?Voxeo 工具去測(cè)試應(yīng)用程序。更好的是,您可以讓您的老板在無(wú)需具備 Voxeo 站點(diǎn)賬戶的情況下完成測(cè)試!
測(cè)試應(yīng)用程序
剩下的工作就是呼叫一個(gè) Voxeo 提供的號(hào)碼。撥號(hào)后,您的 VXML 應(yīng)用程序應(yīng)獲取號(hào)碼,并讓您知道(用一種單調(diào)機(jī)械的聲音):“Things are working correctly! Congratulations.”
好,就是這樣:在大約五分鐘內(nèi),您使您的電話與一個(gè) XML 文件進(jìn)行了交談。現(xiàn)在就可以看看 Java 代碼了,并了解如何動(dòng)態(tài)地生成 VXML。
Java 和 VXML
這里,大多數(shù) Java 開發(fā)人員都試圖在自己的 Java Servlet 中手工編碼 VXML,添加數(shù)百行的 out.println()
語(yǔ)句、為輸出的內(nèi)容類型而操心,通常也會(huì)給許多應(yīng)用程序增加嚴(yán)重且不必要的復(fù)雜性。動(dòng)手處理那些較為復(fù)雜的編程任務(wù)之前(只要應(yīng)用得當(dāng),它們都是很有用的),請(qǐng)先通過(guò)本節(jié)了解一些關(guān)于 VoiceXML Servlet 編程的最基本的內(nèi)容。
創(chuàng)建一個(gè) VXML 文件的原型
首先要開發(fā) VXML 文件。不要打開一個(gè) IDE 并開始編寫 Java 代碼,而是啟動(dòng)一個(gè)文本編輯器,忍住立即添加 package
和 import
語(yǔ)句的渴望。構(gòu)建一個(gè)簡(jiǎn)單的 VXML 文件,就像本文前面給出的示例那樣。
例如,清單 2 是又一個(gè)非常基礎(chǔ)的 VXML 文件。它是一個(gè)語(yǔ)音識(shí)別 VXML 文件,接入一個(gè)恰當(dāng)?shù)脑O(shè)備并提供某些關(guān)于呼叫選擇的注釋。
清單 2. 另外一個(gè)基本的 VXML 文件
<?xml version="1.0" encoding="UTF-8"?><vxml version="2.1"> <form id="MainMenu"> <field name="instrument"> <prompt>What is your favorite musical instrument?</prompt> <!-- Insert an inline grammar --> <grammar type="text/gsl"> [guitar mandolin dobro (violin fiddle) banjo] </grammar> <!-- Handle the case when they give no answer --> <noinput> Did you say something? I didn't hear you. <reprompt /> </noinput> <!-- Handle the case when no match is found --> <nomatch> I suppose that's OK, but it's not on my top five. Want to try again? <reprompt /> </nomatch> </field> <!-- Handle the various options. --> <filled namelist="instrument"> <if cond="instrument == 'guitar'"> <prompt>That's right! Hang up and go practice.</prompt> <elseif cond="instrument == 'mandolin'" /> <prompt>Nice... and only four strings to keep in tune.</prompt> <elseif cond="instrument == 'dobro'" /> <prompt>Boy, that's no fun to learn, is it?</prompt> <elseif cond="instrument == 'violin'" /> <prompt>We call that a fiddle, Mr. Fancy Pants.</prompt> <elseif cond="instrument == 'fiddle'" /> <prompt>Does playing classical music on a fiddle make it a violin?</prompt> <elseif cond="instrument == 'banjo'" /> <prompt>Wow, I hope you live alone.</prompt> </if> </filled> </form></vxml>
編寫這個(gè) VXML、保存它、將它上傳到 ISP,然后為它分配一個(gè)號(hào)碼。只有在您完成所有這些步驟后 —— 確保您的 VXML 正常工作,才是準(zhǔn)備好了,可以開始 考慮編寫 Java 代碼。
如果您直接跳到 Java,那么很可能會(huì)導(dǎo)致輸出中出錯(cuò),代碼中也會(huì)出錯(cuò)。結(jié)果是要在一個(gè) Web 框架內(nèi)嘗試同步調(diào)試一個(gè) VXML 文件(XML)和一個(gè) Servlet(Java),這種調(diào)試極其艱難。不要添加所有這些變量(沒(méi)有雙關(guān)的意思),務(wù)必從一個(gè)可正常工作的 VXML 文件入手。然后 準(zhǔn)備運(yùn)行 Java 代碼。
讀入文件
準(zhǔn)備好 VXML 可供使用后,您也就為開始編碼作好了最終的準(zhǔn)備。首先從一個(gè)僅載入 VXML 文件的 Servlet 開始。清單 3 是一個(gè)實(shí)現(xiàn)此功能的 Servlet —— 載入 清單 2 中開發(fā)的 VXML。這段代碼沒(méi)有任何輸出,所以期望值暫時(shí)不要太高。
清單 3. 載入一個(gè) VXML 文件
package com.ibm.vxml;import java.io.BufferedInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import javax.servlet.*;import javax.servlet.http.*;public class VoiceXMLServlet extends HttpServlet { private static final String VXML_FILENAME = "simple-voice_recog.xml"; public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String vxmlDir = getServletContext().getInitParameter("vxml-dir"); BufferedInputStream bis = null; ServletOutputStream out = null; try { // Load the VXML file File vxml = new File(vxmlDir + "/" + VXML_FILENAME); FileInputStream fis = new FileInputStream(vxml); bis = new BufferedInputStream(fis); // Output the VXML file int readBytes = 0; while ((readBytes = bis.read()) != -1) { // output the VXML } } finally { if (out != null) out.close(); if (bis != null) bis.close(); } }}
這段代碼非常直觀。它載入一個(gè) XML 文件 —— 通過(guò) servlet 的配置上下文中的目錄和一個(gè)常量文件名指定,然后遍歷文件內(nèi)容。您要將文件的路徑硬編碼到 servlet 中,但至少將目錄名存儲(chǔ)到 Web.xml 文件中是一個(gè)非常不錯(cuò)的主意,此文件位于 servlet 上下文的 WEB-INF/ 目錄下。清單 4 展示了 Web.xml 中的上下文參數(shù)。
清單 4. servlet 的上下文參數(shù)
<context-param> <param-name>vxml-dir</param-name> <param-value>/path-to-your-voicexml-dir/voicexml</param-value> </context-param>
若編譯 servlet 并嘗試在 Web 瀏覽器中載入它,您只會(huì)看到一個(gè)空白的屏幕,同樣,您應(yīng)確保至少會(huì)看到這樣的空白屏幕。如果得到錯(cuò)誤,就需要予以更正。例如,常常會(huì)出現(xiàn)文件訪問(wèn)問(wèn)題或 VXML 文件路徑錄入錯(cuò)誤。一旦得到了空白屏幕,也就準(zhǔn)備好實(shí)際輸出 VXML 文件了。
從 servlet 中輸出 VXML
首先,您需要訪問(wèn)一個(gè)輸出對(duì)象,這樣才能向?yàn)g覽器發(fā)送內(nèi)容。這非常簡(jiǎn)單:
// Load the VXML file File vxml = new File(vxmlDir + "/" + VXML_FILENAME); FileInputStream fis = new FileInputStream(vxml); bis = new BufferedInputStream(fis); // Let the browser know that XML is coming out = res.getOutputStream();
從文件提取內(nèi)容也非常簡(jiǎn)單,只要使用一行代碼即可:
// Output the VXML file int readBytes = 0; while ((readBytes = bis.read()) != -1) { // output the VXML out.write(readBytes); }
雖然上述代碼看似已經(jīng)足夠,但您依然需要告知瀏覽器您正在向它發(fā)送 XML。切記,瀏覽器用于 HTML,某些瀏覽器可能無(wú)法順利接收 XML。您可設(shè)置內(nèi)容類型,也可設(shè)置內(nèi)容的長(zhǎng)度,只要再次使用 HttpServletResponse
對(duì)象即可:
// Let the browser know that XML is coming out = res.getOutputStream(); res.setContentType("text/xml"); res.setContentLength((int)vxml.length());
清單 5 展示了添加到前文介紹的 清單 3 給出的 servlet 中的所有代碼。
清單 5. 完整且準(zhǔn)備好載入 VXML 文件的 VoiceXMLServlet
package com.ibm.vxml;import java.io.BufferedInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import javax.servlet.*;import javax.servlet.http.*;public class VoiceXMLServlet extends HttpServlet { private static final String VXML_FILENAME = "simple-voice_recog.xml"; public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String vxmlDir = getServletContext().getInitParameter("vxml-dir"); BufferedInputStream bis = null; ServletOutputStream out = null; try { // Load the VXML file File vxml = new File(vxmlDir + "/" + VXML_FILENAME); FileInputStream fis = new FileInputStream(vxml); bis = new BufferedInputStream(fis); // Let the browser know that XML is coming out = res.getOutputStream(); res.setContentType("text/xml"); res.setContentLength((int)vxml.length()); // Output the VXML file int readBytes = 0; while ((readBytes = bis.read()) != -1) { // output the VXML out.write(readBytes); } } finally { if (out != null) out.close(); if (bis != null) bis.close(); } }}
測(cè)試 servlet 載入的 VoiceXML
完成上述更改后編譯您的 servlet,若需要請(qǐng)重啟 servlet 引擎。瀏覽 servlet,您應(yīng)看到如 圖 4 所示的輸出結(jié)果。成功!
圖 4. VoiceXML servlet 輸出 VXML
若您未得到類似輸出,確定您的文件是否位于您希望的位置,并確保沒(méi)有任何權(quán)限問(wèn)題。您還要檢查 servlet 引擎的日志或請(qǐng)求系統(tǒng)管理員的幫助。
現(xiàn)在就準(zhǔn)備好將電話號(hào)碼映射到您的 servlet 了。重新回到 Voxeo.com 的 Application Mnager,添加一個(gè)新應(yīng)用程序(可能會(huì)看到之前您使用過(guò)的應(yīng)用程序)。確保選中 VoiceXML 2.0,然后輸入新應(yīng)用程序的名稱和 servlet 的 URL。Voxeo 將創(chuàng)建應(yīng)用程序并為其分配一個(gè)電話號(hào)碼。
撥入這個(gè)新號(hào)碼,您應(yīng)聽到 清單 2 中的 VXML 給出的提示。祝賀您!您已經(jīng)編寫好了一個(gè)輸出 VXML 的 Java servlet 的代碼,還在其中掛接了一個(gè)電話號(hào)碼。
部分可選的附加項(xiàng)
您可能希望向 servlet 代碼中添加一些小附加項(xiàng)。它們都不是必需的,但都會(huì)給現(xiàn)有的版本增加一些健壯性和文檔。
首先,您可能想允許用戶通過(guò) POST 請(qǐng)求訪問(wèn) VXML。這可能在用戶單擊表單上的一個(gè)按鈕時(shí)發(fā)生,該表單將對(duì) VoiceXMLServlet
作出一個(gè) POST 請(qǐng)求。在 servlet 中處理這一操作非常簡(jiǎn)單,只要編寫一個(gè)委托已有 doGet()
方法的 doPost()
即可,如下所示:
public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doGet(req, res); }
另外一個(gè)簡(jiǎn)單的附加項(xiàng)用于允許瀏覽器切實(shí)獲知您正在輸出一個(gè) VXML 文件的內(nèi)容。為此,設(shè)置 servlet 中的 Content-disposition
響應(yīng)頭,如下:
// Let the browser know that XML is coming out = res.getOutputStream(); res.setContentType("text/xml"); res.setContentLength((int)vxml.length()); res.addHeader("Content-Disposition", "attachment; filename=" + vxml);
現(xiàn)在讀取您的響應(yīng)的瀏覽器(或其他代碼)就可以發(fā)現(xiàn)所服務(wù)的 VXML 文件了。但務(wù)必不要包含完整的文件路徑,這會(huì)造成安全隱患!
動(dòng)態(tài) VoiceXML
有了輸出 VXML 文件的 servlet 之后,將其轉(zhuǎn)換成動(dòng)態(tài)輸出 VXML 的 servlet(使用代碼作為模型或模板)輕而易舉。換句話說(shuō),您可以超越簡(jiǎn)單地載入靜態(tài)的 VXML 文件,開始通過(guò)編程創(chuàng)建 VXML。
當(dāng)您開始考慮動(dòng)態(tài) VoiceXML 時(shí),Java 平臺(tái)就顯示出了自己的優(yōu)勢(shì)。它提供了輕松輸出 XML 的能力,還有與數(shù)據(jù)庫(kù)、目錄服務(wù)器、身份驗(yàn)證存儲(chǔ)和會(huì)話的交互。此外,它還能夠證實(shí),構(gòu)建動(dòng)態(tài) VXML 將消除基于語(yǔ)音的系統(tǒng)的部分刻板性。
在這一節(jié)中,我將逐步為您介紹創(chuàng)建一個(gè)輸出動(dòng)態(tài) VXML 的 Java servlet 的步驟。
通過(guò) out.println() 輸出 VXML
您已經(jīng)了解了如何訪問(wèn) ServletOutputStream
,然后在輸出流中插入字節(jié)。但如果從源(例如一個(gè)靜態(tài) VXML 文件)傳輸?shù)捷敵隽鞯牟粌H僅是字節(jié),那么直接處理字節(jié)的方式幾乎無(wú)法管理控制。
如果您希望自行創(chuàng)建 VXML,最好使用 PrintWriter
。利用這個(gè)類,您可發(fā)出整個(gè)字符串,使之對(duì)于創(chuàng)建和輸出動(dòng)態(tài)內(nèi)容更為有用。這只需要對(duì)代碼略加修改,如下所示:
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String vxmlDir = getServletContext().getInitParameter("vxml-dir"); BufferedInputStream bis = null; ServletOutputStream out = null; try { // Load the VXML file File vxml = new File(vxmlDir + "/" + VXML_FILENAME); FileInputStream fis = new FileInputStream(vxml); bis = new BufferedInputStream(fis); // Let the browser know that XML is coming PrintWriter out = res.getOutputStream(); res.setContentType("text/xml"); res.setContentLength((int)vxml.length()); // Output content using PrintWriter } finally { if (out != null) out.close(); if (bis != null) bis.close(); } }
另外,不要忘記導(dǎo)入 java.io.PrintWriter
類:它不會(huì)自動(dòng)成為對(duì)您的 servlet 的代碼基可用。
使用 PrintWriter,您現(xiàn)在可以輸出基于字符串的內(nèi)容了。例如,清單 6 輸出與 清單 1 相同的 VXML,但是通過(guò) servlet 輸出,并未從靜態(tài)文件載入 VXML 內(nèi)容。
清單 6. 動(dòng)態(tài)輸出 VXML
package com.ibm.vxml;import java.io.BufferedInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.PrintWriter;import javax.servlet.*;import javax.servlet.http.*;public class DynamicVoiceXMLServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { BufferedInputStream bis = null; PrintWriter out = null; try { // Let the browser know that XML is coming out = res.getWriter(); res.setContentType("text/xml"); // Output VXML out.println("<?xml version=/"1.0/" encoding=/"UTF-8/"?>"); out.println("<vxml version=/"2.1/">"); out.println(" <form><block><prompt>"); out.println(" Things are working correctly! Congratulations."); out.println(" </prompt></block></form>"); out.println("</vxml>"); } finally { if (out != null) out.close(); if (bis != null) bis.close(); } } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doGet(req, res); }}
現(xiàn)在您可編譯這個(gè) servlet、向 Voxeo 注冊(cè)它,并通過(guò)電話訪問(wèn)它,與處理 清單 1 的方法相同。現(xiàn)在我們來(lái)看一些示例,從而展示像 Java 這樣的語(yǔ)言的動(dòng)態(tài)編程能力。
添加時(shí)間提醒
基于 servlet 的 VXML 輸出的一項(xiàng)最簡(jiǎn)單的用途就是添加時(shí)間提醒。利用 Java 代碼獲取當(dāng)前日期和時(shí)間非常輕松,因此這是個(gè)不錯(cuò)的起點(diǎn)。
使用 Calendar 類可輕松獲得一天中的具體時(shí)間(實(shí)際上,可以獲得與當(dāng)前日期相關(guān)的任何內(nèi)容)。清單 7 給出了獲得 Calendar 類新實(shí)例的代碼,從而得到一天中的具體時(shí)間(以 24 小時(shí)的格式返回),然后根據(jù)這個(gè)時(shí)間組合出一條簡(jiǎn)單的歡迎詞。
清單 7. 動(dòng)態(tài)輸出 VXML
package com.ibm.vxml;import java.io.BufferedInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.PrintWriter;import java.util.Calendar;import javax.servlet.*;import javax.servlet.http.*;public class DynamicVoiceXMLServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { BufferedInputStream bis = null; PrintWriter out = null; try { // Let the browser know that XML is coming out = res.getWriter(); res.setContentType("text/xml"); // Output VXML out.println("<?xml version=/"1.0/" encoding=/"UTF-8/"?>"); out.println("<vxml version=/"2.1/">"); out.println(" <form><block><prompt>"); // Output a greeting based on the time of day Calendar cal = Calendar.getInstance(); int hour = cal.get(Calendar.HOUR_OF_DAY); if (hour < 6) { out.println("You're up early. Good morning."); } else if (hour < 12) { out.println("Good morning. How's your day so far?"); } else if (hour < 18) { out.println("Half the day is done... good afternoon!"); } else{ out.println("Hope you are enjoying your evening."); } out.println(" </prompt></block></form>"); out.println("</vxml>"); } finally { if (out != null) out.close(); if (bis != null) bis.close(); } } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doGet(req, res); }}
呼叫者和 VXML 生成者之間的差異
清單 7 也展示了 Voice XML 與動(dòng)態(tài)生成的 VXML 的另外一項(xiàng)特性:呼叫者可能并非 VXML 本身。例如,假設(shè)有一名居住在新西蘭的用戶呼叫 清單 7 所示的應(yīng)用程序。若當(dāng)時(shí)新西蘭時(shí)間是 10:00 PM,但輸出 VXML 的服務(wù)器位于科羅拉多州的丹佛,兩者之間問(wèn)候的消息可能非常古怪,例如 “您起得真早,早上好!” 這完全不恰當(dāng),情況還有可能更糟:如果您已為每周的特定日子添加好了問(wèn)候語(yǔ),那么實(shí)際上總會(huì)出現(xiàn)匹配錯(cuò)誤。
根本問(wèn)題源于 VXML 和 Java 是在特定服務(wù)器所在的位置和時(shí)區(qū)運(yùn)行的,但可為世界各地的呼叫者所用。如果您的 servlet 未考慮到這方面的問(wèn)題,就會(huì)令一些呼叫者倍感困惑。您有以下幾種選擇:
遺憾的是,這些選擇中沒(méi)有任何一種富于吸引力。第一種選擇的所作所為正如所說(shuō)明的那樣:基本忽略呼叫者。不言而喻,忽略呼叫者絕非 贏得和維持業(yè)務(wù)關(guān)系的正確方法。第二種想法 —— 給出本地時(shí)間并明確說(shuō)明這是本地時(shí)間 —— 也沒(méi)有太大的幫助,因?yàn)檫@種方法依然傾向于忽略呼叫者,只是在此過(guò)程中考慮得略微周全一些。
最后一種選擇乍看上去似乎很有吸引力,可以很容易地編寫出 VXML,允許用戶提供一個(gè)與 GMT 的時(shí)差值,然后根據(jù)該信息應(yīng)答。但呼叫者傾向于盡可能迅速地獲得信息,應(yīng)答提示越多,惹惱呼叫者的風(fēng)險(xiǎn)就越大,他們可能會(huì)不滿地掛斷電話。因此,除非您提供的以日期或時(shí)間為基礎(chǔ)的服務(wù),否則要求呼叫者指明時(shí)區(qū)就是浪費(fèi)資源。甚至可能更糟的是,很多呼叫者并不知道自己所在地與 GMT 的時(shí)差,因此您要面臨提供時(shí)區(qū)、時(shí)區(qū)縮寫、夏令時(shí)……那將是一個(gè)冗長(zhǎng)煩瑣的列表。
為什么要自尋煩惱?
那么我們?yōu)槭裁匆榻B這種基于日期的 VXML 生成呢?很大程度上是因?yàn)樗故玖诉@些問(wèn)題!您需要密切關(guān)注您的聽眾,盡力只提供那些與他們有關(guān)的信息,而不是與您的服務(wù)器或您所在地有關(guān)的信息。
對(duì)于基于日期的處理,您要學(xué)習(xí)的課程就是:或許應(yīng)該采用一種更好的最終選擇來(lái)應(yīng)對(duì)呼叫者,除非絕對(duì)必要,否則應(yīng)完全避免采用基于日期或時(shí)間的事務(wù)。如果您預(yù)計(jì)到將有來(lái)自其他時(shí)區(qū)的呼叫者,那么提供與時(shí)間相關(guān)的功能就是自找麻煩。這一原則同樣適用于任何隨州、國(guó)家或陸地標(biāo)線的不同可能發(fā)生變化的數(shù)據(jù)。
最后,有很多時(shí)候使用 servlet 輸出 VXML 并非良策。如果您只是要從一個(gè)靜態(tài)文件中提取 VXML,使用 servlet 帶來(lái)的好處極為有限(可能只有一點(diǎn)靈活性而已),但要添加代碼、編譯、調(diào)試、一個(gè) servlet 引擎和其他許許多多的東西,從而使語(yǔ)音應(yīng)用程序的復(fù)雜性大為增加。在這些簡(jiǎn)單的情況下,應(yīng)繼續(xù)使用靜態(tài) VXML 文件。
一些有趣的想法
致此,您已看到,有時(shí) servlet 生成的 VXML 并無(wú) 意義。在結(jié)束本文之前,考慮以下幾種情況,在這些時(shí)候使用 Java 這樣的語(yǔ)言是絕佳 的電話應(yīng)用程序解決方案。此處未提供完整的示例,以后的文章中將予以介紹。
從一個(gè)數(shù)據(jù)庫(kù)中載入 VXML
最顯而易見的與 VoiceXML 相關(guān)的 Java 應(yīng)用程序就是:使用數(shù)據(jù)庫(kù)提供動(dòng)態(tài) VXML 輸出。這或許也是大部分讀者在選擇閱讀本文時(shí)希望了解的內(nèi)容(但這篇文章中沒(méi)有核心示例,因此您可能未學(xué)到足夠多的知識(shí))。無(wú)論如何,JDBC 都能使您輕松連接數(shù)據(jù)庫(kù),然后利用 SQL 查詢的結(jié)果填充 VXML。
例如,您要開發(fā)一個(gè)表,包含 VXM 的全部語(yǔ)法信息,然后將這些語(yǔ)法載入您所輸出的每個(gè) VXML 文件中。您不必為每一個(gè) VXML 文件編碼語(yǔ)法,而是可以在類似的文件間共享語(yǔ)法。更好的是,您可在所有 servlet 或一個(gè)特定 servlet 的實(shí)例中預(yù)載入這些語(yǔ)法。從而得益于將語(yǔ)法存儲(chǔ)在數(shù)據(jù)庫(kù)中,無(wú)需浪費(fèi)成本為每個(gè)請(qǐng)求載入語(yǔ)法。
根據(jù)用戶憑證載入 VXML
另外一項(xiàng)出色的 Java 功能 —— 特別是在與 servlet、jsp 和基于 Web 的編程相關(guān)時(shí) —— 就是在會(huì)話中存儲(chǔ)用戶憑證的能力。這為您帶來(lái)穩(wěn)定的身份驗(yàn)證和授權(quán),以及高度定制的內(nèi)容。
例如,考慮一個(gè)語(yǔ)音應(yīng)用程序,從詢問(wèn)用戶 ID 號(hào)和 PIN 開始(與當(dāng)今的大多數(shù)銀行或金融應(yīng)用程序類似)。您可根據(jù)數(shù)據(jù)庫(kù)(依靠 Java 平臺(tái)的強(qiáng)大力量)對(duì)這些憑證進(jìn)行驗(yàn)證,然后將呼叫者的 ID 存儲(chǔ)到一個(gè)會(huì)話變量中。此后,每個(gè)處理這名呼叫者的請(qǐng)求的 Java servlet 或 JSP 都可根據(jù)這些憑證了解為用戶提供哪些選擇。
盡管許多 VoiceXML 替代產(chǎn)品都提供了類似的功能性,但很少有產(chǎn)品以與其基于 Web 的應(yīng)用程序版本之間共享代碼為自豪。換言之,Java 平臺(tái)允許您在 VoiceXML 應(yīng)用程序及其基于 Web 的版本間共享的不僅僅是數(shù)據(jù)庫(kù),還包括代碼組件。生成 VXML 的 servlet 可使用相同的身份驗(yàn)證和授權(quán)工具類作為生成 HTML 和 XHTML 的 servlet,應(yīng)答電話呼叫的 JSP 可與處理 HTTP 請(qǐng)求的 JSP 共享緩存數(shù)據(jù)庫(kù)連接。因而,您將得到一個(gè)能夠處理多種類型客戶機(jī)的應(yīng)用基礎(chǔ)設(shè)施,而不必為每種類型的客戶機(jī)創(chuàng)建一個(gè)完整的應(yīng)用程序。
結(jié)束語(yǔ)
本文蜻蜓點(diǎn)水地介紹了可用 VXML 和 Java 平臺(tái)實(shí)現(xiàn)的功能。介紹了開發(fā) VXML 的過(guò)程,然后為您展示了如何將 Java 技術(shù)整合到這一過(guò)程之中。介紹過(guò)程中給出了很多線索,告訴您利用 Java 代碼來(lái)開發(fā)豐富、動(dòng)態(tài)的 VoiceXML 應(yīng)用程序的所有有趣的方式。
我還說(shuō)明了 VoiceXML 開發(fā)人員在語(yǔ)音應(yīng)用程序誤用 Java 技術(shù)的幾種常見形式。處理日期和時(shí)間時(shí)耍小聰明、試圖提供地方性的服務(wù)或是忘卻服務(wù)器當(dāng)?shù)貢r(shí)間和呼叫者當(dāng)?shù)貢r(shí)間之間的差異無(wú)疑會(huì)令用戶灰心離去。應(yīng)將 Java 視為 VoiceXML 的一種工具,不要濫用 Date
和 Calendar
類。
在后續(xù)的文章中,我將繼續(xù)探討這些主題和更多內(nèi)容,以本文給出的原則為基礎(chǔ)進(jìn)行擴(kuò)展。如果您希望了解構(gòu)建豐富的語(yǔ)音應(yīng)用程序、開發(fā)與數(shù)據(jù)庫(kù)交互的電話應(yīng)用程序、跟蹤用戶、提供個(gè)性化內(nèi)容的更多內(nèi)容,請(qǐng)繼續(xù)關(guān)注本系列文章。另外,訪問(wèn) Voxeo.com,獲得一兩個(gè) servlet,來(lái)提供您自己的 VXML。請(qǐng)繼續(xù)關(guān)注下期文章,了解更多內(nèi)容。
QQread.com 推出各大專業(yè)服務(wù)器評(píng)測(cè) linux服務(wù)器的安全性能 SUN服務(wù)器 HP服務(wù)器 DELL服務(wù)器 IBM服務(wù)器 聯(lián)想服務(wù)器 浪潮服務(wù)器 曙光服務(wù)器 同方服務(wù)器 華碩服務(wù)器 寶德服務(wù)器
參考資料
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
討論
關(guān)于作者
Brett McLaughlin 從 Log 時(shí)代就開始使用計(jì)算機(jī)了。(還記得那個(gè)小三角嗎?)近年來(lái),他已經(jīng)成為 Java 和 XML 社區(qū)中最受歡迎的作者和程序員之一了。他曾經(jīng)在 Nextel Communications 實(shí)現(xiàn)過(guò)復(fù)雜的企業(yè)系統(tǒng),在 Lutris Technologies 編寫過(guò)應(yīng)用服務(wù)器,最近在 O'Reilly Media, Inc. 繼續(xù)撰寫和編輯這方面的圖書。Brett 最新的著作 Head Rush Ajax,為 Ajax 帶來(lái)了獲獎(jiǎng)的創(chuàng)新 Head First 方法。他的近作 Java 1.5 Tiger: A Developer's Notebook 是關(guān)于這一 Java 技術(shù)最新版本的第一部專著。經(jīng)典作品 Java and XML 仍然是在 Java 語(yǔ)言中使用 XML 技術(shù)的權(quán)威著作之一。
(出處:http://www.companysz.com)
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注