这个函数通过 Apache 提供的 API:ap_get_client_block 将请求中 POST 的数据读入到缓冲区,如果预分配的缓冲区不够,则重新分配内存存放,并同时修改缓冲区的实际长度。然后,我们在 handler 中调用此函数
/* The sample content handler */ static int echo_post_handler(request_rec *req) { if (strcmp(req->handler, "echo_post")) { return DECLINED; } if((req->method_number != M_GET) && (req->method_number != M_POST)){ return HTTP_METHOD_NOT_ALLOWED; } char *post = (char *)malloc(sizeof(char)*DFT_BUF_SIZE); size_t post_size = DFT_BUF_SIZE; if(post == NULL){ return HTTP_INTERNAL_SERVER_ERROR; } memset(post, '\0', post_size); int ret = read_post_data(req, &post, &post_size); if(ret != OK){ free(post); post = NULL; post_size = 0; return ret; } ap_set_content_type(req, "text/html;charset=utf-8"); ap_set_content_length(req, post_size); if(post_size == 0){ ap_rputs("no post data found", req); return OK; } ap_rputs(post, req); free(post); post = NULL; post_size = 0; return OK; } |
handler 读到客户端 POST 数据之后,将数据原封不动地回显 (echo) 给客户端。在调用 ap_rputs 将数据写回客户端之后,释放动态分配的内存。
运行 echo 模块
配置信息如第一小节中,拷贝模块到 apache_home/modules/ 下之后,重启 httpd,以便重新加载 echo_post 模块。我们仍旧使用 curl 程序来模拟客户端调用,发送一个文件的内容到 echo_post 模块:
$ curl -F "file=@Makefile" http://10.111.43.145:9527/echo_post |
curl 的 -F 选项指定一个本地文件,并将文件内容 POST 到指定的 URL,运行结果如下:
% Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 2334 100 1167 100 1167 28789 28789 --:--:-- --:--:-- --:--:-- 29175 ------------------------------4dba7c85f3d1 Content-Disposition: form-data; name="file"; filename="Makefile" Content-Type: application/octet-stream ## ## Makefile -- Build procedure for sample echo_post Apache module ## Autogenerated via ``apxs -n echo_post -g''. ## builddir=. top_srcdir=/etc/httpd top_builddir=/etc/httpd include /usr/lib/httpd/build/special.mk # the used tools APXS=apxs APACHECTL=apachectl # additional defines, includes and libraries #DEFS=-Dmy_define=my_value #INCLUDES=-Imy/include/dir ...... |
可以看到,echo_post 会将 Makefile 的内容原封不动的返回。我们需要更进一步的改造这个模块,并引入模块的配置等新的内容。
在这一小节,我们继续扩展上例中的 echo_post 模块,我们将 echo_post 扩展为可配置的模块,通过修改配置文件 httpd.conf 中设置 ConvertType 的值,可以使得模块在运行时的行为发生变化。
配置信息读取
首先定义一个配置信息的结构体:
typedef struct{ int convert_type; // 转换类型 }cust_config_t; |
为了便于演示,这个结构体仅有一个成员,convert_type, 表示转换类型,如果在配置文件中该值被设置为 0,则将客户端 POST 的数据转换为大写,如果为 1,则转换为小写。这样即可通过配置信息修改模块运行时的行为。
加入函数声明:
static void *create_config(apr_pool_t *pool, server_rec *server); static const char *set_mod_config(cmd_parms *params, void *config, const char *arg); |
create_config 函数用以创建一个用户自定义的结构体,告诉 Apache 如果创建这个结构体。set_mod_config 函数用以设置配置结构体中的成员,这个函数注册在 command_rec 数组中。而 command_rec 数组则保存在模块声明结构体中: 定义一个 command_rec 结构体类型的数组:
static const command_rec cust_echo_cmds[] = { AP_INIT_TAKE1("ConvertType", set_mod_config, NULL, RSRC_CONF, "convert type of post data"), {0} }; |
这个模块的模块声明部分较前面小节中的例子更复杂一些,我们启用了配置信息表 cust_echo_cmds,并注册了创建配置结构的函数 create_config。
/* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA cust_echo_post_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ create_config, /* create per-server config structures */ NULL, /* merge per-server config structures */ cust_echo_cmds, /* table of config file commands */ cust_echo_post_register_hooks /* register hooks */ }; |
运行可配置 echo 模块
在 httpd.conf 配置文件中,加入 LoadModule 指令加载此模块,并设置配置项 ConvertType 为 0
LoadModule cust_echo_post_module modules/mod_cust_echo_post.so <Location /cust_echo_post> SetHandler cust_echo_post </Location> #configure for cust_echo_post ConvertType 0 |
这样,我们通过 curl 测试可以得到如下结果:
$ curl -d "hello darkness my old friend" \ http://10.111.43.145:9527/cust_echo_post HELLO DARKNESS MY OLD FRIEND |
修改 httpd.conf 中的 ConvertType 为 1,重启 Apache httpd,重新测试:
$ curl -d "HELLO DARKNESS MY OLD FRIEND" \ http://10.111.43.145:9527/cust_echo_post hello darkness my old friend |
过滤器事实上是另一种形式的模块,其生命周期及调用时机请参看第一小节。Apache 对通用的数据结构都做过一些封装,并以库的方式提供 ( 即 APR(Apache Portable Runtime))。在过滤器中,有两个比较重要的数据结构:apr_bucket 和 apr_bucket_brigade。apr_bucket_birgade 相当于一个环状队列,而 apr_bucket 是队列中的元素。这两个数据结构的名字可能来源于救火队。救火人员站成一个长队,一个队头临近水源,另一头用水灭火,然后每个队列中的人员将水桶从上一个人手中接过,然后传递给下一个人。过滤器的工作方式与此类似,所有的过滤器形成一个长链,数据从上一个过滤器流入,进行过滤,然后将加工过的数据流入下一个过滤器。
我们的过滤器非常简单,从上一个过滤器中读到数据,将数据中的字符串转换为大写,然后将桶 (apr_bucket) 传递给下一个过滤器。Apache 提供了丰富的 API 来完成这一系列的操作。
大小写转换过滤器
static apr_status_t case_filter(ap_filter_t *filter, apr_bucket_brigade *bbin){ request_rec *req = filter->r; conn_rec *con = req->connection; apr_bucket *bucket; apr_bucket_brigade *bbout; //create brigade bbout = apr_brigade_create(req->pool, con->bucket_alloc); //iterate the full brigade APR_BRIGADE_FOREACH(bucket, bbin){ if(APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)){ APR_BUCKET_REMOVE(bucket); APR_BRIGADE_INSERT_TAIL(bbout, bucket); return ap_pass_brigade(filter->next, bbout); } char *data, *buffer; apr_size_t data_len; //read content of current bucket in brigade apr_bucket_read(bucket, &data, &data_len, APR_NONBLOCK_READ); buffer = apr_bucket_alloc(data_len, con->bucket_alloc); int i; for(i = 0; i < data_len; i++){ //convert buffer[i] = apr_toupper(data[i]); } apr_bucket *temp_bucket; temp_bucket = apr_bucket_heap_create( buffer, data_len, apr_bucket_free, con->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bbout, temp_bucket); } return APR_SUCCESS; } |
注册这个过滤器:
static void filter_echo_post_register_hooks(apr_pool_t *p) { ap_register_output_filter(filter_name, case_filter, NULL, AP_FTYPE_RESOURCE); } |