随笔-118  评论-133  文章-4  trackbacks-0

页面请求:

1、Openwrt的webserver为uhttpd,根目录在/etc/config/uhttpd文件中指定为www,主页面为/www/index.html:

2、index.html中指定cgi程序启动脚本为/cgi-bin/luci
3、/cgi-bin/luci脚本,指定缓存路径为/tmp/luci-indexcache,指定cgi启动接口为/usr/lib/lua/luci/sgi/cgi.lua的run()函数
注:系统每次启动后,会扫描usr/lib/lua/luci/controller/目录下文件以构建index tree,可以rm -rf /tmp/luci* 来删除luci的备份文件,这样可以清除缓存,执行修改后的操作。

cgi启动流程

run()函数作为CGI程序的启动入口,代码解析如下
function run()
    
-- 获取web请求,放于变量r中(包括环境变量,请求数据,出错处理接口)
    
local r = luci.http.Request(
        luci.sys.getenv(),
        limitsource(io.stdin, tonumber(luci.sys.getenv(
"CONTENT_LENGTH"))),
        ltn12.sink.file(io.stderr)
    )
    
--创建一个协同程序,实现函数为httpdispatch
    
local x = coroutine.create(luci.dispatcher.httpdispatch)
    
local hcache = ""
    
local active = true
    
--主循环中查看协同程序x的协同状态
    
while coroutine.status(x) ~= "dead" do
        
local res, id, data1, data2 = coroutine.resume(x, r)
 
        
if not res then
            print(
"Status: 500 Internal Server Error")
            print(
"Content-Type: text/plain\n")
            print(id)
            break;
        
end
        
-- HTTP的响应报文通过io.write()方式写在stdout上,再由uhttpd架构将这些数据传递给父进程,通过tcp连接返回给client端。
        
if active then
            
if id == 1 then
                
-- 填写HTTP响应状态行
                io.write(
"Status: " .. tostring(data1) .. " " .. data2 .. "\r\n")
            
elseif id == 2 then
                
-- 准备报文头header
                hcache 
= hcache .. data1 .. "" .. data2 .. "\r\n"
            
elseif id == 3 then
                
-- 填写header、blank到stdout上
                io.write(hcache)
                io.write(
"\r\n")
            
elseif id == 4 then
                
-- 填写body到stdout上
                io.write(tostring(data1 
or ""))
            
elseif id == 5 then
                
-- 关闭io接口,EOF
                io.flush()
                io.close()
                active 
= false
            
elseif id == 6 then
                data1:copyz(nixio.stdout, data2)
                data1:close()
            
end
        
end
    
end
end

dispatcher启动流程

在上述run()函数中,创建了一个协同程序,调用httpdispatch()函数,而这个函数位于dispatcher.lua中。通过后续的介绍也可以发现,luci真正的主体部分都在dispatcher.lua脚本里,本小节主要对httpdispatch()和dispatch()函数进行介绍。
注:在版本机dispatcher.lua的同目录下有一个dispatcher.luadoc文件,里面介绍dispatcher.lua的参数说明,仅供参考。
1、httpdispatch():解析请求,获得HTTP request请求参数
[/usr/lib/lua/luci/dispatch.lua]
function httpdispatch(request, prefix)
    http.context.request 
= request
 
    
local r = {}
    context.request 
= r
   
--解析HTTP request,从环境变量PATH_INFO中获取请求路径,并传入dispatch()函数进行处理
    
