luci框架-LUA的一個web框架使用


轉自:http://blog.csdn.net/initphp/article/details/17527639

 

LUCI 這個在百度上搜索除了一篇我的百度文庫 luci 的介紹文章之外,前三頁都是些不知所雲的名詞(足見百度在專業領域的搜索之爛),我卻在大學畢業的大半年的大部分時間里與它糾結,由於開始的發懵到后來逐漸感覺到這家伙還很好玩的,現在就把我對 luci 的淺顯認識介紹給大家。

官網: http://luci.subsignal.org/

  有關luci 的各個方面,你幾乎都可以從這里獲得,當然,只是淺顯的獲得, luci 的文檔寫的還算比較全,但是寫的稍顯簡略,開始看的時候會有一點不知所措。

UCI  熟悉 openwrt 的人都會有所了解,就是 Uni fi ed Con fi guration Interface 的簡稱,而 luci 這個 openwrt上的默認 web 系統,是一個獨立的由嚴謹的德國人開發的 web 框架,是 Lua  Con fi guration Interface 的簡稱,如果在您的應用里, luci 是對 openwrt 的服務,我們就有必要做一下 uci 的簡介,我這里就不說了,見鏈接:

http://www.google.com.hk/url?sa=t&source=web&cd=5&ved=0CEMQFjAE&url=http://nbd.name/openwrt-fosdem-09.pdf&ei=h52iTcXvOcrMcJ-xxOwD&usg=AFQjCNGFhumCIgS5tK_mDJ2dDFU4qsskfQ

 

有的時候,我們開發的luci 是在自己的 Linux PC 上開發,在普通的 linux 上,一般是沒有 uci 命令的,為了開發方便,可以手動編譯一下,方法見鏈接:

https://forum.openwrt.org/viewtopic.php?id=15243

OK ,之前羅里羅嗦的說了很多,現在就進入正題,進入正題的前提是你已經 make install 正確的安裝了 lua  ,luci ,以及編譯好鏈接了相關的 so (如果你需要,比如 uci.so nixio.so ),以及 make install 正確 web server,(我用的 web server 是 thttpd ,也編譯過 mongoose , lighttpd ,在這三個之中, lighttpd 是功能最完善的, mongoose 是最小巧的)。

進入正題:

一:luci 的啟動

  在web server 中的 cgi-bin 目錄下,運行 luci 文件(權限一般是 755 ), luci 的代碼如下:

 

  1. #!/usr/bin/lua      --cgi的執行命令的路徑  
  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/sgi/cgi.lua中  

 

 

 run方法的主要任務就是在安全的環境中打開開始頁面(登錄頁面),在 run 中,最主要的功能還是在dispatch.lua 中完成。

 運行luci 之后,就會出現登錄界面:

 

  1.  -bash-4.0# pwd    
  2. /var/www/cgi-bin    
  3. -bash-4.0# ./luci    
  4.   Status: 200 OK        
  5.   Content-Type: text/html;     
  6.   charset=utf-8         
  7.   Cache-Control: no-cache       
  8.   Expires: 0    
  9. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"     
  10. "http://www.w3.org/TR/html4/strict.dtd">        
  11.  <html class=" ext-strict">    
  12.          
  13.  </html>    

 

二:LUCI 的 MVC

1:用戶管理:

      在luci 的官方網站說明了 luci 是一個 MVC 架構的框架,這個 MVC 做的可擴展性很好,可以完全的統一的寫自己的 html 網頁,而且他對 shell 的支持相當的到位,(因為 luci 是 lua 寫的, lua 是 C 的兒子嘛,       與 shell 是兄弟)。在登錄界面用戶名的選擇很重要,      luci 是一個單用戶框架,公用的模塊放置在 */luci/controller/ 下面,各個用戶的模塊放置在 */luci/controller/ 下面對應的文件夾里面,比如              admin 登錄,最終的頁面只顯示 /luci/controller/admin 下面的菜單。這樣既有效的管理了不同管理員的權限。

