Apache内容生成器 (1)

来源:互联网 发布:淘宝投诉专线 编辑:程序博客网 时间:2024/05/21 10:37

在HTTP请求处理的关键步骤中 Apache对不同类型的文件的处理是不一样的。比如对于所有.html文件,通常的处理方法就是直接将该文件返回给客户端,而对于各种脚本文件比如.pl、.asp或者CGI则需要经过预处理生成html之后才能返回给客户端。不同类型的脚本的处理又由不同的应用程序处理。 为了能够对各种类型的文件方便的进行处理,Apache中引入了处理器的概念。 13.1 内容处理器概述 Apache对不同类型的文件的处理是不一样的。比如对于所有.html文件,通常的处理方法就是直接将该文件返回给客户端,而对于各种脚本文件比如.pl、.asp或者CGI则需要经过预处理生成html之后才能返回给客户端。不同类型的脚本的处理又由不同的应用程序处理。 为了能够对各种类型的文件方便的进行处理,Apache中引入了内容处理器(Handle)的概念。“处理器”是当一个文件被调用时,Apache所执行操作的内部表现。文件一般都有基于其文件类型的隐含处理器。 Apache1.1增加了使用处理器的能力。处理器可以基于文件名后缀或位置进行指定,而不只是文件类型,其优越性不仅在于它是一个优秀的方案,还在于它允许一个文件同时与一种类型和一个处理器相关联。 处理器可以被编译进服务器也可以包含在模块中,还可以用Action指令增加。标准发行版中内建的处理器如下: 文件类型 处理器 处理器解释 .HTML等静态内容 default-handler 静态内容的默认的处理器 CGI脚本 cgi-script CGI脚本对应的处理器。(mod_cgi) 映射表规则文件 Imap-file 将文件作为映射表规则文件解析(mod_imagemap) 服务器的配置信息 Server-info 获取服务器的配置信息。(mod_info) 服务器状态的报告 server-status 获取服务器状态的报告。(mod_status) 类型表文件 type-map 将文件作为类型表文件解析以实现内容协商。(mod_negotiation) 带HTTP头原样返回文件 Send-as-id 按原样带HTTP头发送文件。(mod_asis) 但是Apache中的处理器并不一定总是关联一种文件类型,而有可能是多种文件类型。从apache1.1版本开始,处理器可以基于文件名后缀或者某个位置指定。 由于处理器总是用来生成返回给客户端的内容,因此有的时候处理器又可以被称之为“内容生成器”。 13.2 配置内容处理器 处理器的配置非常简单,主要的任务就是将一个处理函数与特定的文件类型或者文件后缀进行关联。只需要两个指令即可完成:Action和SetHander。 13.2.1 AddHandler AddHandler用于在特定的文件扩展名与特定的处理器之间建立映射,它的指令格式如下: AddHandler handler-name extension [extension] ... 指定带extension扩展名的文件应被handler-name处理器来处理。这个映射关系会添加在所有有效的映射关系上,并覆盖所有相同的extension扩展名映射。例如,为了把扩展名为.cgi的文件作为CGI脚本来处理,你应该定义: AddHandler cgi-script .cgi 一旦将上述定义放在你的http.conf文件中,所有包含.cgi扩展名的文件,都会被当成是CGI程序。extension参数是大小无关的,并且可以带或不带前导点。 13.2.2 RemoveHandler RemoveHandler指令删除任何指定的扩展名与处理器之间的关联。子目录中的.htaccess文件可以通过这条指令取消从父目录或服务器配置文件中继承过来的扩展名与处理器之间的关联关系。举例来说,它可以这样来使用: /foo/.htaccess AddHandler server-parsed .html /foo/bar/.htaccess RemoveHandler .html 这样/foo/bar目录中的.html文件将被当成普通文件来处理,而不是由parsing处理器(参阅mod_include模块)来处理。 13.2.3 SetHandler SetHander用于强制一个被指定的文件使用特定的处理器进行处理,它的语法格式如下: SetHandler handler-name|None 通常该指令用于特定的配置段比如,中。通过将SetHandler放置于这些配置段中,可以强制所有的匹配的文件都用该处理器进行处理,而不管这些文件是什么类型,比如如果想不管某个目录中的文件具有什么扩展名,都将它作为图像映射规则文件来解析,您可以将下例放入那个目录的.htaccess中: SetHandler imap-file 再来一个例子:如果您想当http://servername/status被请求时,服务器显示一个状态报告,您可以将下面的语句放入httpd.conf里面: SetHandler server-status 所有的http://servername/status的URL都将用处理器server-status进行处理。你可以通过使用 None 来改写一个早先定义的SetHandler指令。 13.2.4 Action Action指令针对特定的处理器或者内容类型激活一个CGI脚本,指令的格式如下: Action action-type cgi-script [virtual] 当action-type被请求触发时会执行cgi-script脚本。cgi-script是一个URL路径,指向一个已经被用ScriptAlias或者AddHandler指令指定为CGI脚本的资源。action-type可以是一个处理器或一个MIME内容类型。它使用标准的PATH_INFO和PATH_TRANSLATED环境变量来发送此URL和被请求内容的文件路径。用于该请求的处理器通过REDIRECT_HANDLER变量传递。 比如: Action image/gif /cgi-bin/images.cgi 在该指令中,对于所有的MIME类型为”image/gif”的请求都将被指定的CGI脚本/cgi-bin/images.cgi进行处理。 AddHandler my-file-type .xyz Action my-file-type /cgi-bin/program.cgi 在该示例中,对于所有的具有扩展名”.xyz”的文件的请求都将被指定的CGI脚本/cgi-bin/program.cgi进行处理。 13.3 随Apache提供的处理器 在进一步讨论Apache处理器的细节之前,我们需要对Apache中提供的六种基本处理器有大致的了解。 n Apache核心提供的处理器 大部分用户都会忘记第一个模块,就是Apache提供的最基本的内容处理器,它用于处理返回位于磁盘上的静态文件类型。当所请求的页面位于磁盘上的时候,Apache核心必须能够找到该文件并将它发送给客户端。这是所有的Web服务器都必须提供的默认的行为。在Apache中,这个默认的行为由Apache核心完成。 n mod_asis内容生成模块 这个模块可以允许管理员完全的控制返回给客户端的页面。它的目的是让管理员编写只要简单读取就可以发往客户的恩见。服务器只需要对这个文件进行稍微的改动,基本上就是在响应中添加服务器和日期信息。除了这两个头信息之外,这个asis文件白必须包含将要发送诶客户所有其它的头信息。这个文件还必须有一个状态行,它必须包含3位数字的状态编码,其后跟随状态的文本描述。大多数管理员都不会使用这个模块,除非你想完全的控制响应。如果想使用这个模块,那么就必须使用该模块的处理器与web站点上特定的文件进行关联,比如你可能需要在配置中增加下面的代码行: AddHandler send-as-is asis 这个配置告诉Apache,所有的以.asis为结尾的文件都应该由mod_asis提供。作为这个配置的示例,你可以使用如下内容建立称之为asis_expamle.asis文件: Status:200 Request SUCCESSED! Content-Type: text/html The page was sent using mod_asis 然后,如果你与服务器进行连接,并且请求页面asis-example.asis,那么就会获得如下响应。确保正确发送这个页面的最好方式就是使用Telnet与服务器进行连接,并且手动进行请求。服务器返回的页面如下: HTTP/1.1 200 Request SUCCESSED Data: Mon, 19 Nov 2001 01:00:29 GMT Server: Apache/2.0.26-dev (Win32) DAV/2 Content-Type: text/html The page was sent using mod_asis n mod_info内容生成模块 Mod_info模块通常用于对Apache服务器的管理,这个页面通常应该使用认证或者访问控制指令进行保护。这个模块的目的是显示正在运行的Apache服务器的信息。通过在httpd.conf中添加下面的配置可以启动mod_info模块: 13.4 处理器实现机制 Apache内容处理器也属于Apache请求的一部分,因此Apache核心也将处理器实现为过滤器,过滤器的名称为handler。 内容处理器在http_config.h中被声明: AP_DECLARE_HOOK(int,handler,(request_rec *r)) 与此同时,处理器数组则被声明在config.c中: APR_HOOK_STRUCT( …… APR_HOOK_LINK(handler) …… ) 与其余的过滤器一样,如果某个模块需要使用内容处理器,那么它必须使用ap_hook_handler函数进行注册,比如核心模块的注册函数就是: ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST); 各个不同的处理器会根据自己的情况设置第四个参数,即设置它在处理器链表中的顺序。比如默认处理器通常总是最后一个被调用。只有没有合适的处理器的时候才会调用它。因此它的最后一个参数为APR_HOOK_REALLY_LAST。 任何模块任何时候如果需要执行内容处理器,它只需要调用ap_run_handler函数即可。 13.5 请求数据写入 对于内容处理器而言,向请求中写入数据是必不可少的步骤。从前面的分析中我们可以看到,由于输出过滤器的存在,请求数据写入的实质就是将数据写入到过滤器堆栈的第一个过滤器中。由于在过滤器中流动的数据组织格式是以存储段组为基本单位,因此写入的数据首先必须转换为存储段组的格式。 Apache中提供了三种请求数据写入方式: (1)通过Apache提供的ap_r*函数,比如ap_rputc,ap_rputs等等。 (2)直接对存储段和存储段组操作进行封装处理,生成写入数据。 (3)调用过滤器的类似于stdio的API函数进行操作。 13.5.1 ap_r*写入函数 Ap_r*系列函数从早期的Apache版本中就一直保留,之所以这么做无非是为了保持对低版本的兼容性。这些API都定义在http_protocol.h中,包括下面一些: (1)、AP_DECLARE(int) ap_rputc(int c, request_rec *r) 函数用于向请求r中写入一个字符。 (2)、AP_DECLARE(int) ap_rputs(const char *str, request_rec *r) 函数用于向请求r中写入字符串str。 (3)、AP_DECLARE(int) ap_rwrite(const void *buf, int nbyte, request_rec *r) 该函数用于向请求r中写入最大长度nbyte的缓冲区,函数返回实际写入的字节数目 (4)、AP_DECLARE_NONSTD(int) ap_rvputs(request_rec *r,...) 函数向请求r中写入不固定数目的字符串 (5)、AP_DECLARE(int) ap_vrprintf(request_rec *r, const char *fmt, va_list vlist) (6)、AP_DECLARE_NONSTD(int) ap_rprintf(request_rec *r, const char *fmt,...) __attribute__((format(printf,2,3))) 函数以类似于printf的格式向请求r中写入数据信息 (7)、AP_DECLARE(int) ap_rflush(request_rec *r) 当客户端数据写完后,Apache可以调用ap_rflush函数将数据写入到客户端。 对于ap_rxxx写入函数而言,它们内部调用的都是buffer_output函数,该函数负责实际的请求写入,实现位于protocol.c中: static apr_status_t buffer_output(request_rec *r, const char *str, apr_size_t len) { conn_rec *c = r->connection; ap_filter_t *f; old_write_filter_ctx *ctx; if (len == 0) return APR_SUCCESS; /* future optimization: record some flags in the request_rec to * say whether we've added our filter, and whether it is first. */ /* this will typically exit on the first test */ for (f = r->output_filters; f != NULL; f = f->next) { if (ap_old_write_func == f->frec) break; } if (f == NULL) { /* our filter hasn't been added yet */ ctx = apr_pcalloc(r->pool, sizeof(*ctx)); ap_add_output_filter("OLD_WRITE", ctx, r, r->connection); f = r->output_filters; } /* if the first filter is not our buffering filter, then we have to * deliver the content through the normal filter chain */ if (f != r->output_filters) { apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc); apr_bucket *b = apr_bucket_transient_create(str, len, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); return ap_pass_brigade(r->output_filters, bb); } /* grab the context from our filter */ ctx = r->output_filters->ctx; if (ctx->bb == NULL) { ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); } return ap_fwrite(f->next, ctx->bb, str, len); } 函数需要三个参数:写入的字符串数据,数据长度以及需要写入数据的请求。 通过ap_r*系列的API,将数据写入到请求中则变得简单,比如如果需要将”HelloWorld”写入,则只需要调用 Ap_rputs(r,”Helloworld”)即可。 13.5.2 基于过滤器IO的写入 如果要使用过滤器IO进行数据的写入,则至少包含三个步骤: (1)创建保存数据的存储段组。过滤器中流动的数据的唯一组织形式就是存储段组,所有的数据最终都转换为存储段组的格式。 (2)将数据写入到存储段组中。 (3)将存储短租传递给过滤器堆栈中的第一个过滤器,即r->output_filters。 上面的这三个过程可以无限次的重复。正如我们在Chunk过滤器中看到的一样,如果内容处理器生成的数据太大,或者生成的速度太慢,那么如果等待整个数据都创建完毕再发送给客户端,则显然这是一种最低效率的做法。通过Chunk过滤器,大的数据可以被分成多个小的分块,这样即使所有的数据没有生成完毕,那么部分数据也可以先发送给客户端。 下面是一个典型的使用过滤器IO写入请求数据的示例: static const char* helloworld = "" "helloworld"; apr_bucket_brigade* bb; apr_bucket* b; bb = apr_bridage_create(r->pool, r->connection->bucket_alloc); b = apr_bucket_immortal_create(helloworld, strlen(helloworld), bb->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(bb->bucket_alloc)); ap_pass_brigade(r->filters_out, bb); 13.5.3 基于过滤器IO的写入 13.6 “Helloworld”处理器 在学习大部分计算机程序设计语言的时候,”Helloworld”总是第一个被演示的示例。我们也不例外。通过”HelloWorld”处理器,我们可以对内容处理器有个大致的了解。 “HelloWorld”处理器目的非常的简单明确,就是要在浏览器中显示字符串”HelloWorld”。我们所要做的第一件事情就是创建一个新的模块helloworld_module: module AP_MODULE_DECLARE_DATA helloworld_module = { STANDARD20_MODULE_STUFF, NULL, NULL, NULL, NULL, NULL, helloworld_hooks }; helloworld_module模块中仅仅对内容处理器感兴趣,内容处理器负责生成”HelloWorld”字符串响应。为此它需要在Apache核心中注册使用handler挂钩: static void helloworld_hooks(apr_pool_t*pool) { ap_hook_handler(helloworld_handler, NULL, NULL, APR_HOOK_MIDDLE); } 模块中注册的处理器函数名称为helloworld_handler,因此我们必须实现该过滤器: static int helloworld_handler(request_rec* r) { if (!r->handler || strcmp(r->handler, "helloworld") != 0 ) { return DECLINED; } if (r->method_number != M_GET) { return HTTP_METHOD_NOT_ALLOWED; } ap_set_content_type(r, "text/html;charsetascii"); ap_rputs("", r); ap_rputs("HelloWorld", r); return OK; } Apache处理器函数遵循下面的固定格式: (1)所有的函数都被声明为static函数,只允许在模块文件内部被访问。 (2)函数只有一个参数,就是当前请求。 (3)函数只可能返会三种整数值:OK,DECLINED以及错误码,这跟所有的挂钩函数一致。OK意味着当前处理器已经成功的执行完该处理器,后面的处理器可以不再继续执行。返回DECLINED则意味着当前模块对该处理器不赶兴趣,为此Apache将会继续调用下一个处理器。如果执行过程中出现错误,则函数会返回对应HTTP错误码。一旦在请求的处理过程中发生了错误,则整个请求的处理都会被放弃。Apache核心将会将请求重定向到错误页面。错误页面可能是预先定义好的默认的页面,也有可能是ErrorDocument指令中配置的页面。 (4)为了实现处理器功能, Apache API结构中增加了一个字段: char *handler 该字段用于记录处理器名称。如果你的模块需要使用处理器,只须在对请求执行invoke_handler之前,设置r->handler为该处理器的名称即可。处理器的名称可以有"-",但不能有"/",以避免和介质类型名称冲突。几乎所有的处理器处理函数起始的时候都会检查r->handler是否存在以及是否是我们所需要的处理器。 (5)最后一点就是对请求方法的处理。对于处理器,Apache目前只想支持GET和HEAD两种方法,不允许提供对POST方法的支持。如果我们发现请求方法不是GET或者HEAD方法,此时必须返回对应的HTTP错误码, 需要注意的是,第(4)步和第(5)步的顺序非常的重要。如果着两个检查顺序颠倒的话,那有可能在我们的模块内部导致Apache返回一个错误页面。比如CGI脚本接受到一个POST请求,该请求一旦通过了检查,那么我们就可以进行内容生成处理。在HelloWorld处理器中,我们需要做的仅仅是调用ap_rputs将HTML报文写入到请求中。 一旦完成上面的内容,我们就可以编译模块。加入模块的名字是mod_helloworld.c,那么我们可以使用下面的命令编译和安装该模块: $ apxs –c mod_helloworld.c //编译成mod_helloworld.la 在root用户下安装该模块: # apxs –I mod_helloworld.la 安装后,在httpd.conf中配置加载该模块: Load Module hellworld_module modules/mod_helloworld.so SetHandler helloworld 这样,一旦你请求类似于http://server-name/helloworld的页面,那么helloworld处理器就会产生作用,在浏览器中显示出”HelloWorld”字符串。 13.7 磁盘文件处理器 除了返回固定内容之外,内容处理器也可以返回磁盘上的特定的磁盘文件。事实上,这是Apache中的默认处理器default_handler所做的事情。default_handler所要实现的就是读取磁盘上的静态文件,然后将其返回。默认处理器由核心模块负责提供。如果某个请求没有指定任何的特殊的过滤器,那么默认处理器将被调用。 在核心文件core.c中,核心模块声明了默认处理器的处理函数default_handler: ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST); default_handler函数的实现如下所示: static int default_handler(request_rec *r) { conn_rec *c = r->connection; apr_bucket_brigade *bb; apr_bucket *e; core_dir_config *d; int errstatus; apr_file_t *fd = NULL; apr_status_t status; int bld_content_md5; d = (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module); bld_content_md5 = (d->content_md5 & 1) && r->output_filters->frec->ftype != AP_FTYPE_RESOURCE; MD5是一种为不定长度 (arbitrary – length)的数据计算出一个“消息摘要”(有时也称为“指纹”)的算法,并且保证数据中的任何变化都会反应在消息摘要的变化当中。Content-MD5头提供了一种端到端的针对整个消息体的完整性检测方法。代理或者客户端会检查此头以侦测在传输过程中,消息体是否产生了意外的改变。 Apache中提供了指令ContentDigest on|off 来指示是否需要在返回给客户端的报文中增加”Content-MD5”头,以便客户端进行MD5的值检查,比如: Content-MD5: AuLb7Dp1rqtRtxz2m9kRpA== 如果通过ContentDigest设置了返回报文MD5,那么反映到Apache和心中就是核心模块的core_dir_config结构中的content_md5被设置为1。一般情况下如果该字段被设置为1,那么在生成的返回给客户端的报文中必须增加Content-MD5字段。不过仅有这个还不能确信,还必须确保请求的输出过滤器不是AP_FTYPE_RESOURCE类型,该类型用于对请求报文体内容进行处理。自然,如果通过该过滤器对内容进行了处理,那么前面生成的MD5值自然不作数了。 ap_allow_standard_methods(r, MERGE_ALLOW, M_GET, M_OPTIONS, M_POST, -1); if ((errstatus = ap_discard_request_body(r)) != OK) { return errstatus; } HTTP/1.1协议中 ,任何方法都可以拥有报文体。然而,大部分GET方法的处理句柄在接收到一个含有报文体的HTTP报文的时候并不知道该如何去处理该请求。更为严重的是,如果我们仅仅只需要报文头,但是如果对报文体读取失败,那么如果存在持续链接,这些读取失败的报文将会被解释为下一个请求报文。因此明智的做法就是发现接受的报文中存在报文体的时候将这些报文体丢弃。 ap_discard_request_body 函数读取这个HTTP请求报文,包括报文头和报文体,同时抛弃整个报文体部分。如果HTTP请求是一个畸形的不正常的报文,ap_discard_request_body将返回错误状态值,因此这个函数通常总是在非报文体处理器的第一个被调用,比如: if ((retval = ap_discard_request_body(r)) != OK) { return retval; } 在请求处理的前期阶段,经过translate_name以及map_to_storage两个阶段之后,如果客户端请求的是某个文件,那么HTTP请求中的URI都被映射到磁盘中的特定的路径。这些文件的信息都被保存到对应的请求结果中的fifo成员中。在对文件进行进一步处理之前,必须对文件进行判断: if (r->finfo.filetype == 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "File does not exist: %s", r->filename); return HTTP_NOT_FOUND; } if (r->finfo.filetype == APR_DIR) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Attempt to serve directory: %s", r->filename); return HTTP_NOT_FOUND; } 上面的代码用于剔除无效的文件以目录。默认挂钩只能处理具体的文件,不能处理目录,至于目录则由专门的目录处理器进行处理,比如mod_autoindex。 if ((r->used_path_info != AP_REQ_ACCEPT_PATH_INFO) && r->path_info && *r->path_info) { /* default to reject */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "File does not exist: %s", apr_pstrcat(r->pool, r->filename, r->path_info, NULL)); return HTTP_NOT_FOUND; } if (r->method_number != M_GET) { core_request_config *req_cfg; req_cfg = ap_get_module_config(r->request_config, &core_module); if (!req_cfg->deliver_script) { /* The flag hasn't been set for this request. Punt. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "This resource does not accept the %s method.", r->method); return HTTP_METHOD_NOT_ALLOWED; } } 如果客户端请求的方法不是GET,一般情况下就是指POST方法。GET方法对所有的文件都能使用。它只是简单的返回文件的内容。而POST方法则未必。通常情况下只有脚本文件比如CGI才支持 POST方法。因此对于POST方法,必须检测文件是否支持。这可以通过deliver_script标志进行检测。如果设置了该标志,则意味着该文件是脚本文件,此时可以返回给客户端,否则终止退出。 if ((status = apr_file_open(&fd, r->filename, APR_READ | APR_BINARY #if APR_HAS_SENDFILE | ((d->enable_sendfile == ENABLE_SENDFILE_OFF) ? 0 : APR_SENDFILE_ENABLED) #endif , 0, r->pool)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, "file permissions deny server access: %s", r->filename); return HTTP_FORBIDDEN; } ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); ap_set_etag(r); apr_table_setn(r->headers_out, "Accept-Ranges", "bytes"); ap_set_content_length(r, r->finfo.size); 上面的代码用于打开指定的文件,并对返回的报文头进行设置,包括Last-Modified,Etag,Accept-Ranges等域头。在设置完毕之后,处理器将开始真正的读取请求的文件,处理代码如下所示: bb = apr_brigade_create(r->pool, c->bucket_alloc); if ((errstatus = ap_meets_conditions(r)) != OK) { apr_file_close(fd); r->status = errstatus; } else { if (bld_content_md5) { apr_table_setn(r->headers_out, "Content-MD5", ap_md5digest(r->pool, fd)); } if (sizeof(apr_off_t) > sizeof(apr_size_t) && r->finfo.size > AP_MAX_SENDFILE) { apr_off_t fsize = r->finfo.size; e = apr_bucket_file_create(fd, 0, AP_MAX_SENDFILE, r->pool, c->bucket_alloc); while (fsize > AP_MAX_SENDFILE) { apr_bucket *ce; apr_bucket_copy(e, &ce); APR_BRIGADE_INSERT_TAIL(bb, ce); e->start += AP_MAX_SENDFILE; ①②③ fsize -= AP_MAX_SENDFILE; } e->length = (apr_size_t)fsize; /* Resize just the last bucket */ } else { e = apr_bucket_file_create(fd, 0, (apr_size_t)r->finfo.size, r->pool, c->bucket_alloc); } #if APR_HAS_MMAP if (d->enable_mmap == ENABLE_MMAP_OFF) { (void)apr_bucket_file_enable_mmap(e, 0); } #endif APR_BRIGADE_INSERT_TAIL(bb, e); } 在前面描述存储段和存储段组的时候我们曾经描述过,Apache2.0中所有的数据的组织都是基于存储段和存储段组进行的。因此要能够读懂上面的代码,则必须理解存储段和存储段组。这个处理可以包括下面的几个典型的步骤,(基于所有的存储段和存储段组的使用步骤几乎是相同的): (1)使用apr_brigade_create创建一个存储段组bb 该存储段组中最终实际上只会包含两种存储段:文件存储段以及标识存储段组结束的EOS存储段。但是文件存储段则可能包含多个。 (2)创建文件存储段用以保存实际的文件的内容。在Apache中,单个存储段能够存储的内容是有限的,最大为AP_MAX_SENDFILE(2^24字节)。但是有的时候实际的文件可能会超出单个存储段的容量。此时处理器将会将整个文件拆分到多个文件存储段组和保存,每个存储段组保存AP_MAX_SENDFILE个字节,如②所示。 如果操作系统支持内存映射,那么在对文件进行处理的时候还需要考虑到内存映射的使用。通过EnableMMAP指令,在递送中如果需要读取一个文件的内容,可以指示是否需要使用内存映射。如果操作系统支持,默认情况下都将使用内存映射。内存映射有时会带来性能的提高,但在某些情况下,您可能会需要禁用内存映射以避免一些操作系统的问题: 1. 在一些多处理器的系统上,内存映射会减低一些httpd的性能。 2. 在挂载了NFS的DocumentRoot上,若已经将一个文件进行了内存映射,则删除或截断这个文件会造成httpd因为分段故障而崩溃。 如果Apache中明确禁止在读取文件的时候用内存映射,那么处理器中必须反映到这一点。具体的就是在存储段组中增加一个禁止使用内存映射的标志存储段,这可以通过函数apr_bucket_file_enable_mmap(e,0)实现: APU_DECLARE(apr_status_t) apr_bucket_file_enable_mmap(apr_bucket *e, int enabled) { apr_bucket_file *a = e->data; a->can_mmap = enabled; return APR_SUCCESS; } 该函数就是创建一个文件存储段,并将其中的can_mmap禁止。 一旦整个存储段组的内容存储完毕,那么过滤器就可以调用ap_pass_brigade将整个存储段组递交给输出过滤器。输出过滤器逐一进行处理,最终将整个内容递交到网络并返回到客户端。

原创粉丝点击