1.2 模块化架构:核心与模块的分工 本节摘要:httpd 的可执行本体只负责"流程调度",真正干活的能力几乎全在模块里。本节拆解核心与模块的运行时分工、DSO 动态加载机制、请求处理阶段与钩子的对应关系,并给出"某个功能归哪个模块管"的检索方法。理解钩子机制,是理解后面所有配置指令为什么"长成那样"的钥匙。 学完本节你应当掌握什么 阅读完本节,你应当能够: 画出 httpd 运行时的"核心 + 模块"结构图; 解释静态编译模块与 DSO 动态模块的差别及取舍; 说出请求处理的主要阶段,以及模块如何通过钩子挂进这些阶段; 用模块清单命令查出本机加载了哪些模块; 根据一个配置指令名反查它所属的模块。
本节摘要:httpd 的可执行本体只负责"流程调度",真正干活的能力几乎全在模块里。本节拆解核心与模块的运行时分工、DSO 动态加载机制、请求处理阶段与钩子的对应关系,并给出"某个功能归哪个模块管"的检索方法。理解钩子机制,是理解后面所有配置指令为什么"长成那样"的钥匙。
阅读完本节,你应当能够:
一个反直觉的事实:如果你把所有模块都卸掉,httpd 几乎什么都干不了——不能重写 URL,不能做代理,连基本的目录索引都没有。它的本体核心只做三件事:监听端口、调度连接与请求的流转、在正确的时机调用模块。
这种设计不是为了省代码,而是为了演化。第 1 章讲过,httpd 活了三十年,期间 Web 从静态页面演进到动态应用、代理、HTTP/2。如果所有功能都焊死在核心里,每次演进都是一场大手术;而在"核心定流程、模块填内容"的结构下,HTTP/2 支持只是一个新模块,WAF 只是另一个模块,核心一行不用大改。
打个工业流水线的比方:httpd 核心是传送带的骨架与工位顺序,模块是站在每个工位旁的技工。骨架决定工件(请求)按什么顺序经过哪些工位;技工决定在每个工位上具体对工件做什么。你加装一个模块,就是往某个工位安排了一名新技工——传送带本身不动。

模块有两种进入 httpd 的方式。静态编译:模块代码直接编进可执行文件,启动即生效,少一次加载开销,但加减模块必须重新编译整个服务器。DSO(Dynamic Shared Object):模块编译成独立共享库,运行时按配置加载——这就是 1.3 版本引入后沿用至今的主流方式。
DSO 的日常操作就三条命令(以常见的发行版为例):
# 查看已加载的模块(编译进核心的标 static,动态加载的标 shared) httpd -M 2>&1 | head -20
典型输出片段:
core_module (static) so_module (static) http_module (static) mpm_event_module (static) authz_core_module (shared) dir_module (shared) rewrite_module (shared)
注意两点:so_module 自己必须是 static——它是加载其他所有动态模块的地基;MPM 通常也是 static,因为它参与进程启动,晚了就来不及接连接了。
# 启用 / 禁用一个 DSO 模块(以重写模块为例) a2enmod rewrite && systemctl reload apache2 # Debian 系 a2dismod rewrite && systemctl reload apache2
RedHat 系没有这对封装命令,做法是编辑主配置里的 LoadModule 行后 reload:
LoadModule rewrite_module modules/mod_rewrite.so # 注释掉这一行即禁用;路径相对于模块目录 ServerRoot
取舍很简单:模块加载有内存开销,每个空闲模块也要占一份符号表;更关键的是攻击面——启用 proxy 模块却没配好访问控制,等于给内网开了个免费跳板。生产机器上我的原则是"用不到的模块一律不加载",新机器上线前先 httpd -M 盘点一遍,和基线清单对照。
💡 关键直觉:看到陌生配置指令报错"Invalid command",第一反应不是查语法,而是
httpd -M | grep 模块名——九成是该指令所属的模块没加载。
核心怎么知道该在什么时候调用哪个模块?答案是钩子(hook)机制:核心在请求旅程的固定位置预留了"挂点",模块启动时向感兴趣的挂点注册回调。
主要阶段按请求流向排列:预读取 → 读取请求头 → URL 解析 → 访问控制 → 用户认证 → 授权 → MIME 类型识别 → 内容修复 → 内容生成 → 输出过滤 → 日志记录。听起来很多,但日常打交道的就是四个区块:
| 区块 | 旅程中的位置 | 代表模块 | 典型配置痕迹 |
|---|---|---|---|
| 入口区 | 请求头刚读完 | mod_setenvif、mod_headers | SetEnvIf、RequestHeader |
| 控制区 | 决定"放不放行" | mod_auth_basic、mod_authz_host | Require、Order 系 |
| 改写区 | 找内容之前 | mod_rewrite、mod_alias | RewriteRule、ProxyPass |
| 输出区 | 内容生成之后 | mod_deflate、mod_headers | SetOutputFilter、Header set |
钩子机制解释了一个新手常见困惑:为什么 RewriteRule 在 <Directory> 里和在虚拟主机里行为不一样?因为重写模块在不同阶段注册了两个钩子——虚拟主机级的重写发生在 URL 解析早期(此时还没映射到文件),目录级的重写发生在文件系统映射之后。阶段不同,能看到的上下文就不同。这条线索我们在第 3 章讲重写引擎时会反复用到。
每个配置指令都属于某个模块(少数属于核心)。给你一个指令名,反查模块的可靠方法是翻模块文档页看指令列表;反过来,排查时的顺序是:指令 → 模块 → 是否加载 → 语法作用域。
指令还有作用域属性:有的只能写在主配置(如 Listen),有的可以进虚拟主机,有的连 .htaccess 都能写(如大部分 RewriteRule),还有的必须挂在 <Directory> 这类容器里。AllowOverride 决定 .htaccess 里允许出现哪几类指令——这也是第 3 章的主菜之一。
最后把本节放回旅程图:请求到达后,先由 MPM 接住(核心区),随后每经过一个阶段,就由挂在该阶段的模块依次处理。下一节我们把这整条流水线完整走一遍。
httpd -M 是第一排查命令,static/shared 标识来源;💡 一句话记住本节:配置文件不是清单,是给流水线各工位下工单。