Feedlr:feed驅動的多平台微博客機器人平台
微博客是由Twitter 創造出的一種web 2.0時代的新事物。在微博客上,人們 使用簡短的語言隨時隨地的發表消息,並可以即時地受到好友的消息。由於易用 ,實時等特點,Twitter在06年推 出至今逐步升溫,已經擁有超過300萬用戶。 特別在08年中,Twitter一改起步階段geek玩具的角色,明顯地向主流進化。隨 著Twitter的興 起,也出現了非常多其他的微博客。僅國內就有叽歪、飯否、以 及做啥等等。微博客的興起提供了一種全新的在線溝通方式。
Twitter作為微博客的 鼻祖和最成功的例子,其優秀的API接口功不可沒。通 過Twitter API,開發者們開發出了眾多新奇又好用的Twitter第三方應用。我開 發Feedlr的出發點是建立一個讓用戶可以自行定制feed機器人的服務,核 心功 能類似Twitter上頗受歡迎的twitterfeed,並且可以同時Twitter,叽歪,飯否 以及做啥共4種微博客平台。
通過 Feedlr,用戶可以建立微博客廣播帳號,來隨時追蹤自己感興趣的 RSS/Atom Feed內容。一旦有更新,Feedlr就會自動把新的內容發送到指定的微 博客平台上。Feedlr上線至今,用戶們建立了自定義的新聞播報機器 人,DIY的 免費天氣預報機器人,不同微博客之間的消息同步機器人,甚至國內地震情況實 時監控機器人等等。而通過國內微博客服務的短信通知服務,以上所有 的Feed 內容國內用戶都可以免費在手機上通過短信接收到。
Grails框架的選擇
Grails是一個嶄露頭角的基於 Groovy語言,運行與JVM之上,設計上類似於 Rails的快速web開發框架,在08年初剛推出1.0版。通過Groovy語言和創新的架 構,Grails把成熟的企業級JEE開源組件Spring,Hibernate等巧妙地整合起來, 使用類似Rails的“按約定設計”(design by convention)理念捆綁成一套完整 的web開發框架。JEE開發過程的繁瑣被Groovy靈活多變的動態特性和按約定設計 帶來的精簡配置所取代, 而又保留了企業級組件在穩定和性能方面的優勢,可 以說是把Rails式的快速開發帶給了水深火熱中的JEE開發者們。我來自JEE背景 ,對Groovy語 言也有一定基礎,選用Grails搭建Feedlr是比較自然的選擇,同 時也是為了在一個沒有過多約束的真實項目中體驗Grails的完整開發過程。
如何用Grails實現Feedlr的核心功能Feedlr的核心功能
Feedlr的核心功能主要包括定時查詢用戶提供的feed的更新,把更新的feed 內容發布到微博客,再加上用來增強用戶體驗的多處AJAX實現以及OpenID登錄等 。這裡逐一對這些功能的實現做一下介紹。
定時查詢feed更新
Feedlr 最核心的功能就是定時輪詢用戶提交的feed,發現新增的條目,從而 通過微博客API發送到微博上去。只要使用Grails的Quartz插件就可以非常 方便 的實現這一功能。Quartz是一個用途廣泛的開源Java庫,用於精確地控制定時任 務。由於兼容Unix Cron語法,Quartz的功能非常強大。而在Grails中,Quartz 是框架自帶的核心插件之一,通過Quartz插件來執行定時任務非常方便。 新建 一個Quartz定時任務,只需要在Grails項目根目錄下執行
grails create-job
根 據提示輸入job名稱,Grails就會自動在grails-app/jobs/目錄下生成一 個新的job程序文件。Grails job都是以XXXJob.groovy命名,存放在grails- app/jobs目錄下,Grails啟動時會自動遍歷jobs目錄,定時執行每個定 義好的 job。一個job文件用來定義一種定時執行模式,通過Unix Cron語法來定義定時 邏輯。例如,Feedlr用於輪詢feed的job大致是這樣的:
class UpdateFeedsJob {
def feedService
def cronExpression = "0 * * * * ?" //每分鐘執行一次
def execute() {
feedService.updateFeeds()
}
}
Cron 表達式“0 * * * * ?”表示每分鐘執行一次。需要執行的邏輯通過定 義一個execute()方法來指定。其中feedService是已經定義好的用來查詢feed更 新的一 個Grails Service類,使用Rome來解析feed。注意此處不需要實例化 feedService變量,只要通過按約定設計的規則定義需要使用的 Service的變量 名,Grails會自動找到FeedService這個Service類,注入到UpdateFeedsJob中, 並把 Service實例付給feedService變量,聽起來很神奇吧。這樣,Grails就會 每分鐘觸發一次UpdateFeedsJob,來查詢 feed更新了。
發布feed更新到微博客
目前流行的微博客API都是已REST風格設計,通過GET和POST方法來得到或者 更新內容的。例如發布一條消息到Twitter,就是通過POST方法發送到Twitter指 定的API地址,簡化的代碼實例如下:
def conn = new URL ('http://twitter.com/statuses/update.xml').openConnection()
conn.setRequestProperty ('Authorization', 'Basic ' + 'username:password'.bytes.encodeBase64())
conn.requestMethod = 'POST'
conn.doOutput = true
try{
conn.outputStream.withWriter('UTF8'){
it << "status=" << newMessage
}
}catch(Exception e){
...
}
以上Groovy代碼很清晰易讀。通過Twitter RESTful API發布新消息需要使用 Http Basic驗證用戶登錄信息,所以這裡按照Basic驗證規范在請求中加入了驗 證數據。其中encodeBase64()方法是Grails提供的神奇的 動態方法,對於合適 類型的對象在Grails程序中直接就可以使用這些動態方法,其他的編碼方法還包 括encodeAsURL()等。
Ajax
在web 2.0時代沒有Ajax的網站是不完整的。幸運的是,在Grails中使用Ajax 非常方便。通過Grails內建的多才多藝的render方法,就可以輕松地給前端Ajax 請求返回任何形式的輸出。例如,
直接返回簡單的純文本字串
class FooController{
...
def ajaxResponse = {
...
render("This is an Ajax response.")
}
指定返回內容的格式和編碼
render(text:"<xml>some xml</xml>",contentType:"text/xml",encoding:"UTF-8")
返回模板內容
render(template:"feeds", model:[feeds:feeds], contentType:"text/html", encoding:"UTF-8")
返回JSON,直接自動轉換一個object為JSON
import grails.converters.*
...
def jsonObj = [object:[collection:[[name:‘value1′],[name:‘value2 ′]]]]
render jsonObj as JSON
返回JSON,通過JSON builder DSL直接構造JSON數據
render(contentType:‘text/json’, , encoding:'UTF-8'){
studio(name:‘Pixar’,website:‘pixar.com’)
films{
film(title:‘Toy Story’,year:‘1995′)
film(title:‘Monsters, Inc.’,year:‘2001′)
film(title:‘Finding Nemo’,year:‘2003′)
}
}
OpenID支持
Feedlr支持使用OpenID登錄。由於Grails社區已經提供了OpenID插件,通過 Grails的插件機制,實現OpenID支持也是一件輕松的事情。
首先,安裝OpenID插件,在Grails應用根目錄執行命令:
grails install-plugin openid
然後,使用openid插件提供的taglib來編寫openid登錄表單
<openid:form success="[controller:'login',action:'openidSuccess']" error="[controller:'login',action:'openidError']">
<openid:input size="30" value="http://" class="input-text"/>
...
</openid:form>
OpenID 插件代為處理了具體的OpenID登錄驗證過程,在 <openid:form>中,通過success參數和error參數指定登錄成功或失 敗以 後重定向到哪個controller action。登錄成功後,就可以在controller中直接 得到當前登錄的openid信息。
def openid = session.openidIdentifier
當然,需要實現完整的OpenID和普通帳號的整合還有更多工作要做,包括把 登錄的OpenID和已有的普通帳號關聯起來,從普通帳號添加OpenID信息等。這些 都是需要開發者根據自己的情況自行實現的。
測試
測 試是開發一個完整項目不可缺少的部分,幸運的是Grails已經為開發者考 慮到了這點,測試Grails程序也能像開發一樣輕松。Grails中的測試建 立在 Groovy testing的基礎上,通過使用Groovy來編寫JUnit測試代碼,減輕程序員 的負擔。Grails中的測試分為unit test和integration test兩種,兩者的區別 主要在於unit test是相對獨立的測試,而integration test執行的時候Grails 會按照實際運行的方式啟動框架程序。建立一個unit test或者integration test各自只需要一條命令即可。
grails create-unit-test
grails create-integration-test
Grails 會自動在grails-app/test/unit或者grails-app/test/integration 下面建立相應的 XXXTests.groovy文件。具體的test case定義在 XXXTests.groovy裡,通過定義繼承groovy.util.GroovyTestCase的類來實現, 這些其實都是 Groovy測試的內容,通過JUnit方式編寫測試代碼即可。
准備好了test case之後,Grails同樣已經為你准備好的命令來自動執行測試 。
grails test-app
執行這條命令,Grails就會自動按照unit test到integration test的順序來 執行定義好的所有test case,並將測試結果整理成HTML格式展現出來。test- app命令還有更多具體用法,可以參考Grails文檔。
Feedlr的部署
使用Grails的開發過程是很令人感到愉快的。那麼,一切都搞定以後,怎麼 部署呢?
Grails 文檔中說明Grails經測試可以部署到大多數常用的Java應用服務器上 ,但是具體有關部署的資料文檔比較缺乏。Feedlr選擇的是Tomcat 6,相對來講 比較常用,資料也比較豐富。准備部署Grails應用的時候,首先通過Grails把項 目打包成war文件。
grails prod war feedlr.war
這 裡,"prod"參數用來指定打包的時候使用Config.groovy裡針對生產環境 的配置。部署環境的配置在Config.groovy中設定,包 括"prodcution", “development"和"testing"三種,主要用於對不同環境指定不同的數據源和特 定的環境變量。具體用法可以 參考Grails文檔。war命令默認的打包環境就 是"prod",所以次數"prod"也可以省略。如需指定其他環境只需要將"prod"替換 成"dev"或者"test"即可。
最後指定war文件的文件名是可選的。在Tomcat下如果想讓應用跑在URL根路 徑下,可以指定文件名為ROOT.war。打包完成後,把war文件復制到Tomcat應用 目錄下,啟動Tomcat,正常的話Grails應用就能跑起來了。
在 系統性能和伸縮性方面,我其實沒有花太多力氣去優化。最主要的優化工 作就是在內存占用方面。Feedlr目前使用的是一台540MB內存的VPS服務器, 在 初期曾使用340MB內存的配置,遇到了內存資源緊張的問題,導致JVM性能底下。 由於Feedlr的特定用途,需要解析大量feed內容,所以在內存 方面要求不低。 後來經過一系列的優化措施,目前在這個環境下運行的相對比較穩定了。我之前 也總結過一些Grails服務器優化的經驗,有興趣的朋友可以參考我的這篇博客文 章。
實際開發中的一些困難
在Feedlr的開發中,可以體會到目前階段使用Grails進行完整Web項目開發的 一些困難和問題。
開發環境的不完善
Feedlr 是在Eclipse加上Groovy插件的環境下開發的。但是這個環境目前還 非常不完善,主要是Groovy插件的可用程度還比較低,而且沒有 Grails支持, 不支持GSP語法等,只是能夠支持Groovy語言,加上Eclipse本身的強大功能,能 夠給開發提供一定幫助。不過好在使用 Groovy語言開發比Java要省力不少,所 以不需要非常強大的IDE也可以不錯的完成任務。IDE方面另外的選擇主要包括 IntelliJ IDEA和NetBeans。簡單來講收費的IDEA對Groovy/Grails的支持最為完 整強大,但是代價也不菲。開源方面的選擇,Eclipse 方面還是比剛剛開始支持 Groovy的Netbeans好過不少。
Grails本身尚不夠成熟
在開發過程中也遇到過若干 Grails的bug,有的bug甚至導致某功能無法正常 運行。使用一個尚不成熟的框架,遇到bug也是正常的事情。解決bug的話基本上 可以先到 Grails郵件列表裡提問和尋找答案,需要的話提交bug報告到Grails官 方JIRA,提供bug數據等待修復。不過如果遇到緊急問題,還是自己動 手更好。 可以從Grails主頁下載帶源碼的Grails安裝包,就可以直接調試bug並編譯修改 代碼,這樣不用等官方發布下一個小版本就可以直接解決問 題了。在Feedlr開 發過程中遇到過若干比較緊急的bug,我隨著bug報告也提供過若干補丁程序,在 最新的Grails 1.0中都已被集成。在這裡也要建議大家在使用過程中多多提交 bug報告和提供補丁程序,這是良好的參與建設開源社區的方式。
總結
要 完整地實現Feedlr顯然還有很多工作要做,但Grails確實大大減輕了程序 員編碼工作的負荷。通過grails stats命令可以看到,除去測試代碼,feedlr最 終的代碼規模約是1.9kloc。對於一個完整地具備了包括全文檢索,RSS/Atom feed生成(Feedlr提供最新機器人服務列表的feed),Tag標簽功能,OpenID+普 通登錄方式整合等功能的web 2.0網站來說,這確實意味著我省去了不少打字的 工夫,避免了傳統JEE繁瑣的開發方式。
那麼,Grails到底是不是JEE世界的聖杯呢?我將在本系列的下篇文章中進一 步進行分析。