luci工作流程,程序调用

1,luci在openwrt上使用的是uhttpd服务器,了解不多,uhttpd会将lua的默认io修改连接到uhttpd,当调用io.write时会传到uhttpd中!所以想写到web上,直接把html代码调用io.write就可以显示!

2,入口在/www文件下,lua的入口程序在sgi/cgi.lua中的run函数,在这里会建立httpdispatch的协程,当协程没dead就会不停地调用!所以实际的函数运作在httpdispatch中!以下是run中的程序,在httpdispatch中调用yeild协程,就会把数据返回到这里,分为6种!id=4,传递HTML代码到uhttpd;id=5,刷新缓冲区;id=1,响应消息比如200OK;id=3,4写消息头字段。

if active then
			if id == 1 then
				io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n")
			elseif id == 2 then
				hcache = hcache .. data1 .. ": " .. data2 .. "\r\n"
			elseif id == 3 then
				io.write(hcache)
				io.write("\r\n")
			elseif id == 4 then
				io.write(tostring(data1 or ""))
			elseif id == 5 then
				io.flush()
				io.close()
				active = false
			elseif id == 6 then
				data1:copyz(nixio.stdout, data2)
				data1:close()
			end
		end

3,接下在进入httpdispatch中,在这里调用dispatch中,这是真正实际运作的程序!

这里要介绍一下util.coxpcall程序,在此之后还会继续出现,当调用这个函数时,会为传递的参数(一个函数)再建立一个协程,当协程中的协程yield时,会递归的把上一级直到dispatch这个协程yield,作用待会在说。

local stat, err = util.coxpcall(function()
		dispatch(context.request)
	end, error500)

4,接下来进入dispatch函数,

第一步,调用createtree函数建立node-tree(结点是非常重要的之后再说)。

c = createtree()

我们进入createtree函数中,在这里先调用createindex函数!

createindex()

这是非常重要的函数,进入createindex函数,这里和之后的一些循环获取到controller下的所有lua文件名。

	local controllers = { }
	local base = "%s/controller/" % util.libpath()

在这个循环中,会将所有文件require进来并且得到这些文件中的index函数,并且存储到index表中。

for _, path in ipairs(controllers) do
local idx = mod.index
index[modname] = idx

接下来再回到createtree函数中,遍历index表,将这里的环境设置给表中的函数,运行所有程序index函数!所以你在controller下按照格式添加文件就能,显示在页面上。

	for k, v in pairs(index) do
		scope._NAME = k
		setfenv(v, scope)
		v()
	end

接下来我们进入controller下的lua文件,controller中的环境设置为dispatch的,所以函数在dispatch文件中寻找。

第二行就是index函数,下面是简略的函数,其中重要的就是entry函数,建立"结点"。

function index()
entry({"admin", "services"}, firstchild(), _("Services"), 40).index = true
entry({"admin", "logout"}, call("action_logout"), _("Logout"), 90)
end

说明一下node-tree,这其实就是luci上面的菜单栏,最高一级就是admin;第二级,就是system、status;然后这些还有更低一级的结点,类似一个从上而下的树结构。访问时,就是一级一级的写,比如admin/system/status。

介绍一下entry函数的参数,第一个就是是node-tree的顺序;第二个是这个结点的函数,不过在entry函数中并不是返回这个函数,而是函数运行后的结果,c.target实际是call("action_logout")运行完的return;第三个是名称,前面加_似乎是为了国际化;第四个是顺序,越小越前。targe主要是call、template、cbi之后介绍。

进入entry函数,建立node,node-tree是一大串的node联系在一起,将参数赋值给node。

local c = node(unpack(path))
	c.target = target

进入node函数,调用createnode,进入createnode中。path参数是{"admin", "services"},这里是使用了递归的写法,不算复杂,都会一直递归到admin结点,逐个生成,并且在nodes中记录子节点。当controller下的所有lua文件被加载,就会生成一个node-tree,父节点的nodes会记录它的子节点。

if not c then
		local last = table.remove(path)
		local parent = _create_node(path)
parent.nodes[last] = c

5,返回到dispatch文件中,这时已经建立了node-tree,我们现在顺着程序往下走,先绕过鉴权等一些程序代码。

request是输入url,根据这个找到相应的nodes。最后将nodes对应的target使用copcall运行。