local pathinfo = http.urldecode(request:getenv("PATH_INFO"or ""true)
 
    
if prefix then
        
for _, node in ipairs(prefix) do
            r[#r
+1= node
        
end
    
end
    --利用模式匹配(匹配'/'和'\0'以外的字符串)来分割pathinfo,比如/admin/network/wireless_assoclist会被分割为3个元素(admin  network  wireless_assoclist)
    
local node
    
for node in pathinfo:gmatch("[^/%z]+"do     
        r[#r
+1= node
    
end
 
    determine_request_language()
 
    
local stat, err = util.coxpcall(function()
        dispatch(context.request)
end, error500)
 
    http.close()
 
    
--context._disable_memtrace()
end
2、dispatch():解析请求节点,调度网页显示,分为以下几部分
(1)创建节点树node-tree,解析请求路径,获取节点树节点
createtree()函数主要从controller目录下寻找.lua文件,并且调用每个lua文件中的index()函数。这些index函数通过entry(path,target,title,order)函数定义了菜单栏的每个子菜单选项,包括子菜单的节点位置path、调度行为target、页面标题title以及节点顺序order。当解析完index()下的node节点,对应生成一个node-tree。
-- Build the index before if it does not exist yet.
function createtree()
    
if not index then
        createindex()
    
end
 
    
local ctx  = context
    
local tree = {nodes={}, inreq=true}
 
    ctx.treecache 
= setmetatable({}, {__mode="v"})
    ctx.tree 
= tree
 
    
local scope = setmetatable({}, {__index = luci.dispatcher})
 
    
for k, v in pairs(index) do
        scope._NAME 
= k
        setfenv(v, scope)
        v()
    
end
 
    return tree
end
function createindex()
    
local controllers = { }
    
local base = "%s/controller/" % util.libpath()  --搜索/usr/lib/lua/luci/controller/目录下*.lua文件
    
local _, path

    
for path in (fs.glob("%s*.lua" % base) or function() enddo
        controllers[#controllers
+1= path
    
end

    
for path in (fs.glob("%s*/*.lua" % base) or function() enddo
        controllers[#controllers
+1= path
    
end

    
if indexcache then
        
local cachedate = fs.stat(indexcache, "mtime")
        
if cachedate then
            
local realdate = 0
            
for _, obj in ipairs(controllers) do
                
local omtime = fs.stat(obj, "mtime")
                realdate 
= (omtime and omtime > realdate) and omtime or realdate
            
end

            
if cachedate > realdate and sys.process.info("uid"== 0 then
                assert(
                    sys.process.info(
"uid"== fs.stat(indexcache, "uid")
                    
and fs.stat(indexcache, "modestr"== "rw-------",
                    
"Fatal: Indexcache is not sane!"
                )

                index 
= loadfile(indexcache)()
                return index
            
end
        
end
    
end

    index 
= {}

    
for _, path in ipairs(controllers) do
        
local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/"".")
        
local mod = require(modname)  --加载module
        assert(
mod ~= true,
               
"Invalid controller file found\n" ..
               
"The file '" .. path .. "' contains an invalid module line.\n" ..
               
"Please verify whether the module name is set to '" .. modname ..
               
"' - It must correspond to the file path!")

        
local idx = mod.index   --寻找module里面的index函数
        assert(type(idx) 
== "function",
               
"Invalid controller file found\n" ..
               
"The file '" .. path .. "' contains no index() function.\n" ..
               
"Please make sure that the controller contains a valid " ..
               
"index function and verify the spelling!")

        index[modname] 
= idx
    
end

    
if indexcache then
        
local f = nixio.open(indexcache, "w"600)
        f:writeall(util.get_bytecode(index))  --写入indexcache
        f:close()
    
end
end
(2)解释出请求和参数
    for i, s in ipairs(request) do
        preq[#preq
+1= s
        freq[#freq
+1= s
        c 
= c.nodes[s]
        n 
= i --n用来记录请求路径的index
        
if not c then
            break
        
end

        util.update(track, c) 
--记录到track中

        
if c.leaf then
            
--找到设置了leaf属性的node就break
            break
        
end
    
end

    
if c and c.leaf then
        
for j=n+1, #request do --从n开始的就是args
            args[#args
+1= request[j]
            freq[#freq
+1= request[j]
        
end
    
end
注:为什么要区分请求和参数?比如controller下有的entry条目用到了arcombine,而arcombine通过调用的argv来区分具体调用哪个target:
local function _arcombine(self, )
    
local argv = {}
    
local target = #argv > 0 and self.targets[2or self.targets[1]
    setfenv(target.target, self.env)
    target:target(unpack(argv))
end

function arcombine(trg1, trg2)
    return {type 
= "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
end
(3)根据target的type分别进行处理,每个controller下的lua文件的index函数会生成页面的菜单栏并定义各个页面的调用方法
    local target = nil
    
if c then
        
if type(c.target) == "function" then
            target 
= c.target
        
elseif type(c.target) == "table" then
            target 
= c.target.target
        
end
    
end

    
if c and (c.index or type(target) == "function"then
        ctx.dispatched 
= c
        ctx.requested 
= ctx.requested or ctx.dispatched
    
end

    
if c and c.index then
        
local tpl = require "luci.template"

        
if util.copcall(tpl.render, "indexer", {}) then
            return 
true
        
end
    
end
    
if type(target) == "function" then
        util.copcall(
function()
            
local oldenv = getfenv(target)
            
local module = require(c.module)
            
local env = setmetatable({}, {__index=

            
function(tbl, key)
                return rawget(tbl, key) 
or module[key] or oldenv[key]
            
end})

            setfenv(target, env)
        
end)

        
local ok, err
        
if type(c.target) == "table" then
            ok, err 
= util.copcall(target, c.target, unpack(args)) --比如:arcombine,根据传递的args选择最终的target
        
else
            ok, err 
= util.copcall(target, unpack(args))
        
end
        
if not ok then
            error500(
"Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") ..
                     
" dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" ..
                     
"The called action terminated with an exception:\n" .. tostring(err or "(unknown)"))
        
end
    
else
        
local root = node()
        
if not root or not root.target then
            error404(
"No root node was registered, this usually happens if no module was installed.\n" ..
                     
"Install luci-mod-admin-full and retry. " ..
                     
"If the module is already installed, try removing the /tmp/luci-indexcache file.")
        
else
            error404(
"No page is registered at '/" .. table.concat(request, "/") .. "'.\n" ..
                     
"If this url belongs to an extension, make sure it is properly installed.\n" ..
                     
"If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
        
end
    
end

参考资料:
1、luci实现框架:https://www.cnblogs.com/zmkeil/archive/2013/05/14/3078774.html
2、luci启动流程:https://www.cnblogs.com/yuanqiangfei/p/14664450.html
3、理解Lua中的模式匹配:https://juejin.cn/post/6874512568913199117
4、OpenWrt中的LuCi和Lua一些总结:https://blog.51cto.com/u_15127517/4255759
5、学习luci最好的方法是调试:https://blog.csdn.net/laoxiao1987/article/details/83241363

posted on 2022-08-18 15:08 lfc 阅读(731) 评论(0)  编辑 收藏 引用
只有注册用户登录后才能发表评论。