页面请求:
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() end) do
controllers[#controllers+1] = path
end
for path in (fs.glob("%s*/*.lua" % base) or function() end) do
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[2] or 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
参考资料:
posted on 2022-08-18 15:08
lfc 阅读(731)
评论(0) 编辑 收藏 引用