程式碼高𠅙

2009/02/24

如何取得 Web 應用程式的根目錄 Context URL

我們常常在開發 Web 應用程式時,會有需要取得 Context URL。例如,為了透過這個 URL 取得某些資源。Context URL,也就是 Web 應用程式根目錄的 URL。例如,給定一個 URL 如下:

    http://test.com:8080/AppContext/path/to/your/page

則 Context URL 指的是:

    http://test.com:8080/AppContext   

如果我們都是以相對路徑 (相對於根目錄或或目前路徑) 的方式來存取資源,那直接使用 Context URL 的必要性就比較低了。不過有時候並無法使用相對路徑,例如:

  • 因為配合 API 的呼叫,你不曉得這個 API 會在虛擬路徑的第幾層呼叫,因此就無法提供相對路徑。
  • 因為配合某種 code gen 機制,你要傳入一個 URL。
  • 因為採用了某種 URL dispatch 的機制,導致你網頁相對路徑發生偏差。

當然還有其他原因。而面對這個問題的解法,傳統上是在 web.config 或 web.xml 裡面去定義一個類似 HOST_URL 的參數來解決。但缺點是,每次 deploy 時,就得變更 HOST_URL 的設定,除了顯得不便之外,因會增加 API 與環境變數的相依性,實在不能算是很好的做法。因此透過程式自動抓取 ContextURL 的作法,便有其必要。

