openwrt_用lua_ucode编写动态网页.uHTTPd_独立于luci之外_php_python3
openwrt_用lua_ucode编写动态网页.uHTTPd_独立于luci之外_php_python3
转载注明来源: 本文链接 来自osnosn的博客,写于 2023-02-17.
参考
- 【Example of web interface using uHTTPd and Lua】
- 【Lua 5.1 Reference Manual】
openwrt, op18,op19,op21,op22 都是用的 lua-5.1.5 。 - 【ucode Documentation】
修改openwrt uhttpd 使用的 ssl 证书
- op21, op22, op24 测试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
CGI测试
- 不想安装 php-fpm 或者 python3,它们的体积都比较大,小路由器空间不太够。
- 不修改 openwrt的 uhttpd的配置。因为不想改变 luci的页面。
- uhttpd 的 root 目录是
/www/ - 在
/www/中,只支持静态html文件。不支持lua脚本。 - 在
/www/cgi-bin/中,支持各种可执行文件,包括lua脚本,包括shell脚本,ucode脚本。
脚本文件无后缀要求,需要设置执行权限chmod +x
- uhttpd 的 root 目录是
- 最简单的办法就是,创建
/www/cgi-bin/testchmod +x test- print() 和 io.write() 尽量不要混用,防止因缓存,导致输出顺序不正确。
test 内容如下。op22,op24测试可用。
注意: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")
CGI, lua
- 【Lua 5.1 Reference Manual】
openwrt, op18,op19,op21,op22,op23,op24 都是用的 lua-5.1.5 。
如果要操作 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,op24测试可用。
#!/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,op24测试可用。
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) --local util=require "luci.util" --有函数 util.urlencode() util.urldecode() --local lhttp=require "lucihttp" --有函数 lhttp.urlencode() lhttp.urldecode()
判断文件是否存在。
- 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,op24测试可用。
#!/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中lua版base64脚本】使用nixio库。
- 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管理
方法1
- 暂时只有思路,未实测。// 假设,保存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文件删除了。
方法2
- openwrt中的 ubus 中有 session 的实现。session保存在内存中。
- 【ubus session】
可以创建session_id,然后用这个id设置cookie。
可以销毁session,可以把变量保存到session中。 - 【35-Openwrt rpcd】【35-Openwrt rpcd】【Openwrt进程间通信-Ubus】【关于openwrt ubus框架中的rpcd插件提供的session服务】
- 例程,op24可用,(2026-03测)
【op-22.03/luci/http.lua】【lucihttp】#!/usr/bin/lua local os=require "os" local lhttp=require "lucihttp" local util=require "luci.util" -- 以下两个function抄自op22.03; 后续版本被删除了 function urldecode_params(url, tbl) local parser, name local params = tbl or { } parser = lhttp.urlencoded_parser(function (what, buffer, length) if what == parser.TUPLE then name, value = nil, nil elseif what == parser.NAME then name = lhttp.urldecode(buffer) elseif what == parser.VALUE and name then params[name] = lhttp.urldecode(buffer) or "" end return true end) if parser then parser:parse((url or ""):match("[^?]*$")) parser:parse(nil) end return params end function getcookie(name) return lhttp.header_attribute("cookie; " .. (os.getenv("HTTP_COOKIE") or ""), name) end -- ------------web登录 local cookie_name='my_cookie' local QUERY_STR=os.getenv("QUERY_STRING") local get_param=urldecode_params(QUERY_STR) local session_sid=getcookie(cookie_name) local staffid if session_sid ~= nil then if get_param['do']=='logout' then -- 退出登录 util.ubus("session","unset", { ubus_rpc_session= session_sid, keys= {'staffid', 'other' } }) end local buf=util.ubus("session","get", { ubus_rpc_session= session_sid, keys= {'staffid', 'other' } }) if buf == nil then session_sid=nil else --staffid=buf['values']['staffid'] staffid=buf.values.staffid end end local POSTLength = tonumber(os.getenv("CONTENT_LENGTH")) or 0 if (POSTLength > 0) then POST_DATA = io.read(POSTLength) end local post_param={} if os.getenv('REQUEST_METHOD')=='POST' then post_param=urldecode_params(POST_DATA) end if staffid ==nil then --post_param={"usr":"abc","usrpw":"123"}; if post_param['usr']=='abc' and post_param['usrpw']=='123' then if session_sid==nil then -- create新session local session=util.ubus("session","create", { timeout=110 }) session_sid=session['ubus_rpc_session'] io.write('Set-Cookie: '..cookie_name..'='..session_sid..'; path=/cgi-bin; SameSite=strict;\n') end util.ubus("session","set", { ubus_rpc_session= session_sid, values= {staffid= post_param.usr } }) --staffid=post_param['usr'] staffid=post_param.usr end end io.write("Content-type: text/html;charset=utf8\n\n") local HTTP_COOKIE=os.getenv("HTTP_COOKIE") or '' print('cookie:',HTTP_COOKIE,'<br>\n') print('---get---','<br>\n') for kk,vv in pairs(get_param) do print(kk,vv,'<br>\n') end print('---post---','<br>\n') for kk,vv in pairs(post_param) do print(kk,vv,'<br>\n') end print('----------','<br>\n') local REMOTE_ADDR=os.getenv("REMOTE_ADDR") print('REMOTE_ADDR:', REMOTE_ADDR,'<br>\n') print('session_sid:',session_sid,'<br>\n') print('staffid:', staffid,'<br>\n'); io.write(string.format([[ <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"> <style type="text/css"> body { background: white; font-family: arial, helvetica, sans-serif; } a { color: black; } .err { color: #000; background: #fdd } .done { color: #000; background: #cfc } </style> <title>mysite</title> </head> <body> <div style='margin:20px 0 0 10px'> <a href="?do=logout">退出登录</a><br> <form action="%s" method="POST"> user:<input type=text name=usr size=7> pwd: <input type=text name=usrpw size=7> <input type=submit>   <input type=reset> </form> <br> <p>查看</p> ]],os.getenv('SCRIPT_NAME')or './') )
执行shell命令
-
local sys=require("luci.sys") local buf=sys.exec("ip addr") print(buf)
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: milliSeconds 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, milliseconds)函数,接受毫秒参数。local nixio=require "nixio" nixio.poll({},6000) -- 6 sec
openwrt中随机sleep
- 0-3秒内的sleep
#!/usr/bin/lua require("math") require("nixio") --seed 包含 微秒。如果同时运行两个,应该rand会不同。 sec,usec=nixio.gettimeofday() --秒,微秒 seed=(sec%1000)*1000000 + usec math.randomseed(seed) rand=math.random()*1000 *3 --乘以3 ms=math.floor(rand) print(rand, ms) nixio.poll({},ms) --或者seed 用 os.time仅精确到秒,os.clock有微秒。 seed=os.time()+1000000*os.clock() - 0-3060ms 的sleep
#!/usr/bin/lua require("nixio") require("nixio.fs") local rand=nixio.fs.readfile("/dev/urandom",12) --取12个byte aa={string.byte(rand,1,12)} ms=0 for kk,vv in ipairs(aa) do print(kk,vv) ms = ms+vv end rndhex=nixio.bin.hexlify(rand) print(ms,rndhex) nixio.poll({},ms)
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
CGI, ucode
- 【ucode Documentation】
- 创建
/www/cgi-bin/test2chmod +x test2- test2 内容如下。op24测试可用。(2026-03测)
或者#!/usr/bin/ucode -T Content-type: text/html test <br> {% tm=localtime(); printf("%d-%02d-%02d_%02d:%02d:%02d \n", tm.year, tm.mon, tm.mday, tm.hour, tm.min, tm.sec); %}
或者#!/usr/bin/utpl Content-type: text/html test <br>#!/usr/bin/env utpl Content-type: text/html test <br>
ucode 代码注释
- 块注释,用
{# ... #}可以多行注释,但不能在{% ... %}中。官方文档中有说明。 - 行注释,用
// ...,{% ... %}代码块中使用。官方文档中没有说明。 - 字符串,可以用 双引号"",单引号'',反引号``。官方文档中没有说明。
print("abc"); print('abc'); print(`abc ${variable}`); //变量会被替换为值
打印对象中的"函数"和"变量"
-
import * as lucihttp from 'lucihttp'; printf('%.2J \n', lucihttp);
获取所有环境变量
printf('%.2J \n', getenv());
全局变量
printf('%.2J \n', modules;打印所有载入的模块。printf('%.2J \n', global);打印所有全局变量,内置函数。printf('%.2J \n', SCRIPT_NAME);当前执行的脚本的文件名。printf('%.2J \n', ARGV);执行脚本的命令行参数。
ucode 读取sqlite3
- 没找到方法。
获取GET提交的内容,是通过环境变量获取
- 操作和lua-cgi类似
获取POST提交的内容,是stdin获取
- 操作和lua-cgi类似
- urlencode(), urldecode()
import * as http from 'luci.http'; print(http.urlencode("df=sdf2345&hh=23#44"),'\n'); print(http.urldecode("df%3Dsdf2345%26hh%3D23%23%2544"),'\n'); // import {urlencode,urldecode} from 'lucihttp'
session管理
- openwrt中的 ubus 中有 session 的实现。session保存在内存中。
- 【ubus session】
可以创建session_id,然后用这个id设置cookie。
可以销毁session,可以把变量保存到session中。 - 例程, op24可用, (2026-03测)
ubus 管理的 session,自动管理超时(expires),expires每秒自动减1,当减到0时自动删除session。#!/usr/bin/utpl {% 'use strict'; import { stdin, stdout } from 'fs'; import {urldecode_params as http_urldecode_params} from 'luci.http'; const input_buffsize=2048; let input_available= +getenv('CONTENT_LENGTH') || 0; function read(len) { if (input_available==0) { stdin.close(); return null; } let chunk =stdin.read(min(input_available,len ?? input_buffsize,input_buffsize)); if (chunk==null) { input_available=0; stdin.close(); } else { input_available-=length(chunk); } return chunk; } function write(data) { return stdout.write(data); } //----http弹框认证 let HTTP_AUTHORIZATION=getenv('HTTP_AUTHORIZATION'); let auth_ok=false; //auth_ok=true; if (HTTP_AUTHORIZATION != null){ let user_pwd=substr(HTTP_AUTHORIZATION,6); user_pwd=b64dec(user_pwd); if (user_pwd == 'abc:def'){ auth_ok=true; } } if (auth_ok == false) { write("Status: 401 Unauthorized\n"); write('WWW-Authenticate: Basic realm="My Realm2"\n'); write("Content-type: text/html;charset=utf8\n\n"); write("401 Authorization Required\n"); exit(0); } //----网页登录 const cookie_name='my_cookie'; import { header_attribute } from 'lucihttp'; import { connect } from 'ubus'; function get_cookie(name) { return header_attribute(`cookie; ${getenv('HTTP_COOKIE') ?? ''}`, name); } let QUERY_STR=getenv('QUERY_STRING'); let get_param=http_urldecode_params(QUERY_STR); let session_sid=get_cookie(cookie_name); let staffid; if (session_sid !=null) { let ubus=connect(); if (get_param.do=='logout') { //----退出登录 ubus.call("session", "unset", { ubus_rpc_session: session_sid, keys: [ 'staffid','other' ] }); } let buf=ubus.call("session", "get", { ubus_rpc_session: session_sid, keys: [ 'staffid','other' ] }); ubus.disconnect(); if (buf ==null) { //session expired session_sid=null } else { staffid=buf.values.staffid; } } let POST_STR=read(input_available); let post_param={}; if (getenv("REQUEST_METHOD")=="POST") { post_param=http_urldecode_params(POST_STR); } if (staffid ==null) { //post_param={"usr":"myuser","usrpw":"123"}; if (post_param.usr=='myuser' && post_param.usrpw=='123') { let ubus=connect(); if (session_sid ==null) { //----创建新的session let session=ubus.call("session", "create", {timeout: 310 } ); session_sid=session.ubus_rpc_session; write(`Set-Cookie: ${cookie_name}=${session_sid}; path=/cgi-bin; SameSite=strict;\n`); } ubus.call("session", "set", { ubus_rpc_session: session_sid, values: {staffid: post_param.usr } }); ubus.disconnect(); staffid=post_param.usr; } } write("Content-type: text/html;charset=utf8\n\n"); let REMOTE_ADDR=getenv('REMOTE_ADDR'); print('<pre>'); printf('GET: %.2J\n', get_param); printf('POST: %.2J\n', post_param); printf('REMOTE_ADDR: %.2J\n', REMOTE_ADDR); printf('sessionid: %.2J\n', session_sid); printf('staffid: %.2J\n', staffid); print('</pre>'); %} <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"> <style type="text/css"> body { background: white; font-family: arial, helvetica, sans-serif; } a { color: black; } .err { color: #000; background: #fdd } .done { color: #000; background: #cfc } </style> <title>mysite</title> </head> <body> <div style='margin:20px 0 0 10px'> <a href="?do=logout">退出登录</a><br> <form action="{{getenv("SCRIPT_NAME")}}" method="POST"> user:<input type=text name=usr size=7> pwd:<input type=text name=usrpw size=7> <input type=submit>   <input type=reset> </form> <br> <p>查看</p>
ubus对 session做 get,set,unset操作时,expires自动复位到 timeout值。
使用 ubus的 session,建议不要设置 "acls"权限,会造成 ubus的 web接口权限泄漏,除非你明确知道此机制。
判断文件是否存在
- 用 fs.access()函数
import * as fs from 'fs'; isexists=fs.access("/path/file","f");
获取文件大小
- 用 fs.stat()函数
import * as fs from 'fs'; mystat=fs.stat("/path/file");
文件的读写锁
fs.file.lock()
Basic WEB 认证
- base64, 有
b64dec(),b64enc()函数。
引用其他文件
- 有
include()函数。
ms毫秒级的sleep
sleep(milliseconds)函数。- 随机数,
import { srand, rand } from 'math'; srand(seed); rand(a,b);
ucode获取自身脚本所在的目录
- 有
sourcepath()函数。
ucode 中调用lua
- 需要
opkg install ucode-mod-lua模块。 - 【contrib: introduce ucode-mod-lua#5818】
- op24可用, (2026-03测)
const lua = require("lua"); let vm = lua.create(); printf('%.2J \n', vm); //vm.get('_G'); //获取 lua的全局变量 //vm.set({}); //传递变量到 lua中 vm.set({ hello: function(...args) { print(`Got arguments: ${args}\n`); }, u_data: { bb: true, ff: 1.3, } }); vm.invoke("hello", true, 123, "Foo"); //调用 lua的函数 vm.eval('print("Hi", u_data.ff);'); //执行 lua的语句 vm.include("./xx.lua"); //执行lua脚本 - 可以考虑在lua中操作sqlite3.
配置到uhttpd中
- openwrt官方文档【uHTTPd Web Server Configuration】
lua
opkg install uhttpd-mod-lua
安装了/usr/lib/uhttpd_lua.so- 然后配置
/etc/config/uhttpd中 "main" 部分的配置项lua_prefix和lua_handler - 未测试。
ucode
opkg install uhttpd-mod-ucode
安装了/usr/lib/uhttpd_ucode.so- 然后配置
/etc/config/uhttpd中 "main" 部分的配置项ucode_prefix - 未测试。
php8
- 【OpenWrt 搭建php环境】,【OpenWrt搭建uhttpd+php+sqlite的环境】
- 配置文件
/etc/config/uhttpd中 "main" 部分的配置项option home /mnt/overlay/www option index_page index.php list interpreter ".php=/usr/bin/php-cgi" - 需要
opkg install php8-cgi - 未测试。
python3
- 同 php8
使用list interpreter配置 - 需要
opkg install python3-cgi - 未测试。
----end----
转载注明来源: 本文链接 https://www.cnblogs.com/osnosn/p/17131543.html
来自 osnosn的博客 https://www.cnblogs.com/osnosn/ .

浙公网安备 33010602011771号