Luci實現框架


1.總述

    上一篇總結了uhttpd的工作方式,openwrt中利用它作為web服務器,實現客戶端web頁面配置功能。對於request處理方式,采用的是cgi,而所用的cgi程序就是luci,工作框架如下圖所示:

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

    下面的圖描述了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端聯合認證。

2.luci程序流程

    前面已經說明了,luci作為web服務器的cgi程序,是通過execl函數替換到進程空間的,並且詳細說明了它與其它進程的交互方法。另外上一節給出了初始階段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個部分,下面對它們進行一些描述。

    首先說明一下代碼組成,在openwrt文件系統中,lua語言的代碼不要編譯,類似一種腳本語言被執行,還有一些uhttpd服務器的主目錄,它們是:

/www/index.html

     /cgi-bin/luci

     /luci-static/xxx/xx.css、js、gif

/usr/lib/lua/nixio.so、uci.so

         /luci/http.lua、dispatcher.lua、core…

              /controller/xxx.lua

             /model/xxx.lua

             /view/xxx.lua

2.1節點樹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下的節點都需要認證。

2.2target簡介

    對每個節點,最重要的屬性當然是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端。

3.sysauth用戶認證

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

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

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

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

4.MVC界面生成

    這其實是luci的精華所在,第二節開始介紹/usr/lib/lua/luci/下有三個目錄model、view、controller,它們對應M、V、C。第2.2節介紹了生成的界面怎么傳遞給client,下面簡單介紹生成界面的方法。

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM