根本原因是Apache默认启用sendfile和mmap机制,使静态图片绕过mod_ext_filter;需配置EnableSendfile Off和EnableMMAP Off,并确保AddOutputFilter绑定正确MIME类型、脚本严格处理二进制流、加超时控制,否则易出错或性能崩溃。

mod_ext_filter 配置后图片没加水印,但 HTTP 状态码还是 200
根本原因通常是 mod_ext_filter 没真正触发——它只对响应体内容做流式过滤,而 Apache 默认对静态图片(.jpg、.png)启用 sendfile 机制,绕过过滤器直接由内核发文件。必须关掉 EnableSendfile Off 和 EnableMMAP Off 才能让数据流经 filter。
-
EnableSendfile Off放在全局或虚拟主机配置里,不是 filter 定义里 -
EnableMMAP Off同样要显式关闭,否则 mmap 仍会跳过用户态处理 - 确认
AddOutputFilter绑定的是正确的 MIME 类型,比如image/jpeg,而不是靠扩展名匹配(.jpg可能被识别为image/jpg) - 用
curl -I检查响应头是否含X-Filter-Applied: yes(可自行在脚本里加)来验证 filter 是否执行
写水印脚本时 stdout 无输出或图片变乱码
filter 脚本必须严格遵守二进制流处理规则:读取全部 stdin,原样或修改后写入 stdout,不能有任何额外输出(包括调试 echo、printf 到 stderr 一般没事,但某些 Apache 版本会截断)。Python 或 Perl 脚本尤其容易因编码、缓冲、BOM 或图像解析失败导致输出中断。
- 脚本开头加
#!/usr/bin/env python3 -u(-u强制 unbuffered,避免 stdout 缓存) - 用
sys.stdin.buffer.read()读原始字节,别用sys.stdin.read() - 水印逻辑出错时(如 PIL 打不开损坏图),必须 fallback 输出原始输入字节,否则浏览器收到截断流,显示“损坏的图像”
- 测试脚本时用
cat test.jpg | ./watermark.py > out.jpg独立验证,确保输入输出字节长度一致
并发高时水印延迟大甚至 Apache worker 卡死
mod_ext_filter 是同步阻塞式调用外部程序,每个请求都 fork 一个新进程。PIL 处理一张 2MB JPG 在普通服务器上可能耗 300–800ms,10 并发就吃满 CPU,worker 进程排队等 filter 返回,整体吞吐暴跌。
- 绝不能直接调用
convert或未优化的 Python/PIL 脚本应对线上流量 - 必须加超时控制:
ext_filter_timeout 5(单位秒),防止单张图 hang 住整个 worker - 更现实的方案是改用 Nginx + Lua(
lua-resty-image)或前置 CDN 水印,Apache 只作兜底或低频场景 - 如果坚持用,至少把水印逻辑编译成 C 工具(如用
libgd),启动开销和执行时间能降一个数量级
水印位置/透明度在不同尺寸图片上错位或不可见
脚本里硬编码坐标(如 (10, 10))或固定字体大小,在响应式页面里必然失效。filter 拿不到 URL 参数或 Referer,无法动态适配;而且 JPEG/PNG 的元数据(宽高)需完整解码才能获取,进一步拖慢速度。
- 不要在 filter 脚本里重新解析整图取尺寸——先用
identify -format '%wx%h' -(ImageMagick)快速探针,再决定是否处理 - 水印坐标建议按比例计算:例如右下角留 5% 边距,则
x = width * 0.95 - watermark_width - 透明度统一用 alpha=128(半透),别依赖 PNG 的 alpha 通道——JPEG 没 alpha,强行操作会崩溃
- 字体路径必须用绝对路径,且 Apache 用户(如
www-data)要有读取权限,相对路径几乎必挂
mod_ext_filter 不是 WebP 转换器,也不是实时美颜 API,它只是个脆弱的字节管道。真要稳定加水印,得接受它只适合低频、可控、可降级的场景——比如内部文档预览,而不是商品图服务。