現在,我們假設我們的 ContextURL 就是以上的形式,而不是 WebServer 的根目錄 (例如 http://test.com:8080/),那麼取得 Context URL 的作法,在一般情況下並不難。首先看看 ASP.NET/C# 版本:

   int idx = Request.Url.AbsoluteUri.IndexOf(Request.ApplicationPath);
   string serviceUri = Request.Url.AbsoluteUri.Substring(0, idx); // 伺服器 URL
   string applicationUri = serviceUri + Request.ApplicationPath; // 應用程式 Context URL

JSP/Java 版本如下:

   int idx = request.getRequestURL().toString().indexOf(request.getContextPath());
   String serviceUri = request.getRequestURL().substring(0, idx); // 伺服器 URL
   String applicationUri = serviceUri + request.getContextPath(); // 應用程式 Context URL

不過有些情況比較特殊,例果透過某種 URL dispatch 的機制來存取網頁,你會發現用以上方式取得的 URL,與 Client 端發送的 URL 並不相同。我們以 IBM Tivoli Access Manager 為例,它的 URL 組成格式如下:

      http://sso.com/JUNCTION/AppContext/path/to/your/page
  • sso.com: 是一個用來作 singal sign-on 的網站
  • JUNCTION: 用來試別要將 client 端 request dispatch 到後端哪個實發處理的網站
  • /AppContext/path/to/your/page: 後端處理要求的網頁或程式,實際位址可能是 http://192.168.1.111:8080 /AppContext/path/to/your/page 之類的。

也就是說,當 Client 端發送 http://sso.com/JUNCTION/AppContext/path/to/your/page 這個請求時,經過 Tivoli Access Manager 轉送後,Server 端實際取得的服務位址是 http://192.168.1.111:8080/AppContext/path/to/your/page。

為了解決這個問題,我們需要動用到 HTTP Header 裡面的一個參數 -- referer。referer 是指目前存取這個網頁,是透過哪個網頁連結過來的。有了這層認識,我們可以將上面的程式碼改成如下。 ASP.NET/C# 版本:

   if(IsPostBack) {
      idx = Request.UrlReferrer.AbsoluteUri.IndexOf(Request.ApplicationPath);
      serviceUri = Request.UrlReferrer.AbsoluteUri.Substring(0, idx); // 伺服器 URL
      applicationUri = serviceUri + Request.ApplicationPath; // 應用程式 Context URL
   }

JSP/Java 版本如下:

   if(request.getParameter("IsPostBack").equals("true")){
      idx = request.getHeader("referer").indexOf(request.getContextPath());
      serviceUri = request.getHeader("referer").substring(0, idx); // 伺服器 URL
      applicationUri = serviceUri + request.getContextPath(); // 應用程式 Context URL
   }

如果你是直接在瀏覽器的網址列輸入 URL,或是點選書籤/我的最愛直接連結,那 referer 的值會是 null。因此,我們可以透過檢查這個網頁是否是 post back 網頁,來確保 referer 有值。注意一下,JSP 裡面並沒有預設的 IsPostBack 檢查機制,你要自己做。

另外,有些防火牆或防毒軟體也可能會改寫 Client 端發出的 referer,使得 Server 上取得的值為 null。若是這種情況,可以試著透過 client 端的 Javascript,取得 document.location.href 的值,將它指定給某個隱藏表單欄位,在 post 時傳給 Server 端來加以解決。

2009/01/23

Java SE 6 Update 10 裡面的 Nimbus Look and Feel

Java 視窗應用程式在過去給部分人留下著刻版印象,總是認為那是跑得慢,看來醜的應用程式。不過,這真的只是刻版印象。個人在過去曾經以 Swing 套件,開發了 4 年以上的 Java Client 端應用程式,無論是在啟動速度、執行效能及視覺美觀上,Swing 介面均能達到令人滿意的程度。

而事實上不論是 Sun 或是 Java 社群也一直在 Look and Feel (就相當於佈景主題--Theme 的概念) 下了相當心血。在 Java SE 6 Update 10 裡面,就包括了一個 Nimbus Look and Feel,這是一個簡潔、洗練、具現代感的 Look and Feel。我們先來看看 SwingSet3 在 Windows XP 中的呈現樣式:

SwingSet3Nimbus

是不是還不賴呢? 我們展現一下 Metal Look and Feel 比較一下:

SwingSet3Metal

除了視覺上的輕巧之外,Nimbus Look and Feel 在實質上也是相當輕巧的,因為你在畫面上所看到的各個元件,包括按鈕、捲軸、下拉選單及標題的漸層及陰影等效果,全部是用 Java 2D 向量方式 (而不是透過 bitmap 圖檔) 畫出來的,因此才占用了 56 KB 的空間。

設定 Swing 應用程式使用 Nimbus Look and Feel

這樣漂亮的 Look and Feel,並沒有隨著 Java SE 6 Update 10的到來,而成為 Swing 預設的 Look and Feel。開發人員必須加以設定,才能在應用程式中以它作為佈景主題。隨著需求的不同,設定方式至少有三種。

在應用程式中進行設定--透過程式碼指定,你所要使用的 Look and Feel。這種方式只會將主題套用到應用程式本身,這只需要一行程式碼:

UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");

或者,也可以在啟動 Java 程式時,加入以下的 java 啟動參數:

-Dswing.defaultlaf=com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel

如果你想將 Nimbus Look and Feel 設定成整台電腦中,所有 Swing 程式的預設主題,則可以在 /lib/swing.properties 這個檔案中,加入底下這行:

swing.defaultlaf=com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel

如果上述的設定檔不存在,你可以自行新增一個。

在 jEdit 中設定 Nimbus Look and Feel

除了以上方式之外,有些應用程式可以透過 UI 介面,讓你設定應用程式的 Look and Feel。例如 jEdit 的這個以 Java 開發而成的編輯器,就可以在 Utilities/Global Options... 主選單所開啟的 Options 視窗中,選擇 Appearance 設定 Look and Feel。以下是套用 Nimbus 之後的外觀:

jEditNimbus

附上一張預設樣式 (Metal) 作比較:

jEditDefault

設定 NetBeans 使用 Nimbus Look and Feel

一直覺得 Sun 是一家有著偏執傾向的公司,他們似乎有著一種,不管別人如何批評,想要做的,便會全力以赴做到好的企業精神。因此這幾年來在 NetBeans IDE 上的進展是有目共睹的 (雖然,我還是 Eclipse 的愛用者啊! ),而我也因此相當看好 JavaFX 未來的發展…。OK,回歸正題,我想社群裡面 NetBeans 的愛好者應該也不少,要設定 NetBeans 使用 Nimbus Look and Feel 也很簡單,只要找到 NetBeans 的安裝目錄,在 etc 子目錄下的 netbeans.conf 設定檔中,找到 netbeans_default_options 設定,在設定中加上 --laf Nimbus 即可。例如,我的設定是這樣子的:

netbeans_default_options="-J-Dorg.glassfish.v3.installRoot=\"C:\java\glassfish-v3-prelude-b15b\" -J-Dcom.sun.aas.installRoot=\"C:\java\glassfish-v2ur2\" -J-client -J-Xverify:none -J-Xss2m -J-Xms32m -J-XX:PermSize=32m -J-XX:MaxPermSize=200m -J-Dnetbeans.logger.console=true -J-ea -J-Dapple.laf.useScreenMenuBar=true -J-Dsun.java2d.noddraw=true --laf Nimbus"

以下是套用 Nimbus 之後的外觀:

NetBeansNimbus

再附上預設外觀看看:

NetBeansDefault

其實,隨著應用的不同,你很難找到一套一體適用於所有應用情境的 Look and Feel,不過,這又是另一個主題了,我就此打住。

2008/12/10

小朋友的邏輯測驗

小二的國語參考書上看到一個選擇題,題目是:
「媽媽說,哥哥和我是她的一對寶貝 (1) 兒女 (2) 女兒」
唸小二的大兒子,兒女、女兒亂猜一通…
再問念小一的小兒子,他也不能正確答題。
於是我問他們,說這話的是誰,大兒子就說是哥哥,小兒子也不能知道是誰說的。
這時候老婆很熱心的打 pass:題目都說是媽媽說,所以就是媽媽說的。
為了讓我自己不要昏死在旁邊,我只好給提示:
「哥哥、姐姐、弟弟、妹妹」其中一個說的,你們選誰。
這時候老婆知道我在問什麼了。
二個小朋友還是一臉茫然…
以前唸過皮亞傑談到識知發展的書,簡要的內容就是人的各種認知、邏輯能力,
是隨著年齡而成長的。所以一些成人們認為理所當然的事,
對小朋友而言,並不總是理所當然。
今天這道題目,總算讓我有了另一次體驗。

2008/11/11

Google Translate 非官方翻譯 API

之前為了將系統的多語化,其中一個議題就是要產生各種語系的訊息資源檔。繁簡中文的對譯,只要透過 Word 就行了,要是英日語,就得人工作業。靈機一動,想說何不借助既有的翻譯服務,為 resource 檔提供預設的簡中、英、日翻譯。

就這樣,找到了一些應用 Google Translate API 的討論。試用了之後,多少都有語系編碼的問題。我最後實作了一個版本,可以在輸入正體中文時,正常產出簡中、英、日語系的翻譯。

一開始我是採用 C# 語言測試,程式碼如下:

public static string TranslateText(string fromLang, string toLang, string msg)
{
  string target = "http://www.google.com/translate_t?ie=UTF-8&oe=UTF-8&text={2}&langpair={0}|{1}";
  string url = String.Format(target, fromLang, toLang, msg);
  WebClient webClient = new WebClient();
  webClient.Encoding = System.Text.Encoding.UTF8;
  string result = webClient.DownloadString(url);
  string sign = "<div id=result_box dir=\"ltr\">";
  result = result.Substring(result.IndexOf(sign) + sign.Length);
  result = result.Substring(0, result.IndexOf("</div"));
  return result.Replace("<br>", "\n");;
}

後來又做了一個 Java 的版本,程式碼如下:

public static String translateText(String fromLang, String toLang, String msg) throws IOException{
  String target = "http://www.google.com/translate_t?ie=UTF-8&oe=UTF-8&langpair=%s|%s&text=%s";
  String url = String.format(target, fromLang, toLang, URLEncoder.encode(msg, "utf-8"));
  String result = getUrlContent(url);
  String sign = "<div id=result_box dir=\"ltr\">";
  result = result.substring(result.indexOf(sign) + sign.length());
  result = result.substring(0, result.indexOf("</div"));
  return result.replaceAll("<br>", "\n");
}

public static String getUrlContent(String url) throws IOException {
  URL u = new URL(url);   
  HttpURLConnection conn = (HttpURLConnection)u.openConnection();   
  conn.setRequestProperty("User-agent","Mozilla/4.0");
  conn.setRequestProperty("Content-Language","UTF-8" );
  conn.connect();
  return slurp(conn.getInputStream());            
}

public static String slurp (InputStream in) throws IOException {
  StringBuffer out = new StringBuffer();
  byte[] b = new byte[4096];
  for (int n; (n = in.read(b)) != -1;) {
    out.append(new String(b, 0, n, "UTF-8"));
  }
  return out.toString();
}

測試程式長得像這樣:

public static void main(String []argv) throws IOException{
  System.out.println(GoogleTranslate.translateText("zh-TW", "zh-CN", "書不同文、車不同軌、世界不大同!"));
  System.out.println(GoogleTranslate.translateText("zh-TW", "en", "書不同文、車不同軌、世界不大同!"));
  System.out.println(GoogleTranslate.translateText("zh-TW", "ja", "書不同文、車不同軌、世界不大同!"));          
}

稍微說明一下我的測試心得。

首先,就是使用 Google Translate 時,最好是明確指定你所用的輸入編碼及輸出編碼,即上面 URL 連結中的 ie=UTF-8&oe=UTF-8。若不明確指定,Google 可是會依據你的輸入語言,而決定輸出編碼。例如,你輸入的語言是 "zh-CN",它預設會採用 gb2312 編碼回應。這會造成處理上的困擾。

其次,由於 .NET 與 Java 平台內建皆採用 UTF-8 編碼,因此在我們指定輸入編碼及輸出編碼時,當然是優先考量 UTF-8。

最後,如果你用 Eclipse 做測試,你可能會發現 console 裡有部分亂碼。請依照下圖,在 Run/Run Configurations... 裡面做設定,將 Console Encoding 指定為 UTF-8:

eclipse-console-utf8

最後就能產出正常的顯示了。

eclipse-console-utf8-2

2008/11/07

正妹牆螢幕保護程式

foxsaver-beauty1

辦公室前面擺了一個 40 幾吋的電漿螢幕,放置不用殊為可惜。與同事閒聊之際,突然靈機一動,何不拿來展示正妹牆,24 下時輪播,慰勞整天辛勞的宅男工程師。

有了這個想法,首先想到了就是要找個能播放 media rss 的螢幕保護程式。試了幾個,包括:

效果都出不來。決定拿 FoxSaver 一試 -- 嘿,別看太久,年輕人會流鼻血的。

FoxSaver 是 Firefox 的一個 Extension,可以把 Firefox 變成螢幕保護程式。從它的選項來看,圖片來源可以來自本機目錄,RSS 或一般網頁。由於這個 Extension 還在實驗中,在 addons.mozilla.org 網站上需要登入才能安裝。我在安裝之後,使用上是沒有大問題。重點來了,怎麼設定 FoxSaver 讀取正妹牆 RSS 呢。一圖勝千言,請見右圖 。

如此之後,在 Firefox 狀態列上的 FoxSaver 按鈕上點右鍵,選擇「FoxSaver 開始」就行了。

當然,也有人推薦用 PicLens/Cooliris 來觀正妹牆,除了可以自己操刀,將正妹們拉遠拉近觀看外(是不是第一次感受到那種呼之即來,揮之即去的操作感 >_<|||),要透過 Cooliris 來達到類似螢幕保護程式的效果,記得按一下三角型的播放鈕,這 -- 不就變成美女自動送上門的桌上版了嗎。

2008/10/15

glassfish-v2 JSP request 中文參數亂碼解法

最近幾天用 Microsoft Visual Web Developer 2008 Express Edition 練習 ASP.NET + ADO.NET,順便把 Java 陣營兩大 IDE Eclipse 及 NetBeans 拿來比較一下開發的易用性…

心得是,以 NetBeans 用 JSF + JPA 開發網站,開發的效率並不下於 ASP.NET + ADO.NET。而 Eclipse 的 JPA 工具(稱為 Dali Java Persistence Tool) 的功能其實也相當完善,例如,它可以讓你由 Java class 產生 db table,也可以讓你由 table 產生 entity class。唯一在使用性上不足之處,是 Eclipse 出廠設定較 NetBeans 薄弱。例如,為了使用 JPA,你必須自行下載一個 JPA 的一個實作,而為了使用 JSF,你也必須自行下載一個 JSF 的一個實作。下載後還要自行設定… 需要看一點功夫才設定的起來。而 NetBeans 如果下載的是 Web & Java EE 版,預設就能直接開發 JSF + JPA 程式,甚至連範例專案都含在裡面,deploy 一下,直接就能 run 了。

以上都是雜話,以下是正文,但正文不會很多。

在測試 NetBeans 開發 Web 網站時,遇到一件有趣的事。就是以 NetBeans JSP web application 的預設組態,deploy 到 glassfish-v2 上,輸入中文時,取得的資料會是亂碼。但若是以 JSF 來開發 web application 便不會有中文亂碼的問題。

glassfish 中文亂碼的問題,可參考 FaqHttpRequestParameterEncodingServlet Character Encoding,在 sun-web.xml 中加入 default-charset="UTF-8" 得到解決,如下所示:

   <sun-web-app>
     <locale-charset-info default-locale="">
       <locale-charset-map locale="" charset=""/>
       <parameter-encoding default-charset="UTF-8"/>
     </locale-charset-info>
   </sun-web-app>

(注意,在 NetBeans 裡面,預設將 JSP 及 Java 檔都以 UTF-8 存檔,且 JSP 皆加入 <%@page pageEncoding="UTF-8"%> 指示)。

但 JSF 沒有中文亂碼問題,雖然這是好事,但反道讓我有些不解。用 Google 查詢一下,原來在 JSF 規範裡面有特別提到要如何解析當前 request 參數的編碼,Tips for JSF character encoding 有精要的描述。有了這些資訊,便不需再透過撰寫 filter 來解決編碼問題了。

Technorati : , , , ,

2008/07/09

使用 Google Trends, 你有進行基值/基期校正嗎?

因為在 iThome 看到一位與我有一面之緣、熱衷 Flex 的高手,透過分析 Google Trends 資料,寫了一篇名為「RIA四雄群起:以Google Trends評析現有RIA四大技術(Flex、Silverlight、JavaFX、Curl)」的 blog。由於其中各種技術熱門程度的差異實在太大,激起了我進一步自行探索的動力。

第一件使我產生懷疑的是,文中指出 Flex 技術是 2004 年發行 1.0 版,我到 Wikipedia 查了一下資料,是 2004 年 3 月。那時候 Flex 還是 Macromedia 所提出的一個 Server 端方案,需配合貴死人的 Server 端執行。而由圖一可以明顯看出,Flex 的趨勢線在 2004 年初就一直處於高檔,直覺跟…好吧--年紀--告訴我這不合理。

ria-3

圖一:未經校正的 Google Trends 查詢:Flex, Silverlight, JavaFX, Curl

而第二件讓我覺得更不合理的是,如果你直接透過 Google 查詢 Curl,可以發現十之八九都與 RIA 無關。這樣的查詢流量怎能將它全部歸到 Curl for RIA 這一塊呢。

我相信 Flex 這將近 0.9 的 Search Volumn Index,並非指 Macromedia/Adobe 的 Flex 技術;同樣的,Curl 大多的查詢流量也與 RIA 無關。為了進行檢驗,我將查詢語句作了一些修正,以期找出較具代表性的指標。新的查詢為:Macromedia Flex, Microsoft Silverlight, Sun JavaFX, Adobe Flex。

ria-4

圖二:經過校正的 Google Trends 查詢:Macromedia Flex, Microsoft Silverlight, Sun JavaFX, Adobe Flex

這個查詢中,Flex 的熱門程度可用 Macromedia Flex + Adobe Flex 來代表。基本上可看出,在 2004 年 3 月以前,少有人關注 Flex。而 Microsoft Silverlight 的聲勢,"有段時間" 其實並不小於 Macromedia/Adobe Flex,那 Sun 的 JavaFX,就趴在地上了。

不過,究竟一般網民在查詢時,並不會特別以 Adobe Flex、Microsoft Silverlight 這樣的組字方式去下。以上,我所要說明的是,在以 Google Trends 進行分析時,得對基期或基值進行校正。透過第二個查詢我們已經證明 2004 年 3 月前的 Flex 流量,不能算是 Flex for RIA 這一塊的流量。如果將圖一 Flex 的流量值向下平移 0.9 單位,可以看出 Flex 對 Silverlight 的比值將接近圖二所示。

行文至此,是不是可以建議 Google Trends 提供類似基期/基值校正的功能。不然,就趕快把 Google Treands 的 API 給 release 出來吧!

相關連結:

Technorati : , , , ,