本博文全部内容在 GitHub 仓库上同步,可以在 👉 GitHub 🔗 上找到。 本博文是 GitHub 上的 README 内容,故本文内部分链接是以 GitHub 上相对路径放置的,有需要请在 GitHub 中下载/查看。
因为个人的水平和精力是有限的,如果本目录下的内容存在错误,疏忽之处,欢迎指出:可创建 Issue 或者 fork 修改后向本仓库做 pull request
注:
下文“几个元素”中,属于“总结”。
是在编写本文是为了理清思路,边整理思路边写下的。
若有不明之处可以在看完本目录下的内容之后再回过头来看一遍。
几个元素:
-
在 OpenWrt 使用 Luci – Web。
-
Luci 使用 lua 语言作为后台。
-
Luci 使用 lua 通过 uci 库读取和修改 OpenWrt 协定的 UCI 配置文件。
-
OpenWrt 内协定 UCI 配置文件,并提供了不同的接口操作它,其中之一是实现了 lua 语言的 uci 库。
-
Luci 框架内基于 lua + uci 库编写了 CBI 框架 – CBI 框架是 Luci 的子框架。
-
CBI 框架加载入 uci 配置文件相应的 lua 模块,对于 HTTP GET 能够以 CBI 框架的运行逻辑将 UCI 配置文件转化渲染成用于 Web 前端显示的 HTML 做 HTTP Response;同样对 HTTP POST 也以 CBI 框架运行的逻辑将 form 表单修改写入到 UCI 配置文件中(和生效)。
-
由于 UCI 有着规范的格式,以及 CBI 框架的 OOP 实现,我们不需要从头如 读取 UCI 配置文件,写出如何渲染成 HTML 的代码再 Response 到 Client(浏览器);这些操作是可以抽象出来的一套可重用的参数和运行逻辑(方法),用户(promgramer)只需要编写针对细分的不同的配置文件内容的 CBI 模块代码即可(即,OOP 中的 class 已经被实现好了,只需要根据不同的实体内容实例和调整实例内容即可)。
-
因为 CBI 框架的高度可重用性(配置文件大同小异,用户(user)管理网页风格统一),所以我们可以设想只要在编写的 CBI 模块代码中指明该模块关联的 UCI 配置文件,那么 CBI 框架就能够将该 UCI 配置文件以写好的有限的规则读取出来并显示在浏览器上 – 但是因为我们需要控制配置文件中的哪些部分需要显示,哪些部分不需要显示;哪些部分以何种方式显示,另一些部分又以另外的什么方式显示;所以我们除了声明配置文件外,还需要声明这些内容 – 这很类似“声明式”编程,后面具体会感觉到。
(另外一提,这点和 Web 开发的后台管理网站没有本质区别,后台管理网站也是通过编写大量通用的代码涵盖大范围的内容类型(数据库,数据 type)以做到对大部分编写的数据模型无需额外编写管理页面,直接能够以通用的模板显示出来并且能够操作)
-
Luci 框架亦是 MVC 模式,其中 CBI 即是 model,因为用户(user)管理网页(路由器的 Web GUI)没有很多花样,所以基本上需要的前端模板 luci 都提供了 – 即用户编写了 CBI 模块即可,CBI 框架渲染时的运行逻辑能够使用既有的模板为浏览器提供 HTML 显示。当然,在一些需要以特殊方式显示或者提供更丰富的功能的地方,我们仍然可以自己编写模板(view),然后在 CBI 内指定哪些 UCI 内容使用该模板显示。
-
Luci 框架的控制器是理解 Luci 框架运行逻辑的关键,它主要将用户编写的控制器规则生成 URI(路由)(并且提供了反向解析的接口),用户(user)只需要在浏览器访问 URL,Luci 就能够通过控制器执行程序中定义的处理代码(比如 CBI 模块)得到处理代码(方法)的返回内容作为 HTTP 响应(View),即 controller -> model -> view(view 中又包含 controller URL)。
详解 Luci 框架 – 将分以下步骤进行(可以仔细看一下,有助于理清思路):
- 软件结构:源码结构和运行结构
- 源码分布介绍和“安装/运行”文件分布
- 源码编译/打包方式和上传安装
- HTTP Server
- HTTP Server 和 Luci 交互基本原理
- HTTP Server 简单介绍
- Luci 用户系统
- 多用户支持
- 状态保持原理(HTTP 是无状态协议)
- 登录系统
- Luci 框架
- hello world 级 demo – 接触与感受
- MVC 简介
- Controller 和 URI
- View – 模板简介和基本使用/语法
- Luci 框架前半部运行逻辑
- 从 HTTP Server 到执行 Luci
- 从 Luci 入口到 Controller
- 从 Controller 到执行 target(函数)
- 非 CBI target – call 与 template 实例
- Model(模型 )– CBI 框架
- CBI 的 hello world 级 demo
- 了解 OOP 基本知识和作用
- 基本知识
- 基本作用 -> 可实现的声明式编程
- 加速编程的好处,隐藏大量细节的坏处
- lua OOP 语法与动态语法
- 类与实例 – 不容模糊的
self
- UCI 协议(约定)
- 配置文件格式
- 常规对应 Web 页面显示的控件
- lua 中的 uci 库与 API
- CBI 模块实例 - example 页和 example 配置文件
- 处理表单 – 探究 CBI 框架运行逻辑
- 模块,节,tab 与 option
- 基本类型和动态绑定(组合模式)
- lua 脚本代码在什么时候运行?以及 require 和 loader 区别
- 运行方法(代码执行逻辑)和重写
- 可选的定制化 – 钩子函数
- 配置更新的生效和重定向
- 模板扩展 – javascript 异步请求
- 节选 - 编程关键词与理解
- 类,类型,实例,对象
- API,框架,模块,(数据)模型
- 钩子,重写
- 强类型和弱类型,动态和静态
- 面向过程,面向对象,面向声明
- UML
注:
因为本目录的内容并没有按照上文“步骤”写完,所以只称得上“粗解”。
因为 PPT 是先于本文做完的,虽然 PPT 内容本身可能不够精细,但是根据 PPT 内的文字说明,讲解,结合 Luci 源码查看,想必对框架理解会有一定的帮助。
另外在写 PPT 的时候是为了团队现场讲解,会配置打开的源代码文件讲,所以在 PPT 中主要以概括性、解释性、总结性内容为主,读者需要结合源码看。此处内容后面可能会不定期更新。
本着不等将事情做到完美的那一刻再分享出来的理由,
因为一是这样会过很久之后才分享出来,二是有可能做不到心目中的完美而一直不能分享出来。
若是有需要的读者早一些看到,并且可能能起到一定的帮助,再由读者自身通过结合源码以及翻阅资料补充便可以理解 Luci 框架。那么本目录下分享出来的内容便是有用的。
通过我也期望于即使目前此处的内容还很粗糙,但是若有读者能够 fork > 编辑补重小节小段或者是几句话,几行代码解释介绍,那也是甚好的。
粗解 Luci 框架:
目录
-
软件结构:源码结构和运行结构
- 源码分布介绍和“安装/运行”文件分布
- 源码编译/打包方式和上传安装
-
HTTP Server
- HTTP Server 和 Luci 交互基本原理
- HTTP Server 简单介绍
-
Luci 用户系统
《== 未完成
- 多用户支持
- 状态保持原理(HTTP 是无状态协议)
- 登录系统
-
Luci 框架
-
hello world 级 demo – 接触与感受
-
MVC 简介
-
Controller 和 URI
-
View – 模板简介和基本使用/语法
-
Luci 框架前半部运行逻辑
《== 参见 luci框架代码”逻辑”流程图.pdf
- 从 HTTP Server 到执行 Luci
- 从 Luci 入口到 Controller
- 从 Controller 到执行 target(函数)
-
非 CBI target – call 与 template 实例
-
Model(模型 )– CBI 框架
-
CBI 的 hello world 级 demo
-
了解 OOP 基本知识和作用
《== 未完成
- 基本知识
- 基本作用 -> 可实现的声明式编程
- 加速编程的好处,隐藏大量细节的坏处
- lua OOP 语法与动态语法
- 类与实例 – 不容模糊的
self
-
UCI 协议(约定)
- 配置文件格式
- 常规对应 Web 页面显示的控件
- lua 中的 uci 库与 API《== 未完成
-
CBI 模块实例 - example 页和 example 配置文件
- 模块,节,tab 与 option
- 基本类型和动态绑定(组合模式)《== 未完成
- input-Value,select-ListValue,checkbox-Flag
-
处理表单 – 探究 CBI 框架运行逻辑
- lua 脚本代码在什么时候运行?以及 require 和 loader 区别《== 未完成
- 运行方法(代码执行逻辑)和重写
- 可选的定制化 – 钩子函数《== 未完成
- 配置更新的生效和重定向
-
-
模板扩展 – javascript 异步请求《== 未完成
-
-
节选 - 编程关键词与理解
《== 未完成
- 类,类型,实例,对象
- API,框架,模块,(数据)模型
- 钩子,重写
- 强类型和弱类型,动态和静态
- 面向过程,面向对象,面向声明
- UML
正文
参阅 luci-web(GUI)-for-develop.pptx
以下为 PPT 幻灯片截图:
添加一个导航栏 tab,需要在 controller/ 下增加一个相应名称的 *.lua 脚本文件。 框架会读取 controller/ 下的所有 *.lua 脚本文件生成框架内部使用的结构树。每个 *.lua 脚本文件对应一个导航栏 tab。 框架读取 *.lua 内的 function index() 函数,其函数内的 entry 会在页面显示上作为导航栏 tab 的子页标签(页面入口)。
entry(path, target, title, order) path : router – url target: Target function to call when dispatched title : 显示的名称 order : 顺序
source code(dispatch.lua)
function entry(path, target, title, order) local c = node(unpack(path)) c.target = target c.title = title c.order = order c.module = getfenv(2)._NAME return c end 12345678910
luci 框架本身也就是 lua 脚本代码。 访问 url 并非访问某个特定的 *.lua 脚本文件。 而是通过 luci 框架执行相应的 lua 代码。
Client端和serv端采用cgi方式交互,uhttpd服务器的cgi方式中,fork出一个子进程, 子进程利用execl替换为luci进程空间,并通过setenv环境变量的方式,传递一些固定格式的数据(如PATH_INFO)给luci。
另外一些非固定格式的数据(post-data)则由父进程通过一个w_pipe写给luci的stdin, 而luci的返回数据则写在stdout上,由父进程通过一个r_pipe读取。
- 首次运行时,是以普通的file方式获得docroot/index.html,该文件中以meta的方式自动跳转到cgi的url,这是web服务器的一般做法。
- 然后第一次执行luci,path_info=‘/’,会alise到’/admin’(‘/‘会索引到 tree.rootnode,并执行其target方法,即alise(’/admin’),即重新去索引adminnode,这在后面会详细描述), 该节点需要认证,所以返回一个登录界面。
- 第3次交互,过程同上一次的,只是这时已post来了登录信息,所以serv端会生成一个session值,然后执行’/admin’的target(它的target为firstchild,即索引第一个子节点), 最终返回/admin/status.html,同时会把session值以cookie的形式发给client。这就是从原始状态到得到显示页面的过程,之后主要就是点击页面上的连接,产生新的request。
- 每个链接的url中都会带有一个stok值(它是serv生成的,并放在html中的url里),并且每个新request都要带有session值,它和stok值一起供serv端联合认证。
- 先介绍 luci 如何生成 router(路由)。 然后关于 luci 框架如何加载模板,渲染数据后 response 到 client——先介绍 MVC(和 CBI),然后介绍连接 MVC 和处理请求接口的 dispatcher.lua。 最后 dispatcher.lua 的其它代码,如何调用执行 controller 中的代码。
luci 框架会对解析得到的数据结构缓存在 /tmp/ 目录下 function 是 *.lua -> index -> entry 内的 target
luci框架代码“逻辑”流程图.vsdx == luci框架代码”逻辑”流程图.pdf
用户管理 luci 是一个单用户框架,公用的模块放置在 */luci/controller/ 下面,各个用户的模块放置在 */luci/controller/ 下面对应的文件夹里面。 比如 admin 登录,最终的页面只显示 /luci/controller/admin 下面的菜单。 这样既有效的管理了不同管理员的权限。
HowTo: Write Modules: https://github.com/openwrt/luci/wiki/ModulesHowTo
module <> Tab action <> page
Writing variables and function values Syntax: <% write (value) %> Short-markup: <%=value%>
Note: index 内部的另外一种写法!!!!!!!!!
通过 API 接口“声明式”编程关联配置文件 – 像 html, css 由浏览器读取 html, css 代码,然后执行浏览器内部的代码最后“显示”。
on_commit, on_apply 等方法在源码中以“钩子”的含义运行。
亦:CBI 模块也可以自行编写 template 模板然后指定使用。
类与 API 源码位置:qsdk\qca\feeds\luci\modules\luci-base\luasrc\cbi.lua
Abstract 即说明这是抽象类型;不能直接使用该类,需要继承出类型。 AbstractSection, AbstractValue 即两种不同的类,从效果上看,AbstractValue 的继承类在 CBI 模块中会根据配置文件需要而实例化绑定在继承 AbstractSection 类的实例下;这一点也可以从 AbstractSection 中的 option 方法内的代码看出。
parse 函数决定了框架内运行逻辑。
“uci"是"Unified Configuration Interface”(统一配置界面)的缩写,意在OpenWrt整个系统的配置集中化。 系统配置应容易,更直接且在此有文档描述,从而使你的生活更轻松! (它是White Russian系列OpenWrt基于nvram的配置的后继改进。) 许多程序在系统某处拥有自己的配置文件, 比如/etc/network/interfaces, /etc/exports, /etc/dnsmasq.conf或者 /etc/samba/samba.conf, 有时它们还使用稍有不同的语法。 在OpenWrt中你无需为此烦恼,我们只需更改UCI配置文件! 你不需要为了某个更改起效而重启系统!参阅下文中的命令行实用工具以了解如何做到这点。 还有不要忘了官方程序包(official binaries)里包含了很多后台程序,但默认情况下并未启用! 比如cron后台程序默认并未激活,因而只编辑crontab并无作用。 你需要用/etc/init.d/crond start起动它或用/etc/init.d/crond enable激活它。 大部分后台程序都可以disable(禁用),stop(停止)和restart(重起)。 还有一些非UCI配置你可以参阅。
共同原则 OpenWrt的所有配置文件皆位于/etc/config/目录下。每个文件大致与它所配置的那部分系统相关。可用文本编辑器、“uci” 命令行实用程序或各种编程API(比如 Shell, Lua and C)来编辑/修改这些配置文件。
参考: https://lvii.gitbooks.io/outman/content/openwrt.uci.html
实例:
# cat /etc/config/wireless config wifi-device 'wifi0' option type 'qcawificfg80211' option macaddr '00:03:7f:12:20:df' option hwmode '11a' option channel 'auto' config wifi-iface option device 'wifi0' option mode 'ap' option ssid 'AAAAAAATest-5G' option bssid 'admin' option network 'lan' option key '12345678' option wps_pushbutton '0' option encryption 'none' config wifi-device 'wifi1' 。。。略。。。 # cat /etc/config/ddns config ddns 'global' option ddns_dateformat '%F %R' option ddns_loglines '250' option upd_privateip '0' config service 'myddns_ipv4' option lookup_host 'yourhost.example.com' option domain 'yourhost.example.com' option username 'your_username' option password 'your_password' option interface 'wan' option ip_source 'network' option ip_network 'wan' option service_name 'dyn.com' config service 'myddns_ipv6' 。。。略。。。 12345678910111213141516171819202122232425262728293031323334353637383940
参考: https://blog.csdn.net/rainforest_c/article/details/70139962
- package ‘example’中的’example’实际上就是UCI文件的文件名,例如/etc/config/network对应 package ‘network’,但是这个语句不会存在文件中,需要通过命令uci export network查看。
- config ‘example’ ‘test’语句定义了一个type为example,名字为test的section。section可以只有type而没有名字,这类section称为匿名的section,后文会有说明。
- option ‘string’ ‘some value’语句定义了section下的一个option,该option标识为string,值为some value。
- list ‘collection’ ‘first item’语句定义了section下的一个list,list与option不同之处在于list可以有多个值,该例子中的list collection有first item和second item两个值。
此处直接在 OpenWrt 运行系统中直接修改。
s <= m:section() taboption() 指定了该 Option 类在指定的 section->tab 下,并返回 option 实例。 option:write 方法重写定义了该方法的内容。write 方法在 post form 时被调用 – 参考前面幻灯片和源码。 m.uci:set, m.uci:delete 这些即使用 lua 的 uci 模块操作 UCI 配置文件 – 命令行操作 UCI 配置文件,Web(Lua)操作 UCI 配置文件两种方式的后一种。 面向对象编程中,m.uci 使用的是 lua 的 uci 模块,通过 m.uci 对象绑定(将 m 实例以 self 的形式传递给 uci 模块)即操作的是 m 指定的 UCI 配置文件。