1.2 模块化架构:核心与模块的分工


文档摘要

1.2 模块化架构:核心与模块的分工 本节摘要:httpd 的可执行本体只负责"流程调度",真正干活的能力几乎全在模块里。本节拆解核心与模块的运行时分工、DSO 动态加载机制、请求处理阶段与钩子的对应关系,并给出"某个功能归哪个模块管"的检索方法。理解钩子机制,是理解后面所有配置指令为什么"长成那样"的钥匙。 学完本节你应当掌握什么 阅读完本节,你应当能够: 画出 httpd 运行时的"核心 + 模块"结构图; 解释静态编译模块与 DSO 动态模块的差别及取舍; 说出请求处理的主要阶段,以及模块如何通过钩子挂进这些阶段; 用模块清单命令查出本机加载了哪些模块; 根据一个配置指令名反查它所属的模块。

1.2 模块化架构:核心与模块的分工

本节摘要:httpd 的可执行本体只负责"流程调度",真正干活的能力几乎全在模块里。本节拆解核心与模块的运行时分工、DSO 动态加载机制、请求处理阶段与钩子的对应关系,并给出"某个功能归哪个模块管"的检索方法。理解钩子机制,是理解后面所有配置指令为什么"长成那样"的钥匙。

学完本节你应当掌握什么

阅读完本节,你应当能够:

  1. 画出 httpd 运行时的"核心 + 模块"结构图;
  2. 解释静态编译模块与 DSO 动态模块的差别及取舍;
  3. 说出请求处理的主要阶段,以及模块如何通过钩子挂进这些阶段;
  4. 用模块清单命令查出本机加载了哪些模块;
  5. 根据一个配置指令名反查它所属的模块。

为什么本体要"什么都不会"

一个反直觉的事实:如果你把所有模块都卸掉,httpd 几乎什么都干不了——不能重写 URL,不能做代理,连基本的目录索引都没有。它的本体核心只做三件事:监听端口、调度连接与请求的流转、在正确的时机调用模块。

这种设计不是为了省代码,而是为了演化。第 1 章讲过,httpd 活了三十年,期间 Web 从静态页面演进到动态应用、代理、HTTP/2。如果所有功能都焊死在核心里,每次演进都是一场大手术;而在"核心定流程、模块填内容"的结构下,HTTP/2 支持只是一个新模块,WAF 只是另一个模块,核心一行不用大改。

打个工业流水线的比方:httpd 核心是传送带的骨架与工位顺序,模块是站在每个工位旁的技工。骨架决定工件(请求)按什么顺序经过哪些工位;技工决定在每个工位上具体对工件做什么。你加装一个模块,就是往某个工位安排了一名新技工——传送带本身不动。

图解:核心与模块的运行时结构

图 1-2 运行时结构:核心骨架与挂载的模块

图 1-2 运行时结构:核心骨架与挂载的模块

DSO:像装插件一样装模块

模块有两种进入 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 接住(核心区),随后每经过一个阶段,就由挂在该阶段的模块依次处理。下一节我们把这整条流水线完整走一遍。

本节要点回顾

  • 分工哲学:核心只管流程调度,功能全部住在模块里,这是三十年演化的结构保障;
  • 两种装载:静态编译进核心 vs DSO 动态加载,MPM 与 so 模块必须静态;
  • 模块盘点httpd -M 是第一排查命令,static/shared 标识来源;
  • 钩子机制:模块向固定阶段注册回调,指令行为差异的根源常在阶段不同;
  • 四大区块:入口、控制、改写、输出,对应日常 90% 的配置场景;
  • 安全原则:不用的模块不加载,模块即攻击面。

💡 一句话记住本节:配置文件不是清单,是给流水线各工位下工单。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U