收藏 分销(赏)

Apache中过滤器实现机制分析.doc

上传人:xrp****65 文档编号:7231692 上传时间:2024-12-28 格式:DOC 页数:9 大小:159KB 下载积分:10 金币
下载 相关 举报
Apache中过滤器实现机制分析.doc_第1页
第1页 / 共9页
Apache中过滤器实现机制分析.doc_第2页
第2页 / 共9页


点击查看更多>>
资源描述
Apache中过滤器实现机制的分析 内容提要 Apache中的过滤器形成了链表结构,当响应请求输出的时候,请求将依次流经整个过滤器链,直到到达最后一个过滤器,直接将其发送到网络中去。 在过滤器的最顶端,代码生成客户端请求的内容,通常称之为“内容生成器”,内容生成器产生的内容使用Apache的标准输入机制ap_rputs(),ap_rprintf()或者ap_rwrite()输入过滤器顶端。每一个过滤器都被定义为一个回调,该回调从前一个过滤器接受输出(如果是第一个过滤器的话,没有前一个过滤器,那么此时从内容生成器中接受输入),对其进行操作,同时将处理结果传递给下链表中的下一个过滤器。传递可以使用ap_fc_*函数,比如ap_fc_puts(),ap_fc_printf()以及ap_fc_write()等等。 当内容生成器生成内容之后,Apache系统将传递一个“结束流”标记给过滤器链表,过滤器将使用这个“结束流”来刷新所有的内部状态以及决定那些不完整的语法。 过滤器函数 过滤器函数通常用于执行回调,它将传递一个指针给对应的过滤器以及一个存储段,其中包含需要过滤得内容。在过滤器的ctx中,回调将查找它的上下文,上下文就是通过ctx提供。过滤器可能安装很多次,每次都接受自己的安装上下文指针。回调总是与过滤器关联在一起的,通常通过指定的名字描述。在ap_register_input_filter()和ap_register_output_filter()中会将指定的关联函数与指定的过滤器关联在一起。 如果传递给注册函数的初始化参数不为NULL, 在Apache1.3版本,当Apache为特定请求生成内容,返回客户之前, frec是Apache过滤器结点,其是ap_filter_rec_t结构,该结构用来定义一个Apache过滤器,其定义如下: struct ap_filter_rec_t { const char *name; ap_filter_func filter_func; ap_init_filter_func filter_init_func; ap_filter_type ftype; struct ap_filter_rec_t *next; }; 该结构定义了作为一个过滤器必须具备的各个特性。name是过滤器的名称;filter_func是过滤器对应的函数,当过滤器被激活的时候,其将调用该函数对请求进行过滤处理。filter_init_func用于在过滤处理句柄被激活之前调用。需要注意的是,该过滤器仅能用于HTTP协议。其余协议的初始化由协议本身实现。 yype则是过滤器的类型。next则是指向下一个过滤器结构。 ctx通常用来保存过滤器frec可能需要的额外的信息,由于需要的信息的千差万别,因此只能将其类型定义为void,待真正需要的时候再做强制转换。 Apache的各个过滤器之间通过next形成链表结构。 R则是与当前过滤器关联的请求信息。C则是与当前过滤器关联的连接信息。 ////////////////////////////////////////////////////////////////////////////////////////////////////// Apache中对过滤器的保存是根据过滤器名称进行的,保存结果形成一棵过滤树。由于过滤器分为输入过滤器和输出过滤器两种,因此最终结果将用两棵过滤树保存。这两颗树的树根在Apache中分别用registered_input_filters和registered_output_filters表示。它们是全局变量,类型为filter_trie_node结构,其定义如下: struct filter_trie_node { ap_filter_rec_t *frec; filter_trie_child_ptr *children; int nchildren; int size; }; frec是该结点中实际保存的过滤器结构;children用来记录该结点的所有子结点,Apache中对子结点的分配总是以4为单位进行的,不过即使一次分配了四个单元,但也不一定所有的单元都被使用,其中有一些空闲的单元。因此nchildren用来记录当前结点的子结点中所有的已经被使用的结点的个数,而size则用来记录实际分配的结点的个数,其值总是大于或者等于nchildren。size-nchildren则就是子结点中尚未使用的结点的个数。 现在我们来看一下Apache中是如何进行过滤器注册的,由于过滤器的注册的唯一根据就是过滤器名称,因此我们假设存在下面四个过滤器,其名称以及对应的过滤器执行函数分别如下: 输出过滤器名称 输出过滤器执行函数 face face_func() facd facd_func() fb fb_func() food food_func() 首先我们来看一下“face”过滤器的注册过程。 在Apache中过滤器的注册是通过函数ap_regieste_input_filter和ap_register_output_filter两个函数进行的。它们的实现几乎完成一样,在内部都是调用register_filter函数,只不过输入过滤器是挂结到registered_input_filters树下面,而输出过滤器则是挂结到registered_output_filters树下。 由于face过滤器是第一个过滤器,因此此前registered_output_filters系统初始化为NULL。在register_filter函数内部,函数首先判断register_ouput_filters是否为NULL,如果为NULL,那么其将调用trie_node_alloc函数为其分配空间,该函数完成的事情很简单: (1) 如果分配空间的该结点没有父结点,比如此时的register_output_filters,其是所有结点的祖先,没有父结点, 一次性为filter_trie_node中的children分配四个单元,因此对于register_output_filters,调用trie_node_alloc后的结果将如下图所示: register_output_filters调用filter_trie_node后的图示 分配之后register_output_filters的size为4,而nchildren则0。 一旦分配了register_output_filters所需要的空间之后,就可以将face过滤器挂结到其下面。挂结不是一次性完成的,其包括多个步骤。register_filters函数首先将过滤器的名称全部变为小写进行处理,并对名称中的每一个字母都进行类似处理register_output_filters的过程。对于face,函数首先处理字母“f”。对于f字母,函数也首先调用trie_node_alloc分配其需要的空间,调用形式如下: filter_trie_node* child=filter_node_alloc(FILTER_POOL,node,*n); 与分配registered_output_filters时候调用filter_node_alloc(FILTER_POOL,NULL,0)不同的是此处指定的新分配的结点必须挂结到结点node即register_output_filters下面。 因此实际上,trie_node_alloc内部不仅仅提供内存分配的过程,还提供了层次挂结的过程,除了前面的(1),trie_node_alloc还包括两个重要的功能: (2) 如果需要分配的结点必须挂结到指定的父结点之下,其首先在该父结点的所有孩子中查找是否已经存在该结点。查找过程如下所示: for (i = 0; i < parent->nchildren; i++) { if (c == parent->children[i].c) { return parent->children[i].child; } else if (c < parent->children[i].c) { break; } } 比如对于“f”函数在父结点register_output_filters的所有子结点中查找时候已经存在,如果存在则直接返回该结点;事实上目前register_output_filters中并不存在,因此函数将分配filter_trie_node空间(包括四个子结点空间的分配)并调用trie_node_link进行父子挂结。挂结的具体过程我们在后面描述。一旦进行挂结,则挂结后的示意图如下所示: 一旦将child挂结到根结点下面,那么根结点nchildren变为1,表示已经有一个单元被占用。一旦对字母“f”的处理完毕,当前处理结点node将从根结点register_output_filters移动到child中,此时,你可以从下图中看出。 注册函数在处理完字母“f”后其将继续处理“a”。对a以及后面的“c”和“e”与对f的处理几乎完全相同,处理时候首先在当前的父结点node中查找是否存在同样的结点,如果存在则直接返回,否则生成新的结点,同时进行挂结。 至此函数完成了对挂钩“face”的注册,实际的真正的挂钩数据保存在“e”结点的frec结构中。 现在我们继续看看函数对挂钩“fb”的注册过程。函数首先处理字母“f”,它将首先查找根结点中是否存在“f”结点。事实上此时确实存在,因此函数只是简单的返回f指向的结点,同时将当前结点node指向f结点。函数继续处理b结点,其父亲为node,即f指向的结点。函数在node的children数组中查找b结点,结果没有查找到,则生成一个带有四个空闲单元的结点,并将其挂结到node中合适的位置,同时对b结点进行初始化。当face和fb挂钩都注册之后,这个结点树结构如下所示。 对于其余的结点facd和food的注册过程完全类似,一旦所有的挂钩注册完毕,整个过滤器树如图所示。 到所有的挂钩注册完毕,所有的挂钩形成了一个典型的挂钩树,如果上面的图我们还不能清楚的看出来的话,我们可以用下图表示: ////////////////////////////////////////////////////////////////////////////////////////////////////// 过滤器的查找 Apache中对过滤器的查找也是通过名称来进行的。过滤器的查找主要是通过函数ap_get_output_filter_handle进行的,该函数的内部主要调用的则是静态函数get_filter_handle,其调用原型如下: get_filter_handle(name, registered_output_filters); name则是需要查找的过滤器的名称,registered_output_filters则是过滤树的根结点。一旦查找到name名称的过滤器,该函数将返回该过滤器的结构filter_rec_t;否则返回NULL。 get_filter_handle函数的内部的查找机制与注册过滤器非常类似。在查找过程中,Apache必须维护三个结点指针,一个指向所有的过滤器树的根结点,一个指向当前结点,还有一个指向当前结点的子结点。比如如果需要查找过滤器树中是否存在“face”过滤器,其从首先在根结点register_output_filters的所有子结点中查找是否存在f结点。如果查找到,则将当前结点置为f结点,并继续在当前结点即f结点的所有子结点中查找是否存在“a”结点,如果找到继续将a结点作为当前结点,并在“a”的所有子结点中查找是否存在“c”结点。如此类退,直到找到满足要求的结点,并返回它的过滤器。 有一点需要注意的是,为了加快对当前结点的所有子结点的查找速度,在查找过程中,函数使用了二分法进行查找,二分法的使用能够有效的改善查找速度。 ////////////////////////////////////////////////////////////////////////////////////////////////////// 过滤器的添加 在Apache中,过滤器可以分为三种大的类型:独立过滤器、与请求关联过滤器以及与连接关联过滤器。事实上在查看请求记录结构request_rec的时候,会发现下面的代码: struct request_rec{ …… struct ap_filter_t *output_filters; struct ap_filter_t *input_filters; struct ap_filter_t *proto_output_filters; struct ap_filter_t *proto_input_filters; …… } 同时在连接记录结构conn_rec中,我们也会看到下面的代码: struct conn_rec{ …… struct ap_filter_t *input_filters; struct ap_filter_t *output_filters; …… } 在Apache2.0中为了兼容HTTP1.1标准,服务器在建立连接的时候,在该连接上可能传送多个请求,因此对于请求过滤器而言其只对该针对该请求进行过滤;而如果是连接过滤器的话,其将对该连接中的所有的请求都进行过滤。我们可以用下面的示意图进行描述: 图示:请求过滤器 图示:连接过滤器 请求过滤器和处理过滤器都必须与给定的请求和连接进行关联,如果过滤器没有与任何的请求和连接进行关联,则我们称之为独立过滤器。 对于非独立过滤器,为了能够将它与给定的请求或者连接进行关联,我们必须调用ap_add_input_filter或者ap_add_output_filter。我们仅仅来了解ap_add_input_filter函数, ap_add_output_filter函数与之几乎完全相同,我们不再赘述。 ap_add_input_filter函数原型如下所示: AP_DECLARE(ap_filter_t *) ap_add_input_filter(const char *name, void *ctx, request_rec *r, conn_rec *c) 函数参数中,name则是给定的过滤器的名称,ctx则是传递给过滤器执行函数可能需要的额外参数,通常是一个struct结构,不过由于struct结构内部我们无法知道,因此我们只能通过void进行强制转换。r则是过滤器需要关联的请求结构;c则是过滤器需要进行关联的连接结构。 在进一步分析之前,我们首先来看一下Apache中是如何组织给定的请求或者连接中的过滤器的。Apache中使用ap_filter _t链表结构进行保存,ap_filter_t的定义如下: struct ap_filter_t { ap_filter_rec_t *frec; void *ctx; ap_filter_t *next; request_rec *r; conn_rec *c; }; 每一个连接或者请求占用一个ap_filter_t结构,不同请求之间相互通过next形成一个链表;而对于每个连接或者请求而言,与其关联的所有的过滤器都保存在frec中,过滤器之间也相互形成链表。因此整个结构可以用下面的图示形容: 当然apache中对于ap_filter_t结构的组织并不是随意的,无序的。为了能够更好的组织Apache中的各种过滤器,系统将其分为五种类型:AP_FTYPE_RESOURCE、AP_FTYPE_CONTENT_SET、AP_FTYPE_PROTOCOL、AP_FTYPE_TRANSCODE、AP_FTYPE_CONNECTION、AP_FTYPE_NETWORK。这些类型定义在枚举结构ap_filter_type中。 对于AP_FTYPE_RESOURCE类型的过滤器,其通常用于修改传递给该过滤器的内容,比如SSI或者PHP过滤器就是这种类型。 AP_FTYPE_CONTENT_SET过滤器则主要用来将文档的正文作为整体进行修改,通常在AP_FTYPE_RESOURCE过滤器使用之后才被使用。这种过滤器只修改文档的正文,并不修改文档的类型等信息。典型的就是deflat过滤器,其只对文档正文进行压缩和解压。 AP_FTYPE_PROTOCOL过滤器则仅仅处理服务器和客户机之间的协议,比如HTTP和POP协议过滤器就是这种类型。 AP_FTYPE_TRANSCODE过滤器则仅仅修改传输编码。 AP_FTYPE_CONNECTION是Apache中最经常也是最重要的过滤器之一。该过滤器将会修改文档的正文部分,不过该过滤器更多的是必须与连接进行关联,这真是我们通常称之为连接过滤器的原因。最简单的例子,我们通常需要将一个HTTP连接分裂成为多个请求或者在返回客户端之前将多个请求的返回内容用一个HTTP连接进行返回。不过有一点需要保证的是,这些种类的过滤器不能针对子请求进行过滤。 最后一种过滤器类型则是AP_FTYPE_NETWORK,该请求并不修改任何内容,它们的责任仅仅是将从客户端接受数据送入过滤器队列中或者将过滤后的最终内容返回给客户端而已。 每个过滤器Apache都安排赋以一个整数,从10到60。如果过滤器的类型<30,即AP_FTYPE_PROTOCOL,则认为该过滤器是正文过滤器;如果过滤器的类型介于AP_FTYPE_PROTOCOL和AP_FTYPE_CONNECTION,则称之为协议过滤器;其余的所有的过滤器都称之为连接过滤器。 Apache中对于过滤器的组织的主要依据真是过滤器的类型。 因此如果我们需要将一个过滤器添加到指定的请求或者连接上。 数据传递 当一个过滤器处理完相应的数据之后,它就必须将该数据传递给下一个过滤器继续进行处理。Apache中使用函数ap_pass_brigade在不同的过滤器之间传递缓冲区。函数的原型定义如下: AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *filter, apr_bucket_brigade *bucket); 其中,filter是传递的下一个过滤器,对于当前过滤器而言,下一个过滤器就是filter->next.bucket则是需要传递的存储段组。
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传

当前位置:首页 > 百科休闲 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:0574-28810668  投诉电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服