程式者的胡言亂語

pageicon 星期三 五月 21, 2008

TWJUG “山頂洞人日記 - 回歸到最純樸的開發” 簡報

 

 

上週六(5/17)適逢TWJUG移師至新竹,所以我也準備了一個簡報分享。主題是介紹我自己開發Java應用系統的種種,包括工具、主要的設計方法、以及一個叫做LWDBAlight-weight database access)的輕量級程式庫。簡報的投影片可以在SlideShare上看到

因為同場還有tempo大濕要分享GWT的一些心得,所以之前我還開玩笑說這是「太空人」與「山頂洞人」的對決,因為我時常使用一些很過時的玩意,相較於對各式新技術十分熟稔的tempo來說(想到十年前,初次看到tempo寫的DDEDynamic Data
Exchange
的程式時,簡直嚇了一跳呢),這無疑像是太空人和山頂洞人之間的對比。

這當然有點像是為了戲劇效果而說出來的開玩笑的話(沒辦法,tempo實在太愛演了!),而我在簡報時似乎也刻意強調這種反差,不過,這當然不是指我都不使用任何的新技術或框架,而是在於我覺得問題的核心並不在該處。

我在簡報的一開始提到:「慾望總是超過真實需求,威力強大的東西難免複雜,選擇開發工具要在複雜度與威力之間選擇一個適合你的平衡點。」事實上,有許多人的能力不足以駕馭他們想要使用的工具,舉個很簡單的例子來說吧,我相信還是有一堆使用Java IDE的人,搞不懂CLASSPATH究竟是什麼意思吧。

究竟要使用什麼樣的工具,必須匹配到你自身(或團隊開發成員)的能力(或skill set),也必須匹配到所欲開發本身的需求。好比我提到的例子,過去我們用Struts,現在有時也用,但是對於許多新興、但屬性又單純的專案,我甚至直接用JSP來寫action。這背後的關鍵在於必須評估,使用這樣的工具究竟能為你帶來什麼好處,而不是整個社群或社群大多數的人都這麼做,所以你也從善如流的這麼做。

「回到最簡樸的開發」,好比有很多人想要回到最簡樸的生活一樣。何謂簡樸的生活?在我看來,簡樸的生活是滿足確切真實慾望的一種生活,你不會因為追求不必要的慾望,而為自己招來煩惱和痛苦。其間的關鍵在於審視自己真實的需求。

專案就和生活一樣,也有專案本身的目標和設定的需求。我看到有許多專案,同樣為了不切實際的慾望,為自己創造了不必要的需求,自然也為自己招來不必要的煩惱和痛苦。

我在五年前,也做Web應用程式的程式碼產生器、表單欄位檢核器、。為什麼現在,反而把它們都丟掉了。事實上,這些東西都超乎我現在的需求太多(當然,我是一定不會解釋為什麼我連鑰匙圈都要買GUCCI的,因為在這個理論下實在很難解釋)。

除了審視真實的需求之外,我提到另一個我覺得重要的地方,就是「問題不在你使用什麼framework,而是在於你的設計對不對」。

許多想要利用引進framework來節省自己的開發時間。在你能克服某種程度的複雜度以及學習曲線時,這能做到。不過,我比較懷疑的是,你究竟能節省多少時間。

即使你使用了Struts,你使用了Hibernate,但是,你沒有建立一個好的設計,所以你在Action class裡直接操作資料物件,也就是說在Action class中直接實作了business logic。光是你的Action class和你的資料物件之間的相依性,以及Action classbusiness logic實作之間的相依性,其複雜程度以及引發錯誤的機會,就足以在開發中後期把你拖跨。而這才是你真正很有可能會投入大量時間的地方。

你或許用了被普遍被採用的工具或應用程式框架,而這些工具或框架若是使用得法,也確實會有很大的助益,但根本的問題時常出在只是知其然而不知其所以然,那是一種「反正別人用了,我跟著用準沒錯」的心態在支配著。

把焦點移回你自身的設計,會更有助於真正的生產力。好的設計,能夠提昇品質,也能大幅的提昇開發的效率。有道是:「練拳不練功,到老一場空。」

pageicon 星期一 八月 27, 2007

使用Jakarta Apache之Commons-HttpClient時,忽略對建立SSL連線之certificate的檢查

,這篇blog要講的是使用Jakarta ApacheCommons-HttpClient時,如何略對建立SSL連線之certificate的檢查的方法。


