Apache Websh 配置参数详解

Apache Websh 3.6.0b5 配置参数详解:httpd 层指令(LoadModule/AddHandler/WebshConfig)、websh.conf 解释器管理(web::interpmap/interpclasscfg)、脚本层 web::config 与 web::log 系列参数,逐项给官方默认值与实测回显

Apache WebshApache WebshTclhttpdApache2026/10/16

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.confhttpd 启动装载模块、绑定处理器扩展名、指定全局配置文件
解释器管理层/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 <类名> <属性> <值> 控制每个解释器类的生命周期。三个属性与官方默认值:

属性默认值含义
maxrequests1解释器处理多少个请求后销毁重建;0 表示不限
maxttl0解释器最大存活时长(秒);0 表示不限
maxidletime0解释器最大空闲时长(秒);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>

逐项说明(默认值均为实测值):

参数实测默认值说明
versionwebsh 3.6.0b5 (built: ...)只读,返回版本与编译时间戳
cmdparamcmd命令分发参数名;?cmd=other 中的 cmd 即由此定义(实测可分发,见 7.1 节)
timeparamtweb::cmdurl 附加的时间戳参数名
cmdurltimestamp1web::cmdurl 生成链接时是否附加时间戳(1 开启,配合 timeparam)
logsubst0日志消息中的 Tcl 变量/命令替换开关;0 关闭(消息原样输出,实测见 9.2 节)
safelog1日志安全模式(消息超长截断保护);1 开启
putxmarkupbraceweb::putx 系列识别的标记语法:brace({...})或 tag(<?...?>)
filepermissions0644web::request 等落盘文件的权限掩码(八进制)
uploadfilesize0允许上传的最大字节数;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 &lt;?web::put [clock format [clock seconds]]?&gt;.</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 集成使用指南》