Apache Websh 配置参数详解
Apache Websh 3.6.0b5 配置参数详解:httpd 层指令(LoadModule/AddHandler/WebshConfig)、websh.conf 解释器管理(web::interpmap/interpclasscfg)、脚本层 web::config 与 web::log 系列参数,逐项给官方默认值与实测回显
Apache Websh 配置参数详解
目标:逐项说明 Apache Websh 3.6.0b5 的三层配置体系——httpd 层指令(LoadModule / AddHandler / WebshConfig)、
/etc/apache2/websh.conf层的解释器管理命令(web::interpmap / web::interpclasscfg / web::interpcfg)、脚本层的运行时参数命令(web::config)与日志系列命令(web::log / web::loglevel / web::logdest),每一项均给出官方默认值与 Ubuntu 22.04.5 + Apache 2.4.52 环境下的实测回显,包括错误配置时的真实故障现象。模块编译与挂载流程见《Apache Websh 在 Ubuntu 22.04 集成使用指南》,本文在其集成结果之上展开。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Apache httpd:2.4.52(apache2 2.4.52-1ubuntu4.25),MPM 为 prefork
- Tcl:8.6.12(线程版发行包)
- Websh:3.6.0b5(mod_websh 模块已装载,安装布局同《Apache Websh 在 Ubuntu 22.04 集成使用指南》第四章)
- 配置文件位置:
/etc/apache2/mods-available/websh.load、/etc/apache2/mods-available/websh.conf(httpd 层)、/etc/apache2/websh.conf(websh 全局 Tcl 配置) - 站点根目录:/var/www/websh,httpd 监听 80 端口
模块与页面就位的验证回显(实测):
apache2ctl -M | grep -i websh
websh_module (shared)
curl -s -o /dev/null -w "index -> %{http_code}\n" http://127.0.0.1/
index -> 200
二、配置体系总览
Websh 的配置分布在三个层级,作用对象与加载时机各不相同:
| 层级 | 文件/命令 | 生效时机 | 作用 |
|---|---|---|---|
| httpd 指令层 | mods-available/websh.load、websh.conf | httpd 启动 | 装载模块、绑定处理器扩展名、指定全局配置文件 |
| 解释器管理层 | /etc/apache2/websh.conf(Tcl) | httpd 启动、各子进程初始化时 | 定义请求文件到解释器类的映射、控制解释器生命周期 |
| 脚本/请求层 | 页面脚本中的 web::config、web::log 等 | 每次解释器初始化、每次请求 | 运行时参数(URL 参数名、标记语法、权限等)与日志行为 |
本章按此顺序逐层展开。第三层的大多数参数也可在 websh.conf 的解释器初始化器中预设(官方样例 otherhandler.ws3 正是这样设置 putxmarkup 的)。
三、httpd 配置指令详解
mod_websh 只向 httpd 暴露三个配置指令,全部在 /etc/apache2/mods-available/websh.conf 中:
cat /etc/apache2/mods-available/websh.conf
# mod_websh 处理器注册与全局配置入口
AddHandler websh .html
AddHandler websh .ws3
WebshConfig /etc/apache2/websh.conf
3.1 LoadModule websh_module
写在 websh.load 中,负责装载模块本体。模块文件名必须与 mod_websh 的导出符号匹配,websh_module 是固定名称:
cat /etc/apache2/mods-available/websh.load
# Apache Websh 3.6.0b5 模块加载
LoadModule websh_module /usr/lib/apache2/modules/mod_websh.so
装载结果通过模块清单验证:
apache2ctl -M | grep -i websh
websh_module (shared)
3.2 AddHandler websh .扩展名
把指定扩展名的请求交给 websh 处理器。可写多行绑定多个扩展名(本文绑定 .html 与 .ws3)。注意 AddHandler 只决定「谁处理」,不决定「怎么解释」——具体执行哪个解释器类由 websh.conf 的 web::interpmap 决定,两者必须同步扩展。实测只加 AddHandler 不扩 interpmap 的后果(第四章 4.2 节有完整过程):
页面返回 200 但正文为空,error.log 记录:
[Fri Oct 16 07:16:03.316953 2026] [:error] [pid 2685] Could not readWebInterpCode (id 0, class /etc/apache2/): couldn't read file "/etc/apache2/": illegal operation on a directory
💡 技术拓展:Apache 处理器的绑定除了 AddHandler 按扩展名匹配,还有
SetHandler强制目录级绑定。Websh 只提供了websh这一个处理器名,两种写法都可以触发它;但按扩展名粒度绑定(AddHandler)更贴合 Websh「一个脚本一个解释器类」的模型,也是官方样例 httpd.conf 采用的写法。
3.3 WebshConfig 全局配置文件
WebshConfig <文件路径> 指向一个纯 Tcl 文件(即 /etc/apache2/websh.conf),httpd 每个子进程初始化解释器池时读取执行。它是 web::interpmap、web::interpclasscfg 等命令的唯一入口。
错误路径实测:把 WebshConfig 指向不存在的文件,先看语法检查是否拦截:
sed -i "s|WebshConfig /etc/apache2/websh.conf|WebshConfig /etc/apache2/websh-nonexistent.conf|" /etc/apache2/mods-available/websh.conf
apache2ctl configtest
Syntax OK
configtest 显示 Syntax OK——WebshConfig 的文件内容与存在性不在 httpd 语法检查范围内,这一坑只能靠完全重启后的运行日志暴露。重启后 error.log 逐子进程报错:
apache2ctl -k stop
sleep 2
: > /var/log/apache2/error.log
apache2ctl start
tail -4 /var/log/apache2/error.log
[Fri Oct 16 07:15:58.224154 2026] [:error] [pid 2650] Could not init interp pool
[Fri Oct 16 07:15:58.224227 2026] [:error] [pid 2652] couldn't read file "/etc/apache2/websh-nonexistent.conf": no such file or directory
[Fri Oct 16 07:15:58.224286 2026] [:error] [pid 2652] Could not init interp pool
[Fri Oct 16 07:15:59.228899 2026] [:error] [pid 2648] [client 127.0.0.1:55692] invalid command name "<html>"\n while executing\n"<html>"
现象链条:子进程读不到全局配置 → 解释器池初始化失败(Could not init interp pool)→ 请求进来时回退为直接执行 HTML 原文,把 <html> 当 Tcl 命令报 invalid command name "<html>",页面 HTTP 状态仍是 200(index -> 200)但内容异常。排查 Websh 相关 200 异常页时优先核对 WebshConfig 路径。还原后一切正常:
sed -i "s|WebshConfig /etc/apache2/websh-nonexistent.conf|WebshConfig /etc/apache2/websh.conf|" /etc/apache2/mods-available/websh.conf
curl -s -o /dev/null -w "index -> %{http_code}\n" http://127.0.0.1/
index -> 200
四、web::interpmap:请求到解释器类的映射
web::interpmap 是一个由用户实现的 proc,输入请求命中的脚本文件路径,返回「解释器类」标识。解释器类决定了:该文件由哪个解释器执行、是否与其他文件共享解释器、以及 interpclasscfg 生命周期参数作用于谁。本文环境使用的实现(部署于 /etc/apache2/websh.conf):
sed -n "/proc web::interpmap/,/^}/p" /etc/apache2/websh.conf
proc web::interpmap {file} {
global webshroot
# other.html 用 <?...?> 标记语法
if {[string match */other.html $file]} {
return "$webshroot/conf/otherhandler.ws3"
}
# 其余 .html 用 {...} 片段语法,共享 htmlhandler 解释器
if {[string match *.html $file]} {
return "$webshroot/conf/htmlhandler.ws3"
}
# 每个 .ws3 脚本一个独立解释器类
if {[string match *.ws3 $file]} {
return $file
}
return ""
}
映射策略解读:
- 所有 .html 共享同一个
htmlhandler.ws3解释器类(wrapper 脚本,见 8.1 节),类内解释器常驻; - 每个 .ws3 文件自成一类,类名就是文件路径本身(
return $file),因此 myApp.ws3 的类名是/var/www/websh/myApp.ws3; - 返回空串表示「未映射」,实测后果见 3.2 节——类名解析失败,模块尝试把 server_root 当脚本读取并报错。
⚠️ 注意:
web::interpclasscfg的第一个参数(类名)必须与 interpmap 的返回值逐字一致。配置成/usr/local/share/websh3.6/htdocs/myApp.ws3之类与实际请求路径不符的字符串时,参数不会作用于实际解释器类,且不报任何错误——这是 Websh 配置中最隐蔽的一类问题。
五、web::interpclasscfg:解释器生命周期参数
web::interpclasscfg <类名> <属性> <值> 控制每个解释器类的生命周期。三个属性与官方默认值:
| 属性 | 默认值 | 含义 |
|---|---|---|
| maxrequests | 1 | 解释器处理多少个请求后销毁重建;0 表示不限 |
| maxttl | 0 | 解释器最大存活时长(秒);0 表示不限 |
| maxidletime | 0 | 解释器最大空闲时长(秒);0 表示不限 |
三个属性在 diag.ws3 诊断页的实测输出。诊断页源码(部署于 /var/www/websh/diag.ws3,创建命令见《Apache Websh 在 Ubuntu 22.04 集成使用指南》5.3 节):
cat /var/www/websh/diag.ws3
# mod_websh 运行时诊断页
web::command default {
web::put "<html><body><pre>"
web::put "classid = [web::interpcfg]\n"
web::put "numreq = [web::interpcfg numreq]\n"
web::put "starttime = [clock format [web::interpcfg starttime] -format %Y-%m-%dT%H:%M:%S]\n"
web::put "script = [web::config script]\n"
web::put "server_root = [web::config server_root]\n"
web::put "document_root = [web::config document_root]\n"
web::put "interpclass maxrequests = [web::interpclasscfg [web::interpcfg] maxrequests]\n"
web::put "interpclass maxttl = [web::interpclasscfg [web::interpcfg] maxttl]\n"
web::put "interpclass maxidletime = [web::interpclasscfg [web::interpcfg] maxidletime]\n"
web::put "</pre></body></html>"
}
web::dispatch
实测输出(curl -s http://127.0.0.1/diag.ws3):
<html><body><pre>classid = /var/www/websh/diag.ws3
numreq = 0
starttime = 2003616-12-18T16:19:36
script = /var/www/websh/diag.ws3
server_root = /etc/apache2
document_root = /var/www/websh
interpclass maxrequests = 1
interpclass maxttl = 0
interpclass maxidletime = 0
</pre></body></html>
diag.ws3 未被 interpclasscfg 配置,全部取默认值:maxrequests=1(每次请求销毁重建)、maxttl=0、maxidletime=0。而 /etc/apache2/websh.conf 对 myApp.ws3 类配置了 maxrequests 5,实测其行为差异:
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*" && \
curl -s http://127.0.0.1/myApp.ws3 | grep -o "This is request [0-9]*"
This is request 0
This is request 0
This is request 0
This is request 0
This is request 0
This is request 1
This is request 1
This is request 1
This is request 1
This is request 1
This is request 2
This is request 2
解读:12 次请求中计数器从 0 递增到 2 而不是每次归零——说明解释器在同一子进程内跨请求复用(若 maxrequests=1 则每页都显示 0,对比 diag.ws3 恒为 0 可证)。prefork 下多个子进程各持一套解释器,计数按子进程独立累加,因此 12 次请求分散在约 5 个子进程上(0 出现 5 次、1 出现 5 次、2 出现 2 次)。maxttl 与 maxidletime 未做长时实验,以官方文档语义(秒级时长阈值,0 不限)为准。
六、web::interpcfg:解释器运行时状态查询
web::interpcfg 在页面脚本内查询当前解释器状态,无参调用返回类名,带属性名返回对应值:
| 属性 | 含义 | 实测值 |
|---|---|---|
| (无参) | 当前解释器类名 | /var/www/websh/diag.ws3 |
| numreq | 当前解释器已处理的请求数 | 0(maxrequests=1 时每请求后销毁,恒为 0) |
| starttime | 解释器创建时刻 | 1792134956101788 |
⚠️ 注意:
starttime是微秒级时间戳,不是秒。直接clock format会得到 2003616-12-18 这样的异常年份(diag.ws3 实测输出starttime = 2003616-12-18T16:19:36);正确换算方式是先除以 1000000(1792134956101788 微秒 ≈ 1792134956 秒,对应 2026-10-16)。这是 3.6.0b5 在 64 位现代平台上的行为,属年代久远的实现遗留。
defaults.ws3 诊断页(完整源码如下,部署于 /var/www/websh/defaults.ws3)一并展示 interpcfg 的两个属性:
cat /var/www/websh/defaults.ws3
# web::config 运行时默认值诊断页
web::command default {
web::put "<html><body><pre>"
web::put "version = [web::config version]\n"
web::put "cmdparam = [web::config cmdparam]\n"
web::put "timeparam = [web::config timeparam]\n"
web::put "cmdurltimestamp = [web::config cmdurltimestamp]\n"
web::put "logsubst = [web::config logsubst]\n"
web::put "safelog = [web::config safelog]\n"
web::put "putxmarkup = [web::config putxmarkup]\n"
web::put "filepermissions = [web::config filepermissions]\n"
web::put "uploadfilesize = [web::config uploadfilesize]\n"
web::put "script = [web::config script]\n"
web::put "server_root = [web::config server_root]\n"
web::put "document_root = [web::config document_root]\n"
web::put "interpcfg starttime raw = [web::interpcfg starttime]\n"
web::put "interpcfg numreq = [web::interpcfg numreq]\n"
web::put "</pre></body></html>"
}
web::dispatch
七、web::config:运行时参数逐项详解
web::config <参数名> ?<值>? 查询或设置运行时参数;带值调用返回设置前的旧值。defaults.ws3 的实测输出是本节全部默认值的直接证据:
curl -s http://127.0.0.1/defaults.ws3
<html><body><pre>version = websh 3.6.0b5 (built: Oct 16 2026 07:03:10)
cmdparam = cmd
timeparam = t
cmdurltimestamp = 1
logsubst = 0
safelog = 1
putxmarkup = brace
filepermissions = 0644
uploadfilesize = 0
script = /var/www/websh/defaults.ws3
server_root = /etc/apache2
document_root = /var/www/websh
interpcfg starttime raw = 1792134956101788
interpcfg numreq = 0
</pre></body></html>
逐项说明(默认值均为实测值):
| 参数 | 实测默认值 | 说明 |
|---|---|---|
| version | websh 3.6.0b5 (built: ...) | 只读,返回版本与编译时间戳 |
| cmdparam | cmd | 命令分发参数名;?cmd=other 中的 cmd 即由此定义(实测可分发,见 7.1 节) |
| timeparam | t | web::cmdurl 附加的时间戳参数名 |
| cmdurltimestamp | 1 | web::cmdurl 生成链接时是否附加时间戳(1 开启,配合 timeparam) |
| logsubst | 0 | 日志消息中的 Tcl 变量/命令替换开关;0 关闭(消息原样输出,实测见 9.2 节) |
| safelog | 1 | 日志安全模式(消息超长截断保护);1 开启 |
| putxmarkup | brace | web::putx 系列识别的标记语法:brace({...})或 tag(<?...?>) |
| filepermissions | 0644 | web::request 等落盘文件的权限掩码(八进制) |
| uploadfilesize | 0 | 允许上传的最大字节数;0 表示禁止上传 |
| script | /var/www/websh/defaults.ws3 | 只读(mod_websh 注入),当前请求的脚本文件路径 |
| server_root | /etc/apache2 | 只读(mod_websh 注入),httpd 的 ServerRoot |
| document_root | /var/www/websh | 只读(mod_websh 注入),httpd 的 DocumentRoot |
另有 copyright(只读版权串)、reset(恢复全部参数默认值)两个子命令,以及官方参考手册记载的 encryptchain/decryptchain(web::cmdurl 加密链接的命令链,默认值 web::encryptd/web::decryptd,未在本环境单独改值实测)。mod_websh 注入的 script/server_root/document_root 三个键是纯模块环境下才有的,独立 websh 解释器中为空。
7.1 cmdparam 与命令分发联动实测
cmdparam=cmd 的实际效果:?cmd=other 直接命中 myApp.ws3 里定义的 web::command other 命令块:
curl -s "http://127.0.0.1/myApp.ws3?cmd=other" | grep "<h2>"
<h2>Other page</h2><p>Back to <a href="/myApp.ws3?XDZuRD2bXPt9z4oiyM8gpaBH88HaeVw7F">main</a> page</p><p>This is request 0 in this specific interpreter</p>
而 web::cmdurl 生成的链接参数是加密 token(默认 encryptchain 处理),实测同样可达:
curl -s "http://127.0.0.1/myApp.ws3?XDZuRD2mxrPchT6QNqDtxaCLDENuE8V" | grep "<h2>"
<h2>Other page</h2><p>Back to <a href="/myApp.ws3?XDZuRD2bXPt9z4oiyM8gpaBH88HaeVw7F">main</a> page</p><p>This is request 0 in this specific interpreter</p>
两次实测请求计数均为 0,也侧面印证解释器在请求间被销毁重建(maxrequests 未达 5 前仍可能因子进程轮转而重置)。token 每次生成都不同(加密含随机成分),因此防篡改但不能当会话标识用。
八、页面标记与处理器脚本
8.1 官方处理器脚本(htmlhandler / otherhandler)
interpmap 返回的类名指向「处理器脚本」,脚本决定该类如何渲染页面。官方安装包自带两个样例:
cat /usr/local/share/websh3.6/conf/htmlhandler.ws3
# sample html wrapper for mod_websh
web::initializer {
# this code is only run once per interpreter
# usually you load/source your common source for all pages here
}
# process the actual HTML file
web::putxfile [web::config script]
cat /usr/local/share/websh3.6/conf/otherhandler.ws3
# sample html wrapper for mod_websh (using tag syntax)
web::initializer {
# this code is only run once per interpreter
# setup markup to be "<? ... ?>" instead of default "{ ...}"
web::config putxmarkup tag
}
# process the actual HTML file
web::putxfile [web::config script]
要点:web::initializer 块只在解释器创建时执行一次(适合加载公共代码),web::config putxmarkup tag 在类初始化器中改写标记语法——这就是三层配置互相配合的官方范式;web::putxfile 读取「当前请求的脚本文件」(web::config script)并执行其中的 putx 标记。
8.2 两种标记语法与转义实测
brace 语法页面(index.html)源码与渲染结果:
grep -n "web::put" /var/www/websh/index.html
11:<p>Time on your server is currently {web::put [clock format [clock seconds]]}.<br>
12:Note that the above line is written as:<pre>Time on your server is currently \{web::put [clock format [clock seconds]]\}.</pre>
curl -s http://127.0.0.1/ | grep -A1 "Time on your server"
<p>Time on your server is currently Fri Oct 16 07:28:21 UTC 2026.<br>
Note that the above line is written as:<pre>Time on your server is currently {web::put [clock format [clock seconds]]}.</pre>
</p>
tag 语法页面(other.html)渲染结果(<?...?> 被求值,页面中的字面示例以 HTML 实体呈现):
curl -s http://127.0.0.1/other.html | grep "Time on your server"
<p>Time on your server is currently Fri Oct 16 07:07:34 UTC 2026.<br>
Note that the above line is written as:<pre>Time on your server is currently <?web::put [clock format [clock seconds]]?>.</pre>
putxmarkup 的取值就 brace/tag 两种,切换粒度是解释器类(在类初始化器中设置)或页面脚本(页面内直接 web::config 设置后对本次请求生效)。
九、日志参数详解(web::log / web::loglevel / web::logdest)
Websh 的日志由三个命令协同:web::log <级别> <消息> 发消息;web::loglevel 控制放行哪些消息;web::logdest 决定消息写到哪。级别过滤采用「标签.级别」过滤器语法,- 前缀表示「该级别及更严重」。
9.1 写入 Apache error.log 的完整实验
实验页 logtest2.ws3(部署于 /var/www/websh/logtest2.ws3,源码如下;两个实验页在实验结束后已从站点删除):
cat > /var/www/websh/logtest2.ws3 <<'EOF'
# web::loglevel + web::logdest(apache 插件) 实验页
web::command default {
web::put "<html><body><p>log test 2 done</p></body></html>"
web::loglevel add *.-debug
web::logdest add *.-debug apache
web::log debug "WEBLOG-DEBUG via apache dest"
web::log info "WEBLOG-INFO via apache dest"
web::log error "WEBLOG-ERROR via apache dest"
}
web::dispatch
EOF
curl -s -o /dev/null -w "logtest2 -> %{http_code}\n" http://127.0.0.1/logtest2.ws3
logtest2 -> 200
error.log 中三条消息全部落盘(apache 插件会在消息上叠加 httpd 的时间戳/pid 前缀,websh 自身格式为 日期 [pid] user.级别: 消息):
grep WEBLOG /var/log/apache2/error.log
[Fri Oct 16 07:17:30.053867 2026] [:error] [pid 2706] [client 127.0.0.1:59718] 10/16/26 07:17:30 [2706] user.debug: WEBLOG-DEBUG via apache dest\n
[Fri Oct 16 07:17:30.053888 2026] [:error] [pid 2706] [client 127.0.0.1:59718] 10/16/26 07:17:30 [2706] user.info: WEBLOG-INFO via apache dest\n
[Fri Oct 16 07:17:30.053892 2026] [:error] [pid 2706] [client 127.0.0.1:59718] 10/16/26 07:17:30 [2706] user.error: WEBLOG-ERROR via apache dest\n
⚠️ 注意:
apache是 logdest 的一个插件名,仅 mod_websh 可用(独立 websh 解释器没有该通道)。漏配 logdest 时消息无处可写——web::log 不报错但不落盘,这是日志排障时的第一个检查点。
9.2 级别过滤对照实验
把 loglevel 收紧为 *.-error(仅放行 error 及以上),同页面结构改为三条消息:
cat > /var/www/websh/logtest3.ws3 <<'EOF'
# 仅放开 error 级别的对照页
web::command default {
web::put "<html><body><p>log test 3 done</p></body></html>"
web::loglevel add *.-error
web::logdest add *.-debug apache
web::log debug "WEBLOG3-DEBUG should-be-suppressed"
web::log info "WEBLOG3-INFO should-be-suppressed"
web::log error "WEBLOG3-ERROR should-appear"
}
web::dispatch
EOF
curl -s -o /dev/null -w "logtest3 -> %{http_code}\n" http://127.0.0.1/logtest3.ws3
grep WEBLOG3 /var/log/apache2/error.log
logtest3 -> 200
[Fri Oct 16 07:17:30.062132 2026] [:error] [pid 2708] [client 127.0.0.1:59720] 10/16/26 07:17:30 [2708] user.error: WEBLOG3-ERROR should-appear\n
debug 与 info 两条被过滤,仅 user.error 落盘,与 *.-error 语义完全一致。日志命令族还包括 web::logfilter(细粒度标签过滤)与 logdest 的文件/syslog 通道,本环境以 apache 插件为主验证;logsubst=0(见第七章)意味着消息内的方括号命令不会被执行替换,日志注入风险低。
十、生产配置模板
以下模板是本文实测环境的完整 /etc/apache2/websh.conf,可直接用于小型站点(对应 httpd 层配置见第三章开头三行):
cat /etc/apache2/websh.conf
# mod_websh 全局配置(由 httpd 的 WebshConfig 指令加载)
set webshroot /usr/local/share/websh3.6
# 请求的脚本文件 -> 解释器类 映射
proc web::interpmap {file} {
global webshroot
# other.html 用 <?...?> 标记语法
if {[string match */other.html $file]} {
return "$webshroot/conf/otherhandler.ws3"
}
# 其余 .html 用 {...} 片段语法,共享 htmlhandler 解释器
if {[string match *.html $file]} {
return "$webshroot/conf/htmlhandler.ws3"
}
# 每个 .ws3 脚本一个独立解释器类
if {[string match *.ws3 $file]} {
return $file
}
return ""
}
# htmlhandler 解释器类常驻(0 = 不限请求数)
web::interpclasscfg "$webshroot/conf/htmlhandler.ws3" maxrequests 0
# myApp.ws3 解释器类每 5 个请求重建一次
web::interpclasscfg /var/www/websh/myApp.ws3 maxrequests 5
模板要点与调优方向:
- 扩展名兜底:interpmap 最后
return "",未匹配的请求按 3.2 节实测会失败,新增 AddHandler 扩展名时务必同步增加分支; - 类名一致性:interpclasscfg 的类名来自 interpmap 返回值,
.ws3映射用return $file时类名即站点内实际路径(/var/www/websh/myApp.ws3); - 常驻与重建:纯标记页面建议 maxrequests 0(常驻,性能最好);脚本类页面按内存与隔离需求给 maxrequests/Ttl 有限值,防解释器状态泄漏累积;
- 日志接入:在需要日志的类初始化器中加
web::loglevel add *.-debug与web::logdest add *.-debug apache,生产可收紧为*.-error。
十一、常见问题
Q1:apache2ctl configtest 通过,重启后页面 200 但内容异常?
WebshConfig 文件不参与 httpd 语法检查,路径错误只会在重启后的 error.log 暴露(couldn't read file ...、Could not init interp pool、invalid command name "<html>",见 3.3 节实测链)。
Q2:web::interpclasscfg 配置了 maxrequests 却「没生效」?
核对类名与 web::interpmap 返回值是否逐字一致(含路径大小写)。类名不匹配时参数静默失效,diag 页 interpclass maxrequests 显示的仍是默认值 1。
Q3:页面输出为空、error.log 报 Could not readWebInterpCode ... illegal operation on a directory?
AddHandler 绑定的扩展名没有对应的 interpmap 分支,类名解析为 server_root 目录。补映射分支即可(见 3.2 节实测)。
Q4:web::log 打了日志但 error.log 什么都没有?
三查:是否配置了 web::logdest ... apache(缺省无落盘通道,消息静默丢弃);loglevel 是否放行该级别;级别过滤器语法是否为「标签.级别」(如 *.-debug)。
Q5:web::interpcfg starttime 格式化出乱码年份?
该值是微秒时间戳,需除以 1000000 再交给 clock format(见第六章实测说明)。
Q6:页面里的 {...} 被意外求值了?
putx 标记内容都是要执行的 Tcl 表达式;要显示字面量请用 \{ 转义(见 8.2 节实测对照)。反之若希望页面不被求值,就不要让该扩展名进入 websh 处理器。
十二、参考链接
- Websh 官方归档(源码与样例配置):https://archive.apache.org/dist/tcl/websh/source/
- 源码包内参考手册:doc/quickref.xml、doc/html/(configuration.html、logging.html、Apache_module_specific_commands.html)
- 官方样例:/usr/local/share/websh3.6/conf/(htmlhandler.ws3、otherhandler.ws3、httpd.conf、websh.conf)
- 编译与集成全流程:《Apache Websh 在 Ubuntu 22.04 集成使用指南》
- 同族方案对照:《Apache rivet 在 Ubuntu 22.04 集成使用指南》