2: controller 文件夾下的 lua 文件說明:(以 mini 用戶為例)

     在mini 目錄下面,會有一個 index.lua 文件,簡略的代碼如下:

 

  1.  module("luci.controller.mini.index", package.seeall)    
  2. 17      
  3. 18  function index()    
  4. 19      luci.i18n.loadc("admin-core")    
  5. 20      local i18n = luci.i18n.translate    
  6. 21      
  7. 22      local root = node()    
  8. 23      if not root.lock then    
  9. 24          root.target = alias("mini")    
  10. 25          root.index = true    
  11. 26      end    
  12. 27         
  13. 28      entry({"about"}, template("about")).i18n = "admin-core"    
  14. 29         
  15. 30      local page   = entry({"mini"}, alias("mini", "index"), i18n("essentials", "Essentials"), 10)    
  16. 31      page.i18n    = "admin-core"    
  17. 32      page.sysauth = "root"    
  18. 33      page.sysauth_authenticator = "htmlauth"    
  19. 34      page.index = true    
  20. 35         
  21. 36      entry({"mini", "index"}, alias("mini", "index", "index"), i18n("overview"), 10).index = true    
  22. 37      entry({"mini", "index", "index"}, form("mini/index"), i18n("general"), 1).ignoreindex = true    
  23. 38      entry({"mini", "index", "luci"}, cbi("mini/luci", {autoapply=true}), i18n("settings"), 10)    
  24. 39      entry({"mini", "index", "logout"}, call("action_logout"), i18n("logout"))    
  25. 40  end    
  26. 41      
  27. 42  function action_logout()    
  28. 43      luci.http.header("Set-Cookie", "sysauth=; path=/")    
  29. 44      luci.http.redirect(luci.dispatcher.build_url())    
  30. 45  end    

 

 

這個文件定義了node ,最外面的節點,最上層菜單的顯示等等。在其他的 lua 文件里,定義了其他菜單的顯示和html 以及業務處理路徑。每個文件對應一個菜單相。

例如 system.lua 文件

  1.  function index()    
  2. 19      luci.i18n.loadc("admin-core")    
  3. 20      local i18n = luci.i18n.translate    
  4. 21      
  5. 22      entry({"mini", "system"}, alias("mini", "system", "index"), i18n("system"), 40).index = true    
  6. 23      entry({"mini", "system", "index"}, cbi("mini/system", {autoapply=true}), i18n("general"), 1)    
  7. 24      entry({"mini", "system", "passwd"}, form("mini/passwd"), i18n("a_s_changepw"), 10)    
  8. 25      entry({"mini", "system", "backup"}, call("action_backup"), i18n("a_s_backup"), 80)    
  9. 26      entry({"mini", "system", "upgrade"}, call("action_upgrade"), i18n("a_s_flash"), 90)    
  10. 27      entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100)    
  11. 28  end    

 

mudel是對應文件的, function index 定義了菜單,比如這一句entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100)

第1 項為菜單入口:

{"mini", "system", "reboot"}, mini 是最上層的菜單,即為用戶項, system 為一個具體的菜單, reboot 為這個菜單的子菜單,如果 reboot 還需要加上子菜單的話,可以這樣寫:

entry({"mini", "system", "reboot", "chreboot"}, call("action_chreboot"), i18n("chreboot"), 1), 這樣就會在reboot 上產生一個新的子菜單,以此類推,可以產生 N 層菜單。

第二項為菜單對應的頁面,可以是lua 的源代碼文件,也可以是 html 頁面。

alias cgi form call 等定義了此菜單相應的處理方式, form 和 cgi 對應到 model/cbi 相應的目錄下面,那里面是對應的定制好的 html 和 lua 業務處理。

