LuCI探究

轉載自:http://www.cnblogs.com/gnuhpc/archive/2013/08/31/3293643.html


1. 多語言

1)檢查:

opkg list | grep luci-i18n-

2)安裝語言包:

opkg install luci-i18n-hungarian
 
2.uhttpd
這個是LuCI所在的Web Server。docroot在/www下邊,index-html指向了/cgi-bin/luci,注意這是相對於docroot而言的路徑。

openwrt中利用它作爲web服務器,實現客戶端web頁面配置功能。對於request處理方式,採用的是cgi,而所用的cgi程序就是luci。

1)工作框架如下圖所示:

Client端和serv端採用cgi方式交互,uhttpd服務器的cgi方式中,fork出一個子進程,子進程利用execl替換爲luci進程空間,並通過setenv環境變量的方式,傳遞一些固定格式的數據(如PATH_INFO)給luci。另外一些非固定格式的數據(post-data)則由父進程通過一個w_pipe寫給luci的stdin,而luci的返回數據則寫在stdout上,由父進程通過一個r_pipe讀取。

2)luci 文件(權限一般是 755 ) , luci 的代碼如下:

#!/usr/bin/lua -- 執行命令的路徑

require"luci.cacheloader" -- 導入 cacheloader 包

require"luci.sgi.cgi" -- 導入 sgi.cgi 包

luci.dispatcher.indexcache = "/tmp/luci-indexcache" --cache 緩存路徑地址

luci.sgi.cgi.run() -- 執 行 run 方法,此方法位於 /usr/lib/lua/luci/sgi/cgi.lua中

3)web配置時的數據交互:

  1. 首次運行時,是以普通的file方式獲得docroot/index.html,該文件中以meta的方式自動跳轉到cgi的url,這是web服務器的一般做法。

  2. 然後第一次執行luci,path_info='/',會alise到'/admin'('/'會索引到 tree.rootnode,並執行其target方法,即alise('/admin'),即重新去索引adminnode,這在後面會詳細描述),該節點需要認證,所以返回一個登錄界面。

  3. 第3次交互,過程同上一次的,只是這時已post來了登錄信息,所以serv端會生成一個session值,然後執行'/admin'的target(它的target爲firstchild,即索引第一個子節點),最終返回/admin/status.html,同時會把session值以cookie的形式發給client。這就是從原始狀態到得到顯示頁面的過程,之後主要就是點擊頁面上的連接,產生新的request。

  4. 每個鏈接的url中都會帶有一個stok值(它是serv生成的,並放在html中的url裏),並且每個新request都要帶有session值,它和stok值一起供serv端聯合認證。

初始階段http報文,可以看到從第2次交互開始,所有request都是cgi方式(除一些css、js等resource文件外),且執行的cgi程序都是luci,只是帶的參數不同,且即使所帶參數相同(如都是'/'),由於需要認證,執行的過程也是不同的。

正是由於多種情況的存在,使得luci中需要多個判斷分支,代碼多少看起來有點亂,但openwrt還是把這些分支都糅合在了一個流程線中。下面首先給出整體流程,首先介紹一下lua語言中一個執行方式coroutine,它可以創造出另一個執行體,但卻沒有並行性,如下圖所示,每一時刻只有一個執行體在執行,通過resume、yield來傳遞數據,且數據可以是任意類型,任意多個的。

Luci正是利用了這種方式,它首先執行的是running()函數,其中create出另一個執行體httpdispatch,每次httpdispatch執行yield返回一些數據時,running()函數就讀取這些數據,做相應處理,然後再次執行resume(httpdispath),……如此直到httpdispatch執行完畢,如下圖所示:

如上圖所示,其實luci真正的主體部分正是dispatch,該函數中有多個判斷分支,全部糅合在一起。

4)節點樹node-tree

在controller目錄下,每個.lua文件中,都有一個index()函數,其中主要調用entry()函數,形如entry(path,target,title,order),path形如{admin,network,wireless},entry()函數根據這些創建一個node,並把它放在全局node-tree的相應位置,後面的參數都是該node的屬性,還可以有其他的參數。其中最重要的就是target。

