【OpenWrt】(Luci)OpenWrt Web GUI 開發之 Luci 框架粗解

本博文全部內容在 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 框架:

目錄

  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 框架前半部運行邏輯《== 參見 luci框架代碼”邏輯”流程圖.pdf
      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 配置文件
        1. 模塊,節,tab 與 option
        2. 基本類型和動態綁定(組合模式)《== 未完成
        3. input-Value,select-ListValue,checkbox-Flag
      5. 處理表單 – 探究 CBI 框架運行邏輯
        1. lua 腳本代碼在什麼時候運行?以及 require 和 loader 區別《== 未完成
        2. 運行方法(代碼執行邏輯)和重寫
        3. 可選的定製化 – 鉤子函數《== 未完成
        4. 配置更新的生效和重定向
    8. 模板擴展 – javascript 異步請求《== 未完成
  5. 節選 - 編程關鍵詞與理解《== 未完成
    1. 類,類型,實例,對象
    2. API,框架,模塊,(數據)模型
    3. 鉤子,重寫
    4. 強類型和弱類型,動態和靜態
    5. 面向過程,面向對象,面向聲明
    6. UML

正文

參閱 luci-web(GUI)-for-develop.pptx

以下爲 PPT 幻燈片截圖:
n/a
n/a
n/a
n/a
n/a
n/a
n/a
n/a
n/a

添加一個導航欄 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

n/a
n/a
n/a
n/a

luci 框架本身也就是 lua 腳本代碼。
訪問 url 並非訪問某個特定的 *.lua 腳本文件。
而是通過 luci 框架執行相應的 lua 代碼。

n/a
n/a
n/a

Client端和serv端採用cgi方式交互,uhttpd服務器的cgi方式中,fork出一個子進程,
子進程利用execl替換爲luci進程空間,並通過setenv環境變量的方式,傳遞一些固定格式的數據(如PATH_INFO)給luci。

另外一些非固定格式的數據(post-data)則由父進程通過一個w_pipe寫給luci的stdin,
而luci的返回數據則寫在stdout上,由父進程通過一個r_pipe讀取。

n/a
n/a

  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 中的代碼。

n/a

luci 框架會對解析得到的數據結構緩存在 /tmp/ 目錄下
function 是 *.lua -> index -> entry 內的 target

luci框架代碼“邏輯”流程圖.vsdx == luci框架代碼”邏輯”流程圖.pdf

用戶管理
luci 是一個單用戶框架,公用的模塊放置在 */luci/controller/ 下面,各個用戶的模塊放置在 */luci/controller/ 下面對應的文件夾裏面。
比如 admin 登錄,最終的頁面只顯示 /luci/controller/admin 下面的菜單。
這樣既有效的管理了不同管理員的權限。

n/a
n/a
n/a

HowTo: Write Modules: https://github.com/openwrt/luci/wiki/ModulesHowTo

module <> Tab
action <
> page

n/a
n/a

Writing variables and function values
Syntax:
<% write (value) %>
Short-markup:
<%=value%>

Note: index 內部的另外一種寫法!!!!!!!!!

n/a
n/a

通過 API 接口“聲明式”編程關聯配置文件 – 像 html, css 由瀏覽器讀取 html, css 代碼,然後執行瀏覽器內部的代碼最後“顯示”。

n/a

on_commit, on_apply 等方法在源碼中以“鉤子”的含義運行。

n/a

亦:CBI 模塊也可以自行編寫 template 模板然後指定使用。

n/a

類與 API 源碼位置:qsdk\qca\feeds\luci\modules\luci-base\luasrc\cbi.lua

n/a

Abstract 即說明這是抽象類型;不能直接使用該類,需要繼承出類型。
AbstractSection, AbstractValue 即兩種不同的類,從效果上看,AbstractValue 的繼承類在 CBI 模塊中會根據配置文件需要而實例化綁定在繼承 AbstractSection 類的實例下;這一點也可以從 AbstractSection 中的 option 方法內的代碼看出。

parse 函數決定了框架內運行邏輯。

n/a

“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)來編輯/修改這些配置文件。

n/a

參考:
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'
        。。。略。。。

n/a

參考:
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兩個值。

n/a

此處直接在 OpenWrt 運行系統中直接修改。

n/a
n/a
n/a
n/a

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

n/a

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章