alias是等同於別的鏈接, call 調用了相應的 action_function 。還有一種調用,是 template ,是直接鏈接到view 相應目錄下面的 htm 頁面。(說明: luci 框架下面的 htm 都是可以嵌入 lua 語句的,做業務處理,相當於 jsp 頁面的內部的 Java 語句)。

問價查找對應簡介:

entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100)  :對應此文件的action_reboot function

entry({"mini", "system", "index"}, cbi("mini/system", {autoapply=true}), i18n("general"), 1):對應*/model/cbi/mini/system.lua  {autoapply=true}   這個失傳的參數。

。。。。。

第三項為i18n 顯示,比如entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100),菜單的名字為admin-core 文件內的對應顯示。此處也可以這樣寫,  i18n("reboot"," 重啟 ") ,即直接做了國際化。菜單上顯示的就是“重啟”。

第四項為現實的順序,這個數字越小,顯示越靠前,靠上。

 

 

現在說一下這些文件的解析是怎么解析的呢?你當然是說dispatch.lua中,你說對了,但是真正解析成菜單的遞歸算法確實在header.htm中 位置:*/view/themes/openwrt/
代碼如下:
  1.  <%    
  2. require("luci.sys")    
  3. local load1, load5, load15 = luci.sys.loadavg()    
  4. local request  = require("luci.dispatcher").context.path    
  5. local category = request[1]    
  6. local tree     = luci.dispatcher.node()    
  7. local cattree  = category and luci.dispatcher.node(category)    
  8. local node     = luci.dispatcher.context.dispatched    
  9. local hostname = luci.sys.hostname()    
  10. local c = tree    
  11. for i,r in ipairs(request) do    
  12.      if c.nodes and c.nodes[r] then    
  13.           c = c.nodes[r]    
  14.           c._menu_selected = true    
  15.      end    
  16. end    
  17. require("luci.i18n").loadc("default")    
  18. require("luci.http").prepare_content("application/xhtml+xml")    
  19. -%>    
  20. <?xml version="1.0" encoding="utf-8"?>    
  21. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">    
  22. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%=luci.i18n.context.lang%>" lang="<%=luci.i18n.context.lang%>">    
  23. <head>    
  24. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
  25. <meta http-equiv="Content-Script-Type" content="text/javascript" />    
  26. <link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />    
  27. <!--[if lt IE 7]><link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/ie6.css" /><![endif]-->    
  28. <!--[if IE 7]><link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/ie7.css" /><![endif]-->    
  29. <% if node and node.css then %><link rel="stylesheet" type="text/css" media="screen" href="<%=resource%>/<%=node.css%>" />    
  30. <% end -%>    
  31. <mce:script type="text/javascript" src="<%=resource%><!--    
  32. /VarType.js">    
  33. // --></mce:script>    
  34. <mce:script type="text/javascript" src="<%=resource%><!--    
  35. /XHTML1.js">    
  36. // --></mce:script>    
  37. <mce:script type="text/javascript" src="<%=resource%><!--    
  38. /Dropdowns.js">    
  39. // --></mce:script>    
  40. <title><%=striptags( hostname .. ( (node and node.title) and ' - ' .. node.title or '')) %> - LuCI</title>    
  41. </head>    
  42. <body class="lang_<%=luci.i18n.context.lang%>">    
  43. <class="skiplink">    
  44. <span id="skiplink1"><href="#navigation" mce_href="#navigation"><%:skiplink1 Skip to navigation%></a></span>    
  45. <span id="skiplink2"><href="#content" mce_href="#content"><%:skiplink2 Skip to content%></a></span>    
  46. </p>    
  47. <div id="header">    
  48. <h1><%=luci.version.distname%></h1>    
  49. <p>    
  50. <%=luci.version.distversion%><br />    
  51. <%:load%>: <%="%.2f" % load1%<%="%.2f" % load5%<%="%.2f" % load15%><br />    
  52. <%:hostname%>: <%=hostname%>    
  53. </p>    
  54. </div>    
  55. <div id="menubar">    
  56. <h2 class="navigation"><id="navigation" name="navigation"><%:navigation Navigation%></a></h2>    
  57. <ul id="mainmenu" class="dropdowns">    
  58. <%-    
  59. local function submenu(prefix, node)    
  60.      if not node.nodes or node.hidden then    
  61.           return false    
  62.      end    
  63.      local index = {}    
  64.      local count = 0    
  65.      for k, n in pairs(node.nodes) do    
  66.           if n.title and n.target then    
  67.                table.insert(index, {name=k, order=n.order or 100})    
  68.                count = count + 1    
  69.           end    
  70.      end    
  71.      table.sort(index, function(a, b) return a.order b.order end)    
  72.      if count > 0 then    
  73. %>    
  74. <ul id="submenu_<%=string.gsub(string.gsub(prefix, "/", "_"), "^_(.-)_$", "%1")%>">    
  75. <%-    
  76.           for j, v in pairs(index) do    
  77.                if #v.name > 0 then    
  78.                     local nnode = node.nodes[v.name]    
  79.                     local href = controller .. prefix .. v.name .. "/"    
  80.                     href = (nnode.query) and href .. luci.http.build_querystring(nnode.query) or href    
  81.                         
  82.                     if nnode.nodes then    
  83.                          for k1, n1 in pairs(nnode.nodes) do    
  84.                               href = "#"    
  85.                              
  86.                          end    
  87.                     end         
  88. %>    
  89. <li><a<% if nnode._menu_selected then %class="active"<%end%href="<%=luci.util.pcdata(href)%>"><%=nnode.title%></a><%-    
  90. submenu(prefix .. v.name .. "/", nnode)    
  91. %></li>    
  92. <%-    
  93.                end    
  94.           end    
  95. %>    
  96. </ul>    
  97. <%    
  98.      end    
  99. end    

 