Createtree()函數就是要找到controller目錄下所有的.lua文件,並找到其中的index()函數執行,從而生成一個node-tree。這樣做的io操作太多,爲了效率,第一次執行後,把生成的node-tree放在/tmp/treecache文件中,以後只要沒有更新(一般情況下,服務器裏的.lua文件是不會變的),直接讀該文件即可。生成的node-tree如下:

這裏要注意的是,每次dispatch()會根據path_info逐層索引,且每一層都把找到的節點信息放在一個變量track中,這樣做使得上層node的信息會影響下層node,而下層node的信息又會覆蓋上層node。比如{/admin/system},最後的auto=false,target=aa,而由於admin有sysauth值,它會遺傳給它的子節點,也即所有admin下的節點都需要認證。

5)target簡介

對每個節點,最重要的屬性當然是target,這也是dispatch()流程最後要執行的方法。target主要有:alise、firstchild、call、cbi、form、template。這幾個總體上可以分成兩類,前兩種主要用於鏈接其它node,後一個則是主要的操作、以及頁面生成。下面分別描述。

鏈接方法:在介紹初始登錄流程時,已經講到了這種方法。比如初始登錄時,url中的path_info僅爲'/',這應該會索引到rootnode節點。而該節點本身是沒有內容顯示的,所以它用alias('admin')方法,自動鏈接到admin節點。再比如,admin節點本身也沒有內容顯示,它用firstchild()方法,自動鏈接到它的第一個子節點/admin/status。

操作方法:這種方法一般用於一個路徑的葉節點leaf,它們會去執行相應的操作,如修改interface參數等,並且動態生成頁面html文件,傳遞給client。這裏實際上是利用了所謂的MVC架構,這在後面再描述,這裏主要描述luci怎麼把生成的html發送給client端。

Call、cbi、form、template這幾種方法,執行的原理各不相同,但最終都會生成完整的http-response報文(包括html文件),並調用luci.template.render(),luci.http.redirect()等函數,它們會調用幾個特殊的函數,把報文內容返回給luci.running()流程。

如上圖所示,再聯繫luci.running()流程,就很容易看出,生成的完整的http-response報文會通過io.write()寫在stdout上,而uhttpd架構已決定了,這些數據將傳遞給父進程,並通過tcp連接返回給client端。

6)sysauth用戶認證

由於節點是由上而下逐層索引的,所以只要一個節點有sysauth值,那麼它所有的子節點都需要認證。不難想象,/admin節點有sysauth值,它以下的所有子節點都是需要認證才能查看、操作的;/mini節點沒有sysauth值,那麼它以下的所有子節點都不需要認證。

luci中關於登陸密碼,用到的幾個函數爲:

可以看出它的密碼是用的linux的密碼,而openwrt的精簡內核沒有實現多用戶機制,只有一個root用戶,且開機時自動以root用戶登錄。要實現多用戶,必須在web層面上,實現另外一套(user、passwd)系統。

另外,認證後,serv端會發給client一個session值,且它要一直以cookie的形式存在於request報文中,供serv端來識別用戶。這是web服務器的一般做法,這裏就不多講了。

7)MVC界面生成

這其實是luci的精華所在,/usr/lib/lua/luci/下有三個目錄model、view、controller,它們對應M、V、C。下面簡單介紹生成界面的方法。

Call()方法會調用controller裏的函數,主要通過openwrt系統的uci、network、inconfig等工具對系統進行設置,如果需要還會生成新界面。動態生成界面的方法有兩種,一是通過cbi()/form()方法,它們利用model中定義的模板map,生成html文件;另一種是通過template()方法,利用view中定義的htm(一種類似html的文件),直接生成界面。

上面的標題是由node-tree生成的,下面的內容由每個node通過上面的方法來動態生成。這套系統是很複雜的,但只要定義好了,使用起來就非常方便,增加頁面,修改頁面某個內容等操作都非常簡單。

 
8)啓動:
/etc/init.d/uhttpd start
9)開機自啓動:
/etc/init.d/uhttpd enable
 
5.參考文獻
http://www.cnblogs.com/zmkeil/archive/2013/05/14/3078774.html
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章