Apache源代码全景分析(第1卷):体系结构与核心模块
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.3 指令相关概念

3.3.1 指令概述

由于Apache只定义了一些配置的框架和配置段规则,因此Apache配置文件的结构通常很容易理解。每个可用的指令及指令的参数并不是由Apache核心决定的,而是由模块完全决定并实现和控制的。因此,一般情况下,Apache 配置文件的结构可以使用如下的语法片段进行描述:

  configuration              ::=   directive*
  directive                  ::=   section-directive | simple-directive
  section-directive          ::=   section-open configuration section-close
  section-close              ::=   “<”directive-name directive-argument*”>”
  simle-directive            ::=   directive-name directive-argument*
  directive-name             ::=   “directory” | “documentroot”|…
  directive-argument         ::=   …

换句话说,一个Apache配置文件可能是一个空文件,或者是包含了一个或多个配置指令,每个指令包含指令名称及指令所需要的参数。指令的名称唯一标识该指令本身,指令参数差异性则很大,参数类型、参数的个数都不尽相同,下面是一个指令片断:

  ……
  ServerRoot "C:/Program Files/Apache Group/Apache2"
  TimeOut 30
  <Directory "C:/Program Files/Apache Group/Apache2/manual">
        Options Indexs
        <Files *.html>
            SetHandler type-map
        </Files>
  </Directory>
  ……

从上面的片断可以看出,指令只是用于控制Apache的简单命令而已,Apache从配置文件中读取这些指令,然后执行相应的操作从而执行这些指令。通过使用指令,Apache 管理员可以控制整个Web服务器的行为。由于Apache中提供了种类繁多的指令,这些指令使得Apache成为一个高度可配置的Web服务器。

Apache的指令可以分为两种:简单指令及配置段指令。配置段指令都是被包含在“<…>”中的指令,比如<Directory>…</Directory>。配置段指令总是会包含其余的指令。