3: model 業務處理和頁面生成簡介

    我認為model 的業務處理和 html 生成,是 luci 框架的精華,但是想用好它,最終擴展定義自己的頁面也是最難的,但是一旦定義好了,后面的工作就會輕松高效簡介統一,不失為一種好的解決方案。但是它又有缺點,就是寫頁面雖然統一,但是不夠靈活。

下面以 SimpleForm為例,講解一下。

具體文件 */luci/model/cbi/passwd.lua

f = SimpleForm("password", translate("a_s_changepw"), translate("a_s_changepw1"))  --調用SimpleForm 頁面  當然還是 I18N 從中搗亂,看上去沒那么直觀,不理他 
pw1=f:field(Value,"pw1",translate("password")) --  

SimpleForm 里面加一個 field   至於 SimpleForm  和 fiemd 是什么,一會去看 SimpleForm 頁面去 
pw1.rmempty=false -- 把 SimpleForm的 rmempty 為不顯示  后面就不做注釋了 應該看得懂了 

 

  1. pw2 = f:field(Value, "pw2", translate("confirmation"))   
  2. pw2.rmempty = false   
  3. function pw2.validate(self, value, section)   
  4.      return pw1:formvalue(section) == value and value   
  5. end   
  6. function f.handle(self, state, data)   
  7.      if   state == FORM_VALID   then     --這個就是業務處理了  你懂得  呵呵   
  8.           local stat = luci.sys.user.setpasswd("admin", data.pw1) == 0  -- root --> admin       
  9.           if stat then   
  10.                f.message = translate("a_s_changepw_changed")   
  11.           else   
  12.                f.errmessage = translate("unknownerror")   
  13.           end   
  14.             
  15.           data.pw1 = nil   
  16.           data.pw2 = nil   
  17.      end   
  18.      return true   
  19. end   
  20. return f  

 

 

說明:( simpleForm  位於 view/cbi   下面,可以研究一下,各個元素是如何定義的 )

現在在給一個小例子:

