apache2.x filter编程入门介绍

apache2.x filter编程入门介绍

from: http://www.loveopensource.com/?p=35
Apache2.x filter编程(方便的控制apache的输入和输出)
by linux_prog

Apache2中引入了filter的概念,可以利用filter很方便的对接受到的数据和发出去的数据进行更改或者替换。
在Apache1中做这些事情比较的困难,利用filter,做这些事情变得非常的简单。
先分析一下apache2自带的一个非常简单的filter:
#include “httpd.h”
#include “http_config.h”
#include “apr_buckets.h”
#include “apr_general.h”
#include “apr_lib.h”
#include “util_filter.h”
#include “http_request.h”
#include  <ctype.h>

static const char s_szCaseFilterName[]=”CaseFilter”;
module AP_MODULE_DECLARE_DATA case_filter_module;

typedef struct
    {
    int bEnabled;
    } CaseFilterConfig;

static void *CaseFilterCreateServerConfig(apr_pool_t *p,server_rec *s)
    {
    CaseFilterConfig *pConfig=apr_pcalloc(p,sizeof *pConfig);

    pConfig->bEnabled=0;

    return pConfig;
    }

static void CaseFilterInsertFilter(request_rec *r)
    {
    CaseFilterConfig *pConfig=ap_get_module_config(r->server->module_config,
                                                   &case_filter_module);

    if(!pConfig->bEnabled)
        return;

    ap_add_output_filter(s_szCaseFilterName,NULL,r,r->connection);
    }