尽管从以上内容可以看到Apache指令的语法非常复杂,但实际上非常简单。当进行指令处理的时候,Apache 将逐行地读取这些配置指令,如果某行不是空行(即不匹配正则表达式“^[\t]*$”),同时也不是一个注释行(不匹配正则表达式“^[\t]*#.*$”),那么Apache将该行的第一个单词视为指令字,后面的单词全部算作参数。如果某行以“\”结尾,则下一行是上一行的继续。

因此,Apache配置文件中指令的规则可以概括如下。

■ 使用UNIX路径法则:在所有路径中使用“/”代替DOS下“\”作为路径的分隔符。

■ 所有的注释行以“#”开始,同时注释行必须在一行结束,如果一行注释容不下,下一行必须继续以“#”开始。

■ 配置文件对大小写不敏感。但建议对非关键字均小写,而关键字则使用匈牙利方法,比如ServerRoot、TypesConfig等。

■ 每行只能配置一个参数,配置的基本格式为:

参数 参数值

■ 如果指令过长,不能够在一行中完整地放置,此时需要分割成为多行,各个行之间用 “\” 进行组合。如果使用“\”字符,那么在反斜杠和行的末尾不能存在任何内容和字符,包括空格或水平制表符。

■ 系统将忽略配置文件中多余的空白字符。

3.3.2 指令参数

参数类型

从原则上讲,指令的参数可以是任何的字符串,只要指令处理函数能够理解即可。不过对于一些常用的指令参数,Apache 中有一些默认的规定。一般指令名称后面可以跟一个或多个用空格分开的参数。如果参数中有空格,则必须用双引号括起来,用方括号“[]”括起来的是可选的参数。如果一个参数可以取多个值,则各个可能的值用“|”分开。应该原样输入的文字使用默认的字体,而必须按实际情况加以替换的可变部分会加强显示。使用可变参数个数的指令以“...”结尾,以示最后一个参数可以重复。

指令的参数类型非常多,以下列出很常用的部分。

1. URL

一个完整的包括类型、主机名和可选路径名的统一资源引用名,如http://www.example.com/path/to/file.html

2. URL-path

即url中类型和主机名之后的部分,如/path/to/file.html. url-path是表示资源在网络空间而不是在文件系统中的位置。

3. file-path

即文件在本地文件系统中相对于根目录的路径,如/usr/local/apache/htdocs/path/to/file.html.除非指定了其他的值,不以斜杠开头的file-path将被视为对ServerRoot的相对路径。

4. directory-path

即目录在本地文件系统中相对于根目录的路径,如 /usr/local/apache/htdocs/path/to/。

5. filename

即不带路径信息的文件名,如file.html。

6. regex

正则表达式,是对文本匹配模式的描述。指令的定义中会说明应该使用什么regex。

7. extension

一般是指filename中最后一个“.”号后面的部分。但是,Apache可以辨认多个文件后缀,如果filename含有多个“.”,则第一个“.”后面由每个“.”分隔开的部分都是此文件的后缀。比如filename,file.html.en有两个后缀.html和.en。在Apache指令中指定extension时,可以有也可以没有前导的“.”,而且不区分大小写。

8. MIME-type

一种用一个主格式类型和一个副格式类型并用斜杠分隔的描述文件格式的方法,如text/html、img/jpeg等。

9. env-variable

这是Apache配置进程中定义的环境变量的名称。 注意,它不一定与操作系统中的环境变量相同。比如:

  SetEnvIf Referer "^http://www.example.com/" local_referal
  SetEnvIf Referer "^$" local_referal
  <Directory /web/images>
    Order Deny,Allow
    Deny from all
    Allow from env=local_referal
  </Directory>

上面的代码就使用了环境变量local_referal,使用的时候必须通过env=xxxx进行指定。

参数默认值

如果该指令有默认值(即,如果你没有在配置中明确指定,那么Apache网站服务器会设置一个特定的值,并认为它是你设置的),会在此处说明。如果没有,则会指明是“None”。注意,此处的默认值并不一定与服务器发行版中默认的httpd.conf中该指令的取值相同。

3.3.3 指令上下文

上下文介绍

配置文件中的各个指令的作用范围是不一样的,可以分为主配置、虚拟主机配置,局部指令,以及条件指令。默认情况下,配置文件中的指令是作用于整个服务器的,比如前面示例中的ServerRoot和TimeOut指令,它们的作用范围则是针对整个服务器而言的,但并不是所有的指令都这样,有些指令只是针对某个特定的目录,文件或URL 地址。通常情况下,我们将这类指令称之为局部指令,这类指令总是嵌在相关的配置指令段中,比如<Directory>、<DirectoryMatch>、<Files>、<FilesMatch>、<Location>及<LocationMatch>。比如上面示例片断<Directory “C:/Program Files/Apache Group/Apache2/manual”>…</Directory>内部的指令仅对目录C:/Program Files/Apache Group/Apache2/manual中的文件产生作用。

还有一些指令我们称之为“条件指令”,它们并不是针对某个目录,而是在特定的条件下才会产生效果,比如<IfDefine>、<IfModule>及<IfVersion>等。

局部指令和条件指令都是在<…>…</…>之间,我们将这两种指令称之为配置段指令。

一个指令所能影响的范围及它产生效果的条件,我们称之为指令的上下文,在用户使用任何一个核心指令之前,了解指令能够使用的上下文环境是一件非常重要的工作,换句话说,你必须能够知道指令的作用上下文或指令的范围。

主服务器上下文(Server Config

如果指令上下文是主服务器,那么它能够作用的范围将是配置文件中容器配置段之外的所有范围,即可以出现在httpd.conf、srm.conf及access.conf,但却不能出现在<Directory>配置指令片断中。该指令也不能出现在.htaccess文件中。主服务器上下文通常是指全局上下文。

如果某个配置是针对主服务器上下文的,那么我们称该配置为“主服务器配置”。

虚拟主机上下文

Apache服务器允许同时支持多个虚拟主机,这些虚拟主机通过IP地址或通过IP地址和端口或通过IP地址及Host域进行区分。每一个虚拟主机通过<VirtualHost>配置段进行区分。<VirtualHost>配置段内的所有的指令都是针对该虚拟主机。

虚拟主机配置段中的配置我们称之为“针对虚拟主机的配置”。主服务器上下文和虚拟主机上下文我们统称之为全局上下文。

局部上下文(Local Config

局部上下文通常是指某个虚拟主机、某个目录、某个URI,以及某个Location,这种上下文之间的关系如图3-4所示。

图3-4 配置指令上下文

从图3-4可以看出,局部上下文可以分为两大类,一种是直接通过局部配置段指定,比如<Directory>和<Location>,另外一种则是通过.htaccess文件进行指定。

最常用的配置段是针对文件系统和网络空间特定位置的配置段。首先必须理解文件系统和网络空间这两个概念的区别,文件系统是指操作系统所看见的磁盘视图,比如,在 Unix文件系统中,Apache 会被默认安装到/usr/local/apache2,在 Windows 文件系统中,Apache会被默认安装到“C:/Program Files/Apache Group/Apache2”(注意:即使是在Windows中, Apache始终用正斜杠而不是反斜杠作为路径的分隔符)。相反,网络空间是网站被Web服务器发送及被客户在浏览器中所看到的视图。所以,如果<Location>中的路径为/dir/,那么在 Apache 采用默认安装路径的情况下,对应 Unix 文件系统的本地路径则为/usr/local/apache2/htdocs/dir/。由于网页可以从数据库或其他地方动态生成,因此,网络空间无须直接映射到文件系统。

1. 文件系统容器

<Directory>和<Files>指令与其相应的正则表达式版本(<DirectoryMatch>和<FilesMatch>)一起作用于文件系统的特定部分,<Directory>配置段中的指令作用于指定的文件系统目录及其所有子目录,.htaccess文件可以达到同样的效果。下例中,/var/web/dir1 及其所有子目录被允许进行目录索引。

  <Directory /var/web/dir1>
      Options +Indexes
  </Directory>

<Files>配置段中包含的指令总是应用于特定的文件,而无论这个文件实际存在于哪个目录,指定的文件可以是普通的文件名称。另外,可以使用<FilesMatch>指定正则表达式,是正则表达式的文件名称。下例中的配置指令如果出现在配置文件的主服务器段,则会拒绝对位于任何目录下的private.html的访问。

  <Files private.html>
      Order allow,deny
      Deny from all
  </Files>

<Files>和<Directory>段的组合可作用于文件系统中特定目录下的特定文件。下例中的配置会拒绝对/var/web/dir1/private.html、/var/web/dir1/subdir2/private.html,/var/web/dir1/subdir3/private.html等任何/var/web/dir1/目录下private.html的访问。

  <Directory /var/web/dir1>
      <Files private.html>
          Order allow,deny
          Deny from all
      </Files>
  </Directory>

2. 网络空间容器

<Location>指令与其相应的正则表达式版本(<LocationMatch>)一起作用于网络空间的特定部分。<Location>或<LocationMatch>中包含的指令通常应用于特定的 URL 或它的一部分,URL可以是普通的URL地址,也可以是正则表达式格式的URL。

如果指令的作用范围仅仅限于.htaccess文件,那么该指令的上下文应该是属于针对目录级别的。当Apache处理HTTP请求从而遍历文件系统的时候进行读取并将指令作用于对应的目录。该上下文也被细分为五种子上下文,这些上下文在 httpd.conf 中的 AllowOverride指令允许的时候发生作用。

下例中的配置会拒绝对任何以“/private”开头的URL路径的访问,比如:http://yoursite. example.com/private、http://yoursite.example.com/private123、http://yoursite.example.com/private/dir/file.html等所有以“/private”开头的URL路径。

  <Location /private>
      Order Allow,Deny
      Deny from all
  </Location>

<Location>指令与文件系统无关,下例演示了如何将特定的URL映射到Apache内部的处理器mod_status,而并不要求文件系统中确实存在server-status文件。

  <Location /server-status>
      SetHandler server-status
  </Location>

<Directory>、<Files>和<Location>指令可以使用类似C标准库中fnmatch的外壳通配符。符号“*”匹配任何字符串,“?”匹配任何单个的字符,“[seq]”匹配seq序列中的任何字符,符号“/”不为任何通配符所匹配,所以不能显式使用。

这些指令都有一个正则的配对指令,<DirectoryMatch>、<FilesMatch>和<LocationMatch>可以使用与perl一致的正则表达式,以提供更复杂的匹配。但是还须注意下文配置的合并中有关使用正则表达式会如何作用于配置指令的内容。

下例使用非正则表达式的通配符来改变所有用户目录的配置:

  <Directory /home/*/public_html>
      Options Indexes
  </Directory>

下例使用正则表达式一次性拒绝对多种图形文件的访问:

  <FilesMatch \.(?i:gif|jpe?g|png)$>
      Order allow,deny
      Deny from all
  </FilesMatch>

如果某个配置是针对特定的目录、文件或URI,那么我们称该配置为目录配置。

3. 什么情况下使用

选择使用文件系统容器还是网络空间容器其实很简单:当指令作用于文件系统时总是会使用<Directory>或<Files>;而当指令作用于不存在的文件系统对象时就用<Location>,比如由一个数据库生成的网页。

不要试图使用<Location>去限制对文件系统中对象的访问,因为许多不同的网络空间路径可能会映射到同一个文件系统目录,导致你的访问限制被突破。比如:

  <Location /dir/>
      Order allow, deny
      Deny from all
  </Location>

上述配置对 http://yoursite/example.com/dir 请求的确会起作用,但是设想在一个不区分大小写的文件系统中,这个访问限制会被 http://yoursite.example.com/DIR 轻易突破。而<Directory>指令才会真正作用于对这个位置的任何形式的请求。有一个例外,就是 Unix 文件系统中的符号连接。符号连接可以是一个目录出现在文件系统中的多个位置。<Directory>指令能不经过重置路径名而跟随符号连接,因此,如果对系统的安全性要求较高,则应该使用Options指令禁止对符号连接的跟随。

不要认为使用大小写跟随的文件系统无关紧要,因为有很多方法会将不同的网络空间路径映射为同一个文件系统路径,所以,能使用文件系统容器时就应该使用。但是也有一个例外的情况,就是把访问限制放在<Location>配置段中可以很安全地作用于除了某些特定URL以外的所有URL。

4. .htaccess中的隐含上下文

除了上面显式的指令上下文之外,上下文之间还包含了隐含的上下文关系,包括:

■ .htaccess 文件上下文 AuthConfig 和 Limit 通常总是包含服务器配置<Directory>、<Files>和<Location>配置段。

■ .htaccess文件上下文Options、FileInfo及Indexs总是包含所有针对服务器的配置,即整个httpd.conf。

除此之外,<Location>和<Files>上下文中允许的指令与<Directory>中的指令一样对待。

条件上下文

Apache 中允许设定的某些指令在特定的条件下才产生效果。这三种上下文主要是指<IfDefine>、<IfModule>及<IfVersion>。

<IfDefine>容器中的指令只有在httpd命令行中设定了特定的参数后才有效。下例中,只有当服务器用 httpd -DClosedForNow 方式启动时,所有的请求才会被重定向到另一个站点:

  <IfDefine ClosedForNow>
      Redirect / http://otherserver.example.com/
  </IfDefine>

<IfModule>容器很相似,但是其中的指令只有当服务器启用特定的模块时才有效(或是被静态地编译进了服务器,或是被动态装载进了服务器),注意,配置文件中该模块的装载指令LoadModule行必须出现在此容器之前。这个容器应该用于无论特定模块是否安装,配置文件都是否能正常运转的场合,而不应该用于容器中的指令在任何情况下都必须生效的场合,因为它会抑制类似“模块没找到”之类的有用出错信息。

下例中,MimeMagicFiles指令仅当mod_mime_magic模块启用时才有效。

  <IfModule mod_mime_magic.c>
      MimeMagicFile conf/magic
  </IfModule>

<IfVersion>指令与<IfDefine>和<IfModule>很相似,但是其中的指令只有当正在执行的服务器版本与指定的版本要求相符时才有效。这个模块被设计用于测试套件,以及在一个存在多个不同httpd版本的大型网络中需要分别针对不同版本使用不同配置的情况。

  <IfVersion >= 2.1>
      # 仅在版本高于 2.1.0版的时候才生效
  </IfVersion>

<IfDefine>、<IfModule>、<IfVersion>都可以在条件前加一个“!”以实现条件的否定,而且都可以嵌套以实现更复杂的配置。

上下文嵌套关系

Apache 中各个上下文之间所能影响的范围并不是相同的,相反,它们之间有着严格的包含关系,通常各个上下文之间的包含关系从大到小如图3-5所示。

图3-5 配置段上下文嵌套关系

从图 3-5 中可以看出,包含范围大小依次为<VirtualHost>、<Directory>或<Location>、<Files>及<Limit>。因此,各种上下文在嵌套中必须遵循下面的嵌套规则。

■ <Directory>配置段不允许出现在<Limit>、<Location>、<Files>,以及其余的<Directory>配置段之间。

■ <Location>配置段不允许出现在<Limit>、<Directory>、<Files>,以及其余的<Location>配置段之间。

■ <Files>配置段不允许出现在<Limit>、<Directory>、<Location>,以及其余的<Files>配置段之间,只能出现在全局配置或<Directory>配置段中。

■ <Directory>和<Location>配置段不允许出现在.htaccess文件中,但是<Files>则允许出现。

更加详细的嵌套关系可以用表3-1进行描述。

表3-1

从语义上看,允许在<Directory>段中使用的指令当然也可以在<DirectoryMatch>、<Files>、<FilesMatch>、<Location>、<LocationMatch>、<Proxy>和<ProxyMatch>段中使用,但是有几个例外:

■ AllowOverride指令只能出现在<Directory>段中。

■ Options中的FollowSymLinks和 SymLinksIfOwnerMatch只能出现在<Directory>段或.htaccess文件中。

■ Options指令不能用于<Files>和<FilesMatch>段。

上下文合并和继承

Apache 中允许同一个指令在多个地方出现,但是这会导致一个问题,即到底以哪个指令的配置为最终结果呢?Apache 中有两种处理方式:完全覆盖和继承合并。对于所有的配置段,各个配置段会按照特定的顺序依次生效,这种生效次序会对配置指令的处理结果产生重大的影响,生效的顺序依次为:

■ <Directory>(除了正则表达式)和.htaccess同时处理;如果允许,.htaccess的设置会覆盖<Directory>的设置。这意味着如果同一个指令出现在<Directory>及该目录下的.htaccess中,则以.htaccess中的结果为最终结果。

■ <DirectoryMatch>和<Directory ~>同时处理。

■ <Files>和<FilesMatch>同时处理。

■ <Location>和<LocationMatch>同时处理。

除了<Directory>,每个组都按照它们在配置文件中出现的顺序依次被处理,而<Directory>配置段,会按字典顺序由短到长被依次处理。例如,<Directory /var/web/dir>会优先于<Directory/var/web/dir/subdir>被处理。如果有多个指向同一个目录的<Directory>段,则按它们在配置文件中的顺序被依次处理。用Include指令包含进来的设置被视为按原样插入到Include指令的位置。

位于<VirtualHost>段中的配置段在外部相对应的段处理完毕以后再处理,这样就允许虚拟主机覆盖主服务器的设置。

而各个配置段相同指令之间的覆盖继承关系如下。

■ 如果两个指令位于同一个层次,则后面的指令将覆盖前面的指令。

■ 指令的作用域越小,则它的优先级别越高。

这是一个假设的演示合并顺序的例子。如果这些指令都起作用,则会按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>

在下面这个更具体的例子中,无论在<Directory>段中加了多少访问限制,由于<Location>段将会被最后处理,从而会允许不加限制地访问服务器,可见合并的顺序是很重要的,千万小心!

  <Location />
      Order deny,allow
      Allow from all
  </Location>
      # Woops! This<Directory> section will have no effect
  <Directory />
      Order allow,deny
      Allow from all
      Deny from badguy.example.com
  </Directory>

通常情况下各个指令完全独立,这意味着指令的次序并不重要,大多数指令都不会影响服务器怎样解释其余的指令,不过也有例外,有些指令相互之间还是具有依赖性的,比如LoadModule 指令,当配置文件读取每一行指令的时候,都会尝试在当前已经加载到服务器的模块中找到该指令。如果你试图在加载实现指令的模块之前就使用指令,那么服务器将会输出错误信息,并且退出。

指令位置

从前面的讨论中我们已经知道了指令上下文的概念。一个指令能够出现的位置,即它能出现在哪些上下文环境中,我们称之为指令位置控制。Apache 中提供了指令位置字段的概念来控制一个指令所允许出现的上下文位置。位置字段主要用于控制各个指令在配置文件中允许出现的位置,其中包括三种:顶层位置、目录区和虚拟主机区。如果服务器发现一个指令出现在不允许出现的地方,比如 LoadModule 只允许出现在顶层位置,但发现其在<Directory>中出现,服务器将报错,同时打印出错信息退出。另外位置字段还将控制指令是否允许在文件.htaccess中使用。

对于指令位置字段,Apache中提供了下面几个控制选项:

  #define OR_NONE           0
  #define OR_LIMIT          1
  #define OR_OPTIONS        2
  #define OR_FILEINFO       4
  #define OR_AUTHCFG        8
  #define OR_INDEXES        16
  #define OR_UNSET          32
  #define ACCESS_CONF       64
  #define RSRC_CONF         128
  #define EXEC_ON_READ      256
  #define OR_ALL(OR_LIMIT|OR_OPTIONS|OR_FILEINFO|OR_AUTHCFG|OR_INDEXES)

对于上面的选项,Apache又分成两类:普通配置文件说明选项和.htaccess文件说明选项。ACCESS_CONF、RSRC_CONF、EXEC_ON_READ三类属于普通配置文件位置段描述标志,而其余的则专属于.htaccess位置段描述标志。

1. 普通配置文件位置选项

普通配置文件位置选项包括下面三种:

■ ACCESS_CONF:该位置字段允许指令出现在Directory或Location区间以内的顶级配置文件中,因此该选项通常用来对Apache中的目录或文件进行控制。

■ RSRC_CONF:该选项允许指令出现在 Directory 或 Location 区间以外的顶级配置文件中,当然也可以出现在VirtualHost区间中,因此该选项通常用来对Apache服务器或虚拟主机进行某些控制。

■ EXEC_ON_READ:该选项是Apache 2.0中新增加的。在Apache 1.3中,对指令的处理是边读边执行的,而在Apache 2.0中并不是这样。Apache 2.0首先预处理配置文件,将所有的配置指令读取到一个树型结构中,树的每个结点为 ap_directive_t类型,我们称之为配置树。一旦建立起配置树,Apache才会遍历并处理所有指令。通常情况下,这种处理方式会很好,因为延后处理可以控制模块之间的依赖性。比如,线程化的MPM需要在定义MaxClients指令之前就定义ThreadsPerChild指令。MPM 不会强求管理员处理这种情况,即使在配置文件中 ThreadsPerChild 定义在MaxClients之后,Apache也会在分析之前在配置树中进行次序调整。

不过延后处理也不是完美无缺的,有时可能出现问题。比如,Include 通常用于在配置文件中读取另外一个配置文件。如果不能立即读取到配置文件,那么第二个配置文件中的配置指令将不可能生成到配置树中。解决的方法就是在读取到Include 指令的时候取消滞后策略,而是立即执行该指令。EXEC_ON_READ选项可以强制服务器在将指令从配置文件中读取之后立即执行。

通过前面的分析可以看出,如果某个指令可能会改变配置树,该指令就应该为 EXEC_ON_READ,不过所谓的改变配置树不是指改变配置树的顺序,而是改变配置树的信息。如果想要改变配置树的次序,那么应该在预先配置阶段进行或在处理配置指令时完成这样的工作。

这三个标志通常在指定指令的时候就必须预先设置好,而不能通过配置文件进行配置。比如<Directory>,其指定的时候为:

  AP_INIT_RAW_ARGS("<Directory", dirsection, NULL, RSRC_CONF,
    "Container for directives affecting resources located in the specified "
    "directories"),

<Directory>指令只允许出现在<Directory>和<Location>指令之外。

而另一个指令 AllowOverride 则只允许出现在<Directory>和<Location>之内,因此它的标志位为ACCESS_CONF:

  AP_INIT_RAW_ARGS("AllowOverride", set_override, NULL, ACCESS_CONF,
    "Controls what groups of directives can be configured by per-directory "
    "config files"),

如果一个指令既可以出现在<Directory>和<Location>之内,也可以出现在<Directory>和<Location>指令之外,那么它的标志位将被设置为ACCESS_CONF|RSRC_CONF:

  AP_INIT_TAKE1("HostnameLookups", set_hostname_lookups, NULL,
    ACCESS_CONF|RSRC_CONF,
    "\"on\" to enable, \"off\" to disable reverse DNS lookups, or \"double\" to "
    "enable double-reverse DNS lookups"),

2. 目录级别位置选项

除了上面三个用于对配置文件进行控制的选项外,Apache中还允许控制目录及.htaccess文件中出现的指令,这些由AllowOverride控制。该指令仅允许存在于<Directory>配置段中,而且是不包含正则表达式的<Directory>配置段。在<Location>、<DirectoryMatch>、<Files>配置段中都是无效的。

(1)如果 AllowOverride指令被设置为None,那么.htaccess文件将被完全忽略。事实上,服务器根本不会读取.htaccess文件。

(2)当此指令设置为All时,所有具有“.htaccess”作用域的指令都允许出现在.htaccess文件中。

(3)除此之外,AllowOverride后的参数还允许是下面的指令类型:

AuthConfig

允许在.htaccess文件中使用与认证授权相关的指令(AuthDBMGroupFile、AuthDBMUser-File、AuthGroupFile、AuthName、AuthTypeAuthUserFile、Require等)。

FileInfo

允许在.htaccess 文件中使用控制文档类型相关的指令(DefaultType、ErrorDocument、ForceType、LanguagePriority、SetHandler、SetInputFilter、SetOutputFilter、mod_mime中的 Add*和 Remove*指令等)、控制文档元数据的指令(Header、RequestHeader、SetEnvIf、SetEnvIfNoCase、BrowserMatch、CookieExpires、CookieDomain、CookieStyle、CookieTracking、CookieName)、mod_rewrite 中的指令(RewriteEngine、RewriteOptions、RewriteBase、RewriteCond、RewriteRule)和mod_actions中的Action指令。

Indexes

允许在.htaccess 中使用控制目录索引相关的指令(AddDescription、AddIcon、AddIconByEncoding、AddIconByType、DefaultIcon、DirectoryIndex、FancyIndexing、HeaderName、IndexIgnore、IndexOptions、ReadmeName等)。

Limit

允许使用控制主机访问的指令(Allow、Deny、Order)。

Options[=Option,...]

允许使用控制指定目录功能的指令(Options和XBitHack)。可以在等号后面附加一个逗号分隔的(无空格的)Options选项列表,用来控制允许Options指令使用哪些选项。

例如以下指令只允许在.htaccess中使用AuthConfig和Indexes组的指令:

        AllowOverride AuthConfig Indexes

Apache中定义了八个宏用以说明这些不同的情况:

OR_NONE

该选项不允许在.htaccess文件中使用任何指令,这是默认值。不过大多数指令都会对其进行修改。只有那些仅在顶级配置文件中才有效的指令及那些仅有服务器管理员才可以使用的指令才会设置该选项。

OR_LIMIT

只有那些可能涉及虚拟主机访问的指令才会设置该选项。在核心服务器上,Allow、Deny及Order指令都会设置该选项。使用这个选项的指令允许放置在Directory和Location标签中,以及AllowOverride设置为Limit的.htaccess文件中。

OR_OPTIONS

使用该选项的指令通常用来控制特定的目录设置。在标准的Apache发行版本中,Options指令和XbitHack指令都会使用该选项。使用该选项的指令必须置于Directory和Location及AllowOverride设置为Options的.htaccess文件中。

OR_FILEINFO

使用该选项的指令通常用于控制文档类型或文档信息。设置该选项的指令包括SetHandler、ErrorDocument、DefaultType等。这种类型的指令可以存在于Directory和Location标签及AllowOverride设置为FileInfo的.htaccess文件中。

OR_AUTHCFG

使用该选项的指令通常用于控制授权或认证等信息。设置该选项的指令包括AuthUserFile、AuthName及Require。这种指令可以存在于Directory、Location及AllowOverride设置为AuthConfig的.htaccess文件中。

OR_INDEXS

使用该选项的指令通常用于控制目录索引的输出。示例指令包括 AddDescription、AddIcon、AddIconByEncoding。这种指令可以存在于 Directory、Location 及 AllowOverride设置为Indexs的.htaccess文件中。

OR_ALL

这个选项是前面所有选项的组合。使用该选项的指令可以存在于 Directory 和 Location标签中,以及只要AllowOverride不为None的.htaccess文件中。

OR_UNSET

这个特殊的值指出,这个目录没有设置重写,模块不应该使用该值。核心会使用这个值来正确地控制继承。

指令的位置记录在指令描述数据结构command_rec中,使用req_override进行记录,目前在配置文件中,对于<Directory>标签外部的部分,req_override状态为:

  RSRC_CONF|OR_OPTIONS|OR_FILEINFO|OR_INDEXS

而在<Directory>标签内部的部分,状态为:

  ACCESS_CONF|OR_LIMIT|OR_OPTIONS|OR_FILEINFO|OR_AUTHCFG|OR_INDEXS

指令的出现位置在定义指令的时候就必须同时进行定义,比如对AllowOverride指令,它的指令位置为ACCESS_CONF,因此它的定义语句如图3-6所示。

图3-6 指令定义时指定指令的上下文位置

指令如何定义此处暂不讨论。我们只需要知道必须在定义指令的时候同时定义指令的上下文位置即可。

3. Options选项

在这些指令中,Options指令是一个特殊的指令,Options指令控制了在特定目录中将使用哪些服务器特性。option 可以为 None,在这种情况下,将不启用任何额外特性,或者设置为以下选项中的一个或多个。

All

除MultiViews之外的所有特性。这是默认设置。

ExecCGI

允许使用mod_cgi执行CGI脚本。

FollowSymLinks

服务器允许在此目录中使用符号连接。

注意:即使服务器会使用符号连接,但它不会改变用于匹配<Directory>段的路径名;同时如果此配置位于<Location>配置段中,则此设置会被忽略

Includes

允许使用mod_include提供的SSI(Server-Side Include)功能。

IncludesNOEXEC

允许服务器端包含,但禁用“#exec cmd”和“#exec cgi”。仍可以从ScriptAlias目录使用“#include virtual”虚拟CGI脚本。

Indexes

如果一个映射到目录的 URL 被请求,而此目录中又没有 DirectoryIndex(例如:index.html),那么服务器会返回由mod_autoindex生成的一个格式化后的目录列表。

MultiViews

允许使用mod_negotiation提供内容协商的“多重视图”(MultiViews)。

SymLinksIfOwnerMatch

服务器仅在符号连接(与其目的目录或文件的拥有者具有相同的uid)时才使用它。

Apache中定义了一系列的选项宏与之对应如下:

    #define OPT_NONE              0
    #define OPT_INDEXES           1
    #define OPT_INCLUDES          2
    #define OPT_SYM_LINKS         4
    #define OPT_EXECCGI           8
    #define OPT_UNSET             16
    #define OPT_INCNOEXEC        32
    #define OPT_SYM_OWNER       64
    #define OPT_MULTI             128
    #define OPT_ALL(OPT_INDEXES|OPT_INCLUDES|OPT_SYM_LINKS|OPT_EXECCGI)

一般来说,如果一个目录被多次设置了 Options,则最特殊的一个会被完全接受(其他的被忽略),而各个可选项的设定彼此并不融合(参见配置段的合并)。然而,如果所有作用于Options指令的可选项前都加有“+”或“-”符号,此可选项将被合并。所有前面加有“+”号的可选项将强制覆盖当前的可选项设置,而所有前面有“-”号的可选项将强制从当前可选项设置中去除。

比如,没有任何“+”或“-”符号,代码如下:

    <Directory /web/docs>
        Options Indexes FollowSymLinks
    </Directory>
    <Directory /web/docs/spec>
        Options Includes
    </Directory>

那么只有将Includes设置到/web/docs/spec目录上。然而,如果第二个Options指令使用了“+”和“-”符号,代码如下:

    <Directory /web/docs>
        Options Indexes FollowSymLinks
    </Directory>
    <Directory /web/docs/spec>
        Options +Includes –Indexes
    </Directory>

就会有 FollowSymLinks 和 Includes 设置到/web/docs/spec 目录上。使用-IncludesNOEXEC 或-Includes时,不论前面如何设置,都会完全禁用服务器端包含。无其他设置时,默认设置为All。

Options指令的处理由函数set_allow_opts完成,该函数定义在core.c中。

上下文检查

在使用指定的过程中,一个重要的内容就是对指令的上下文进行检测,判断其是否在合法的范围之内。指令的上下文信息都保存在数据结构cmd_parms中。不过最有用也是最简单的判断方法是通过函数ap_check_cmd_context,该函数定义在http_config.h中。

在上面中我们解释了一些位置字段宏,这些宏允许指令出现在哪儿。Apache 中还提供了另外一些简单的宏用以禁止指令出现在某些位置。

为了明确表示指令的作用上下文,Apache在http_config.h中定义了相关的常量来进行描述,代码如下:

    #define  NOT_IN_VIRTUALHOST         0x01 /**< Forbidden in<Virtualhost> */
    #define  NOT_IN_LIMIT               0x02 /**< Forbidden in<Limit>*/
    #define  NOT_IN_DIRECTORY           0x04 /**< Forbidden in<Directory> */
    #define  NOT_IN_LOCATION            0x08 /**< Forbidden in<Location> */
    #define  NOT_IN_FILES               0x10 /**< Forbidden in<Files>*/
    /** Forbidden in<Directory>/<Location>/<Files>*/
    #define  NOT_IN_DIR_LOC_FILE   (NOT_IN_DIRECTORY|NOT_IN_LOCATION|NOT_IN_FILES)
    /** Forbidden in<VirtualHost>/<Limit>/<Directory>/<Location>/<Files> */
    #define  GLOBAL_ONLY   (NOT_IN_VIRTUALHOST|NOT_IN_LIMIT| NOT_IN_DIR_LOC_FILE)

NOT_IN_VIRTUALHOST 意味着当前的指令不允许出现在<VirtualHost>配置段中;NOT_IN_LIMIT 则意味着当前的指令不允许出现在<LIMIT>配置段中;NOT_IN_DIRECTORY 则意味着当前指令不允许出现在<DIRCTORY>配置段中;NOT_IN_LOCATION则意味着当前指令不允许出现在<Location>配置段中;NOT_IN_FILES则意味着当前指令不允许出现在<Files>配置段中;NOT_IN_DIR_LOC_FILE 则意味着当前的指令不允许出现在<Directory>、<Location>及<Files>的任意一个之中;GLOBAL_ONLY 则意味着当前的指令只能是全局配置指令,不允许出现在任何的配置段中。

如果我们不允许某个指令出现在<VirtualHost>中,则可以用如下代码示例:

    static const char* my_conf(cmd_parms* cmd, void* cfg, …)
    {
        const char*err_msg;
        errmsg= ap_check_cmd_context(cmd, NOT_IN_VIRTUALHOST);
        if(errmsg != NULL){
              return errmsg;
        /*OK, not in a<VirtualHost>; go ahead and process the directive */
        return NULL;
    }

ap_check_cmd_context函数返回非空字符串,则意味着指令的出现位置发生了错误。我们看一下Apache是如何检测指令的上下文的,代码如下:

AP_DECLARE(const char *) ap_check_cmd_context(cmd_parms *cmd,
unsigned forbidden)
{ const char *gt = (cmd->cmd->name[0] == '<'
&& cmd->cmd->name[strlen(cmd->cmd->name)-1] != '>')
? ">" :"";
const ap_directive_t *found;
if ((forbidden& NOT_IN_VIRTUALHOST) && cmd->server->is_virtual) {
return apr_pstrcat(cmd->pool, cmd->cmd->name,gt,
" cannot occur within<VirtualHost> section",NULL);
}

如果指令出现在<VirtualHost>中,则该指令描述结构中的虚拟主机成员变量将不为空。不过此时并不能判断该指令一定就是虚拟主机成员,因此,如果指令是全局指令,则指令的server也不为空。只有is_virtual为true时才能完全断定指令一定是出现在<VirtualHost>中。如果指令的上下文是 NOT_IN_VIRTUALHOST,但指令却出现在<VirtualHost>中,则意味着指令的位置发生了错误,代码如下:

        if ((forbidden& NOT_IN_LIMIT) && cmd->limited != -1) {
          return apr_pstrcat(cmd->pool, cmd->cmd->name,gt,
                            " cannot occur within<Limit> section", NULL);
        }

判断当前指令是否在<Limit>中,只需要判断指令的limited是否被设置,如果该成员被设置,则意味着指令一定在<Limit>中,否则不是,代码如下:

        if ((forbidden& NOT_IN_DIR_LOC_FILE) ==NOT_IN_DIR_LOC_FILE) {
          if (cmd->path != NULL){
                return apr_pstrcat(cmd->pool, cmd->cmd->name, gt,
                              " cannot occur within<Directory/Location/Files>"
                              "section", NULL);
          } if (cmd->cmd->req_override & EXEC_ON_READ) {
                /* EXEC_ON_READ must be NOT_IN_DIR_LOC_FILE,if not,it will
                * (deliberately) segfault below in the individual tests...
                */
                return NULL;
          }
        }

判断当前指令是否位于<Directory>、<Location>及<File>中,只要判断当前指令的工作路径 path 是否被设置,如果被设置,则意味着当前指令肯定出现在这三个配置段中的某一个,否则不是。另外,位于<Directory>等配置段中的指令一定不允许是 EXEC_ON_READ类型,代码如下:

        if (((forbidden & NOT_IN_DIRECTORY)
            && ((found = find_parent(cmd->directive,"<Directory"))
                || (found = find_parent(cmd->directive, "<DirectoryMatch"))))
          || ((forbidden& NOT_IN_LOCATION)
                && ((found = find_parent(cmd->directive, "<Location"))
                  || (found = find_parent(cmd->directive,"<LocationMatch"))))
          || ((forbidden& NOT_IN_FILES)
                && ((found = find_parent(cmd->directive, "<Files"))
                  || (found = find_parent(cmd->directive,"<FilesMatch"))))) {
          return apr_pstrcat(cmd->pool, cmd->cmd->name,gt,
                            " cannot occur within", found->directive,
                            "> section",NULL);
        }
        return NULL;
    }

如果当前指令的上下文为 NOT_IN_DIRECTORY、NOT_IN_LOCATION 或 NOT_IN_FILES,此时须检查当前指令的所有父结点中是否为<Directory>、<DirectoryMatch>等指令,而不仅仅是检查当前指令的直接父结点,通过find_parent则可以查找当前结点的所有父结点。

3.3.4 指令参数类型

Apache 中不同指令的参数差异很大,从没有参数到多个参数不等。为了能够准确地处理各种指令及它的参数,Apache 中使用指令参数类型来标识一个指令的参数。不同的指令参数类型指导指令处理程序如何处理指令的参数。Apache中提供了12种类型的指令,这些类型是与实际的配置文件中指令处理相一致的。每种指令都大同小异,唯一的区别就在于其处理的参数的数目及在将指令传递给指令实现函数之前,服务器如何解释这些参数的方式。由于各个指令的参数不相同,为此也导致了指令的处理函数的格式不相同。

Apache中对于指令类型的定义是通过枚举类型cmd_how来实现的,cmd_how定义如下:

    enum cmd_how
    { RAW_ARGS,               /**< cmd_func parses command line itself*/
        TAKE1,                /**< one argument only */
        TAKE2,                /**< two arguments only */
        ITERATE,              /**< one argument, occuring multiple times*(e.g., IndexIgnore)
                                */
        ITERATE2,             /**< two arguments, 2nd occurs multiple times
                                * (e.g., AddIcon)
                                */
        FLAG,                 /**< One of 'On'or 'Off' */
        NO_ARGS,              /**< No args at all, e.g. </Directory> */
        TAKE12,               /**< one or two arguments */
        TAKE3,                /**< three arguments only */
        TAKE23,               /**< two or three arguments */
        TAKE123,              /**< one, two or three arguments */
        TAKE13,               /**< one or three arguments */
        TAKE_ARGV             /**< an argc and argv are passed */
    };

对于所有的指令处理函数,其都将返回字符串。如果指令处理函数正确地处理了指令,那么函数返回NULL,否则应该返回错误提示信息。对于各种指令,服务器的处理方法如下。

TAKE_ARGV

这种类型的处理函数会将指令参数作为 argc/argv 的格式进行处理。这种类型的指令处理函数的参数格式如下:

    const char *(*take_argv) (cmd_parms *parms, void *mconfig,
                              int argc, char *const argv[]);

RAW_ARGS

该指令会通知Apache不对传入的参数做任何处理,只须原封不动地传递给指令处理函数即可。使用这种指令会存在一定的风险,因为服务器不做任何的语法检查,因此难免会有错误成为“漏网之鱼”。

这种指令的处理函数通常如下所示:

    const char * func(cmd_parms* parms , void* mconfig , char* args);

args是传入的参数,它只是简单的指令行内容,当然也包括指令在内,对于该参数func函数不做任何检查,直接使用。

TAKE1

顾名思义,这种类型的指令“Take 1 argument”,它只允许传入一个参数。这种指令的处理函数通常如下所示:

    const char * func(cmd_parms* parms , void* mconfig , const char* first);

first则是需要传入的第一个指令参数。

ITERATE

有的时候指令的参数是不确定的,比如mod_autoindex模块中的IndexIgnore指令描述了一个或多个在目录列表中被忽略的文件的扩展名称,参数格式如下:

    IndexIgnore  .bak  .sav  .hide  .conf

对于ITERATE后的每一个参数,它们都具有等同的含义,类似于“或者”的意思。由于指令的不确定性能,TAKExxx 格式的指令都无法使用,不过 Apache 中提供了新的ITERATE 进行处理。该类型指令属于迭代类型。这种指令允许传入多个参数,不过一次只能处理一个,服务器必须遍历处理它们。每次遍历处理的过程又与TAKE1类型指令相同。因此这种指令的处理函数与TAKE1指令相同,参数格式如下:

    const char * func(cmd_parms* parms , void* mconfig , const char* first);

由于一次只能处理一个参数,因此,如果指令具有N个参数,则func函数必须调用N次,每次将第i个参数传给函数。

TAKE2,TAKE12

TAKE2类型必须向指令处理函数传入两个参数,而TAKE12可以接受一个或两个参数。如果只向TAKE12传递一个参数,那么服务器将把第二个参数设置为NULL。这两种类型的指令处理函数原型如下:

    const char*two_args_func(cmd_parms*parms,void*mconfig,const char*first,const
    char* second);

对TAKE2类型而言,函数two_args_func只被调用一次,两个参数一次性传递给函数;对TAKE12而言,如果只传递一个参数,第二个参数将被设置为NULL,如果传递两个参数,设置与TAKE2相同;对ITERATE2而言,如果有N个参数,则处理函数至少将调用N-1次。

ITERATE2

ITERATE2与ITERATE1类似,都属于参数迭代处理类型,但ITERATE2要求至少传递两个参数给函数。不过,函数每次都从配置中提取第一个参数,然后对剩余的参数进行迭代,服务器会遍历它们,直到所有的参数都传递给处理函数。一个简单的例子就是AddType指令,它的第一个参数是一个MIME类型,剩余的参数则是对应的支持的文件扩展类型,如:

    AddType image/jpeg JPG JPEG JFIF jfif

该类型函数的声明与TAKE2相同,唯一的不同是在读取指令行中的参数,然后传递给TAKE2的方式不同。

    const char *(*take2) (cmd_parms *parms, void *mconfig, const char *w,
                    const char *w2);

TAKE3,TAKE23,TAKE13,TAKE123

这组指令最多可以接受三个参数,如果参数超过三个,则处理函数将会报错。TAKE3意味着参数必须是三个;TAKE23则意味着至少两个参数,也可以为三个,也就是不能少于两个或多于三个。TAKE13 则意味着可以接受一个或三个参数,除此之外都是非法的。TAKE123意味着可以接受一个、两个或三个参数。这四种指令的处理函数原型如下:

    const  char  *three_args_func(cmd_parms*  parms  ,  void*  mconfig,const  char*
    first,const char* second,const char* third);

NO_ARGS

该类型的指令不接受任何的参数,它通常就是作为复杂指令的闭标签存在。比如<Directory>总是必须有</Directory>与之对应。尽管<Directory>通常需要一个参数来指定其所设置的目录,不过,对于</Directory>则没有这个参数。因此它就是 NO_ARGS 指令类型。这种指令的处理函数通常如下所示:

    const char *no_args_func(cmd_parms* parms , void* mconfig);

FLAG

这种类型最简单,它只允许用来作为启动或关闭的指令。与前面几种类型中的服务器直接将配置指令后的参数传递给函数不同,这种指令不会直接传递参数,而是首先对参数进行处理,并将处理的结果true或false作为进一步的参数传递给函数。这种指令的处理函数通常如下所示:

    const char *flag_args_func(cmd_parms* parms , void* mconfig,int flag);

不管是什么指令,其对应的处理函数都是以两个参数开始的:cmd_parms* parms和void*mconfig。cmd_parms 结构用来存储处理配置指令时所需要的辅助内容。当处理任何配置信息文件的时候,该结构都将被创建。Apache核心通过它将各种需要的参数传递给处理函数。我们在后文将给出关于cmd_parms的具体解释。

另一个参数void* mconfig表示针对指令位置的配置记录,基于所遇到的指令位置的不同,该配置记录可以是服务器配置记录,也可以是目录配置记录。