第 6-1 章 Linux httpd 服务基础配置

作者: Brinnatt 分类: ARM64 Linux 进阶架构 发布时间: 2022-01-21 13:50

目前最流行的 web 服务器软件叫做 httpd,httpd 还有一个俗称叫 apache,Apache 是一个软件基金会,httpd 也是这个软件基金会的一个项目。

httpd 是一个开源软件,市场应用非常广泛,功能也十分丰富。当然这就造成 httpd 体系庞大,结构臃肿,好在 httpd 是一个高度模块化软件,由核心(core)和模块(module)组成。这些模块大都是动态模块,因此可以随时加载。

本文主要针对 httpd 的基础配置和基本指令加以说明。

6.1、httpd/apachectl 命令

[root@arm64v8 ~]# httpd -h
Usage: httpd [-D name] [-d directory] [-f file]
             [-C "directive"] [-c "directive"]
             [-k start|restart|graceful|graceful-stop|stop]
             [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S] [-X]
Options:
  -D name            : 在< IfDefine name >中定义配置参数,当服务启动或者重启时执行命令或设置条件。
  -d directory       : 指定ServerRoot
  -f file            : 指定配置文件
  -C "directive"     : 指定在加载配置文件前要处理的指令(directive)
  -c "directive"     : 指定在加载配置文件后要处理的指令
  -e level           : 显示httpd启动时的日志调试级别
  -E file            : 将启动信息记录到指定文件中
  -v                 : 显示版本号
  -V                 : 显示编译配置选项
  -h                 : 显示帮助信息
  -l                 : 显示已编译但非动态编译的模块,即静态编译的模块
  -L                 : 显示静态模块可用的指令列表
  -t -D DUMP_VHOSTS  : 显示虚拟主机的设置信息
  -t -D DUMP_RUN_CFG : 显示运行参数
  -S                 : 等价于-t -D DUMP_VHOSTS -D DUMP_RUN_CFG。在调试如何解析配置文件时非常非常有用
  -t -D DUMP_MODULES : 显示所有已被加载的模块,包括静态和动态编译的模块
  -M                 : 等价于-t -D DUMP_MODULES
  -t                 : 检查配置文件语法
  -T                 : 不检查DocumentRoot,直接启动
  -X                 : 调试模式,此模式下httpd进程依赖于终端
  -k                 : 管理httpd进程,接受start|restart|graceful|graceful-stop|stop

apachectl 命令和 httpd 命令基本相同。httpd 接受的选项,apachectl 都接受。但 apachectl 还可以省略 "-k" 选项直接管理 httpd 进程。

  • apachectl [-k] start:按照默认路径,读取默认配置文件,并启动 httpd。
  • apachectl [-k] stop:关闭 httpd 进程。
  • apachectl [-k] restart:重启 httpd 进程。
  • apachectl [-k] graceful-stop:graceful stop,表示让已运行的 httpd 进程不再接受新请求,并给他们足够的时间处理当前正在处理的事情,处理完成后才退出。所以在进程退出前,日志文件暂时不会关闭,正在进行的连接暂时不会断开。
  • apachectl [-k] graceful:graceful restart,即 graceful-stop+start。
  • apachectl [-k] configtest:语法检查。

在 systemd 环境下,还可以使用 apacectl statussystemctl status httpd 查看 httpd 进程的详细信息。

6.2、配置文件规则和常见指令

httpd 的核心体现在配置文件,各种功能都通过配置文件来实现。使用 rpm 包安装的 httpd 默认配置文件为 /etc/httpd/conf/httpd.conf。可以使用 httpd -f config_path 指定要加载的配置文件。