在這邊我必須說,有個情況我也還沒有找出真正的原因。就是前文中所提及的方法,有時對採用Jakarta ApacheCommons-HttpClient的程式可以起作用,有時不行。這裡挺怪的,因為HttpClient所賴以建立SSL連線的,應該也是Java的底層SSL Socket程式庫,所以前文中所提到的方法應該可以適用,不過我發現,有時候不行,而且目前還沒法子解釋。不過,下述提供一個方法,可以讓HttpClient忽略對憑證的檢查。基本上,可以先參考Commons官方網站上的SSL Guide。在這份文件裡,提到了如何自訂HttpClient中的 SSL行為。你可以從它的範例中找到EasySSLProtocolSocketFactory的例子,利用它來改就可以了。


HttpClient的原始碼中,可以找到這個packageorg.apache.commons.httpclient.contrib.ssl。其中有兩個class是我們會用到的:它是


EasySSLProtocolSocketFactoryEasyX509TrustManager。把它們分別rename成你想要的名稱並且設定適當的package,例如MySSLProtocolSocketFactoryMyX509TrustManagerMySSLProtocolSocketFactory會使用到MyX509TrustManager,所以把原先EasySSLProtocolSocketFactory中指涉EasyX509TrustManager的地方,全部改成MyX509TrustManager。接著修改MyX509TrustManager的這幾個methods






public MyX509TrustManager(){


      super();


}


public boolean isClientTrusted(X509Certificate[] certificates) {


      return true;


}


public boolean isServerTrusted(X509Certificate[] certificates) {


      return true;


}


public X509Certificate[] getAcceptedIssuers() {


      return null;


}


接著就必須在你的主程式裡註冊你的MySSLProtocolSocketFactory


 






Protocol myhttps = new Protocol("https",  new MySSLProtocolSocketFactory(), 443);


Protocol.registerProtocol("https", myhttps);


 


這代表你向HttpClient註冊https協定,採用443 port(標準的https port),由你的MySSLProtocolSocketFactory來處理。透過上述的設定,應能順利的在HttpClient中忽略對憑證的檢查。

Java中忽略對建立SSL連線之certificate的檢查

建立SSL連線時,伺服器會提供一份電子憑證(certificate),以供用戶端程式在連線時用以編解與伺服器之間的訊息交換。正常的情況下,在建立連線之際,用戶端程式收到伺服器提供的ceritificate,會向這張ceritifccate的憑證授權單位(CA)要求檢驗此張憑證的有效性。


有許多網站,尤其是還在測試中的網站,因為向憑證授權單位購買憑證是需要花錢的,所以都會自行利用一些簽證的工具,自行產生憑證,一般來說,就被稱為是 self-signedcertificate。當我們透過像IE這樣子的瀏覽器來檢視網址為https://開頭的網站時,倘若看到下述的視窗:



通常都是遇上self signed的網站。瀏覽器處理這種情況很簡單,因為它可以詢問使用者是否願意接受,再進行下一步的處理。但是使用Java來連線此類的網站時,事情就比較複雜。倘若我們寫下下述的程式碼:







import java.io.*;


import java.net.*;


 


public class TestCert


{


      public static void main(String argv[])


      {


             try


             {


                   String _url = "https://www.cs.nthu.edu.tw/";


                   URL url = new URL(_url);


                   URLConnection uc = url.openConnection();


                   InputStream is = uc.getInputStream();


                   InputStreamReader isr = new InputStreamReader(is);


                   BufferedReader br = new BufferedReader(isr);


                   String s = br.readLine();


                   System.out.println(s);


                   br.close();


                   isr.close();


             }


             catch(Exception e)


             {


                   e.printStackTrace();


             }


      }


}


這段程式碼基本上就是連接到一個https://的網站,但由於這個網站的憑證是self signed的,所以發生了下述的錯誤:







javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:


PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderExce


ption: unable to find valid certification path to requested target


        at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)


        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1


518)


這便是JavaSSL socket實作在收到伺服器回傳的憑證時,由於無法認證該憑證,而引發的錯誤。有一個解決的方法,便是把這個憑證加到你Java執行環境中儲存信任憑證的地方,不過這不是本篇blog的主旨,本篇的主旨在於如何忽略檢查,方法如下:


 







import javax.net.ssl.*;


 


TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()


{


        public java.security.cert.X509Certificate[] getAcceptedIssuers()


        {


                return null;


        }


        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)


        {


        }


 


        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)


        {


        }


}};


SSLContext sc = SSLContext.getInstance("SSL");


