ACF mvc.lua 示例
使用 mvc.lua 设置主机名
在此示例中,我们将使用 mvc.lua 创建一个简单的主机名设置命令行应用程序。构建控制器/模型后,您可以使用相同的代码通过 Web 和基于 Web 的应用程序控制器来设置主机名。
在此示例中,我们将假定您对您正在运行的 Linux 机器(最好是 Alpine Linux 机器!)具有 root 访问权限。
获取 mvc.lua 模块
从 git 仓库获取 mvc.lua 模块。
wget https://gitlab.alpinelinux.org/acf/acf-core/-/blob/master/lua/mvc.lua
创建模型和控制器
创建文件 hostname-controller.lua,定义“最终用户”可以运行的函数。我们将仅创建一个操作 edithostname,它将用于读取和更新主机名。由于该操作会更改系统,因此自然采用“表单”的形式
hostname-controller.lua
-- Controller for editing hostname local mymodule = {} mymodule.edithostname = function (self) return self.handle_form(self, self.model.get_hostname, self.model.set_hostname, self.clientdata, "Update", "Edit Hostname", "Hostname Updated") end return mymodule
创建文件 hostname-model.lua,定义用于获取和设置主机名的模型函数。我们为每个函数返回一个 cfe 表,其中包括一个用于主机名的条目的表单
hostname-model.lua
-- Model functions for retrieving / setting the hostname local mymodule = {} -- Create a cfe defining the form for editing the hostname and containing the current value mymodule.get_hostname = function(self, clientdata) local retval = cfe({ type="group", value={}, label="Hostname" }) -- Warning - io.popen has security risks, never pass user data to io.popen local f = io.popen ("/bin/hostname") local n = f:read("*a") or "none" f:close() n=string.gsub(n, "\n$", "") retval.value.hostname = cfe({ value=n, label="Hostname" }) return retval end -- Set the hostname from the value contained in the cfe created by get_hostname mymodule.set_hostname = function(self, hostnameform, action) local success = true -- Check to make sure the name is valid if (hostnameform.value.hostname.value == "") then success = false hostnameform.value.hostname.errtxt = "Hostname must not be blank" elseif (#hostnameform.value.hostname.value > 16) then success = false hostnameform.value.hostname.errtxt = "Hostname must be 16 characters or less" elseif (string.find(hostnameform.value.hostname.value, "[^%w%_%-]")) then success = false hostnameform.value.hostname.errtxt = "Hostname can contain alphanumerics only" end -- If it is valid, set the hostname if ( success ) then local f = io.open("/etc/hostname", "w") if f then f:write(hostnameform.value.hostname.value .. "\n") f:close() end -- Warning - io.popen has security risks, never pass user data to io.popen f = io.popen ("/bin/hostname -F /etc/hostname") f:close() else hostnameform.errtxt = "Failed to set hostname" end return hostnameform end return mymodule
(可选)测试模型代码(不使用 mvc.lua)
如果需要,您可以创建一个 test.lua 脚本来验证模型代码是否可以独立工作
test.lua
require("mvc") -- Needed for cfe function definition m=require("hostname-model") local form = m.get_hostname() form.value.hostname.value = arg[1] or "" form = m.set_hostname(nil, form) if form.errtxt then print("FAILED: "..form.value.hostname.errtxt or form.errtxt) end form = m.get_hostname() print(form.value.hostname.value)
然后您可以使用以下命令进行测试
#lua test.lua "Alpine" Alpine
#lua test.lua "Invalid Name" FAILED: Hostname can contain alphanumerics only Alpine
将软件包添加到 ACF 框架
为了使模型和控制器在 ACF mvc.lua 框架内工作,我们必须做几件事。
1. 安装 ACF 核心软件包
apk add acf-core
(可选)您可以添加整个基于 Web 的 ACF 框架
setup-acf
2. 修改 ACF 配置文件以在 /etc/acf/app/ 中查找其他软件包。编辑 /etc/acf/acf.conf 文件,将 /etc/acf/app/ 目录添加到 appdir 逗号分隔的列表中
appdir=/etc/acf/app/,/usr/share/acf/app/
3. 将模型和控制器移动到新的软件包目录。我们将软件包称为“test”
mkdir -p /etc/acf/app/test mv hostname-*.lua /etc/acf/app/test
4. 使用 acf-cli 应用程序测试新软件包
# acf-cli /test/hostname/edithostname result = {} result["label"] = "Edit Hostname" result["option"] = "Edit" result["type"] = "form" result["value"] = {} result["value"]["hostname"] = {} result["value"]["hostname"]["label"] = "Hostname" result["value"]["hostname"]["type"] = "text" result["value"]["hostname"]["value"] = "Alpine" # acf-cli /test/hostname/edithostname hostname=test submit=true result = {} result["descr"] = "Hostname Updated" result["label"] = "Edit Hostname" result["option"] = "Edit" result["type"] = "form" result["value"] = {} result["value"]["hostname"] = {} result["value"]["hostname"]["label"] = "Hostname" result["value"]["hostname"]["type"] = "text" result["value"]["hostname"]["value"] = "test"
请注意,传递给 acf-cli 的操作字符串的格式为“/prefix/controller/action”。前缀是 /etc/acf/app 中可以找到控制器的路径,因此在我们的例子中为“/test/”。控制器由控制器文件名确定,因此在我们的例子中为“hostname”。操作对应于控制器中要调用的函数,因此在我们的例子中为“edithostname”。另请注意,在上面的两个示例中,第一种情况读取现有主机名,第二种情况更新主机名。acf-cli 应用程序的输出是 cfe 表单的序列化版本,这对于测试很有用,但在现实生活中不太有用。
在 ACF Web 界面中启用软件包
1. 添加基于 Web 的 ACF 框架
setup-acf
2. 配置所有用户都有权访问新的主机名操作。编辑 “/etc/acf/app/test/hostname.roles” 文件,并为 edithostname 操作添加 GUEST 权限
echo "GUEST=hostname/edithostname" > /etc/acf/app/test/hostname.roles
现在,通过浏览 https://IP-of-host/cgi-bin/acf/test/hostname/edithostname 应该可以看到新操作。显然,您可能需要重新考虑为该操作提供 GUEST 访问权限,因为这将允许未经身份验证的用户修改您的主机名。
3. 将新的主机名操作添加到 ACF 菜单。编辑 “/etc/acf/app/test/hostname.menu” 文件并添加一个菜单项
echo "Test Hostname Edit edithostname" > /etc/acf/app/test/hostname.menu
您需要先从 ACF 界面注销(或删除会话 cookie),然后新的菜单项才会可见。
创建基于 MVC 的应用程序
您有两种选择来创建您自己的基于 MVC 的应用程序。
1. 使用 dispatch 函数。这是 Web 界面和 acf-cli 应用程序使用的方法。要调度的操作以字符串格式 /prefix/controller/action 传递,输入值作为 clientinfo 表传递。输出由视图确定。
2. 使用 new 函数加载所需的控制器,然后直接调用控制器操作或模型函数。使用控制器操作需要使用 clientdata 结构来传递参数。对于调用模型函数,应用程序将调用 get 函数来检索表单 cfe,将所需的输入值填充到 cfe 中,然后调用 set 函数来提交表单并执行所需的操作。MVC 应用程序负责任何用户交互和结果显示。
使用 Dispatch 方法
test_dispatch
#!/usr/bin/lua -- Simple CLI based mvc application -- load the mvc module mvc = require("acf.mvc") -- create an new "mvc object" MVC=mvc:new() -- load the config file so we can find the appdir MVC:read_config("acf") -- dispatch the request local clientdata = {hostname=arg[1], viewtype=arg[2], submit="Update"} MVC:dispatch("/test/", "hostname", "edithostname", clientdata) -- destroy the mvc object MVC:destroy()
测试应用程序。如上面的代码所示,第一个参数是新的主机名,第二个参数是视图类型。
alpine:~# chmod 755 test_dispatch alpine:~# ./test_dispatch test test:~# ./test_dispatch alpine alpine:~#
请注意,应用程序可以运行,但没有输出。这是因为没有提供视图类型。内置支持以下标准视图类型(并非全部适用):html、json、stream、serialized
alpine:~# ./test_dispatch test serialized result = {} result["descr"] = "Hostname Updated" result["label"] = "Edit Hostname" result["option"] = "Update" result["type"] = "form" result["value"] = {} result["value"]["hostname"] = {} result["value"]["hostname"]["label"] = "Hostname" result["value"]["hostname"]["type"] = "text" result["value"]["hostname"]["value"] = "test" test:~# ./test_dispatch alpine json {"type":"form","label":"Edit Hostname","value":{"hostname":{"value":"alpine","type":"text","label":"Hostname"}},"option":"Update","descr":"Hostname Updated"} alpine:~#
您还可以使用自定义视图类型和/或视图来自定义输出。这依赖于 haserl 来解析视图文件,并且只有从 haserl 脚本启动时,haserl lua 函数才可用(如果 haserl 可以作为独立的 lua 库使用就好了)。Haserl 脚本希望由 Web 浏览器启动以处理 CGI 数据,因此传递输入比较棘手。我相信有更好的方法来做到这一点,但这只是一个例子
test_haserl
#!/usr/bin/haserl-lua5.2 --shell=lua <% -- Simple CLI based mvc application -- load the mvc module mvc = require("acf.mvc") -- create an new "mvc object" MVC=mvc:new() -- load the config file so we can find the appdir MVC:read_config("acf") -- dispatch the request local clientdata = {hostname=FORM.hostname, viewtype=FORM.viewtype, submit="Update"} MVC:dispatch("/test/", "hostname", "edithostname", clientdata) -- destroy the mvc object MVC:destroy() %>
/etc/acf/app/test/hostname-edithostname-text.lsp
<% local data, viewlibrary, page_info, session = ... if data.errtxt then print(data:print_errtxt()) else print(data.descr) end %>
测试应用程序
test:~# chmod 755 test_haserl test:~# QUERY_STRING='hostname=alpine&viewtype=text' REQUEST_METHOD=GET ./test_haserl Hostname Updated alpine:~# QUERY_STRING='hostname=asdfasdfasdfasdfasdf&viewtype=text' REQUEST_METHOD=GET ./test_haserl Failed to set hostname hostname: Hostname must be 16 characters or less alpine:~#
您可以通过创建自己的应用程序特定控制器来进一步自定义应用程序。这就是 Web 界面和 acf-cli 应用程序的编写方式。在这两种情况下,都编写了一个简单的应用程序来加载的不仅是 mvc:new() 引用,而是一个应用程序特定的控制器来包装 mvc:new() 对象。然后对应用程序特定的控制器对象调用 dispatch 函数,该对象可以覆盖 mvc.lua 中的任何函数以添加自定义实现。例如,这两个应用程序都覆盖了 handle_clientdata 函数,以自定义在 clientdata 结构中提供输入数据的方式,并且 Web 界面特定的控制器包含大量代码来处理菜单、模板、皮肤、身份验证、重定向等功能... 理解这一点的最佳方法是直接阅读代码。
- Web 应用程序
- 应用程序 - acf
- 控制器 - acf_www-controller.lua
- acf-cli
- 应用程序 - acf-cli
- 控制器 - acf_cli-controller.lua
使用 New 方法
test_new
#!/usr/bin/lua -- Simple CLI based mvc application -- load the mvc module mvc = require("acf.mvc") -- create an new "mvc object" MVC=mvc:new() -- load the config file so we can find the appdir MVC:read_config("acf") -- load the hostname controller HOSTNAME=MVC:new("/test/hostname") local hostname -- METHOD 1 - controller action HOSTNAME.clientdata = {hostname=arg[1], submit="Update"} hostname = HOSTNAME:edithostname() -- METHOD 2 - model functions hostname = HOSTNAME.model:get_hostname() hostname.value.hostname.value = arg[1] hostname = HOSTNAME.model:set_hostname(hostname) if hostname.errtxt then print(hostname:print_errtxt()) else print(hostname.descr or "Hostname Updated") end HOSTNAME:destroy() MVC:destroy()
一旦你有一个主机名控制器对象,你可以访问它的操作,通过 clientdata 传递数据,或者你可以直接访问模型的 get/set 函数。测试应用程序
test:~# chmod 755 test_new test:~# ./test_new alpine Hostname Updated alpine:~# ./test_new asdfasdfasdfasdfasdf Failed to set hostname hostname: Hostname must be 16 characters or less alpine:~#
![]() 请随时帮助我们制作最新版本。(讨论) |
mvc 加载和执行特殊功能
mvc.lua 模块提供在模块加载时、执行控制器操作之前、执行控制器操作之后以及模块卸载时执行代码的功能。
这是通过控制器中的 mvc 表完成的。为了演示,让我们向 helloworld/app/app-controller.lua 添加一些函数
mvc = {} mvc.on_load = function (self, parent) print ("This is the app controller's on_load function") end mvc.pre_exec = function (self) print ("This is the app controller's pre_exec function") end mvc.post_exec = function (self) print ("This is the app controller's post_exec function") end
mvc.on_unload = function (self) print ("This is the app controller's on_unload function") end
现在运行我们的脚本显示函数何时被调用
# lua helloworld.lua update "Alpine" This is the app controller's on_load function This is the app controller's pre_exec function This is the app controller's post_exec function Controller: hostname Action: update Returned a table with the following values: value Alpine type string This is the app controller's on_unload function
我们也可以向特定控制器添加 mvc 函数。将其添加到 helloworld/app/hostname-controller.lua
mvc = {} mvc.on_load = function (self, parent) print ("This is the hostname controller's on_load function") end mvc.pre_exec = function (self) print ("This is the hostname controller's pre_exec function") end mvc.post_exec = function (self) print ("This is the hostname controller's post_exec function") end mvc.on_unload = function (self) print ("This is the hostname controller's on_unload function") end
结果如下
# lua helloworld.lua update "Alpine" This is the app controller's on_load function This is the hostname controller's on_load function This is the hostname controller's pre_exec function This is the hostname controller's post_exec function This is the hostname controller's on_unload function Controller: hostname Action: update Returned a table with the following values: value Alpine type string This is the app controller's on_unload function
请注意,app 和 hostname 的 on_load 和 on_unload 函数都已运行,但只有 hostname 的 pre_exec 和 post_exec 函数运行了。这是因为 pre 和 post exec 函数作为“action”的一部分运行,并且 dispatch 函数在最低级别的控制器中查找 pre/post_exec 函数。由于 hostname 现在定义了这些函数,因此它会运行它们。
要同时运行 hostname 和 app 的 pre_exec 函数,您必须安排 hostname 的 pre_exec 函数调用其父级的 pre_exec 函数
mvc = {} mvc.on_load = function (self, parent) print ("This is the hostname controller's on_load function") mvc.parent_pre_exec = parent.worker.mvc.pre_exec end mvc.pre_exec = function (self) mvc.parent_pre_exec (self) print ("This is the hostname controller's pre_exec function") end