关于luci的几个问题一
最近,由于項目的原因,現在總結幾點:
1.luci運行的流程?
答:
首先,我們從/www/cgi-bin/文件開始,運行luci文件中代碼:
#!/usr/bin/lua luci.dispatcher.indexcache = “tmp.luci-indexcache” luci.sgi.cgi.run()接著進入到sgi/的cgi.lua文件中,一個函數是limitsource(handle,?limit),主要是將limit個數的字符從stdin里面。
另一個函數是run()后面的頁面生成將一直在這個函數進行。
run()Local x = coroutine.create(luci.dispatcher.httpdispatcher)While coroutine.status(x) ~= “dead” do//運行上面創建的協同程序,即運行httpdispatcher,參數為上面local rLocal res. Id, data1, data2 = coroutine.resume(x, r)if active thenIf id == 1 then -- http.response-lineIf id == 2 then --準備headerIf id == 3 then --寫header、blank,默認到stdoutIf id == 4 then --寫bodyIf id == 5 then --EOFIf id == 6 then EndEnd End然后,進入httpdispatcher(request,?prefix)????//參數都放在context里
<pre name="code" class="html"> <pre name="code" class="html">Local pathinfo = http.urldecode(request:getenv(“PATH_INFO”) or “”, true)For _,node in ipairs(prefix) do R[#r+1] = node --將node賦給r{}EndLocal stat, err = util.coxpcall(function() Dispatch(context.request)End, error500) 接著,進入 dispatch(request) 函數中,A.設置語言B.創建node-tree節點樹結構 Local c = ctx.tree Local stat If not c then C = createtree() //此函數從controller下的index函數來創建node-tree結構 文件 End B. createtree()函數If not index then Createindex() //此函數定義了path、suff,判斷條件然后進入不同分支, //Createindex_fastindex(path, suff)、createindex_plain(path, suff) EndLocal track = {} -- 每一層把找到的node信息放在這個track()中 For i, s in ipairs(request) do Util.update(track, c) -- update(t, updates) t要更新的表,updates包含需要更新的值得表。C.需要顯示的部分 If (c and c.index) or not track.template then 初始化模板 定義了tpl.context.viewns = setmetatable() D.認證 If track.sysauth then Local sauth = require “luci.sauth” //將getcookie和sysauth屬性賦給sess If not sess then Sess = luci.http.getcookie(“sysauth”) Sess = sess and sess:match(“^[a-f0-9]*$”) Verifytoken = trueEnd//讀取session值返回它的contentLocal sdat = sauth.read(sess) If sdat then Else Local eu = http.getenv(“HTTP_AUTH_USER”) Local ep = http.getenv(“HTTP_AUTH_PASS”)End F.顯示/處理 c是createtree()的返回值tree,根據不同類型不同處理。 If c then If type(c.target) ==”function” then target = c.target Elseif type(c.target) == “table” then target = c.target.target End End 2.關于root和mini登錄的問題?
答:
Root=node()調用dispatcher.lua中node(...)函數,node(...)調用_create_node(path)函數,定義了最外面的節點,也就是最上層的菜單顯示。
If not root.target then Root.target = alias(“admin”) //調用dispatcher.lua中alias(...)函數,重定向到另外一個節點,在函數將admin節點繼續給了req,然后dispatch(req) Root.index = true End Local page = node(“admin”) //將admin.寫到context.treecache[name]中 Page.target = firstchild() //調用dispatcher.lua中firstchild()函數,return {type = “firstchild”, target = _firstchild},然后調用_firstchild(),將最低順序的node給dispatch()函數執行。 Page.title = _(“Administration”) //標題 Page.order = 10 //順序 Page.sysauth = “root” //認證用戶的登錄 Page.sysauth_authenticator = “htmlauth” //調用dispatcher.lua中htmlauth()函數,檢測登錄的合法性。 Page.ucidata = true Page.index = true方法:將admin文件夾拷貝一份給mini,將mini中index.lua中將page.sysauth?=?“root”和page.sysauth_authenticator?=?“htmlauth”注釋掉,那么在彈出的頁面上點擊User按鈕,會直接進入到系統中。
3.對于entry()函數的分析?答:
3.1?entry(path,?target,?title,?order)??例如:
Entry({“admin”,?“system”},?alias(“admin”,?“system”,?“system”),?_(“System”),?30).index?=?true
?
Path:虛擬路徑
Target:目標函數調用
Title:顯示在頁面上的標題
Order:在同一級別下,node的順序。(可選)
?
Local?c?=?node(unpack(path))????//unpack返回path中的所有值,并傳給node做參數,然后調用node兩次,將node節點創建出來
//將參數分別傳進去
C.target?=?target???//將值傳進去后,剛開始構造node-tree的時候,alias(..)函數會在entry(...)函數之前調用,然后alias()函數中調用dispatch(req)。
C.title?=?title
C.order?=?order
C.module?=?getfenv(2).__NAME
3.2.?Entry({“admin”,?“system”,?“system”},?cbi(“admin_system/system”),?_(“System”),?1)
首先,cbi()函數先執行,return?{type?=?“cbi”,?config?=?config,?model?=?model,?target?=?_cbi}這句話,開始的時候,_cbi函數不會被執行,只有到了dispatch()函數里面才可以執行。
Cbi函數返回四個值,type,config,model,target
當點擊與cbi有關的request的時候,在dispatch()函數的顯示/處理部分,有個if判斷,
if c thenIf type(c.target) == “function” then //當c.target類型為函數時候Target = c.targetElseif type(c.target) == “table” then //當c.target類型為table時候Target = c.target.target //也就是把表中屬性target = “_cbi”給了target,進行后續處理。EndEnd在_cbi(self,?...)函數中,
Local cbi = require “luci.cbi” Local tpl = require “luci.template” Local http = require “luci.http”Local config = self.config or {} //將cbi()返回的第二個參數給config Local maps = cbi.load(self.model, ...) //將第三個參數給load(),然后給maps 在cbi.lua文件中,model其實就是路徑,load路徑接下來是一個for循環,每一個node首先需要map畫出框架,然后一層一層的畫控件。
For i, res in ipairs(maps) doRes.flow = config //map.flowLocal cstate = res:parse() //調用Map.parse(self, readinput,...) 調用Node.parse(self, ...),(uci主要是與luci進行數據交互的平臺。) Function Map.parse(self, readinput, ...)Formvalue(“cbi.skip”) Node.parse(self, ...)If self.save then // 如果map的保存按鈕被點擊,或者其他按鈕被點擊,都會觸發uci里面的函數,來處理相關操作。 //比如:on_save, on_before_save,on_after_save, on_before_commit, on_after_commit,on_before_apply之類If self:submitstate() then //如果map的提交按鈕被點擊 EndMap = class(Node) //Map是Node的子類,那么map.parse會執行Node.parse()方法,function Node.parse(self, ...)For k, child in ipairs(self.children) doChild:parse()EndEnd在此函數執行之前,調用Node.__init__(self,?title,?description),self.children?=?{}
Local?class=?util.class???class()函數return?setmetatable({},?{__call?=?_instantiate,__index?=?base)}??當調用的時候,調用_instantiate(class,?...),函數中調用inst:__init__(...)函數初始化具體的類,返回類。
Map = class(Node) Function Map.__init__() Function Map.fromvalue(self, key) Function Map.formvaluetable(self, key) Function Map.get_scheme() Function Map.submitstate(self) Functoin Map.chain() Function Map.state_handler() Function Map.parse() Function Map.render() Function Map.section(self, class,...) Function Map.add(self, sectiontype) Function Map.set(self, section,...) Function Map.del() Function Map.get() <pre name="code" class="html"> 3.3 m:chain(“luci”) //向map中插入外部的config信息 S = m:section(TypedSection,””, “”) //Map:section創建了一個子section,其中如果是abstraction的實例,那么調用Node:append()中table.insert(self.children, obj)語句。S是類TypedSection產生的實例。 S.addremove = false //當執行TypedSection()函數的時候就會判斷這個和下面的選項 S.anonymous = true S.tab = s:tab("general", translate("General Settings")) //定義一個tab給s,調用abstractSection : tab(tab,title,desc)函數,其中self.tab_names[#self.tab_names+1] = tabself.tabs[tab] = {title = title,description = desc,childs = { }} 將將tab給了tab_names,tab的各個參數給了tabs數組。 o = s:taboption("general", DummyValue, "_systime", translate("Local Time")) //調用AbstractSection:taboptoin(),然后調用AbstractSection.option(self, ...), If instanceof(class, AbstractValue) then //如果DummyValue是AbstractSection的實例local obj = class(self.map, self, option, ...) //實例化DummyValue類self:append(obj) //返回的obj追加給AbstractSectionself.fields[option] = obj //將對象賦給fields[option]return obj //返回obj End o.template = "admin_system/clock_status" //DummyValue實例后的對象o調用template function DummyValue.__init__(self, ...)AbstractValue.__init__(self, ...)self.template = "cbi/dvalue" //這句話o.template調用template/cbi/dvalue.htm文件self.value = nil end o = s:taboption("general", Value, "hostname", translate("Hostname")) //Value類實例化的實例給了o o.datatype = "hostname" //o連接到cbi文件夾下,datatype.lua文件function o.write(self, section, value)Value.write(self, section, value) //調用AbstractValue.write()方法,調用uci:set()寫到config文件中luci.sys.hostname(value) //獲得或者更改當前的hostname End 3.4 entry({"admin", "services"}, firstchild(), _("Services"), 40).index = true 調用dispatcher.lua 中的Firstchild(), function firstchild()return { type = "firstchild", target = _firstchild } //當后期顯示部分執行dispatch()時候,調用_firstchild()函數, if node and node.nodes and next(node.nodes) thenlocal k, vfor k, v in pairs(node.nodes) doif not lowest or(v.order or 100) < (node.nodes[lowest].order or 100)thenlowest = kendendEndpath[#path+1] = lowest --將多出來的節點追加給pathdispatch(path) -- 最關鍵的一句代碼,調用dispatch(path),path已經改變 3.5 entry({"admin", "logout"}, call("action_logout"), _("Logout"), 90) 調用dispatcher.lua中的call(), function call(name, ...)return {type = "call", argv = {...}, name = name, target = _call} //當后期顯示部分執行dispatch()時候,調用_call()函數, local function _call(self, ...)local func = getfenv()[self.name] //獲取當前函數所在文件夾路徑if #self.argv > 0 thenreturn func(unpack(self.argv), ...) elsereturn func(...) //調用當前函數所在路徑下的對應函數end End 3.6 entry({“admin”, “system”, “startup”},form(“admin_system/startup”),_(“Startup”), 45) Function form(model)Return {type = “cbi”, model = model, target = _form} End 當執行dispatch()函數時,執行_form()函數, Local maps = luci.cbi.load(self.model, ...) For i, res in ipairs(maps) do Local cstate = res:parse() If cstate and (not state or cstate < state) then State = cstateEnd End Http.header(“X-CBI-State”, state or 0) //context.headers[key:lower()] = value //Coroutine.yield(2, key, value) Tpl.render(“header”) //render(name, scope) return template(name):render(scope or //getenv(2))然后調用Template(name):reader()畫出header.htm For i, res in ipairs(maps) doRes:render() End Tpl.render(“footer”) //畫出footer.htm 3<span style="font-family: 宋體;">.7 entry({“admin”,“network”,“wireless”}, arcombine(template(“admin_network/wifi_overview”), cbi(“admin_network/wifi”)), _(“Wifi”), 15)</span> Arcombine(template, cbi)調用dispatcher.lua文件中function arcombine(trg1, trg2)Return {type = “arcombine”, env = getfenv(), target = _arcombine, targets = {trg1, trg2}}Function _arcombine(self, ...)Local argv = {...}Local target = #argv > 0 and self.targets[2] or self.targets[1] Setfenv(target.target,self.env)Target:target(unpack(argv)) //一個接著一個執行相應的函數, End 3.8 entry({“admin”, “status”, “overview”}, template(“admin_status/index”), _(“Overview”), 1) Template()調用dispatcher.lua文件中template(name)函數, Return {type = “template”, view = name, target = _template} 當執行dispatch()函數的時候,則執行_template = function (self, ...) require “luci.template”.render(self.view) ,畫出admin_status/index.htm4.formvalue怎么處理值?
答:dispatcher.lua中authenticator.htmlauth()函數中,
Local user = luci.http.formvalue(“username”) Local pass = luci.http.formvalue(“password”)這句話是從http.lua文件中調用formvalue()函數獲取值
調用function?formvalue(name,?noparse)
Return?context.request:formvalue(name,?noparse)
End
然后調用Request.formvalue(self,?name,?noparse)
If?name?then?return?self.message.params[name]???
然后返回Request中的message.params[name]
- HTTP-Message table Self.message = {Env = env,Headers = {},Params = protocol.urldecode_params(env.QUERY_STRING or “”), }將最初的“Username”傳到了params這里,然后調用http/protocol.lua文件中的
Function Urldecode_params(url, tbl) Local params = tbl or {} If url.find(“?”) then Url = url:gsub(“^.+%?([^?]+)”, “%1”) //^開頭表示匹配開始部分,+匹配1次或者多次,%?轉義問號,第三個參數表示捕獲第一個匹配字符串。^?表示非問號的部分,.+進行的是最長匹配。 End <span style="color:#3366ff;">//由wireshark分析,Post /cgi-bin/luci HTTP/1.1(application/x-www-form-urlencoded) Url:http://192.168.1.1/cgi-bin/luci?username=root&password=admin Content-length:28form iten :”username” = “root”Key:usernameValue:rootForm item:”password” = “admin”Key: passwordValue:admin 可見:還是處理成key-value對。</span> For pair in url:gmatch(“[^&;]+”) do-- 查找key、valueLocal key = urldecode(pair:match(“^([^=]+)”) )Local val = urldecode(pair:match(“^[^=]+=(.+)$”)) //調用urldecode中pair.match()查找key、val-- 存儲值If type(key) == “string” and key:len() > 0 then //key就是第一句話傳進去的id(username),然后val賦給params[name]If type(val) ~= “string” then val = “” endIf not params[key] then //登錄頁面傳進來的值進入這里面Params[key] = val Elseif type(params[key] ~= “table” thenParams[key] = {params[key], val}ElseTable.insert(params[key],val) EndEnd End Return params End至此,將url中的所需的值就獲得了。然后再進行后續處理。
5.點擊login按鈕后發生了什么?
答:在sysauth.htm中,
<formmthod=”post”?action=”<%=pcdata(luci.http.getenv(“REQUEST_URI”))%>”>
當點擊按鈕時候,就會跳轉到action指定的url.在dispatcher.lua文件dispatch()函數中,
tpl.context.views setmetable({
.......},{__index=function(table,key)If key == “controller” then return build_url() Elseif key == “REQUEST_URI” then return build_url(unpack(ctx.requestpath)) }) 當點擊登錄后,頁面會跳轉到/,接著/admin,如果需要認證,那么接下來會彈出htmlauth.htm頁面,然后如果沒有驗證成功,則繼續本頁面,如果成功了,那么繼續跳轉/,然后admin/,此時post來了信息,然后alias到entry.order最小的那個node,很顯然是/admin/status.lua,然后status這個node會alias到overview這個node。最主要的還是在diapatcher.lua文件中的dispatch()的認證部分。 Apply,apply&save,reset的邏輯跟這個也是一樣的。當點擊登錄后,頁面會跳轉到/,接著/admin,如果需要認證,那么接下來會彈出htmlauth.htm頁面,然后如果沒有驗證成功,則繼續本頁面,如果成功了,那么繼續跳轉/,然后admin/,此時post來了信息,然后alias到entry.order最小的那個node,很顯然是/admin/status.lua,然后status這個node會alias到overview這個node。最主要的還是在diapatcher.lua文件中的dispatch()的認證部分。
Apply,apply&save,reset的邏輯跟這個也是一樣的。
總結
以上是生活随笔為你收集整理的关于luci的几个问题一的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记录工作中第一次解决bug的小事
- 下一篇: 关于luci的几个问题二