以.*/luci/model /cbi/admin_system/version_manage.lua 為例,介紹一下 luci 中 web 頁面 lua 代碼 

  1.   6 local h = loadfile("/usr/local/luci/help.lua")   
  2.   7 if h then   
  3.   8     h()   
  4.   9 end   
  5. 10 local help_txt = help_info and  help_info.version   


加載幫助幫助文件help.lua, 關於 loadfile() 的用法可以查看 lua 的手冊 ( 我還沒完全弄明白,先用了 ) 
help_txt 是一個全局變量 

12 appadmin_path = "/usr/local/appadmin/bin/" 


定義一個全局變量,其實跟功能跟宏一樣,定義appadmin 的絕對路徑 

  1. 14 versionlist = {}   
  2. 15   
  3. 16 function getline (s)   
  4. .........   
  5. 32 end   
  6. 33   
  7. 34 function get_versionlist()   
  8. .........   
  9. 68 end   
  10. 69   
  11. 70 versionlist = get_versionlist()   


定義一個全局變量和兩個函數,並初始化此變量 



接下來就是和最終展現的Web 頁面直接相關的代碼了,大部分都是對 luci 封裝好的一些 html 控件(代碼)的使用和擴展。 luci  封裝好的 html 控件 

類可以在以下文件查看:./host/usr/lib/lua/luci/cbi.lua 

  1. 71 m = SimpleForm("version", translate("版本管理 "))   
  2. 72 m.submit = false   
  3. 73 m.reset = false   
  4. 74 m.help = help_txt and true or false   
  5. 75 m.helptxt = help_txt or ""   


使用了一個SimpleForm 的控件, SimpleForm 實際上對應一個 html 表單,是整個頁面最大的 " 容器 " ,本頁面內的絕大部分控件都處於 SimpleForm 內 

,是它的子控件 。我知道的可以做> 頁面最大 " 容器 " 的控件還有一個 Map, 但它需要 ./host/etc/config/ 目錄下的一個配置文件,我沒有使用。 
submit reset是 luci 默認就封裝好的一個屬性,分別控制 html 表單的 " 提交 "" 重置 " 按鈕 ;help helptxt 是我擴充的表單屬性,分別控制 web 頁面的 

"幫助 " 功能和幫助內容。關於控件屬 
性的意義、實現和擴充可以按以下步驟進行: 


    在文件./host/usr/lib/lua/luci/cbi.lua 中查找控件名 SimpleForm,  然后可以找到以下行 664     self.template = "cbi/simpleform" 這 

表明SimpleForm 的 html 模版文件為 ./host/usr/lib/lua/luci/view/cbi /simpleform.htm ,通過研究 simpleform.htm 文件內容可以知道各屬性的 

功能以及模版中使用lua 代碼的方法,然后可以按類似的方法添加自定義的 
屬性。 
77 s = m:section(Table, versionlist) 
新建了一個section,section 內定義了一個表格類, versionlist 是與其相關的變量( lua 的所有變量都可歸類於 table 類型 ) 
與Table 關聯的 table 變量應該是這種結構的: 

  1. t = {   
  2.     row1 = {column1 = "xxx", column2 = "xxx", .... },   
  3.     row2 = {column1 = "xxx", column2 = "xxx", .... },   
  4.     row3 = {column1 = "xxx", column2 = "xxx", .... },   
  5.     row4 = {column1 = "xxx", column2 = "xxx", .... },   
  6. }   


然后定義Table 的列控件 

  1. 79 enable = s:option(DummyValue, "_enabled", translate("軟件狀態 "))   
  2. 83 appid  = s:option(DummyValue, "_appid", translate("軟件版本 "))   
  3. 84 appname = s:option(DummyValue, "_appname", translate("軟件名稱 "))   


DummyValue是只讀的文本框,只輸出不輸入。 Value 是單行文本框,可輸出也可輸入。 Flag 是一個 checkbox,值為 "1" 時被選中,為 "0" 時未選中。 

