openwrt luci界面修改


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

 

 

代碼分析

 

1,啟動:

在瀏覽器中輸入:http://192.168.1.1/   會自動跳到http://192.168.1.1/cgi-bin/luci

luci\modules\base\htdocs\cgi-bin\luci

1 #!/usr/bin/lua -- 執行命令的路徑
2 require"luci.cacheloader" -- 導入 cacheloader 包
3 require"luci.sgi.cgi" -- 導入 sgi.cgi 包
4 luci.dispatcher.indexcache = "/tmp/luci-indexcache" --cache 緩存路徑地址
5 luci.sgi.cgi.run() -- 執 行 run 方法,此方法位於 luci\modules\base\luasrc\sgi\cgi.lua 中

run方法

1 luci\modules\base\luasrc\sgi\cgi.lua
 1 function run()
 2     local r = luci.http.Request(
 3         luci.sys.getenv(),
 4         limitsource(io.stdin, tonumber(luci.sys.getenv("CONTENT_LENGTH"))),
 5         ltn12.sink.file(io.stderr)
 6     )
 7      
 8     local x = coroutine.create(luci.dispatcher.httpdispatch) //開啟協助線程---->調用luci\modules\base\luasrc\dispatcher.lua里的httpdispatch函數
 9     local hcache = ""
10     local active = true
11      
12     while coroutine.status(x) ~= "dead" do
13         local res, id, data1, data2 = coroutine.resume(x, r)
14  
15         if not res then
16             print("Status: 500 Internal Server Error")
17             print("Content-Type: text/plain\n")
18             print(id)
19             break;
20         end
21  
22         if active then
23             if id == 1 then
24                 io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n")
25             elseif id == 2 then
26                 hcache = hcache .. data1 .. ": " .. data2 .. "\r\n"
27             elseif id == 3 then
28                 io.write(hcache)
29                 io.write("\r\n")
30             elseif id == 4 then
31                 io.write(tostring(data1 or ""))
32             elseif id == 5 then
33                 io.flush()
34                 io.close()
35                 active = false
36             elseif id == 6 then
37                 data1:copyz(nixio.stdout, data2)
38                 data1:close()
39             end
40         end
41     end
42 end

 

 

 

2,進入網頁

 

在瀏覽器中輸入:http://192.168.1.1/   會自動跳到http://192.168.1.1/cgi-bin/luci   登陸默認root 密碼是空,默認的幾個一級菜單都是在modules\admin-full\luasrc\controller\admin\ 這個目錄下,index.lua為執行文件

 1 module("luci.controller.admin.index", package.seeall)           //聲明一下這個模塊, 模塊入口為函數
 2  
 3 function index()
 4     local root = node()   //定義了最外面的節點,也就是最上層的菜單顯示
 5     if not root.target then
 6         root.target = alias("admin")
 7         root.index = true
 8     end
 9  
10     local page   = node("admin")
11     page.target  = firstchild() //----->luci\modules\base\luasrc\dispatcher.lua--->firstchild()
12     page.title   = _("Administration")
13     page.order   = 10
14     page.sysauth = "root"
15     page.sysauth_authenticator = "htmlauth" //---->luci\modules\base\luasrc\dispatcher.lua---->htmlauth() 找到哪個用戶
16         page.ucidata = true
17     page.index = true
18  
19     -- Empty services menu to be populated by addons
20     entry({"admin", "services"}, firstchild(), _("Services"), 40).index = true
21  
22     entry({"admin", "logout"}, call("action_logout"), _("Logout"), 90)
23 end

entry 定義了菜單下的一個子菜單。

1 entry(路徑, 調用目標, _("顯示名稱"), 顯示順序)
2 entry(path, target, title=nil, order=nil)

第1項為菜單入口;

第2項為菜單對應的頁面,可以是lua的源代碼文件,也可以是html頁面,甚至可以是以上兩種頁面的組合。(alias是指向別的entry的別名,from調用的某一個view,cbi調用某一個model,call直接調用函數)

第3項是菜單的文本,直接添加string不會國際化,_("string"),就國際化了

第4項是同級菜單下,此菜單項的位置,從大到小。

 

3,登陸

目錄:luci\modules\base\luasrc\dispatcher.lua

 1 function authenticator.htmlauth(validator, accs, default)
 2     local user = luci.http.formvalue("username")
 3     local pass = luci.http.formvalue("password")
 4  
 5     if user and validator(user, pass) then
 6         return user
 7     end
 8  
 9     require("luci.i18n")
10     require("luci.template")
11     context.path = {}
12     luci.template.render("sysauth", {duser=default, fuser=user})
13     return false
14  
15 end

4.子菜單項

 1 function _firstchild()
 2    local path = { unpack(context.path) }
 3    local name = table.concat(path, ".")   //當前目錄下,既:luci\modules\admin-full\luasrc\controller\admin\
 4    local node = context.treecache[name]
 5  
 6    local lowest
 7    if node and node.nodes and next(node.nodes) then
 8       local k, v
 9       for k, v in pairs(node.nodes) do
10          if not lowest or
11             (v.order or 100) < (node.nodes[lowest].order or 100)
12          then
13             lowest = k
14          end
15       end
16    end
17  
18    assert(lowest ~= nil,
19           "The requested node contains no childs, unable to redispatch")
20  
21    path[#path+1] = lowest
22    dispatch(path)
23 end
24  
25 --- Alias the first (lowest order) page automatically
26 function firstchild()
27    return { type = "firstchild", target = _firstchild }
28 end

1 entry({"admin", "services"}, {"firstchild","network.lua system.lua ......"}, _("Services"), 40).index = true

如上可以看出,登陸后第一級目錄如下:luci\modules\admin-full\luasrc\controller\admin\下的xxx.lua文件

 

增加一個菜單:

1 http://www.right.com.cn/forum/thread-131035-1-1.html

 


免責聲明!

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



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