sc.init(null, trustAllCerts, new java.security.SecureRandom());


HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());


基本上,就是為目前SSLSocketFactory設定一個自訂的TrustManager,並且在TrustManager該有的介面中,pass所有的檢查。如此一來,便可以忽略掉上述所提到的錯誤。


使用上述的方式,必須小心整個application是否會連線到不在預期中的網站,因為這樣子的寫法,等於是開放完全不檢查來自所有網站的憑證是否有效。我是偷懶的人,所以不想把憑證加到信任的cert store去,但因為明確的知道憑證的來源網站,所以才使用忽略的方式,倘若你的情況並非如此,就應該審慎的評估。


另外,如果你所收到的憑證,其中的domain name並未和其實際的domain name相符(我有遇到domain nameIP address的情況:p),也會引發憑證的檢查錯誤,解決之道(其實就是忽略之道)如下:







HostnameVerifier hv = new HostnameVerifier()


{


      public boolean verify(String urlHostName, SSLSession session)


      {


             System.out.println("Warning: URL Host: "+urlHostName+" vs. "+session.getPeerHost());


             return true;


      }


};


HttpsURLConnection.setDefaultHostnameVerifier(hv);


java.net.ssl.HttpsURLConnection設定一個自訂的HostnameVerifier,並且在其verify()中回傳檢查成功(true),就可以把對憑證host name的檢查予以忽略。


以上兩種情況,約略是連接至測試用之https網站的主要問題。


 

pageicon 星期一 七月 16, 2007

用ROME parse Yahoo新聞的RSS內容(ROME所能parse的日期格式)

今天不願透露姓名且有意角逐2028年總統大位的邱姓學弟來託夢,表示ROME 0.9版不能正確的parse Yahoo的新聞RSS。夢醒來後立即一試,發現的確不能正確的parse。其中不能parse的原因在於,其中的pubDate欄位,ROME怎麼取都會取到null。仔細追查,發現在於Yahoo RSSpubDate欄位寫法,是非標準的寫法,像是:Sun 15 Jul 2007 21:49:09 GMT


ROME支援幾種日期的寫法,包括RFC822W3C規範的眾日期格式,但偏偏Yahoo用的就不在這裡頭。trace了一下com.sun.syndication.io.impl.DateParser這個ROME用來parse日期字串的類別,就可以知道,你可以將額外的日期格式設定到一個叫做rome.properties的檔案中,並且將額外的格外設到叫做datetime.extra.masks的欄位中(如果你有多種額外的日期格式,可以用|分隔)。所以在rome.properties設定一行


datetime.extra.masks=EEE dd MMM yyyy HH:mm:ss z


並且將rome.properties設到CLASSPATH所在的位置上,的確就可以正確的parse Yahoo RSS所用的日期格式了(EEE代表三位的每週的那一天,dd代表每月第幾天,MMM代表三位的月份文字表示式,yyyy代表四位的西元年份,HH :mm :ss代表時分秒,而z是時區)。

pageicon 星期六 六月 30, 2007

五分鐘DWR快速上手

最近因為特殊的需要,開始研究AJAX。我的習慣是傾向用落後一到兩個世代的技術來開發,所以之前對AJAX完全沒有接觸。所以,便從tempo大濕的名著《荒島漂流記》開始讀起,一讀之後發現這DWR實在是很單純,很適合像我這樣的初學者入門,只要把握要領,有經驗的Java程式員應該要在五分鐘內就可以運用起來才對。所以,我覺得可以試著整理幾個重點,有心採用DWR(但無意漂流荒島)來開發AJAX應用程式的朋友們可以參考看看。


第一個重點就是,DWR是一種RPCremote procedure call)式的AJAX framework。在RPC模式下,clientcaller)透過某個包裝procedue call的通訊協定,呼叫位在遠端的程序(callee)。在使用DWR時,caller就是瀏覽器上執行的javascript程式,而部署在遠端的Java類別所提供的method就是callee


再來就是,那你要如何實作遠端的函式呢?基本上,你只要撰寫一個普通的Java class,提供希望被叫用的methods就行了。但是,你必須要做兩樣設定的工作,第一個是在你Web應用程式的web.xml加上DWR的一個servlet






