luci启动的流程
1、uhttpd Web server的根目录在/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()函数
注:可以rm -rf /tmp/luci* 来删除luci的备份文件,这样可以清除缓存,执行修改后的操作。
cgi启动流程
run()函数作为CGI程序的启动入口,代码解析如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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) ) --创建一个协同程序 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 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请求参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // /usr/lib/lua/luci/dispatch .luafunction httpdispatch(request, prefix) http.context.request = request local r = {} context.request = r --解析HTTP request,获取请求路径,并传入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 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | -- 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 |
(2)认证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | if type (auth) == "table" and type (auth.methods) == "table" and #auth.methods > 0 then local sid, sdat, sacl for _, method in ipairs(auth.methods) do sid, sdat, sacl = check_authentication(method) if sid and sdat and sacl then break end end if not (sid and sdat and sacl) and auth.login then local user = http.getenv( "HTTP_AUTH_USER" ) local pass = http.getenv( "HTTP_AUTH_PASS" ) if user == nil and pass == nil then user = http.formvalue( "luci_username" ) pass = http.formvalue( "luci_password" ) end if user and pass then sid, sdat, sacl = session_setup(user, pass) end if not sid then context.path = {} http.status(403, "Forbidden" ) http.header( "X-LuCI-Login-Required" , "yes" ) local scope = { duser = "root" , fuser = user } local ok, res = util.copcall(tpl.render_string, [[<% include( "themes/" .. theme .. "/sysauth" ) %>]], scope) if ok then return res end return tpl.render( "sysauth" , scope) end http.header( "Set-Cookie" , 'sysauth=%s; path=%s; SameSite=Strict; HttpOnly%s' %{ sid, build_url(), http.getenv( "HTTPS" ) == "on" and "; secure" or "" }) http.redirect(build_url(unpack(ctx.requestpath))) return end if not sid or not sdat or not sacl then http.status(403, "Forbidden" ) http.header( "X-LuCI-Login-Required" , "yes" ) return end ctx.authsession = sid ctx.authtoken = sdat.token ctx.authuser = sdat.username ctx.authacl = sacl end if #required_path_acls > 0 then local perm = check_acl_depends(required_path_acls, ctx.authacl and ctx.authacl[ "access-group" ]) if perm == nil then http.status(403, "Forbidden" ) return end page. readonly = not perm end local action = (page and type (page.action) == "table" ) and page.action or {} if action. type == "arcombine" then action = ( #requested_path_args > 0) and action.targets[2] or action.targets[1] end if cors and http.getenv( "REQUEST_METHOD" ) == "OPTIONS" then luci.http.status(200, "OK" ) luci.http.header( "Access-Control-Allow-Origin" , http.getenv( "HTTP_ORIGIN" ) or "*" ) luci.http.header( "Access-Control-Allow-Methods" , "GET, POST, OPTIONS" ) return end if require_post_security(action) then if not test_post_security() then return end end |
(3)会根据action.type处理相应的动作,每个controller下的lua文件的index函数会生成页面的菜单栏并定义各个页面的调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | if action. type == "view" then tpl.render( "view" , { view = action.path }) elseif action. type == "call" then local ok, mod = util.copcall(require, action.module) if not ok then error500(mod) return end ... elseif action. type == "alias" then ... elseif action. type == "rewrite" then ... elseif action. type == "template" then tpl.render(action.path, getfenv(1)) elseif action. type == "cbi" then _cbi({ config = action.config, model = action.path }, unpack(requested_path_args)) elseif action. type == "form" then _form({ model = action.path }, unpack(requested_path_args)) else local root = find_subnode(menu, {}, true ) if not root 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(requested_path_full, "/" ) .. "'.\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 end |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)