【OpenWrt】(Luci)OpenWrt Web GUI 开发之 Luci 框架粗解(转载)


原文链接:【OpenWrt】(Luci)OpenWrt Web GUI 开发之 Luci 框架粗解_
原作者github:Embedded-GUI-Develop/Luci-of-OpenWrt at master · RDpWTeHM/Embedded-GUI-Develop

本博文全部内容在 GitHub 仓库上同步,可以在 ? GitHub ? 上找到。
本博文是 GitHub 上的 README 内容,故本文内部分链接是以 GitHub 上相对路径放置的,有需要请在 GitHub 中下载/查看。

因为个人的水平和精力是有限的,如果本目录下的内容存在错误,疏忽之处,欢迎指出:可创建 Issue 或者 fork 修改后向本仓库做 pull request

理解 Luci 架构
    • 目录
    • 正文

  • 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 框架 – 将分以下步骤进行(可以仔细看一下,有助于理清思路):

    1. 软件结构:源码结构和运行结构
      1. 源码分布介绍和“安装/运行”文件分布
      2. 源码编译/打包方式和上传安装
    2. HTTP Server
      1. HTTP Server 和 Luci 交互基本原理
      2. HTTP Server 简单介绍
    3. Luci 用户系统
      1. 多用户支持
      2. 状态保持原理(HTTP 是无状态协议)
      3. 登录系统
    4. Luci 框架
      1. hello world 级 demo – 接触与感受
      2. MVC 简介
      3. Controller 和 URI
      4. View – 模板简介和基本使用/语法
      5. Luci 框架前半部运行逻辑
        1. 从 HTTP Server 到执行 Luci
        2. 从 Luci 入口到 Controller
        3. 从 Controller 到执行 target(函数)
      6. 非 CBI target – call 与 template 实例
      7. Model(模型 )-- CBI 框架
        1. CBI 的 hello world 级 demo
        2. 了解 OOP 基本知识和作用
          1. 基本知识
          2. 基本作用 -> 可实现的声明式编程
          3. 加速编程的好处,隐藏大量细节的坏处
          4. lua OOP 语法与动态语法
          5. 类与实例 – 不容模糊的 self
        3. UCI 协议(约定)
          1. 配置文件格式
          2. 常规对应 Web 页面显示的控件
          3. lua 中的 uci 库与 API
        4. CBI 模块实例 - example 页和 example 配置文件
        5. 处理表单 – 探究 CBI 框架运行逻辑
          1. 模块,节,tab 与 option
          2. 基本类型和动态绑定(组合模式)
          3. lua 脚本代码在什么时候运行?以及 require 和 loader 区别
          4. 运行方法(代码执行逻辑)和重写
          5. 可选的定制化 – 钩子函数
          6. 配置更新的生效和重定向
      8. 模板扩展 – javascript 异步请求
    5. 节选 - 编程关键词与理解
      1. 类,类型,实例,对象
      2. API,框架,模块,(数据)模型
      3. 钩子,重写
      4. 强类型和弱类型,动态和静态
      5. 面向过程,面向对象,面向声明
      6. UML

    注:

    因为本目录的内容并没有按照上文“步骤”写完,所以只称得上“粗解”。

    因为 PPT 是先于本文做完的,虽然 PPT 内容本身可能不够精细,但是根据 PPT 内的文字说明,讲解,结合 Luci 源码查看,想必对框架理解会有一定的帮助。

    另外在写 PPT 的时候是为了团队现场讲解,会配置打开的源代码文件讲,所以在 PPT 中主要以概括性、解释性、总结性内容为主,读者需要结合源码看。此处内容后面可能会不定期更新。

    本着不等将事情做到完美的那一刻再分享出来的理由,

    因为一是这样会过很久之后才分享出来,二是有可能做不到心目中的完美而一直不能分享出来。

    若是有需要的读者早一些看到,并且可能能起到一定的帮助,再由读者自身通过结合源码以及翻阅资料补充便可以理解 Luci 框架。那么本目录下分享出来的内容便是有用的。

    通过我也期望于即使目前此处的内容还很粗糙,但是若有读者能够 fork > 编辑补重小节小段或者是几句话,几行代码解释介绍,那也是甚好的。

    粗解 Luci 框架:

    luci框架代码”逻辑”流程图.pdf
    1. 从 HTTP Server 到执行 Luci
    2. 从 Luci 入口到 Controller
    3. 从 Controller 到执行 target(函数)
  • 非 CBI target – call 与 template 实例
  • Model(模型 )-- CBI 框架
    1. CBI 的 hello world 级 demo
    2. 了解 OOP 基本知识和作用《== 未完成
      1. 基本知识
      2. 基本作用 -> 可实现的声明式编程
      3. 加速编程的好处,隐藏大量细节的坏处
      4. lua OOP 语法与动态语法
      5. 类与实例 – 不容模糊的 self
    3. UCI 协议(约定)
      1. 配置文件格式
      2. 常规对应 Web 页面显示的控件
      3. lua 中的 uci 库与 API《== 未完成
    4. CBI 模块实例 - example 页和 example 配置文件
      1. 模块,节,tab 与 option
      2. 基本类型和动态绑定(组合模式)《== 未完成
      3. input-Value,select-ListValue,checkbox-Flag
    5. 处理表单 – 探究 CBI 框架运行逻辑
      1. lua 脚本代码在什么时候运行?以及 require 和 loader 区别《== 未完成
      2. 运行方法(代码执行逻辑)和重写
      3. 可选的定制化 – 钩子函数《== 未完成
      4. 配置更新的生效和重定向
  • 模板扩展 – javascript 异步请求《== 未完成
  • 节选 - 编程关键词与理解《== 未完成
    1. 类,类型,实例,对象
    2. API,框架,模块,(数据)模型
    3. 钩子,重写
    4. 强类型和弱类型,动态和静态
    5. 面向过程,面向对象,面向声明
    6. 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

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10




    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读取。


    1. 首次运行时,是以普通的file方式获得docroot/index.html,该文件中以meta的方式自动跳转到cgi的url,这是web服务器的一般做法。

    2. 然后第一次执行luci,path_info=’/’,会alise到’/admin’(’/‘会索引到 tree.rootnode,并执行其target方法,即alise(’/admin’),即重新去索引adminnode,这在后面会详细描述),
      该节点需要认证,所以返回一个登录界面。

    3. 第3次交互,过程同上一次的,只是这时已post来了登录信息,所以serv端会生成一个session值,然后执行’/admin’的target(它的target为firstchild,即索引第一个子节点),
      最终返回/admin/status.html,同时会把session值以cookie的形式发给client。这就是从原始状态到得到显示页面的过程,之后主要就是点击页面上的连接,产生新的request。

    4. 每个链接的url中都会带有一个stok值(它是serv生成的,并放在html中的url里),并且每个新request都要带有session值,它和stok值一起供serv端联合认证。

    5. 先介绍 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'
    。。。略。。。

    • 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

    参考:
    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 配置文件。

    相关