<servlet>


             <servlet-name>dwr-invoker</servlet-name>


             <servlet-class>


                   org.directwebremoting.servlet.DwrServlet


             </servlet-class>


             <init-param>


                   <param-name>debug</param-name>


                   <param-value>true</param-value>


             </init-param>


      </servlet>


      <servlet-mapping>


             <servlet-name>dwr-invoker</servlet-name>


             <url-pattern>/dwr/*</url-pattern>


      </servlet-mapping>


第二個,便是必須把一個叫做dwr.xml的檔案放到你的Web應用程式的WEB-INF目錄中(和web.xml擺在一)。這個dwr.xml的長相看起來會有點像是這樣:






<dwr>


      <allow>


             <create creator="new" javascript="FriendWidgetDWR" scope="session">


                   <param name="class"


                          value=" widget.friend.dwr.FriendWidgetDWR" />


             </create>


      </allow>


</dwr>


基本上dwr.xml要描述的就是在你的系統中那些class是要提供DWR遠端程序的以及相對應classjavascript來說的名稱。例如本例中,對javascript來說,它叫做FriendWidgetDWR,而對應到Java的遠端物件類別名稱,就是widget.friend.dwr.FriendWidgetDWR。可以想見的是,DWRinvoker在收到來自javascript的請求時,會依據dwr.xml裡的設定,透過reflection的機制,來把遠端的Java物件產生出來,並且呼叫相對應的method


另一方面,因為有DWRinvoker的存在,所以不可能無限制開放任何一個class都允許位在client sidejavascript呼叫,這個dwr.xml的目的之一,也是為了保護不希望開放的classjavascript使用到。


第三個重點,撰寫你的DWR class。基本上就跟一般的Java class沒有什麼兩樣。通常你的method不是回傳void就是String。傳入的參數如果是String的話,是不需要做什麼額外的設定的,一會再來說如果不是String的情況要怎麼辦。倘若你要回傳的是String,那麼你要回傳什麼String呢?通常是一段要給javascript使用的字串,最常見的就是一段HTML碼,這段HTML碼是要讓javascript程式拿來操作DOM的。






public String genBlogTitle(String id)


{


return "<table border=0 bgcolor=#FFFFFF width=240><tr><td aligh=center>title</td></tr></table>";


}


第四個重點,就是就是撰寫你的javascript程式。你得include一些制式的javascript檔案,包括:






<script type='text/javascript'


      src='/friendwidget/dwr/interface/FriendWidgetDWR.js'></script>


<script type='text/javascript' src='/friendwidget/dwr/engine.js'></script>


<script type='text/javascript' src='/friendwidget/dwr/util.js'></script>


標紅色的friendwidgetWeb應用程式context的名稱,而FriendWidgetDWR則是在dwr.xml中所設定的。下面程式碼裡的FriendWidgetDWR同樣就是在dwr.xml裡設定的,而genBlogTitle就是在Java class裡寫的methodJava methodgenBlogtitle()接受一引數,但在使用javascript呼叫它時,因為這個method會傳回字串,所以可以指定一個callback函式來加以處理,在下例中的callback函式,是當genBlogTitle()完成後,將它所回傳的字串,設成idblogTitle這個DOM節點的innerHTML






window.onload = function() {


FriendWidgetDWR.genBlogTitle ("qing", function(returnStr) {


             $('blogTitle').innerHTML = returnStr;


      });


}


到此,大概就把DWR的主要流程說完了。基本上只要注意(1)要怎麼設定(2)寫Java class方式(3)寫javascript程式的方式,就可以了。


使用DWR是很簡單的,只要你把它想像是讓javascript去呼叫一個遠端的函式就成了。此外,最大的特別處在於這遠端函式回傳的通常是一段要拿來動態操作DOMHTML片段。當然,直接讓遠端類別回傳一段javascript碼,再於javascript端透過eval()去執行該段javascript碼,應用起來會更有彈性。


所以我說,DWR觀念上十分簡單,五分鐘上手非難事,就連毒菇木也都能輕易的運用的得心應手呢。


最後要附註的就是,DWR裡遠端呼叫函式的傳入引數,其型別若非預設可轉換的型別時,得在dwr.xml中進行額外的指定,例如:






<signatures>


             <![CDATA[


             import java.util.Map ;


             qing.QPOperationTabGen.submitFormData(Map);


             ]]>


</signatures>


因為qing.QPOperationTabGen.submitForData()希望能傳入一個Map引數,所以我們在dwrxml裡特別定義了這段signatures的部份,明確的指定其signature,以便使DWRengine本身能夠正確的判斷所欲傳入的型別究竟為何。


那倒底什麼情況會傳入Map呢?最常見的情況就是把某個表單的資料(多個name value pair)傳入囉。以下的這個函式,就會取出DOMid分別為field1field2