配置文件中全是一些指令配置,每个指令都是某个模块提供的。以下是配置文件的一些规则:

  1. 指令生效方式是从上往下读取,这一点非常非常重要。很多指令的位置强烈建议不要改变,例如 Include conf.d/*.conf 指令建议不要移动位置。
  2. "#" 开头的行为注释行,只能行头注释,不能行中注释。
  3. 对大小写不敏感,但是建议指令名称采用 "驼峰式" 命名。例如 ServerRoot,DocumentRoot。
  4. 一行写不下的可以使用 "\" 续行,但是 "\" 后不能有任何字符,包括空格也不允许。
  5. 指令配置格式为 "Directive value",例如 "ServerRoot /etc/httpd",如果 value 中包含特殊字符或空格,则必须使用双引号包围。
  6. 由于可以通过 Include 指令包含其他配置文件,又支持各种路径的容器,所以在 httpd 启动时会先进行配置文件的合并。理解合并规则非常重要,具体见 配置文件合并规则

6.2.1、Listen 指令

设置监听套接字。设置方式很简单,包括以下几种情况:

# 监听两个端口
Listen 80
Listen 8000

# 监听套接字绑定在给定地址和端口上
Listen 192.170.2.1:80
Listen 192.170.2.5:8000

6.2.2、ServerRoot 指令

该指令设置 httpd 的安装位置,也就是常称之为的 basedir,在此目录下应该具有 module、logs 等目录。rpm 安装的 httpd 的 ServerRoot 默认为 /etc/httpd,编译安装的 ServerRoot 路径由 "--prefix" 选项指定,例如 /usr/local/apache。

[root@arm64v8 ~]# ll /usr/local/apache/
total 52
drwxr-xr-x  2 root root  4096 Oct 21 06:36 bin
drwxr-xr-x  2 root root  4096 Oct 21 06:36 build
drwxr-xr-x  2 root root  4096 Oct 21 06:36 cgi-bin
drwxr-xr-x  3 root root  4096 Oct 21 06:36 error
drwxr-xr-x  2 root root  4096 Oct 21 06:02 htdocs
drwxr-xr-x  3 root root  4096 Oct 21 06:36 icons
drwxr-xr-x  2 root root  4096 Oct 21 06:36 include
drwxr-xr-x  2 root root  4096 Oct 21 06:36 logs
drwxr-xr-x  4 root root  4096 Oct 21 06:36 man
drwxr-xr-x 14 root root 12288 Oct 21 06:02 manual
drwxr-xr-x  2 root root  4096 Oct 21 06:36 modules
[root@arm64v8 ~]#

这个指令很关键,安装好 apache 后一般不会去做任何修改,因为很多指令的路径以及相对路径都是基于此路径的。严格地说,除了网络路径,基本上所有本地文件系统类的路径只要不是绝对路径,相对路径都基于此路径展开

例如,当指定 "ServerRoot /usr/local/apache" 时,下面几个指令中描述的本地路径,等号前面的采用的都是相对路径,等号右边的都是他们等价的绝对路径写法。

DocumentRoot "htdocs"                    = DocumentRoot "/usr/local/apache/htdocs"
LoadModule dir_module modules/mod_dir.so = LoadModule dir_module /usr/local/apache/modules/mod_dir.so
ErrorLog "logs/error_log"                = ErrorLog /usr/local/apache/logs/error_log
Alias /net_path local_fs_path            = Alias /net_path /usr/local/apache/local_fs_path
Include conf.d/vhost.conf                = Include /usr/local/apache/conf.d/vhost.conf

但注意,容器 < Directory PATH > 的 PATH 一般设置为文件系统的绝对路径,因为它是路径匹配性质的。但它仍可以使用相对路径时,此时它相对的是根文件系统的 "/",而非 ServerRoot。

所以,这个指令强烈不建议做任何修改,修改是很简单,但是牵一发而动全身。

6.2.3、DocumentRoot 指令

如果说,ServerRoot 是 httpd 中本地文件相对路径的根,那么 DocumentRoot 就是网络路径相对路径的根。

顾名思义,DocumentRoot 是文档的根目录,这个文档的意思是展现在网络上的文档。使用 rpm 包安装的 httpd 的 DocumentRoot 默认值为 "/var/www",编译安装的 httpd,其 DocumentRoot 默认为 "PREFIX/htdocs",也就是 $ServerRoot/htdocs

设置 DocumentRoot 后,将需要在网络上访问的文件都放进此目录下即可。

例如,假设 httpd 所在主机 IP 为 192.168.0.88,DocumentRoot 使用默认的 /usr/local/apache/htdocs,那么下面几个 URL 中,左边的是浏览器中输入的值,右边的是其访问的服务器上的资源路径。

http://192.168.0.88/index.html         ==> /usr/local/apache/htdocs/index.html
http://192.168.0.88/index.php          ==> /usr/local/apache/htdocs/index.php
http://192.168.0.88/subdir/index.html  ==> /usr/local/apache/htdocs/subdir/index.html
http://192.168.0.88/subdir/index.php   ==> /usr/local/apache/htdocs/subdir/php

也就是说,DocumentRoot 的值对应的是 http://192.168.0.88/ 的 "/"。

6.2.4、DirectoryIndex 指令

该指令设置的是 "当搜索的 URL 中的路径使用了 "/" 结尾时,httpd 将搜索该指令所指定的文件响应给客户端"。也就是说,当 url 表示搜索的是目录时,将查找该目录下的 DirectoryIndex。

注意,很多时候如果没有给定尾部的 "/",httpd 的 dir_module 模块会自行加上 "/",当然,是否补齐尾随的 "/",也是可以控制的,见 DirectorySlash 指令。

DirectoryIndex 的设置格式为:

DirectoryIndex disabled | local-url [local-url]

例如,当设置 "DirectoryIndex index.html" 时,如果在浏览器中输入下面左边的几个 URL,httpd 将响应右边对应的文件。

http://192.168.0.88           ==> $DocumentRoot/index.html
http://192.168.0.88/newdir/   ==> $DocumentRoot/newdir/index.html

可以指定多个 index 文件,它们将按顺序从左向右依次查找,并返回第一个找到的 index 文件。例如:

<IfModule dir_module>
    DirectoryIndex index.php index.html /mydir/index.html
</IfModule>

当浏览器中输入 http://192.168.0.88/ 时,将首先搜索 index.php,如果该文件不存在,则再搜索 index.html,如果还找不到,则再找该目录的子目录下的文件 /mydir/index.html。但这不表示 http://192.168.0.88/mydir/ 会搜索 /mydir/index.html。

可以使用多个 DirectoryIndex 指令进行追加设置,它等价于单行设置多个值,例如下面的设置等价于 DirectoryIndex index.php index.html

DirecotryIndex index.php
DirectoryIndex index.html

如果要替换某个值,则直接修改或使用 disabled 关键字禁用其前面的 Directoryindex。例如禁用 index.php,只提供 index.html 的索引。

DirectoryIndex index.php
DirectoryIndex disabled
DirectoryIndex index.html

但注意,"disabled" 关键字必须独自占用一个 DirectoryIndex 指令,否则它将被解析成字面意思,也就是说将其当作一个 index 文件响应给客户端。

DirectoryIndex 指令可以设置在 Server、Virtual host、Location 和 Directory 上下文。所以,当设置在 location 或 Directory 容器中时,它将覆盖全局设置。例如,当 DocumentRoot 为 /usr/local/apache/htdocs 时:

DirectoryIndex index.php
<directory /usr/local/apache/htdocs/newdir>
    DirectoryIndex index.html
</directory>

# 或者

<location /newdir>
    DirectoryIndex index.html
</location>
  • 在输入 http://IP/newdir/ 时,将提供 index.html 而非 index.php。

当 DirectoryIndex 提供的索引文件都不存在时,将根据 Options 中的 Indexes 选项设置决定是否列出文件列表,除非是提供文件下载,否则出于安全考虑,这个选项是强烈建议关闭的。

例如以下设置为打开:

<directory /usr/local/apache/htdocs/newdir>
    Options Indexes
    DirectoryIndex index.html
</directory>

6.2.5、ServerName 和 ServerAlias

ServerName 用于唯一标识提供 web 服务的主机名,只有在基于名称的虚拟主机中该指令才是必须提供的。

也就是说,如果不是在基于名称的虚拟主机中,可以任意指定该指令的值,只要你认为它能唯一标识你的主机。但如果不设置该指令,那么 httpd 在启动时,将会反解操作系统的 IP 地址。

唯一标识主机的方式,也即 ServerName 的语法为:

ServerName {domain-name|ip-address}[:port]

例如,在主机 web.brinnatt.com 上提供了一个 httpd web 服务,如果还想使用 www.brinnatt.com 提供同样的服务,还想效率更高点,则在设置 DNS 别名后再配置:

ServerName www.brinnatt.com

ServerAlias 用于定义 ServerName 的别名。如果在定义 ServerName 之后再定义 ServerAlias,那么 ServerName 和 ServerAlias 没有任何区别。当然,为了区分基于名称的虚拟主机,还是必须要定义 ServerName。

例如,下面几个 ServerName 和 ServerAlias 是完全等价的。

<VirtualHost *:80>
  ServerName  server.example.com
  ServerAlias server server2.example.com server2
  ServerAlias *.example.com
  # ...
</VirtualHost>

6.2.6、Include 指令

在 httpd 启动时,首先会解析配置文件。httpd 支持 include 指令来包含其他文件,在解析配置文件时会进行配置合并。

支持通配符 "*"、"?""[]",但它们不能匹配斜线 "/",如有必要,它们会按照文件名的字母顺序依次进行加载。

如果 include 指令中指定包含一个目录,则会按照字母顺序加载该目录内的所有文件,这比较容易出错,因为有些时候会产生一些临时文件或非配置类的文件。

例如:

Include /usr/local/apache/conf/ssl.conf
Include /usr/local/apache/conf/vhosts/*.conf

可以使用绝对路径,也可以使用相对路径,如果使用相对路径,则它相对于 ServerRoot。

Include conf/ssl.conf
Include conf/vhosts/*.conf

如果 include 包含的文件不存在时,将报错。这时可以使用 IncludeOptional 指令进行加载,这表示存在则加载,不存在就算了。

例如下面的第一条指令中,如果 vhosts 下没有子目录,或者子目录中没有 ".conf" 文件都将失败,而第二条指令则不会。

Include conf/vhosts/*/*.conf
IncludeOptional conf/vhosts/*/*.conf

6.2.7、Define 和 UnDefine 指令

该指令用于定义参数或定义向后全局生效的变量。语法格式为:

Define param [value]

当只给定一个 param 时,表示定义一个参数,这个参数用于 < IfDefine param > 容器进行判断,只有定义了的参数 param,该容器才返回真,其内封装的指令才生效。它的等价行为是在 httpd 启动时(必须是启动时),使用 "-D" 选项定义参数。例如下面两个方法是等价的:

# startup command
shell> httpd -DMyName ......
# in config 
Define MyName

当给定了两个参数,即还指定了 value 时,将表示定义一个变量,该变量具有向后全局性。也就是说,定义在某个虚拟主机中的变量在后面的另一个虚拟主机中也有效。引用变量时,使用 ${var} 的方式。注意,变量名中不能包含冒号 ":"

例如:

<IfDefine !TEST>
  Define servername www.example.com
</IfDefine>

DocumentRoot "/var/www/${servername}/htdocs"

使用 UnDefine 指令则是取消 Define 定义的参数或变量。语法为 UnDefine param

6.2.8、VirtualHost 指令

无疑,这是最重要的指令之一。用于封装一组指令只作用于指定主机名或 IP 地址的虚拟主机上。

语法格式为:

<VirtualHost addr[:port] [addr[:port]] ...> ... </VirtualHost>

其中 addr 部分可以是以下几种情况:

  • 虚拟主机的 IP 地址
  • 虚拟主机 IP 地址对应的 FQDN(不推荐)
  • 字符 "*",匹配任意 IP 地址
  • 字符串 "_default_",是 "*" 的别名

例如:

<VirtualHost 10.1.2.3:80>
  ServerAdmin webmaster@host.example.com
  DocumentRoot "/www/docs/host.example.com"
  ServerName host.example.com
  ErrorLog "logs/host.example.com-error_log"
  TransferLog "logs/host.example.com-access_log"
</VirtualHost>

需要为虚拟主机指定 ServerName,否则它将会从主配置继承。对于基于名称的虚拟主机,ServerName 更是不可缺少,否则将继承操作系统的 FQDN。

当一个请求到达时,将按照最佳匹配进行主机匹配:通配的内容越少,优先级越高,也就越佳。例如 "192.168.0.88:80" 的优先级高于 "*:80"

如果基于名称的虚拟主机无法匹配上,则采用虚拟主机列表中的第一个虚拟主机作为响应主机。如果所有虚拟主机都无法匹配上,则采用从主配置段落中的主机,如果主配置段落中注释了 DocumentRoot,则返回对应的错误。

6.2.9、Options 和 AllowOverride 指令

Options 启用或禁用指定目录下的某些特性。有效值包括:All、None、ExecCGI、FollowSymLinks、Includes、IncludesNOEXEC、Indexes、MultiViews、SymLinksIfOwnerMatch。

不指定 options 时,默认为 all。一般除了提供下载服务会开启一个 Indexes 选项,其他选项都会关掉,即使用:

Options None

AllowOverride 指令用于控制是否读取 ".htaccess" 配置文件。

如何设置这个指令要看具体情况,有以下几种值,此外还可以设置为 all 和 none,表示启用、禁用所有特性。

  • AuthConfig:基于用户认证时设置该值,此时将可以使用 AuthGroupFile, AuthName, AuthType, AuthUserFile, equire 等认证相关指令。
  • FileInfo: 控制文档类型时使用该值,此时将可以使用 ErrorDocument, SetHandler,以及一些 URL 重写的指令。
  • Indexes:控制目录索引时使用该值,此时可以使用 AddIcon, DirectoryIndex。
  • Limit:是否允许使用 order、allow、deny 指令,这三个指令已经废弃,目前还存在是为了兼容老版本。

例如下面的指令使得在使用非认证类和索引控制类指令时,将产生服务器类的错误。

AllowOverride AuthConfig Indexes

6.2.10、Require 指令

参见 Require 指令

6.2.11、长连接相关指令

KeepAlive 指令用于开启和关闭长连接功能。

KeepAlive on/off

在没有开启长连接时,客户端每请求一个资源都需重新建立一次 TCP 连接,而使用了长连接后,客户端只需在最初请求一次 TCP 连接,之后就可以使用同一个 TCP 连接发送其他的 http 请求。

长连接的状态是指在服务端处理完某一个请求后,它立即进入长连接状态以保持 TCP 连接不断开,等待客户端再次发送请求。

但长连接自身的缺陷是会一直占用着连接不释放,所以必须得给出一个长连接的超时时间。这个超时时间由 KeepAliveTimeout 指令控制,进入长连接后如果在此时间间隔内客户端还没有发送新请求,则 TCP 连接自动断开。

如果在长连接状态下,客户端再次发送了请求,则服务端处理请求,并在处理完请求后又再次进入长连接状态并计算 KeepAliveTimeout。

此外,还可以通过指令 MaxKeepAliveRequests 控制每个长连接下的 TCP 连接的能接受的最大请求数。无疑,这个值应该设置的大一些,设置为 0 表示无限制。这个指令是从数量的角度控制长连接的 TCP 应该何时断开。

例如,在长连接超时时间内接受同一个客户端的 500 个请求才断开,然后该客户端再有新的请求只能重新建立 TCP 连接。

MaxKeepAliveRequests 500

6.3、容器类指令

路径和条件判断容器包括:

  • < Directory >、< DirectoryMatch >
  • < Files >、< FilesMatch >
  • < Location >、< LocationMatch >
  • < IfModule >
  • < IfDefine >
  • < IfVersion >
  • < if >
  • < elseif >
  • < else >

6.3.1、容器 < Directory > 和 < Files >

还包括它们的正则匹配容器 < DirectoryMatch >、< FilesMatch >。

< Directory > 容器的作用是 "对于匹配到的目录,封装一组指令,这些指令只作用于该目录以及它的子目录中的文件"。注意,< Directory > 容器通常都是用绝对路径,即 < Directory /PATH/to/DIR >,如果使用相对路径,则它相对于根文件系统的 "/"。例如 < directory newdir > 等价于 < directory /newdir >。

例如:

<Directory "/">
    AllowOverride none
    require all denied
</Directory>

<Directory "/usr/local/apache/htdocs">
    require all granted
</Directory>

第一个容器表示拒绝所有对 "/" 下内容的访问,包括子目录中的文件,这个根是根文件系统的根,而不是 ServerRoot。而第二个容器则表示允许 /usr/local/apache/htdocs 目录下文件的访问。

由此可以想象得出,出于安全考虑,应该总是先将父目录进行限制,再在需要放宽权限的子目录中指定特定的权限。正如上面的设置,将最顶级目录 "/" 完全限制,然后在小范围的 htdocs 目录中放行。

再看 < Files > 容器,它针对的是某个或某些特定的能被匹配上的文件。它匹配的范围是它所在的上下文。

例如,下面的指令如果写在 server 上下文,那么将对任意 private.html 文件拒绝。

<Files private.html>
    require all denied
</Files>

而如果将其写在 < directory > 容器中,则只对该目录容器中的所有 private.html 生效。由于 < directory > 会递归到子目录中,所以子目录中的 private.html 也会拒绝,但非 private.html 将被允许。

<Directory "/usr/local/apache/htdocs">
    require all granted
    <Files private.html>
        require all denied
    </Files>
</Directory>

< directory > 和 < files > 容器可以使用通配符,"*" 表示任意字符,"?" 表示任意单个字符,"[]" 表示范围,如 [a-z]、[0-9],但是这些通配符都不能匹配 "/"。所以要跨目录匹配时,必须显式指定各个目录的 "/" 符号。

例如,<directory /*/public.html> 无法匹配 /home/user/public.html,但 directory /home/*/public.html 可以匹配。

它还可以使用正则表达式匹配,只需使用一个 "~" 符号即可。这时和使用 < DirectoryMatch >、< FilesMatch > 是一样的,只不过 Match 类指令不需要使用 "~" 符号。

例如,下面的设置。其中后两个 Directory 容器是等价的。

# 匹配不区分大小写的gif/jpg/jpeg/png
<FilesMatch "\.(?i:gif|jpe?g|png)$">
    Require all denied
</FilesMatch>

<Directory ~ "^/usr/local/apache/htdocs/[0-9]{3}">
    DirectoryIndex digest.html
</Directory>

<DirectoryMatch "^/usr/local/apache/htdocs/[0-9]{3}">
    DirectoryIndex digest.html
</DirectoryMatch>

需要注意的是,httpd 采用的 pcre 库提供的 perl 兼容正则。以下是官方手册提供的一个示例,使用的命名捕获语法,它将匹配 /var/www/combined/ 目录下的一级子目录,但不进行递归。

将每个匹配到的结果保存到命名的分组 sitename 中,并通过环境变量 "MATCH_capturename" 进行引用,其中 capturename 必须转为大写字母,因为它就是这样赋值的。

<DirectoryMatch "^/var/www/combined/(?<sitename>[^/]+)">
    Require ldap-group cn=%{env:MATCH_SITENAME},ou=combined,o=Example
</DirectoryMatch>

目前已经不能使用未命名的后向引用,例如 $0,$1...。在 URL 重写时,正则语法至关重要,像 grep/sed/awk 中天然支持的基础正则和扩展正则语法虽然能解决大部分问题,但想要实现复杂的需求,只能使用语义丰富、完整的正则,如 pcre 提供的正则。

6.3.2、容器 < Location >

该容器和 < Directory >、< Files > 容器差不多,都是对满足匹配条件的路径封装一组指令,这些指令只生效于这些能匹配的路径。但是 < Location > 和 < Directory >、< Files > 最大的区别是:前者匹配的目标是 WebSpace,即匹配 URL 中的路径,而后两者匹配的是本地文件系统的路径。

例如,当设置下面的 location 容器时,将匹配 http://192.168.0.88/newdir/index.html

<Location "/newdir">
    ......
</Location>

location 支持三种匹配模式:

  • 精确匹配:location 的模式和 URL 中的路径部分精确对应。
  • 加尾随斜线:location 的模式中加了尾随斜线时,将匹配该目录里面的内容。
  • 无尾随斜线:location 的模式中没有尾随斜线时,将匹配该目录和目录里面的内容。

例如,下面两个容器,第一个将匹配 /private1、/private1/ 和 /private1/file.txt,但不能匹配 /private1other,而第二个将匹配 /private2/ 和 /private2/file.txt,但不能匹配 /private2 和 /private2other。

<Location "/private1">
    ......
</Location>

<Location "/private2/">
    ......
</Location>

location 和 sethandler 指令一起使用时很方便。例如,开启状态信息页面:

<Location "/server-status">
    SetHandler server-status
    Require all granted
</Location>

同样,除了支持 "*"、"?"、"[]" 的通配符匹配,还支持 "~" 和 LocationMatch 指令的正则匹配。方法见上面的 < Directory > 容器。

6.3.3、< IfDefine >、< IfModule > 和 < IfVersion > 条件判断

这三个容器都是条件判断容器,且都只在 httpd 启动时进行判断,判断为真,则封装在其内的指令生效,否则忽略。且都可以在条件前加一个 "!" 以实现条件的否定,而且都可以嵌套以实现更复杂的配置。

< IfModule > 容器是指当启动时加载了某模块时,该容器内的指令生效。可以是静态加载的模块,或者使用 LoadModule 指令加载的,但如果这样的话,加载对应模块的 LoadModule 指令必须在 < IfModule > 指令之前。例如:

LoadModule status_module modules/mod_status.so

<IfModule "status_module">
    <Location "/server-status">
        SetHandler server-status
        Require all granted
    </Location>
</IfModule>

< IfDefine param > 容器用于判断参数 param 是否已经定义,如果定义了,则条件为真,封装在其内的指令生效,否则忽略。加上感叹号则表示取反,例如 < IfDefine !param >。

那么如何定义参数呢?有两种方法:使用 httpd 命令的 "-D" 选项;使用 Define 指令。

例如,在使用 httpd 启动时,加上一个 "-D" 选项定义 MyName 参数。

httpd -DMyName ......

或者在配置文件中使用 Define 指令进行定义,但必须在 < IfDefine > 容器之前定义。例如:

Define MyName

< IfDefine > 可以进行嵌套。例如下面是官方的一个示例:

httpd -DReverseProxy -DUseCache -DMemCache ...

<IfDefine ReverseProxy>
  LoadModule proxy_module   modules/mod_proxy.so
  LoadModule proxy_http_module   modules/mod_proxy_http.so
  <IfDefine UseCache>
    LoadModule cache_module   modules/mod_cache.so
    <IfDefine MemCache>
      LoadModule mem_cache_module   modules/mod_mem_cache.so
    </IfDefine>
    <IfDefine !MemCache>
      LoadModule cache_disk_module   modules/mod_cache_disk.so
    </IfDefine>
  </IfDefine>
</IfDefine>

< IfVersion > 容器用于判断 httpd 的版本。例如:

<IfVersion >= 2.4>
    # this happens only in versions greater or equal 2.4.0.
</IfVersion>

6.3.4、< If >、< ElseIf > 和 < Else > 容器

意义不言自明。< If >...< /If > 判断表达式是否为真,如果为真,则封装在其内的指令生效;< ElseIf >...< /ElseIf > 作用于 < If >...< /If > 之后,而 < Else >...< /Else > 则作用于最后。

表达式的写法和 shell 脚本的表达式差不多,例如数值比较 "-eq"、"-gt",字符串比较 "=="、">=",以及其他一些表达式 "-z"、"-n"、"-f" 等,此外,它还支持正则匹配表达式 "~="、"!~"。具体相关函数、变量、表达式、语法等见 http://httpd.apache.org/docs/2.4/expr.html

例如:

# 请求首部没有Host字段时,该段指令将生效。
<If "-z req('Host')">
...
</If>

# 如果请求主机地址属于0/16,则if段生效,否则如果属于0/8,则elseif段生效,否则else生效
<If "-R '10.1.0.0/16'">
  #...
</If>
<ElseIf "-R '10.0.0.0/8'">
  #...
</ElseIf>
<Else>
  #...
</Else>

6.4、配置文件的合并规则

配置文件的段落以一种非常特殊的顺序生效。理解配置文件的合并规则非常重要,否则配置了半天可能发现根本不会生效。

以下是 5 个组类合并的顺序:

  1. < Directory > (正则匹配的容器除外)
  2. < DirectoryMatch > (以及 < Directory "~" >)
  3. < Files > 和 < FilesMatch > 同时处理
  4. < Location > 和 < LocationMatch > 同时处理
  5. < If >

此外,还需要注意的一些规则是:

  • 除了 < Directory > 容器,每个组以它们出现的顺序进行合并。例如一个 /foo 请求可以匹配 < Location "/foo/bar" > 和 < Location "/foo" >,它们都属于上面列出的第 4 组,所以对于这两个 Location 容器,谁配置在前面就匹配谁。
  • < Directory > 容器即上面的第一组处理的顺序是先处理路径 "短" 的,再处理路径长的。这里的短指的是离根文件系统的 "/" 越近就越短。由于这个组不包含正则匹配的表达式(即 < Directory ~ >),所以这里的 "短" 就代表它的路径表达式短。例如 < Directory "/var/web/dir" > 将优先于 < Directory "/var/web/dir/subdir" > 被处理。
  • 如果出现多个 < Directory > 的路径完全一样的极端情况,那么将按照出现顺序处理。
  • 使用 Include 指令包含的文件将被插入到该指令的位置,然后按规则进行处理。
  • < VirtualHost > 段落的配置将在外部对应的段处理完毕以后再处理,这样就允许虚拟主机覆盖主服务器的设置。
  • 当请求是由 mod_proxy 处理的时候,< Proxy > 容器将会在处理顺序中取代 < Directory > 容器的位置。

需要注意的是,配置文件中的指令都是由各个模块提供的,所以各指令是由各对于模块来解析、处理、合并的,配置文件的作用只不过是将各个模块的指令整合在一起方便定义。另外,上面定义的 5 个组别都是由 httpd 的核心模块提供的,因此它们才有处理顺序的要求。

当在运行时进行请求匹配,将先按照上面合并规则提供的顺序进行匹配,如果某个组中出现了能成功匹配请求的模块,将提升一次合并的层次,使得这次模块的匹配变为第三次匹配。

例如下面的配置使用了由 mod_headers 提供的 Header 指令用于设置 HTTP 的首部,如果请求 /example/index.html,那么最终设置的 CustomHeaderName 首部的值是什么呢?

<Directory "/">
    Header set CustomHeaderName one
    <FilesMatch ".*">
        Header set CustomHeaderName three
    </FilesMatch>
</Directory>

<Directory "/example">
    Header set CustomHeaderName two
</Directory>

首先按照前面提供的合并顺序匹配到 "/",这会初始化设置 CustomHeaderName 的值为 one,再匹配到 /example,CustomHeaderName 被设置为 two。最后分组中提供的指令 FilesMatch 匹配成功,提升一次合并的层次,这是第三次匹配,导致 CustomHeaderName 最终设置为 three。

下面的例子中,如果这些指令都对请求生效,它们将按照 "A > B > C > D > E" 的顺序生效。

<Location "/">
    E
</Location>

<Files "f.html">
    D
</Files>

<VirtualHost *>
<Directory "/a/b">
    B
</Directory>
</VirtualHost>

<DirectoryMatch "^.*b$">
    C
</DirectoryMatch>

<Directory "/a/b">
    A
</Directory>

D 和 E 无疑是最后生效的。再看三个 Directory 类的容器,对于 Directory 和 DirectoryMatch,前者先生效,所以 C 排在 A 和 B 后,对于 A 和 B,虚拟主机会在外部段落处理完后再处理,所以在 A 和 B 进行合并时,B 将覆盖 A,也即 A 先生效。所以顺序为 "A>B>C>D>E"。但如果将上面的 A 段落改为:

<Directory "/a/b">
    A
    <FilesMatch f.html>
        D1
    </Files>
</Directory>

那么最终的顺序为 "A>B>C>D>D1>E"。

以下示例则更有教育意义。尽管 Directory 设置了更严格的权限,但因为 Location 比 Directory 更后生效,它对所有访问都不做任何限制。也就是说,Directory 在这里的权限设置是完全多余的。所以说,理解配置文件的合并规则对写配置文件至关重要。

<Location "/">
    Require all granted
</Location>

# Whoops!  This <Directory> section will have no effect
<Directory "/">
    <RequireAll>
        Require all granted
        Require not host badguy.example.com
    </RequireAll>
</Directory>
标签云