ListValue是列表框 ... 具體的用法可 
以看./host/usr/lib/lua/luci /model/cbi/ 下的文件( find ./host/usr/lib/lua/luci/model/cbi/ -name "*.lua" |xargs grep 

"ListValue") 


對於table 內普通的字符串類的值,只需要把列控件的 id (括號內第二個值,如 "_appid" )定義為 table 內對應的變量名(比如 column1 ) 
對於非變通字符串類的值,或者為字符串但需要進行一定的處理然后再顯示的值,可以按以下方法顯示:定義該控件的cfgvalue 函數屬性 

  1. 127     newinfo = up_s:option(TextValue, "_newifo", translate("新版本信息 "))   
  2. 128     newinfo.readonly = true   
  3. 129     newinfo.rows = 11   
  4. 130     newinfo.cfgvalue = function(self, section)   
  5. 131         local t = string.gsub(info, "Archive:[^/n]*", "")   
  6. 132         return t   
  7. 133     end   


定義cfgvalue 后, luci 的處理函數會調用此函數為此控件賦值,(傳入的 section 參數值為 row1/row2/row3等,當處理到 row 幾時值就為 row 幾 ) 
對於DummyValue 等只輸出不輸入的類,還有一種賦值方法: 控件實例名(如 enable).value = xxx 
對於有輸入的控件Value 等,  .value 方法賦值在處理輸入里會有一些問題,有什么問題以及如何解決可以做實驗試試  , 也許是我使用方法不對造 
成的 


對有輸入控件的處理有兩種方法: 
1 定義控件的 .write 屬性 
    這種方法對處理比較獨立的輸入(與其它控件輸入關系不大)比較適用 

  1. 88 up_s = m:section(SimpleSection)   
  2. 89 up_version = up_s:option(Button, "_up_version", translate("上傳新版本 "))   
  3. 90 up_version.onlybutton = true   
  4. 91 up_version.align = "right"   
  5. 92 up_version.inputstyle = "save"   
  6. 93 up_version.write = function(self, section)   
  7. 94     luci.http.redirect(luci.dispatcher.build_url("admin", "system", "version_manage", "upload"))  
  8. 95 end   


ps:只有當 Value 的 rmempty == false 時, Value 輸入為空也會觸發 write 函數 ,  需要對 rmemtpy 顯示賦值為false ( xx.rmempty = false) 

4: view 下面的 html 簡介

這個是最好理解的  例:passwd.htm

    1.  <%+header%>  
    2.   
    3. <h2><id="content" name="content"><%:system%></a></h2>  
    4.   
    5. <h3><%:reboot%></h3>  
    6.   
    7. <p><%:a_s_reboot1%></p>  
    8.   
    9. <%-  
    10.   
    11. local c = require("luci.model.uci").cursor():changes()  
    12.   
    13. if c and next(c) then  
    14.   
    15. -%>  
    16.   
    17.        <class="warning"><%:a_s_reboot_u%></p>  
    18.   
    19. <%-  
    20.   
    21. end  
    22.   
    23. if not reboot then  
    24.   
    25. -%>  
    26.   
    27. <p><href="<%=controller%>/admin/system/reboot?reboot=1"><%:a_s_reboot_do%></a></p>  
    28.   
    29. <%- else -%>  
    30.   
    31. <p><%:a_s_reboot_running%></p>  
    32.   
    33. <script type="text/javascript">setTimeout("location='<%=controller%>/admin'", 60000)</script>  
    34.   
    35. <%- end -%>  
    36.   
    37. <%+footer%>  
    38.   
    39.   
    40. <%+header%<%+footer%>  加載公用的頭部和尾部  
    41.   
    42. <% lua code%>  
    43.   
    44. <%:i18n%>  
    45.   
    46. <%lua code%>  
    47.   
    48. <%=lua 變量 %>  


免責聲明!

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



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