On this page
Apache 1.3 API Comments
Warning
尚未更新本文档,以考虑到 Apache HTTP Server 2.0 版中所做的更改。一些信息可能仍然有用,但是请谨慎使用。
这些是有关 Apache API 和您必须处理的数据结构的一些说明,* etc. 尚不完整,但是希望它们能帮助您理解。请记住,随着我们积累经验,API 仍可能会发生变化。 (请参阅 TODO 文件以了解可能*会出现的情况)。但是,很容易使模块适应所做的任何更改。 (我们比您有更多的模块可以适应)。
这里有一些关于一般教学风格的注意事项。为了简洁起见,此处的所有结构声明都不完整-实际的声明还有更多我没有告诉您的位置。在大多数情况下,这些保留给服务器核心的一个组件或另一个组件,并且应谨慎使用模块进行更改。但是,在某些情况下,它们确实是我尚未了解的事情。欢迎来到前沿。
最后,这是一个概述,以使您对即将发生的事情和 Sequences 有一些粗略的了解:
Basic concepts
我们首先概述 API 背后的基本概念,以及它们如何在代码中体现。
处理程序,模块和请求
Apache 将请求处理分为一系列步骤,或多或少与 Netscape 服务器 API 的方式相同(尽管此 API 比 NetSite 的步骤多一些,因为我认为将来可能有用的东西的钩子)。这些是:
URI->文件名转换
Auth ID 检查[他们说的是用户吗?]
身份验证访问检查[用户是否在这里得到授权?]
验证以外的访问检查
确定所请求对象的 MIME 类型
“修复程序”(Fixups)–目前还没有,但该阶段旨在作为诸如SetEnv之类的可能扩展的钩子,这些扩展在其他地方并不是很合适。
实际上将响应发送回 Client 端。
记录请求
通过查看一系列* module *的每个模块,查看每个模块是否具有该阶段的处理程序,然后尝试调用它们,来处理这些阶段。处理程序通常可以执行以下三种操作之一:
处理请求,并通过返回魔术常数
OK
来表明请求已完成。通过返回魔术整数常量
DECLINED
,* Decline *来处理请求。在这种情况下,服务器在所有方面都表现得好像处理程序根本不在那儿。通过返回 HTTP 错误代码之一来发出错误 signal。尽管可以调用 ErrorDocument 尝试清除,但这会终止对请求的正常处理,并且在任何情况下都将记录该错误文档。
大多数阶段都由处理它们的第一个模块终止。但是,对于日志记录,“修复程序”和非访问身份验证检查,所有处理程序始终运行(除非出现错误)。同样,响应阶段是唯一的,因为模块可以通过键入请求对象的 MIME 类型上的分派表为其声明多个处理程序。模块可以通过指定键*/*
(即,通配 MIME 类型规范)来声明可以处理* any *请求的响应阶段处理程序。但是,仅当服务器已尝试并且未能为请求的对象的 MIME 类型找到更具体的响应处理程序(不存在或全部拒绝)时,才调用通配符处理程序。
处理程序本身是一个自变量(request_rec
结构,见下文)的函数,该自变量返回整数,如上所述。
模块简介
在这一点上,我们需要解释一个模块的结构。我们的候选人将是 CGI 模块之一,这将是更混乱的应用程序之一-它可以处理 CGI 脚本和ScriptAlias config file 命令。实际上,它比大多数模块都要复杂得多,但是如果我们只想举一个例子,那么它也许就是一个举手之劳的例子。
让我们从处理程序开始。为了处理 CGI 脚本,模块为它们声明了一个响应处理程序。由于ScriptAlias,它还具有用于名称转换阶段(识别ScriptAlias ed URI),类型检查阶段(将任何ScriptAlias ed 请求键入为 CGI 脚本)的处理程序。
模块需要维护一些(虚拟)服务器信息,即有效的ScriptAlias;因此,模块结构包含指向构建这些结构的功能的指针,以及包含将它们两个结合的功能的指针(如果主服务器和虚拟服务器都声明了ScriptAlias es)。
最后,此模块包含用于处理ScriptAlias命令本身的代码。这个特定的模块仅声明一个命令,但是可能会声明更多命令,因此模块具有* command table *,这些命令表声明了它们的命令,并描述了允许它们的位置以及如何调用它们。
关于这些命令中某些参数的声明类型的最后说明:pool
是指向资源池结构的指针;服务器使用它们来跟踪已分配的内存,打开的文件* etc. ,以服务特定的请求或处理自身的配置过程。这样,当请求结束时(或对于配置池,当服务器重新启动时),可以释放内存,并关闭文件 en masse *,而无需任何人编写明确的代码来跟踪所有请求下来处理它们。同样,cmd_parms
结构包含有关正在读取的配置文件的各种信息以及其他状态信息,这些信息有时会用于处理配置文件命令的功能(例如ScriptAlias)。事不宜迟,该模块本身:
/* Declarations of handlers. */ int translate_scriptalias (request_rec *); int type_scriptalias (request_rec *); int cgi_handler (request_rec *); /* Subsidiary dispatch table for response-phase * handlers, by MIME type */ handler_rec cgi_handlers[] = { { "application/x-httpd-cgi", cgi_handler }, { NULL } }; /* Declarations of routines to manipulate the * module's configuration info. Note that these are * returned, and passed in, as void *'s; the server * core keeps track of them, but it doesn't, and can't, * know their internal structure. */ void *make_cgi_server_config (pool *); void *merge_cgi_server_config (pool *, void *, void *); /* Declarations of routines to handle config-file commands */ extern char *script_alias(cmd_parms *, void *per_dir_config, char *fake, char *real); command_rec cgi_cmds[] = { { "ScriptAlias", script_alias, NULL, RSRC_CONF, TAKE2, "a fakename and a realname"}, { NULL } }; module cgi_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
NULL, /* dir config creator */
NULL, /* dir merger */
make_cgi_server_config, /* server config */
merge_cgi_server_config, /* merge server config */
cgi_cmds, /* command table */
cgi_handlers, /* handlers */
translate_scriptalias, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
type_scriptalias, /* type_checker */
NULL, /* fixups */
NULL, /* logger */
NULL /* header parser */
};
处理程序的工作方式
处理程序的唯一参数是request_rec
结构。该结构描述了代表 Client 端对服务器的特定请求。在大多数情况下,与 Client 端的每个连接仅生成一个request_rec
结构。
request_rec 简介
request_rec
包含指向资源池的指针,该指针将在服务器完成请求处理后清除;到包含每个服务器和每个连接信息,最重要的是有关请求本身的信息的结构。
最重要的此类信息是一小串字符串,描述了所请求对象的属性,包括其 URI,文件名,Content Type 和内容编码(这些内容由处理请求的翻译和类型检查处理程序填写) , 分别)。
其他常用的数据项包括在 Client 端原始请求上提供 MIME 头的表,将与响应一起发送回的 MIME 头(模块可以随意添加)以及在运行过程中产生的任何子流程的环境变量。服务请求。这些表使用ap_table_get
和ap_table_set
例程进行操作。
Note
请注意,模块内容处理程序无法使用ap_table_*()
例程设置Content-type
Headers 值*。而是通过将request_rec
结构中的content_type
字段指向适当的字符串来设置的。 例如,
r->content_type = "text/html";
最后,有两个数据结构的指针,它们依次指向每个模块的配置结构。具体来说,这些保留指向模块所构建的数据结构的指针,以描述其在服务于请求过程中所构建的私有数据,以描述其被配置为在给定目录中运行的方式(通过.htaccess
文件或<Directory>节)。 (因此,一个阶段的模块处理程序可以将“Comments”传递给其他阶段的处理程序)。 request_rec
指向的server_rec
数据结构中还有另一个这样的配置向量,其中包含每个(虚拟)服务器配置数据。
这是一个简短的声明,提供最常用的字段:
struct request_rec { pool *pool; conn_rec *connection; server_rec *server; /* What object is being requested */ char *uri; char *filename; char *path_info;
char *args; /* QUERY_ARGS, if any */
struct stat finfo; /* Set by server core;
* st_mode set to zero if no such file */
char *content_type;
char *content_encoding;
/ * MIME 头环境,进出。也,
*包含环境变量的数组
*传递给子流程,以便人们可以编写
*添加到该环境的模块。
*
- headers_out 和之间的区别
- err_headers_out 是后者被打印
*即使出现错误,并在内部保持不变
*重定向(因此打印的标题为 - ErrorDocument处理者将拥有它们)。
*/
table *headers_in;
table *headers_out;
table *err_headers_out;
table *subprocess_env;
/ *有关请求本身的信息... * /
int header_only; /* HEAD request, as opposed to GET */
char *protocol; /* Protocol, as given to us, or HTTP/0.9 */
char *method; /* GET, HEAD, POST, etc. */
int method_number; /* M_GET, M_POST, etc. */
/* Info for logging */ char *the_request; int bytes_sent; /* A flag which modules can set, to indicate that * the data being returned is volatile, and clients * should be told not to cache it. */ int no_cache; /* Various other config info which may change * with .htaccess files * These are config vectors, with one void* * pointer for each module (the thing pointed * to being the module's business). */
void *per_dir_config; /* Options set in config files, etc. */
void *request_config; /* Notes on *this* request */
};
request_rec 结构来自哪里
大多数request_rec
结构是通过从 Client 端读取 HTTP 请求并填写字段来构建的。但是,有一些 exceptions:
如果请求是针对图像 Map,类型 Map(* ie ,
*.var
文件)或返回本地“ Location:”的 CGI 脚本,则用户请求的资源将最终由某个 URI 定位。除了 Client 最初提供的东西。在这种情况下,服务器执行内部重定向*,为新 URI 构造一个新的request_rec
,并几乎像 Client 端直接请求新 URI 一样对其进行处理。如果某个处理程序发出错误 signal,并且范围为
ErrorDocument
,则将使用相同的内部重定向机制。最后,处理程序有时需要调查“如果运行其他请求会发生什么”。例如,目录索引模块需要知道将为每个目录条目的请求分配哪种 MIME 类型,以便找出要使用的图标。
这样的处理程序可以使用函数ap_sub_req_lookup_file
,ap_sub_req_lookup_uri
和ap_sub_req_method_uri
构造一个* sub-request *;它们构建了一个新的request_rec
结构,并按您的期望进行处理,直到但不包括实际发送响应为止。 (如果子请求与原始请求位于同一目录中,则这些功能会跳过访问检查)。
(服务器端包括通过构建子请求,然后实际上通过功能ap_run_sub_req
为其调用响应处理程序的工作)。
处理请求,拒绝和返回错误代码
如上所述,每个处理程序在被调用以处理特定的request_rec
时,必须返回int
来指示发生了什么。那可以是
OK
-请求已成功处理。这可能会也可能不会终止该阶段。DECLINED
-不存在错误情况,但是模块拒绝处理该阶段;服务器尝试寻找另一个。HTTP 错误代码,该代码将中止请求的处理。
请注意,如果返回的错误代码为REDIRECT
,则模块应在请求的headers_out
中放置Location
,以指示应将 Client 端重定向到到的位置。
响应处理程序的特殊注意事项
大多数阶段的处理程序只需在request_rec
结构中设置几个字段即可完成其工作(或者,对于访问检查器,只需返回正确的错误代码即可)。但是,响应处理程序必须实际将请求发送回 Client 端。
他们应该首先使用功能ap_send_http_header
发送 HTTP 响应 Headers。 (您不必做任何特殊的事情就可以跳过发送 HTTP/0.9 请求的 Headers;该函数自行指出它不应该做任何事情)。如果请求被标记为header_only
,那就是他们应该做的;他们应该在此之后返回,而不尝试任何进一步的输出。
否则,他们应产生一个请求主体,以适当地响应 Client。用于内部生成输出的 Primitives 是ap_rputc
和ap_rprintf
,以及用于将FILE *
的内容直接复制到 Client 端的ap_send_fd
。
在这一点上,您应该或多或少地理解以下代码,即处理GET
请求的处理程序,而这些请求不再具有特定的处理程序。它还显示了如何处理条件GET
,如果需要在特定的响应处理程序中进行处理ap_set_last_modified
会检查 Client 端提供的If-modified-since
值(如果有),并返回适当的代码(如果非零,是 USE_LOCAL_COPY)。没有类似的考虑因素适用于ap_set_content_length
,但是它返回对称性的错误代码。
int default_handler (request_rec *r) { int errstatus; FILE *f; if (r->method_number != M_GET) return DECLINED; if (r->finfo.st_mode == 0) return NOT_FOUND; if ((errstatus = ap_set_content_length (r, r->finfo.st_size)) || (errstatus = ap_set_last_modified (r, r->finfo.st_mtime))) return errstatus; f = fopen (r->filename, "r"); if (f == NULL) { log_reason("file permissions deny server access", r->filename, r); return FORBIDDEN; } register_timeout ("send", r); ap_send_http_header (r); if (!r->header_only) send_fd (f, r); ap_pfclose (r->pool, f); return OK; }
最后,如果所有这些挑战太大了,那么有几种方法可以解决。首先,如上所述,尚未生成任何输出的响应处理程序可以简单地返回错误代码,在这种情况下,服务器将自动生成错误响应。其次,它可以通过调用ap_internal_redirect
来启动其他处理程序,这是调用上述内部重定向机制的方式。内部重定向的响应处理程序应始终返回OK
。
(从不是响应处理程序的处理程序中调用ap_internal_redirect
将导致严重的混乱)。
身份验证处理程序的特殊注意事项
应该在这里详细讨论的东西:
除非为目录配置了 auth,否则不会调用身份验证阶段处理程序。
通用身份验证配置存储在核心的每个目录配置中;它具有访问器
ap_auth_type
,ap_auth_name
和ap_requires
。至少用于 HTTP 基本身份验证的通用例程(至少用于 HTTP 基本身份验证)(
ap_get_basic_auth_pw
自动设置connection->user
结构字段,ap_note_basic_auth_failure
安排适当的WWW-Authenticate:
Headers 发送回去)。
日志处理程序的特殊注意事项
在内部对请求进行重定向后,就会出现要记录什么的问题。 Apache 通过将整个重定向链 Binding 到通过r->prev
和r->next
指针进行线程化的request_rec
结构列表中来进行处理。在这种情况下,传递给日志处理程序的request_rec
是最初为 Client 端的初始请求而构建的request_rec
。请注意,bytes_sent
字段仅在链中的最后一个请求(实际发送了响应的请求)中才是正确的。
资源分配和资源池
编写和设计服务器池服务器的问题之一是防止泄漏,即分配资源(内存,打开的文件等),而不随后释放它们。资源池机制旨在通过允许以如下方式分配资源,从而轻松防止这种情况的发生:服务器完成处理后,资源会自动释放。
它的工作方式如下:为处理特定请求而分配的内存,文件打开的* etc. 与为该请求分配的 resource pool *绑定在一起。池是一个数据结构,它本身跟踪有问题的资源。
处理完请求后,将“清除”池。此时,将释放与其关联的所有内存以供重用,并关闭与之关联的所有文件,并运行与池关联的任何其他清除功能。当结束时,我们可以确信与该池相关的所有资源都已释放,并且没有一个泄漏。
服务器重新启动,以及按服务器配置的内存和资源分配以类似的方式处理。有一个“配置池”,该池跟踪在读取服务器配置文件并处理其中的命令时分配的资源(例如,为每个服务器模块配置分配的内存,日志文件和其他文件)。被打开,依此类推)。当服务器重新启动并必须重新读取配置文件时,将清除配置池,因此上次读取它们所占用的内存和文件 Descriptors 可供重新使用。
应该注意的是,除了诸如日志记录处理程序之类的情况外,通常不强制使用池机制,在这种情况下,您确实需要注册清除操作以确保在服务器重启时关闭日志文件(这很容易做到)。使用ap_pfopen函数,该函数还安排在任何子进程(例如 CGI 脚本)exec
ed 之前关闭基础文件 Descriptors,或者在您使用超时机制的情况下(此处尚未介绍) )。但是,使用它有两个好处:分配给池的资源永远不会泄漏(即使您分配了暂存字符串,也只是忘记了它);同样,对于内存分配,ap_palloc
通常比malloc
快。
我们从描述内存如何分配到池开始,然后讨论资源池机制如何跟踪其他资源。
池中的内存分配
通过调用函数ap_palloc
将内存分配给池,该函数带有两个参数,一个是指向资源池结构的指针,另一个是要分配的内存量(以char
s 为单位)。在用于处理请求的处理程序中,获取资源池结构的最常见方法是查看相关request_rec
的pool
插槽;因此,以下成语在模块代码中反复出现:
int my_handler(request_rec *r) { struct my_structure *foo; ... foo = (foo *)ap_palloc (r->pool, sizeof(my_structure)); }
请注意,只有清除关联的资源池时,才释放ap_pfree
*-ap_palloc
的内存。这意味着ap_palloc
不必执行与malloc()
一样多的计费;在典型情况下,它所做的只是舍入大小,增加指针大小和进行范围检查。
(这也增加了大量使用ap_palloc
可能导致服务器进程变得过大的可能性.下面介绍了两种处理方法;简要地说,您可以使用malloc
,并尝试确保所有的内存明确为free
d,或者您可以分配主池的一个子池,在该子池中分配您的内存,并定期清除它.后一种技术将在下面的子池部分中讨论,并且在目录索引代码中使用,以便在列出包含数千个文件的目录时避免过多的存储分配)。
分配初始化的内存
有一些函数分配初始化的内存,并且经常有用。函数ap_pcalloc
具有与ap_palloc
相同的接口,但是在返回之前清除了它分配的内存。函数ap_pstrdup
将资源池和char *
作为参数,并为指针所指向的字符串副本分配内存,并返回指向副本的指针。最后ap_pstrcat
是一个 varargs 样式的函数,它使用一个指向资源池的指针和至少两个char *
参数,最后一个参数必须为NULL
。它以一个单位分配足够的内存以适合每个字符串的副本;例如:
ap_pstrcat (r->pool, "foo", "/", "bar", NULL);
返回一个指向 8 字节内存的指针,该指针已初始化为"foo/bar"
。
Apache Web 服务器中的常用池
实际上,池的生命周期定义比其他任何事情都重要。 http_main 中有一些静态池,它们会在适当的时候作为参数传递给各种非 http_main 函数。他们来了:
permanent_pool
- 从来没有传给其他人,这是所有泳池的始祖
pconf
-
- 永久池的子池
-
在配置“周期”开始时创建;在服务器终止或重新启动之前一直存在;通过 cmd-> pool 传递给所有配置时间例程,或者作为不占用池的例程的“ pool * p”参数传递
传递给模块 init()函数
ptemp
-
- 对不起,我撒谎了,在 1.3 中当前未将此池称为此池,我在 pthreads 开发中将其重命名。我指的是在父级中使用 ptrans ...与此相反,在子级中使用 ptrans 的更高版本定义。
-
永久池的子池
在配置“周期”开始时创建;存在直到配置解析结束;通过* cmd-> temp_pool 传递给配置时例程。有点像“野孩子”,因为它并非随处可见。用于某些配置例程可能需要的临时暂存空间,但在配置末尾将其删除。
pchild
-
- 永久池的子池
-
在产生一个孩子(或创建一个线程)时创建;一直活到那个孩子(线程)被破坏为止
传递给模块 child_init 函数
破坏在调用 child_exit 函数后立即发生...(这可能解释了为什么我认为 child_exit 是多余且不需要的)
ptrans
-
- 应该是 pchild 的子池,但当前是 permanent_pool 的子池,请参见上文
-
在进入 accept()循环以接收连接之前由孩子清除
用作连接->池
r->pool
-
- 对于主要请求,这是 connection-> pool 的子池;对于子请求,它是父请求池的子池。
-
一直存在直到请求结束(,即,ap_destroy_sub_req 或 process_request 完成后在 child_main 中)
注意,r 本身是从 r-> pool 分配的; 即,首先创建 r-> pool,然后首先是 r 是 palloc()d
对于几乎所有人所做的一切,r->pool
是要使用的池。但是您可以看到其他生存期(例如 pchild)对某些模块有什么用处……例如需要每个孩子一次打开数据库连接,并希望在孩子死后进行清理的模块。
您还可以看到一些错误是如何表现出来的,例如将connection->user
设置为r->pool
中的值-在这种情况下,存在ptrans
的生命周期,该生命周期比r->pool
长(尤其是r->pool
是子请求!)。因此,正确的做法是从connection->pool
进行分配。
mod_include/mod_cgi中还有另一个有趣的错误。您会在这些工具中看到他们进行了此测试,以确定是否应该使用r->pool
或r->main->pool
。在这种情况下,他们注册用于清除的资源是子进程。如果它已在r->pool
中注册,则子请求完成时,代码将对子代wait()
。使用mod_include可以是任何旧#include
,并且延迟可能长达 3 秒...并且经常发生。而是在r->main->pool
中注册了子进程,这会导致在整个请求完成后(即),在将输出发送到 Client 端并进行日志记录后将其清除。
跟踪打开的文件等
如上所述,资源池还用于跟踪内存以外的其他种类的资源。最常见的是打开的文件。通常用于此目的的例程是ap_pfopen
,它使用一个资源池和两个字符串作为参数;字符串与fopen
的典型参数相同,例如,
... FILE *f = ap_pfopen (r->pool, r->filename, "r"); if (f == NULL) { ... } else { ... }
还有一个ap_popenf
例程,与较低级别的open
系统调用并行。这两个例程都安排在清除相关资源池时关闭文件。
与内存不同,具有函数来关闭分配给ap_pfopen
和ap_popenf
的文件,即ap_pfclose
和ap_pclosef
。 (这是因为在许多系统上,单个进程可以打开的文件数量非常有限)。使用这些函数关闭用ap_pfopen
和ap_popenf
分配的文件很重要,因为否则可能会导致致命错误,例如 Linux,如果同一FILE*
被关闭多次,则会导致严重错误。
(使用close
函数不是强制性的,因为无论如何该文件最终都会关闭,但是在模块正在打开或可能打开很多文件的情况下,您应该考虑使用它)。
其他种类的资源-清理功能
更多 Literals 在这里。描述实现文件内容的清理 Primitives;还有spawn_process
。
池清除将一直进行到调用clear_pool()
为止:clear_pool(a)
在a
的所有子池上递归调用destroy_pool()
;然后调用a
的所有清理;然后释放a
的所有内存。 destroy_pool(a)
调用clear_pool(a)
,然后释放池结构本身。 即,clear_pool(a)
不会删除a
,它只是释放所有资源,您可以立即再次使用它。
精细控制-创建和处理子池,并带有关于子请求的 Comments
在极少数情况下,过于随意地使用ap_palloc()
和关联的 Primitives 可能会导致资源分配不合理。您可以通过创建* sub-pool *,在子池而不是主池中分配,清除或销毁该子池来释放与之关联的资源来处理这种情况。 (这确实是一种罕见的情况;在标准模块集中出现这种情况的唯一情况是列出目录,然后仅在非常大的目录中出现.不必要使用此处讨论的 Primitives 会产生麻烦您的代码很多,几乎没有收益)。
创建子池的 Primitives 是ap_make_sub_pool
,它以另一个池(父池)作为参数。清除主池后,子池将被销毁。也可以通过分别调用函数ap_clear_pool
和ap_destroy_pool
随时清除或销毁子池。 (不同之处在于ap_clear_pool
释放与池关联的资源,而ap_destroy_pool
也会释放池本身.在前一种情况下,您可以在池中分配新资源,然后再次清除它,依此类推;在后一种情况下,它可以简直不见了)。
最后一点-子请求具有自己的资源池,这些资源池是主请求的资源池的子池。回收与您已分配(使用ap_sub_req_...
函数)的子请求关联的资源的有礼貌的方式是ap_destroy_sub_req
,这将释放资源池。在调用此函数之前,请确保将您关心的任何内容复制到子请求的资源池中可能会分配到不稳定程度较小的位置(例如,request_rec
结构中的文件名)。
(同样,在大多数情况下,您不应该调用此函数;典型的子请求仅分配 2K 左右的内存,并且在清除主请求池后仍将释放该内存.仅当您为单个主请求分配了许多子请求,因此您应该认真考虑ap_destroy_...
函数)。
配置,命令等
该服务器的设计目标之一是保持与 NCSA 1.3 服务器的外部兼容性,即读取相同的配置文件,正确处理其中的所有指令,并且通常是对它们的直接替代。 NCSA。另一方面,另一个设计目标是将尽可能多的服务器功能转移到与单片服务器核心尽可能少的模块中。协调这些目标的唯一方法是将大多数命令的处理从中央服务器移到模块中。
但是,仅提供模块命令表不足以将其与服务器核心完全分离。服务器必须记住命令以便以后对它们执行操作。这涉及维护模块专用的数据,这些数据可以是每个服务器或每个目录。大多数事情都是针对每个目录的,尤其包括访问控制和授权信息,还包括有关如何根据后缀确定文件类型的信息,可以通过AddType和ForceType指令对其进行修改,等等。总的来说,Management 哲学是所有可以通过目录配置的东西都应该是;每个服务器的信息通常在标准模块集中用于Alias es 和Redirect s 之类的信息,这些信息在请求被绑定到基础文件系统中的特定位置之前就开始起作用。
模拟 NCSA 服务器的另一个要求是能够处理每个目录的配置文件,通常称为.htaccess
文件,尽管即使在 NCSA 服务器中,它们也可以包含与访问控制完全无关的指令。因此,在 URI->文件名转换之后,但在执行任何其他阶段之前,服务器将沿着转换的路径名遍历基础文件系统的目录层次结构,以读取可能存在的任何.htaccess
文件。然后必须将读取的信息与服务器自己的配置文件中的适用信息“合并”(来自access.conf
中的<Directory>部分,或来自srm.conf
中的默认值,实际上在大多数情况下其行为几乎与<Directory />
一样)。
最后,在处理了涉及读取.htaccess
个文件的请求后,我们需要丢弃为处理它们而分配的存储空间。通过将那些结构绑定到每个事务资源池,可以用与解决其他类似问题的方法相同的方式解决该问题。
每个目录的配置结构
让我们看看mod_mime.c
中所有这些如何发挥作用,mod_mime.c
定义了文件键入处理程序,该处理程序模仿 NCSA 服务器从后缀确定文件类型的行为。在这里,我们将看到实现AddType和AddEncoding命令的代码。这些命令可以出现在.htaccess
文件中,因此必须在模块的私有每个目录数据中进行处理,实际上,该数据由两个用于 MIME 类型和编码信息的单独表组成,并声明如下:
typedef struct {
table *forced_types; /* Additional AddTyped stuff */
table *encoding_types; /* Added with AddEncoding... */
} mime_dir_config;
当服务器读取配置文件或<Directory>节(其中包含 MIME 模块的命令之一)时,它需要创建mime_dir_config
结构,因此这些命令需要执行一些操作。它通过调用在模块的“ create per-dir config slot”中找到的函数来执行此操作,该函数带有两个参数:此配置信息所应用到的目录的名称(对于srm.conf
来说为NULL
),以及指向资源池的指针应该在其中进行分配。
(如果我们正在读取.htaccess
文件,则该资源池是请求的每个请求资源池;否则,它是一个用于配置数据并在重新启动时清除的资源池.无论哪种方式,它对于结构都很重要被创建为在清除池时消失,如果需要,可以通过在池上注册清除来创建)。
对于 MIME 模块,每个目录的配置创建函数只是ap_palloc
上面的结构,并且会创建几个表来填充它。看起来像这样:
void *create_mime_dir_config (pool *p, char *dummy) { mime_dir_config *new = (mime_dir_config *) ap_palloc (p, sizeof(mime_dir_config)); new->forced_types = ap_make_table (p, 4); new->encoding_types = ap_make_table (p, 4); return new; }
现在,假设我们刚刚读了一个.htaccess
文件。我们已经有了针对层次结构中下一个目录的按目录的配置结构。如果我们刚刚读入的.htaccess
文件没有任何AddType或AddEncoding命令,则它的 MIME 模块按目录的配置结构仍然有效,我们可以使用它。否则,我们需要以某种方式合并这两个结构。
为此,服务器调用模块的每目录配置合并功能(如果存在)。该函数带有三个参数:两个要合并的结构,以及在其中分配结果的资源池。对于 MIME 模块,所有需要做的就是将新的按目录配置结构中的表与父目录中的表进行覆盖:
void *merge_mime_dir_configs (pool *p, void *parent_dirv, void *subdirv) { mime_dir_config *parent_dir = (mime_dir_config *)parent_dirv; mime_dir_config *subdir = (mime_dir_config *)subdirv; mime_dir_config *new = (mime_dir_config *)ap_palloc (p, sizeof(mime_dir_config)); new->forced_types = ap_overlay_tables (p, subdir->forced_types, parent_dir->forced_types); new->encoding_types = ap_overlay_tables (p, subdir->encoding_types, parent_dir->encoding_types); return new; }
注意-如果不存在按目录的合并功能,则服务器将仅使用子目录的配置信息,而忽略父目录的配置信息。对于某些模块,它工作得很好(* eg *,对于 includes 模块,其按目录配置信息仅由XBITHACK
的状态组成),对于这些模块,您只能声明一个,然后保留对应的模块本身NULL
中的结构插槽。
Command handling
现在我们有了这些结构,我们需要能够弄清楚如何填充它们。这涉及处理实际的AddType和AddEncoding命令。要查找命令,服务器将在模块的命令表中查找。该表包含有关命令使用多少个参数,以什么格式,允许在哪里等等的信息。该信息足以使服务器使用预解析的参数调用大多数命令处理功能。事不宜迟,让我们看一下AddType命令处理程序,它看起来像这样(AddEncoding命令看起来基本相同,这里不再显示):
char *add_type(cmd_parms *cmd, mime_dir_config *m, char *ct, char *ext) { if (*ext == '.') ++ext; ap_table_set (m->forced_types, ext, ct); return NULL; }
该命令处理程序非常简单。如您所见,它包含四个参数,其中两个是预先解析的参数,第三个是所讨论模块的按目录的配置结构,第四个是指向cmd_parms
结构的指针。该结构包含一堆参数,这些参数经常用于某些(但不是全部)命令,包括资源池(可以从中分配内存以及应绑定到哪些清理)以及正在配置的(虚拟)服务器,如果需要,可以从中获取模块的每服务器配置数据。
该特定命令处理程序异常简单的另一种方式是,它不会遇到任何错误情况。如果存在,它将返回错误消息,而不是NULL
;这会导致在服务器的stderr
上打印出错误,如果该错误位于主配置文件中,则会快速退出;对于.htaccess
文件,语法错误会记录在服务器错误日志中(并附带指示来源),并且该请求会与服务器错误响应一起退回(HTTP 错误状态,代码 500)。
MIME 模块的命令表包含这些命令的条目,如下所示:
command_rec mime_cmds[] = { { "AddType", add_type, NULL, OR_FILEINFO, TAKE2, "a mime type followed by a file extension" }, { "AddEncoding", add_encoding, NULL, OR_FILEINFO, TAKE2, "an encoding (e.g., gzip), followed by a file extension" }, { NULL } };
这些表中的条目是:
命令名称
处理它的功能
(void *)
指针,该指针在cmd_parms
结构中传递给命令处理程序---在许多相同的命令由同一函数处理的情况下很有用。指示命令可能出现位置的位掩码。有与每个
AllowOverride
选项相对应的掩码位,以及一个额外的掩码位RSRC_CONF
,指示该命令可能出现在服务器自己的配置文件中,但没有出现在任何.htaccess
文件中。一个标志,指示命令处理程序希望预先解析多少个参数以及应如何传递它们。
TAKE2
表示两个预先解析的参数。其他选项是TAKE1
,它表示一个预先解析的参数FLAG
,它表示该参数应为On
或Off
,并作为布尔值RAW_ARGS
传入,这导致服务器向命令提供原始的,未解析的参数。 (除了命令名称本身)。还有ITERATE
,这意味着处理程序看起来与TAKE1
相同,但是如果存在多个参数,则应多次调用它,最后是ITERATE2
,这表明命令处理程序看起来像TAKE2
,但是如果有更多参数如果存在,则应多次调用,并保持第一个参数不变。最后,我们有一个字符串,描述了应该出现的参数。如果实际配置文件中的参数不符合要求,则此字符串将用于帮助提供更具体的错误消息。 (您可以放心离开此
NULL
)。
最后,设置好所有这些之后,我们必须使用它。最终,这是在模块的处理程序中完成的,特别是对于其文件类型处理程序而言,看起来或多或少都是这样。请注意,通过使用ap_get_module_config
函数从request_rec
的每个目录配置向量中提取了每个目录的配置结构。
int find_ct(request_rec *r) { int i; char *fn = ap_pstrdup (r->pool, r->filename); mime_dir_config *conf = (mime_dir_config *) ap_get_module_config(r->per_dir_config, &mime_module); char *type; if (S_ISDIR(r->finfo.st_mode)) { r->content_type = DIR_MAGIC_TYPE; return OK; } if((i=ap_rind(fn,'.')) < 0) return DECLINED; ++i; if ((type = ap_table_get (conf->encoding_types, &fn[i]))) { r->content_encoding = type; /* go back to previous extension to try to use it as a type */ fn[i-1] = '\0'; if((i=ap_rind(fn,'.')) < 0) return OK; ++i; } if ((type = ap_table_get (conf->forced_types, &fn[i]))) { r->content_type = type; } return OK; }
旁注-每个服务器的配置,虚拟服务器等。
每服务器模块配置的基本思想与按目录配置的基本思想基本相同。有一个创建函数和一个合并函数,在虚拟服务器已部分覆盖基本服务器配置的情况下调用后者,并且必须计算一个组合结构。 (与按目录配置一样,如果未指定合并功能且在某些虚拟服务器中配置了模块,则默认情况是基本配置将被忽略)。
唯一的实质区别是,当命令需要配置每个服务器的私有模块数据时,它需要转到cmd_parms
数据来获取它。这是一个来自 alias 模块的示例,该示例还指示了如何返回语法错误(请注意,命令处理程序的按目录配置参数声明为虚拟,因为该模块实际上没有按目录配置数据):
char *add_redirect(cmd_parms *cmd, void *dummy, char *f, char *url) { server_rec *s = cmd->server; alias_server_conf *conf = (alias_server_conf *) ap_get_module_config(s->module_config,&alias_module); alias_entry *new = ap_push_array (conf->redirects); if (!ap_is_url (url)) return "Redirect to non-URL"; new->fake = f; new->real = url; return NULL; }