uhttp luci cgi-bin 自定义输出内容
uhttp luci cgi-bin 自定义输出内容
来源 https://www.cnblogs.com/osnosn/p/17131543.html
参考
- 【Example of web interface using uHTTPd and Lua】
- 【Lua 5.1 Reference Manual】
openwrt, op18,op19,op21,op22 都是用的 lua-5.1.5 。
修改openwrt uhttpd 使用的 ssl 证书
- op21, op22 测试OK。
- 如果使用 https 访问,对默认证书的信息不满意。
- 修改
/etc/config/uhttpd
中config cert 'defaults'
部分的内容。uci -q batch << EOF set uhttpd.defaults.organization='my organization' #组织 set uhttpd.defaults.commonname='my common name' #通用名称 set uhttpd.defaults.location='my location' #地址 set uhttpd.defaults.state='my state' #省/州 set uhttpd.defaults.country='CN' #国家代码 set uhttpd.defaults.days='730' #有效期,两年 commit uhttpd EOF
- 删除旧证书 (.crt和.key)。
rm /etc/uhttpd.*
- 重启 uhttpd,会自动重新生成证书。
/etc/init.d/uhttpd restart
我的测试
- 不想安装 php-fpm 或者 python3,它们的体积都比较大,小路由器空间不太够。
- 不修改 openwrt的 uhttpd的配置。因为不想改变 luci的页面。
- uhttpd 的 root 目录是
/www/
- 在
/www/
中,只支持静态html文件。不支持lua脚本。 - 在
/www/cgi-bin/
中,支持各种可执行文件,包括lua脚本,包括shell脚本。
脚本文件无后缀要求,需要设置执行权限chmod +x
- uhttpd 的 root 目录是
- 最简单的办法就是,创建
/www/cgi-bin/test
chmod +x test
- print() 和 io.write() 尽量不要混用,防止因缓存,导致输出顺序不正确。
test 内容如下。op22测试可用。
注意:CGI脚本必须要输出"Content-type: text/html\n\n",uhttpd才会认为脚本正确进行了响应,才会输出内容到浏览器。#!/usr/bin/lua io.write("Content-type: text/html\n\n") io.write('test\n') -- print('test') io.write(os.date("%x", os.time()).."\n")
如果要操作 sqlite3文件。试试这两个包。
- 二选一。
opkg install lsqlite3 #lsqlite3(64kB) 依赖 libsqlite3(840kB) opkg install luasql-sqlite3 #luasql-sqlite3(64kB) 依赖 libsqlite3(840kB)
- 在 lua中 可以用
require "lsqlite3"
或者require "luasql.sqlite3"
引入。- 【lsqlite3 luasql.sqlite3 区别】,【Difference between lua-sqlite3 and lsqlite3】,
个人觉得还是用 luasql-sqlite3, 似乎使用了一个统一的前端接口,另外,网上的教程多是luasql的。
- 【lsqlite3 luasql.sqlite3 区别】,【Difference between lua-sqlite3 and lsqlite3】,
- sqlite3 命令行工具
opkg install sqlite3-cli #sqlite3-cli(193kB) 依赖 libsqlite3(840kB),libedit(194kB)
lua 读取 sqlite3 测试,TEST.db 是有内容的数据库文件。
- op22测试可用。
#!/usr/bin/lua io.write("Content-type: text/html;charset=utf-8\n\n") local sql3=require "luasql.sqlite3" local env=sql3.sqlite3() local conn=env:connect("/tmp/tmp/TEST.db") name="tom" sql_str=string.format("select * from tasktab where id='%s' order by rowid desc limit 10",name) cur,err=conn:execute(sql_str) print(cur,err,"<br>") row=cur:fetch({},"a") while row do for k,v in pairs(row) do -- 打印所有字段 print(k,v,"<br>") end row=cur:fetch(row,"a") end cur:close() conn:close() env:close()
openwrt中cgi脚本获取GET提交的内容,是通过环境变量获取。
- op22测试可用。
os.getenv("QUERY_STRING")
lua 遍历全局变量,环境变量
- 遍历全局变量。
for k,v in ipairs(_G) do print(k,v) end
- lua中,没找到遍历 局部变量 的方法。
- 可以使用 shell脚本打印出所有的环境变量。op22测试可用。
#!/bin/sh echo Content-type: text/html;charset=utf8 echo echo 'test <pre>' set
- 或者,用 openwrt中的 NIXIO POSIX library,遍历环境变量。op22测试可用。
local nixio=require "nixio" local env=nixio.getenv() for k,v in ipairs(env) do print(k,v) end
openwrt中cgi脚本获取POST提交的内容。
- op22测试可用。
local POST_DATA = nil local POSTLength = tonumber(os.getenv("CONTENT_LENGTH")) or 0 if (POSTLength > 0) then POST_DATA = io.read(POSTLength) --POST_DATA = io.read("*a") end -- POST_DATA -> "test1=1234&test2=abcd%2B"
- urldecode
local http=require "luci.http" posted_str=http.urldecode(posted_str, true)
判断文件是否存在。
- op22测试可用。
function file_exists(name) local f=io.open(name,"rb") if f~=nil then io.close(f) return true else return false end end
- 或者,用 openwrt中的 NIXIO POSIX library。op22测试可用。
local fs=require "nixio.fs" function file_exists(name) local f=fs.stat(name) if f~=nil then return true else return false end end
获取文件大小,不引入其他库。用"rb"二进制读方式,不容易出错。
- op22测试可用。
function length_of_file(filename) local fh = assert(io.open(filename, "rb")) local len = assert(fh:seek("end")) fh:close() return len end -- file:seek([whence][,offset]) -- whence=set, cur, end 文件头,当前位置,文件尾。offset可为负数。
文件的写锁。
- lua 本身没找到 文件读写锁 的操作函数,无论是咨询锁,还是强制锁,都没有。
- openwrt中的 nixio.open() 提供 文件锁 的操作。 看 nixio.File 的文档。op22测试可用。
local nixio=require "nixio" fp=nixio.open("test-file.txt","w") fp:lock('lock') fp:write('abcdefghij\n') fp:lock('ulock') fp:close()
- nixio.open() 打开后,没有 lines() 函数。
- nixio.open() 以只读打开,不能加锁 lock("lock"),lock("tlock")。
Basic WEB 认证。
- op22测试可用。
#!/bin/sh echo Content-type: text/html;charset=utf8; if [ -z $HTTP_AUTHORIZATION ]; then echo Status: 401 Unauthorized echo WWW-Authenticate: Basic realm="My Realm" fi echo echo HTTP_AUTHORIZATION=$HTTP_AUTHORIZATION # 如:user=test, pwd=test, HTTP_AUTHORIZATION="Basic dGVzdDp0ZXN0"
- 【纯lua实现Base64加密与解密】
- openwrt 的 nixio包,有base64decode函数。op22测试可用。
#!/usr/bin/lua require "os" local nixio=require "nixio" -- "test:test" -> "Basic dGVzdDp0ZXN0" local HTTP_AUTHORIZATION=os.getenv("HTTP_AUTHORIZATION") local auth_ok=false if HTTP_AUTHORIZATION ~= nil then local user_pwd=HTTP_AUTHORIZATION:sub(7) user_pwd=nixio.bin.b64decode(user_pwd) if user_pwd == 'test:test' then auth_ok=true end end if auth_ok == false then io.write("Status: 401 Unauthorized\n") io.write('WWW-Authenticate: Basic realm="My Realm2"\n') end io.write("Content-type: text/html;charset=utf8\n\n") print(HTTP_AUTHORIZATION, '<br>') print('auth_ok=',auth_ok, '<br>')
session管理。
- 暂时只有思路,未实测。// 假设,保存session文件的目录是
/tmp/myweb/
。目录路径自己定义。 - 检查 header 有没有 sessionid 的 cookie。
- 有。就从
/tmp/myweb/
中吧session文件读入table中。
如果session文件找不到。则,同下面的"无"。重新生成 sessionid。 - 无。通过
/dev/urandom
设备获取随机数,加上当前时间,混合生成 sessionid。
通过 cookie把 sessionid写入返回的 header中。
用 sessionid创建 lua的table,用于保存session数据。
- 有。就从
- 通过 读,改,增,删 这个table中的成员变量。实现对 session变量的读写。
- 在cgi结束前,把这个 session 的 table写入文件,在
/tmp/myweb/
目录中。 - 另外跑一个 cron任务,定时把长时间未修改的 session文件删除了。
openwrt中其他的 lua可用包
local jsonc=require "luci.jsonc"
local sys= require "luci.sys"
local http=require "luci.http"
local ip= require "luci.ip"
local xml= require "luci.xml"
local util=require "luci.util"
-
这些包的使用文档【luci api Reference】
lua 引用其他文件
- 把其他文件写成模块,放入系统目录,用 xx=require 引入
- 用 xx=assert(dofile()) 引入,不限位置。
- 用 xx=assert(loadfile()) 引入,
读写ini配置文件
openwrt的shell字符串通配符比较
- openwrt 的 shell支持这个写法
[[ ]]
。op22测试可用。CHECK="abc 123 456 789" fnd="456" if [[ "$CHECK" == abc* ]]; then echo found abc fi if [[ "$CHECK" == *"$fnd"* ]]; then echo found 456 fi
openwrt中ms毫秒级的sleep
三个方案,都不会占用 CPU时间。op22测试可用。
- 方案一,安装
opkg install coreutils-sleep
,约占32k的rom空间。
支持毫秒级精度的 sleep。定时误差就是启动 sleep进程的时间。 - 方案二,使用 lua的
nixio.nanosleep(seconds, nanoseconds)
函数,接受纳秒参数。
毫秒级精度应该 OK,纳秒级精度估计不行。定时误差就是启动 lua进程的时间。mt7620的小路由,启动 lua要20ms。
下面的例子,可以在 shell中执行./luasleep 2.5
延时 2s+500ms。#!/usr/bin/lua -- filename: luasleep if arg[1] == nil then local usage="\nUsage: %s s[.ms]\n s; seconds\n ms: microSecond 0-999\n" print(usage:format(arg[0]) ) os.exit() end local nixio=require "nixio" local ss,ms=math.modf(arg[1]) ms=ms *1000 --nixio.nanosleep(ss, ms*1000000) --因精度原因,有的数相乘后的结果值带小数,就会出错。例如xxx.99999,xxx.000001 nixio.nanosleep(ss, math.floor(ms*1000000))
- 方案三,使用 lua的
nixio.poll(fds, microseconds)
函数,接受毫秒参数。local nixio=require "nixio" nixio.poll({},6000) -- 6 sec
lua获取自身脚本所在的目录
- 比如文件
/root/abc/def/test.lua
。op22测试可用。local selfpath=debug.getinfo(1,'S').source:sub(2):match('^.*/') print(selfpath) -- "/root/abc/def/"或"def/"或"./" 相对路径,有"/"结尾 local selfname=debug.getinfo(1,'S').source:sub(2) local fs=require "nixio.fs" print(fs.dirname(selfname)) --相对路径,无"/"结尾 print(fs.realpath(fs.dirname(selfname))) --"/root/abc/def" 绝对路径,无"/"结尾 local nixio=require "nixio" print(nixio.getcwd()) -- 工作目录 current working directory
========== End