static apr_status_t CaseFilterOutFilter(ap_filter_t *f,
                                        apr_bucket_brigade *pbbIn)
    {
    request_rec *r = f->r;
    conn_rec *c = r->connection;
    apr_bucket *pbktIn;
    apr_bucket_brigade *pbbOut;

    pbbOut=apr_brigade_create(r->pool, c->bucket_alloc);
  
    for (pbktIn = APR_BRIGADE_FIRST(pbbIn);
         pbktIn != APR_BRIGADE_SENTINEL(pbbIn);
         pbktIn = APR_BUCKET_NEXT(pbktIn))
    {
        const char *data;
        apr_size_t len;
        char *buf;
        apr_size_t n;
        apr_bucket *pbktOut;

        if(APR_BUCKET_IS_EOS(pbktIn))
            {
            apr_bucket *pbktEOS=apr_bucket_eos_create(c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(pbbOut,pbktEOS);
            continue;
            }

        /* read */
        apr_bucket_read(pbktIn,&data,&len,APR_BLOCK_READ);

        /* write */
        buf = apr_bucket_alloc(len, c->bucket_alloc);
        for(n=0 ; n < len ; ++n)
            buf[n] = apr_toupper(data[n]);

        pbktOut = apr_bucket_heap_create(buf, len, apr_bucket_free,
                                         c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(pbbOut,pbktOut);
        }

    /* XXX: is there any advantage to passing a brigade for each bucket? */
    return ap_pass_brigade(f->next,pbbOut);
    }

static const char *CaseFilterEnable(cmd_parms *cmd, void *dummy, int arg)
    {
    CaseFilterConfig *pConfig=ap_get_module_config(cmd->server->module_config,
                                                   &case_filter_module);
    pConfig->bEnabled=arg;

    return NULL;
    }

static const command_rec CaseFilterCmds[] =
    {
    AP_INIT_FLAG(”CaseFilter”, CaseFilterEnable, NULL, RSRC_CONF,
                 “Run a case filter on this host”),
    { NULL }
    };

static void CaseFilterRegisterHooks(apr_pool_t *p)
    {
    // 插入filter
    ap_hook_insert_filter(CaseFilterInsertFilter,NULL,NULL,APR_HOOK_MIDDLE);
    // 注册filter
    ap_register_output_filter(s_szCaseFilterName,CaseFilterOutFilter,NULL,
                              AP_FTYPE_RESOURCE);
    }

module AP_MODULE_DECLARE_DATA case_filter_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    CaseFilterCreateServerConfig, // 模块初始化
    NULL,
    CaseFilterCmds,   // 命令处理,设置该模块开关
    CaseFilterRegisterHooks //注册output filter
};

安装方法:
./bin/apxs -c ./mod_case_filter.c
./bin/apxs -a -i -n case_filter ./mod_case_filter.la

在httpd.conf中加入:
CaseFilter on

这个时候访问启动apache,访问一个页面看看,查看页面源代码,发现页面中的每个字符都变成了大写。
这就是filter改变了输出的内容的结果。
这个是一个很好的例子,在这个例子上做一些改变可以很方便的实现自己对所有经过apache出去的内容做更改。

下面对filter编写中的一些难点做一些解释:
(1) 常见filter的种类
   output filter / input filter
(2) filter类型

util_filter.h中有定义:
typedef enum {
    /** These filters are used to alter the content that is passed through
     *  them. Examples are SSI or PHP. */
    AP_FTYPE_RESOURCE     = 10,
    /** These filters are used to alter the content as a whole, but after all
     *  AP_FTYPE_RESOURCE filters are executed.  These filters should not
     *  change the content-type.  An example is deflate.  */
    AP_FTYPE_CONTENT_SET  = 20,
    /** These filters are used to handle the protocol between server and
     *  client.  Examples are HTTP and POP. */
    AP_FTYPE_PROTOCOL     = 30,
    /** These filters implement transport encodings (e.g., chunking). */
    AP_FTYPE_TRANSCODE    = 40,
    /** These filters will alter the content, but in ways that are
     *  more strongly associated with the connection.  Examples are
     *  splitting an HTTP connection into multiple requests and
     *  buffering HTTP responses across multiple requests.
     *
     *  It is important to note that these types of filters are not
     *  allowed in a sub-request. A sub-request’s output can certainly
     *  be filtered by ::AP_FTYPE_RESOURCE filters, but all of the “final
     *  processing” is determined by the main request. */
    AP_FTYPE_CONNECTION  = 50,
    /** These filters don’t alter the content.  They are responsible for
     *  sending/receiving data to/from the client. */
    AP_FTYPE_NETWORK     = 60
} ap_filter_type;

可见,上面是一个AP_FTYPE_RESOURCE的filter,用来修改页面内容,类似于PHP。
AP_FTYPE_CONTENT_SET filter是把内容当作一个整体来处理,像mod_deflate将输出压缩。
一般应用中,这两种filter是我们常用到的。

(3) filter执行顺序
  按照ap_filter_type从小到大的顺序执行,也就是说也执行AP_FTYPE_RESOURCE,再执行AP_FTYPE_CONTENT_SET。。。。。
  当然,你也可以这样定义filter:
  ap_register_output_filter(s_szCaseFilterName,CaseFilterOutFilter,NULL,
                              AP_FTYPE_RESOURCE-1);
  这时filter将会在所有AP_FTYPE_RESOURCE filter前面执行。
  如果两个filter都属于一个级别的filter,比如都属于AP_FTYPE_RESOURCE,那么按照httpd.conf中:
  LoadModule php5_module        modules/libphp5.so
  LoadModule case_filter_module modules/mod_case_filter.so
  的先后顺序依次执行各个filter.

(4) 编写filter中用到的重要数据结构:
    apr_bucket_brigade *pbbOut; // 这个东西把所有的输入或者输出串起来,类似于一个链表
    apr_bucket *pbktIn;         // 这个东西就是链表中的一个元素
   
    apache提供很多的宏对这两个结构做处理。
    比如:
    // 遍历输出内容
    for (pbktIn = APR_BRIGADE_FIRST(pbbIn);
         pbktIn != APR_BRIGADE_SENTINEL(pbbIn);
         pbktIn = APR_BUCKET_NEXT(pbktIn))
    {
      
    }
   
    // 得到下一个bucket
    apr_bucket *newb = APR_BUCKET_NEXT(pbktIn);
   
    //从当前apr_bucket_brigade中删除apr_bucket,但不释放资源
    APR_BUCKET_REMOVE(pbktIn);
   
    //从当前apr_bucket_brigade中删除apr_bucket,且释放资源
    apr_bucket_delete(pbktIn);
   
    对于一个apr_bucket,你可以做split,分割成多个apr_bucket,方便你修改数据。
    比如:
    int index;
    apr_bucket_split(pbktIn, index); //从pbktIn的index位置开始分裂
   
  filter是非常重要的一个功能,利用它我们可以开发出功能非常强大的apache module.
  希望对大家有用。
牛人啊。