for i, s in ipairs(request) do
if type(c.target) == "table" then
			ok, err = util.copcall(target, c.target, unpack(args))
		else
			ok, err = util.copcall(target, unpack(args))
		end

6,接下来看一下cbi是怎么运作的,以及页面的生成。target主要是cbi、template、call这些返回的都是table,firstchildred、alias返回的是函数,都是修改request然后再次调用dispatch,可以直接看源码。

调用cbi最后返回的是这样一个table,实际调用的是_cbi(有一个下划线)函数。

return {
		type = "cbi",
		post = { ["cbi.submit"] = true },
		config = config,
		model = model,
		target = _cbi
	}

接下来进入_cbi函数,调用cbi.load函数。参数是entry中cbi中的值,实际上是model下的文件路径。

local maps = cbi.load(self.model, ...)
cbi("admin_system/fstab/mount")

接下来进入load函数,将lua文件loadfile进来,并且运行。

if fs.access(cbidir..cbimap..".lua") then
		func, err = loadfile(cbidir..cbimap..".lua")
	elseif fs.access(cbimap) then
		func, err = loadfile(cbimap)
	else
		func, err = nil, "Model '" .. cbimap .. "' not found!"
	end

在进入model下的lua文件,大致就是调用Map函数,然后这个m在调用section、value等,代表着页面上的一些模块,比如文本框等。这是面对对象的方式实现的,map对应页面、section、value等对应着页面上的框。这样就生成了一整个页面,同时所有的结点继承Node,section是map的子类,value是section的子类(可能不太合适)。这里每一种,都会有其对应的htm文件。

m = Map("system", translate("Router Password"),
	translate("Changes the administrator password for accessing the device"))

返回到_cbi中,会调用一个parse函数,这个函数在cbi中。parse会递归map上的每一个section、value。

似乎是会检查当点击save或其他操作,对应点是否有输入(比如文本框,没仔细看)。

local cstate = res:parse()

最后生成页面,调用的是render函数,实际上最后调用的是template下的render函数。说明一下Template以及其他一些对象都是

用util.class()生成的,当你Template(name)时会调用其对应的__init__函数。

res:render({
			firstmap   = (i == 1),
			redirect   = redirect,
			messages   = messages,
			pageaction = pageaction,
			parsechain = parsechain
		})
Template(name):render(scope or getfenv(2))

看一下template.__init__函数,这里有一个非常重要的一段代码。

在找一下name的参数,可以再map调用__init__看到,self.template = "cbi/map",这就是name参数,实际上是view下的文件。

这里就是会解析对应view文件。parse函数实际上是用C语言写的代码,被编译成了so文件。

parse函数会将view下的整个文件解析成一个lua函数,并存储起来,当需要是可以调用这个函数。

render函数实际就是调用相应文件的函数,使用copcall运行这个函数。

if name then
			sourcefile = viewdir .. "/" .. name .. ".htm"
			self.template, _, err = tparser.parse(sourcefile)
		else
			sourcefile = "[string]"
			self.template, _, err = tparser.parse_string(template)
		end

最后进入这个parse解析文件,可以直接看源码。

简单说一下具体思想,在view下都是htm文件,这些文件有lua代码,html代码,使用<%、%>以及其他一些:、+分隔开来。

当解析文件时,会根据上述的一些分隔符做响应处理,对于lua代码,直接作为代码;对于html代码,作为字符串变为write("htmlstring")这样的代码;其他也类似,源码不复杂。最后整个文件作为一个函数使用lua_load传回到lua中。

在dispatch中,会设置这个数组,将一些函数作为env设置到这些htm的lua函数去,write函数就是这个。

tpl.context.viewns = setmetatable({
    write       = http.write;

我们用write做一个简单说明,这里会将要传到uhttpd的html代码,返回到协程,然后在copcall函数逐层的yield最后返回到httpdispatch中去。然后再次resume这个函数,接着运行lua代码,这样就可以在html代码中使用lua控制。

coroutine.yield(4, content)

7,这里可以进入map.htm文件可以看到,有一行self:render_children(),递归显示map下的所有结点。

到这里已经明白cbi是怎么把html代码传回到uhttpd,记录一下学习。

鉴权什么的就不说了!

 

你可能感兴趣的:(luci工作流程,程序调用)