apache / 2.4 / reference / developer-modguide.html

为 Apache HTTP Server 2.4 开发模块

本文档说明了如何为 Apache HTTP Server 2.4 开发模块

Introduction

我们将在本文档中讨论什么

本文档将通过探索名为mod_example的示例模块,讨论如何为 Apache HTTP Server 2.4 创建模块。在本文档的第一部分中,每当我们访问 URL http://hostname/filename.sum时,本模块的目的就是为您的 Web 服务器上的现有文件计算并打印出各种摘要值。例如,如果我们想知道位于http://www.example.com/index.html的文件的 MD5 摘要值,则可以访问http://www.example.com/index.html.sum

在本文档的第二部分,它涉及配置指令和上下文感知,我们将研究一个简单地将其自己的配置写出给 Client 端的模块。

Prerequisites

首先,最重要的是,您应该具有 C 编程语言的基本知识。在大多数情况下,我们将尝试尽可能地采用教学方法,并链接至描述示例中使用的功能的文档,但是在许多情况下,有必要要么只是假设“它起作用”,要么就自己进行深入研究各种函数调用的方式和原因。

最后,您需要基本了解如何在 Apache HTTP Server 中加载和配置模块,以及如何在尚未拥有 Apache 头的情况下获取它们的头,因为这些头是编译新模块所必需的。

编译模块

要编译本文档中构建的源代码,我们将使用APXS。假设您的源文件名为 mod_example.c,则编译,安装和激活模块非常简单:

apxs -i -a -c mod_example.c

定义模块

模块名称标签

每个模块都以相同的声明或名称标签(如果需要的话)开头,该声明将模块定义为* Apache *中的独立实体:

module AP_MODULE_DECLARE_DATA   example_module =
{ 
    STANDARD20_MODULE_STUFF,
    create_dir_conf, /* Per-directory configuration handler */
    merge_dir_conf,  /* Merge handler for per-directory configurations */
    create_svr_conf, /* Per-server configuration handler */
    merge_svr_conf,  /* Merge handler for per-server configurations */
    directives,      /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};

这部分代码使服务器知道我们现在已经在系统中注册了一个新模块,并且其名称为example_module。模块的名称主要用于两件事:

  • 让服务器知道如何使用 LoadModule 加载模块

  • 设置模块的命名空间以在配置中使用

现在,我们只关心模块名称的第一个用途,当我们需要加载模块时,它才起作用:

LoadModule example_module modules/mod_example.so

从本质上讲,这告诉服务器打开mod_example.so并查找名为example_module的模块。

在我们的这个名称标签中,还有一堆关于我们希望如何处理事情的引用:我们在配置文件或.htaccess 中响应哪些指令,我们如何在特定上下文中操作,以及我们对哪些处理程序感兴趣在 Apache HTTP 服务中注册。在本文档的后面,我们将返回所有这些元素。

使用入门:挂接到服务器

钩子简介

在 Apache HTTP Server 2.4 中处理请求时,您需要做的第一件事是在请求处理过程中创建一个钩子。钩子实际上是一条消息,告诉服务器您愿意服务或至少看一眼 Client 端发出的某些请求。所有处理程序,无论是 mod_rewrite,mod_authn _ *,mod_proxy 等,都被挂接到请求过程的特定部分。如您所知,模块有不同的用途。一些是身份验证/授权处理程序,其他是文件或脚本处理程序,而其他一些第三模块则重写 URI 或代理内容。此外,最后,由服务器用户决定如何以及何时安装每个模块。因此,服务器本身不假定知道哪个模块负责处理特定请求,而是询问每个模块是否对给定请求感兴趣。然后,由每个模块决定是否像身份验证/授权模块那样轻轻地拒绝服务请求,接受服务请求或拒绝服务请求。

httpd 中的钩子处理

为了使诸如 mod_example 之类的处理程序更容易知道 Client 端是否在请求我们应处理的内容,服务器具有用于向模块提示是否需要其协助的指令。其中两个是AddHandlerSetHandler。让我们来看一个使用AddHandler的示例。在我们的示例示例中,我们希望每个以.sum 结尾的请求都由mod_example处理,因此我们将添加一个配置指令,该指令指示服务器执行此操作:

AddHandler example-handler .sum

这告诉服务器以下内容:*每当我们收到以.sum 结尾的 URI 请求时,我们都要让所有模块知道我们正在寻找名称为“ example-handler” *的对象。因此,当服务请求以.sum 结尾时,服务器将通知所有模块,该请求应由“ example-handler”服务。就像您稍后将看到的那样,当我们开始构建 mod_example 时,我们将检查由AddHandler中继的此处理程序标签,并根据该标签的值回复服务器。

迷上 httpd

首先,我们只想创建一个简单的处理程序,当请求特定的 URL 时,该处理程序会回复 Client 端浏览器,因此,我们现在就不必设置配置处理程序和指令。我们的初始模块定义将如下所示:

module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    register_hooks   /* Our hook registering function */
};

这使服务器知道我们对任何花哨的内容都不感兴趣,我们只想挂接到请求并可能处理其中的一些请求。

示例声明中的引用register_hooks是将创建的函数的名称,该函数将用于 Management 如何连接到请求流程。在此示例模块中,该功能仅具有一个目的。为了创建一个简单的钩子,在所有重写,访问控制等都已处理之后被调用。因此,我们将让服务器知道我们希望作为最后一个模块之一加入到其进程中:

static void register_hooks(apr_pool_t *pool)
{
    /* Create a hook in the request handler, so we get called when a request arrives */
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}

example_handler引用是将处理请求的函数。在下一章中,我们将讨论如何创建处理程序。

其他有用的钩子

钩入请求处理阶段只是您可以创建的许多钩子之一。钩子的其他方式有:

  • ap_hook_child_init:放置一个在生成子进程时执行的钩子(通常用于在分支服务器后初始化模块)

  • ap_hook_pre_config:放置一个在读取任何配置数据之前执行的钩子(非常早的钩子)

  • ap_hook_post_config:放置一个在解析配置之后但在服务器分叉之前执行的钩子

  • ap_hook_translate_name:在服务器上需要将 URI 转换为文件名时放置一个钩子(请考虑mod_rewrite)

  • ap_hook_quick_handler:类似于ap_hook_handler,除了它在任何其他请求钩子(翻译,身份验证,修复程序等)之前运行

  • ap_hook_log_transaction:放置一个在服务器将要添加当前请求的日志条目时执行的钩子

构建处理程序

处理程序本质上是一个向服务器发出请求时接收回调的函数。它将当前请求的记录(如何进行,传递了哪些 Headers 和请求,谁发出请求等)传递给它,并负责告诉服务器对请求不感兴趣或使用提供的工具处理请求。

一个简单的“你好,世界!”处理程序

让我们从制作一个非常简单的请求处理程序开始,该请求处理程序执行以下操作:

  • 检查这是“ example-handler”应满足的请求

  • 将输出的 Content Type 设置为text/html

  • 写“你好,世界!”回到 Client 端浏览器

  • 让服务器知道我们已经处理了这个请求,一切正常

在 C 代码中,我们的示例处理程序现在将如下所示:

static int example_handler(request_rec *r)
{
    /* First off, we need to check if this is a call for the "example-handler" handler.
     * If it is, we accept it and do our things, if not, we simply return DECLINED,
     * and the server will try somewhere else.
     */
    if (!r->handler || strcmp(r->handler, "example-handler")) return (DECLINED);
    
    /* Now that we are handling this request, we'll write out "Hello, world!" to the client.
     * To do so, we must first set the appropriate content type, followed by our output.
     */
    ap_set_content_type(r, "text/html");
    ap_rprintf(r, "Hello, world!");
    
    /* Lastly, we must tell the server that we took care of this request and everything went fine.
     * We do so by simply returning the value OK to the server.
     */
    return OK;
}

现在,我们将所有学到的知识放在一起,最后得到一个类似于mod_example_1.c的程序。稍后将在“您应该知道的一些有用的功能”部分中说明此示例中使用的功能。

request_rec 结构

任何请求中最重要的部分是* request record *。在对处理程序函数的调用中,这由与进行的每次调用一起传递的request_rec*结构表示。该结构在模块中通常简称为r,包含模块完全处理任何 HTTP 请求并相应做出响应所需的所有信息。

request_rec结构的一些关键元素是:

  • r->handler (char*):包含服务器当前正在请求处理此请求的处理程序的名称

  • r->method (char*):包含正在使用的 HTTP 方法 f.x。 GET 或 POST

  • r->filename (char*):包含 Client 端请求的翻译文件名

  • r->args (char*):包含请求的查询字符串(如果有)

  • r->headers_in (apr_table_t*):包含 Client 端发送的所有 Headers

  • r->connection (conn_rec*):包含有关当前连接信息的记录

  • r->user (char*):如果 URI 需要身份验证,则将其设置为提供的用户名

  • r->useragent_ip (char*):连接到我们的 Client 端的 IP 地址

  • r->pool (apr_pool_t*):此请求的内存池。我们将在“ Memory management”一章中对此进行讨论。

可以在httpd.h头文件或http://ci.apache.org/projects/httpd/trunk/doxygen/structrequest__rec.html中找到request_rec结构中包含的所有值的完整列表。

让我们在另一个示例处理程序中尝试其中一些变量:

static int example_handler(request_rec *r)
{
    /* Set the appropriate content type */
    ap_set_content_type(r, "text/html");

    /* Print out the IP address of the client connecting to us: */
    ap_rprintf(r, "<h2>Hello, %s!</h2>", r->useragent_ip);
    
    /* If we were reached through a GET or a POST request, be happy, else sad. */
    if ( !strcmp(r->method, "POST") || !strcmp(r->method, "GET") ) {
        ap_rputs("You used a GET or a POST method, that makes us happy!
", r); } else { ap_rputs("You did not use POST or GET, that makes us sad :(
", r); } /* Lastly, if there was a query string, let's print that too! */ if (r->args) { ap_rprintf(r, "Your query string was: %s", r->args); } return OK; }

Return values

Apache 依赖于处理程序的返回值来表示是否处理了请求,如果是,则表示请求是否正常。如果模块对处理特定请求不感兴趣,则应始终返回值DECLINED。如果正在处理请求,则应返回通用值OK或特定的 HTTP 状态代码,例如:

static int example_handler(request_rec *r)
{
    /* Return 404: Not found */
    return HTTP_NOT_FOUND;
}

返回OK或 HTTP 状态代码并不一定意味着请求将结束。服务器可能仍然具有对该请求感兴趣的其他处理程序,例如日志记录模块,在成功请求后,该日志记录模块将记录所请求内容及其进行方式的摘要。要完全停止并防止在模块完成后进行任何进一步的处理,可以返回值DONE,以使服务器知道应停止该请求上的所有活动并 continue 执行下一个,而无需通知其他处理程序。
一般回应代码:

  • DECLINED:我们未处理此请求

  • OK:我们处理了这个请求,一切顺利

  • DONE:我们处理了此请求,服务器应关闭该线程,而无需进一步处理

HTTP 特定的返回码(摘录):

  • HTTP_OK (200):请求正常

  • HTTP_MOVED_PERMANENTLY (301):资源已移至新 URL

  • HTTP_UNAUTHORIZED (401):Client 无权访问此页面

  • HTTP_FORBIDDEN (403):权限被拒绝

  • HTTP_NOT_FOUND (404):找不到文件

  • HTTP_INTERNAL_SERVER_ERROR (500):内部服务器错误(自我解释)

一些您应该知道的有用功能

  • ap_rputs(const char *string, request_rec *r) :
    将文本字符串发送到 Client 端。这是ap_rwrite的简写版本。
ap_rputs("Hello, world!", r);
  • ap_rprintf:
    此功能与printf一样工作,除了将结果发送到 Client 端。
ap_rprintf(r, "Hello, %s!", r->useragent_ip);
  • ap_set_content_type(request_rec * r,const char * type):
    设置要发送的输出的 Content Type。
ap_set_content_type(r, "text/plain"); /* force a raw text output */

Memory management

得益于内存池系统,在 Apache HTTP Server 2.4 中 Management 资源非常容易。本质上,每个服务器,连接和请求都有自己的内存池,该内存池在其作用域结束时会被清除,例如请求完成或服务器进程关闭时。您模块所需要做的就是将其锁存到该内存池中,而您不必担心必须自己清理一下-非常整洁,是吗?

在我们的模块中,我们将主要为每个请求分配内存,因此在创建新对象时使用r->pool引用是适当的。在池中分配内存的一些功能是:

  • void * apr_palloc(apr_pool_t * p,apr_size_t 大小):为您分配size个池中的字节数

  • void * apr_pcalloc(apr_pool_t * p,apr_size_t 大小):为您分配size池中的字节数并将所有字节设置为 0

  • char * apr_pstrdup(apr_pool_t * p,const char * s):创建字符串s的副本。这对于复制常量值很有用,因此您可以对其进行编辑

  • char * apr_psprintf(apr_pool_t * p,const char * fmt,...):与sprintf类似,不同之处在于服务器为您提供了适当分配的目标变量

让我们将这些函数放入示例处理程序中:

static int example_handler(request_rec *r)
{
    const char *original = "You can't edit this!";
    char *copy;
    int *integers;
    
    /* Allocate space for 10 integer values and set them all to zero. */
    integers = apr_pcalloc(r->pool, sizeof(int)*10); 
    
    /* Create a copy of the 'original' variable that we can edit. */
    copy = apr_pstrdup(r->pool, original);
    return OK;
}

这对于我们的模块非常好,不需要任何预先初始化的变量或结构。但是,如果我们想尽早初始化某些东西,则在请求进入之前,我们可以简单地在register_hooks函数中添加对函数的调用以对其进行整理:

static void register_hooks(apr_pool_t *pool)
{
    /* Call a function that initializes some stuff */
    example_init_function(pool);
    /* Create a hook in the request handler, so we get called when a request arrives */
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}

在此请求前初始化函数中,我们不会使用与为基于请求的函数分配资源时使用的池相同的池。相反,我们将使用服务器给我们的池在基于每个进程的级别上分配内存。

解析请求数据

在示例模块中,我们想添加一个功能,该功能可以检查 Client 端希望查看的摘要类型,MD5 或 SHA1.这可以通过向请求添加查询字符串来解决。查询字符串通常由几个关键字和值组成,这些关键字和值放在一个字符串中,例如valueA=yes&valueB=no&valueC=maybe。由模块本身来解析它们并获取所需的数据。在我们的示例中,我们将查找名为digest的键,如果将其设置为md5,则将生成 MD5 摘要,否则将生成 SHA1 摘要。

自从 Apache HTTP Server 2.4 引入以来,从 GET 和 POST 请求解析请求数据从未如此简单。我们解析 GET 和 POST 数据所需要做的只是四行:

apr_table_t *GET; 
apr_array_header_t*POST; 

ap_args_to_table(r, &GET); 

ap_parse_form_data(r, NULL, &POST, -1, 8192);

在特定的示例模块中,我们从查询字符串中查找digest值,该值现在位于名为GET的表中。要提取此值,我们只需要执行一个简单的操作即可:

/* Get the "digest" key from the query string, if any. */
const char *digestType = apr_table_get(GET, "digest");

/* If no key was returned, we will set a default value instead. */
if (!digestType) digestType = "sha1";

用于 POST 和 GET 数据的结构并不完全相同,因此,如果我们要从 POST 数据中获取值而不是查询字符串,我们将不得不再用几行,如最后this example所述本文档的章节。

制作高级处理程序

现在,我们已经学习了如何解析表单数据和 Management 资源,接下来我们可以 continue 创建模块的高级版本,该高级版本会释放文件的 MD5 或 SHA1 摘要:

static int example_handler(request_rec *r)
{
    int rc, exists;
    apr_finfo_t finfo;
    apr_file_t *file;
    char *filename;
    char buffer[256];
    apr_size_t readBytes;
    int n;
    apr_table_t *GET;
    apr_array_header_t *POST;
    const char *digestType;
    
    
    /* Check that the "example-handler" handler is being called. */
    if (!r->handler || strcmp(r->handler, "example-handler")) return (DECLINED);
    
    /* Figure out which file is being requested by removing the .sum from it */
    filename = apr_pstrdup(r->pool, r->filename);
    filename[strlen(filename)-4] = 0; /* Cut off the last 4 characters. */
    
    /* Figure out if the file we request a sum on exists and isn't a directory */
    rc = apr_stat(&finfo, filename, APR_FINFO_MIN, r->pool);
    if (rc == APR_SUCCESS) {
        exists =
        (
            (finfo.filetype != APR_NOFILE)
        &&  !(finfo.filetype & APR_DIR)
        );
        if (!exists) return HTTP_NOT_FOUND; /* Return a 404 if not found. */
    }
    /* If apr_stat failed, we're probably not allowed to check this file. */
    else return HTTP_FORBIDDEN;
    
    /* Parse the GET and, optionally, the POST data sent to us */
    
    ap_args_to_table(r, &GET);
    ap_parse_form_data(r, NULL, &POST, -1, 8192);
    
    /* Set the appropriate content type */
    ap_set_content_type(r, "text/html");
    
    /* Print a title and some general information */
    ap_rprintf(r, "<h2>Information on %s:</h2>", filename);
    ap_rprintf(r, "<b>Size:</b> %u bytes
", finfo.size); /* Get the digest type the client wants to see */ digestType = apr_table_get(GET, "digest"); if (!digestType) digestType = "MD5"; rc = apr_file_open(&file, filename, APR_READ, APR_OS_DEFAULT, r->pool); if (rc == APR_SUCCESS) { /* Are we trying to calculate the MD5 or the SHA1 digest? */ if (!strcasecmp(digestType, "md5")) { /* Calculate the MD5 sum of the file */ union { char chr[16]; uint32_t num[4]; } digest; apr_md5_ctx_t md5; apr_md5_init(&md5); readBytes = 256; while ( apr_file_read(file, buffer, &readBytes) == APR_SUCCESS ) { apr_md5_update(&md5, buffer, readBytes); } apr_md5_final(digest.chr, &md5); /* Print out the MD5 digest */ ap_rputs("<b>MD5: </b><code>", r); for (n = 0; n < APR_MD5_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", digest.num[n]); } ap_rputs("</code>", r); /* Print a link to the SHA1 version */ ap_rputs("
<a href='?digest=sha1'>View the SHA1 hash instead</a>", r); } else { /* Calculate the SHA1 sum of the file */ union { char chr[20]; uint32_t num[5]; } digest; apr_sha1_ctx_t sha1; apr_sha1_init(&sha1); readBytes = 256; while ( apr_file_read(file, buffer, &readBytes) == APR_SUCCESS ) { apr_sha1_update(&sha1, buffer, readBytes); } apr_sha1_final(digest.chr, &sha1); /* Print out the SHA1 digest */ ap_rputs("<b>SHA1: </b><code>", r); for (n = 0; n < APR_SHA1_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", digest.num[n]); } ap_rputs("</code>", r); /* Print a link to the MD5 version */ ap_rputs("
<a href='?digest=md5'>View the MD5 hash instead</a>", r); } apr_file_close(file); } /* Let the server know that we responded to this request. */ return OK; }

可以在以下位置找到此版本的完整版本:mod_example_2.c

添加配置选项

在本文档的下一部分中,我们将把目光从摘要模块上移开,并创建一个新的示例模块,其唯一功能是写出自己的配置。这样做的目的是检查服务器如何与配置一起使用,以及开始为模块编写高级配置时会发生什么。

配置指令简介

如果您正在阅读本文,那么您可能已经知道什么是配置指令。简而言之,指令是一种告诉单个模块(或一组模块)如何工作的方式,例如这些指令控制mod_rewrite的工作方式:

RewriteEngine On
RewriteCond "%{REQUEST_URI}" "^/foo/bar"
RewriteRule "^/foo/bar/(.*)$" "/foobar?page=$1"

每个配置指令均由单独的函数处理,该函数解析给定的参数并相应地设置配置。

进行示例配置

首先,我们将在 C 空间中创建一个基本配置:

typedef struct {
    int         enabled;      /* Enable or disable our module */
    const char *path;         /* Some path to...something */
    int         typeOfAction; /* 1 means action A, 2 means action B and so on */
} example_config;

现在,让我们通过创建一个很小的模块以打印出硬编码配置的方式来对此进行透视。您会注意到,我们使用register_hooks函数将配置值初始化为其默认值:

typedef struct {
    int         enabled;      /* Enable or disable our module */
    const char *path;         /* Some path to...something */
    int         typeOfAction; /* 1 means action A, 2 means action B and so on */
} example_config;

static example_config config;

static int example_handler(request_rec *r)
{
    if (!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);
    ap_set_content_type(r, "text/plain");
    ap_rprintf(r, "Enabled: %u\n", config.enabled);
    ap_rprintf(r, "Path: %s\n", config.path);
    ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction);
    return OK;
}

static void register_hooks(apr_pool_t *pool) 
{
    config.enabled = 1;
    config.path = "/foo/bar";
    config.typeOfAction = 0x00;
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}

/* Define our module as an entity and assign a function for registering hooks  */

module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,            /* Per-directory configuration handler */
    NULL,            /* Merge handler for per-directory configurations */
    NULL,            /* Per-server configuration handler */
    NULL,            /* Merge handler for per-server configurations */
    NULL,            /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};

到目前为止,一切都很好。要访问我们的新处理程序,我们可以在配置中添加以下内容:

<Location "/example">
    SetHandler example-handler
</Location>

当我们访问时,我们将看到我们的模块吐出了当前的配置。

向服务器注册指令

如果要更改配置,而不是通过将新值硬编码到模块中,而是使用 httpd.conf 文件或可能的.htaccess 文件来更改配置,该怎么办?是时候让服务器知道我们希望做到这一点了。为此,我们必须首先更改我们的* name 标签*以包含对我们要向服务器注册的配置指令的引用:

module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,               /* Per-directory configuration handler */
    NULL,               /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    example_directives, /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

这将告诉服务器我们现在正在接受配置文件中的指令,并且名为example_directives的结构保存有关指令是什么以及它们如何工作的信息。由于我们的模块配置中包含三个不同的变量,因此我们将添加一个结构,该结构具有三个指令,最后是一个 NULL:

static const command_rec        example_directives[] =
{
    AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"),
    AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "The path to whatever"),
    AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Special action value!"),
    { NULL }
};

Directives structure

如您所见,每个指令至少需要设置 5 个参数:

  • AP_INIT_TAKE1:这是一个宏,它告诉服务器此指令仅接受一个参数。如果需要两个参数,则可以使用宏AP_INIT_TAKE2,依此类推(有关更多宏,请参见 httpd_conf.h)。

  • exampleEnabled:这是我们指令的名称。更准确地说,这是用户必须在其配置中放入的内容才能在我们的模块中调用配置更改。

  • example_set_enabled:这是对 C 函数的引用,该 C 函数解析指令并相应地设置配置。我们将在下面的段落中讨论如何做到这一点。

  • RSRC_CONF:这告诉服务器允许该指令的位置。在后面的章节中,我们将详细介绍该值,但是RSRC_CONF表示服务器将仅在服务器上下文中接受这些指令。

  • "Enable or disable....":这只是该指令的简要说明。

(我们定义中的“ missing”参数通常设置为NULL,它是一个可选函数,可以在运行初始函数以解析参数后运行.通常会省略此参数,因为用于验证参数的函数可能会以及用于设置它们.)

指令处理程序功能

既然我们已经告诉服务器期望模块有一些指令,那么现在是时候编写一些函数来处理这些指令了。服务器在配置文件中读取的是文本,因此很自然地,它传递给我们的指令处理程序的是一个或多个字符串,我们自己需要对其进行识别并采取行动。您会注意到,由于我们将exampleAction指令设置为接受两个参数,因此它的 C 函数还定义了一个附加参数:

/* Handler for the "exampleEnabled" directive */
const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg)
{
    if(!strcasecmp(arg, "on")) config.enabled = 1;
    else config.enabled = 0;
    return NULL;
}

/* Handler for the "examplePath" directive */
const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg)
{
    config.path = arg;
    return NULL;
}

/* Handler for the "exampleAction" directive */
/* Let's pretend this one takes one argument (file or db), and a second (deny or allow), */
/* and we store it in a bit-wise manner. */
const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2)
{
    if(!strcasecmp(arg1, "file")) config.typeOfAction = 0x01;
    else config.typeOfAction = 0x02;
    
    if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10;
    else config.typeOfAction += 0x20;
    return NULL;
}

全部放在一起

现在我们已经设置了指令,并为其配置了处理程序,我们可以将模块组装到一个大文件中:

/* mod_example_config_simple.c: */
#include <stdio.h>
#include "apr_hash.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"

/*
 ==============================================================================
 Our configuration prototype and declaration:
 ==============================================================================
 */
typedef struct {
    int         enabled;      /* Enable or disable our module */
    const char *path;         /* Some path to...something */
    int         typeOfAction; /* 1 means action A, 2 means action B and so on */
} example_config;

static example_config config;

/*
 ==============================================================================
 Our directive handlers:
 ==============================================================================
 */
/* Handler for the "exampleEnabled" directive */
const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg)
{
    if(!strcasecmp(arg, "on")) config.enabled = 1;
    else config.enabled = 0;
    return NULL;
}

/* Handler for the "examplePath" directive */
const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg)
{
    config.path = arg;
    return NULL;
}

/* Handler for the "exampleAction" directive */
/* Let's pretend this one takes one argument (file or db), and a second (deny or allow), */
/* and we store it in a bit-wise manner. */
const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2)
{
    if(!strcasecmp(arg1, "file")) config.typeOfAction = 0x01;
    else config.typeOfAction = 0x02;
    
    if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10;
    else config.typeOfAction += 0x20;
    return NULL;
}

/*
 ==============================================================================
 The directive structure for our name tag:
 ==============================================================================
 */
static const command_rec        example_directives[] =
{
    AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"),
    AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "The path to whatever"),
    AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Special action value!"),
    { NULL }
};
/*
 ==============================================================================
 Our module handler:
 ==============================================================================
 */
static int example_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);
    ap_set_content_type(r, "text/plain");
    ap_rprintf(r, "Enabled: %u\n", config.enabled);
    ap_rprintf(r, "Path: %s\n", config.path);
    ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction);
    return OK;
}

/*
 ==============================================================================
 The hook registration function (also initializes the default config values):
 ==============================================================================
 */
static void register_hooks(apr_pool_t *pool) 
{
    config.enabled = 1;
    config.path = "/foo/bar";
    config.typeOfAction = 3;
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}
/*
 ==============================================================================
 Our module name tag:
 ==============================================================================
 */
module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,               /* Per-directory configuration handler */
    NULL,               /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    example_directives, /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

现在,在我们的 httpd.conf 文件中,可以通过添加几行来更改硬编码配置:

ExampleEnabled On
ExamplePath "/usr/bin/foo"
ExampleAction file allow

因此,我们应用了配置,请访问我们的网站上的/example,我们看到该配置已适应我们在配置文件中编写的内容。

上下文相关的配置

上下文相关配置简介

在 Apache HTTP Server 2.4 中,不同的 URL,虚拟主机,目录等对于服务器用户可能具有非常不同的含义,因此模块必须在不同的上下文中运行。例如,假设您为 mod_rewrite 设置了以下配置:

<Directory "/var/www">
    RewriteCond "%{HTTP_HOST}" "^example.com$"
    RewriteRule "(.*)" "http://www.example.com/$1"
</Directory>
<Directory "/var/www/sub">
    RewriteRule "^foobar$" "index.php?foobar=true"
</Directory>

在此示例中,您将为 mod_rewrite 设置两个不同的上下文:

  • /var/www内部,对http://example.com的所有请求都必须转到http://www.example.com

  • /var/www/sub内部,对foobar的所有请求都必须转到index.php?foobar=true

如果 mod_rewrite(或与此有关的整个服务器)不了解上下文,则这些重写规则将仅应用于每个请求,无论它们在何处以及如何进行,但是由于模块可以提取特定于上下文的配置直接从服务器开始,它不需要知道自身,在该上下文中哪个指令是有效的,因为服务器会处理这个问题。

那么模块如何获得有关服务器,目录或位置的特定配置?为此,只需进行一个简单的调用即可:

example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module);

而已!当然,幕后还有很多事情,我们将在本章中进行讨论,首先是服务器如何知道我们的配置,以及如何在特定环境下进行设置。

我们的基本配置设置

在本章中,我们将对先前的上下文结构进行稍作修改的版本。我们将设置一个context变量,该变量可用于跟踪服务器在各个地方使用的上下文配置:

typedef struct {
    char        context[256];
    char        path[256];
    int         typeOfAction;
    int         enabled;
} example_config;

我们的请求处理程序也将被修改,但仍然非常简单:

static int example_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);
    example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module);
    ap_set_content_type(r, "text/plain");
    ap_rprintf("Enabled: %u\n", config->enabled);
    ap_rprintf("Path: %s\n", config->path);
    ap_rprintf("TypeOfAction: %x\n", config->typeOfAction);
    ap_rprintf("Context: %s\n", config->context);
    return OK;
}

选择上下文

在开始使我们的模块上下文意识之前,我们必须首先定义我们将接受的上下文。如上一章所述,定义指令需要设置五个元素:

AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"),

RSRC_CONF定义告诉服务器,我们仅允许在全局服务器上下文中使用此指令,但是由于我们现在正在尝试使用模块的上下文识别版本,因此我们应将其设置为更宽松的值,即值ACCESS_CONF,这样可以我们在和块中使用指令。为了更好地控制指令的位置,可以将以下限制组合在一起以形成特定规则:

  • RSRC_CONF:允许在<目录>或<位置>之外的.conf 文件(不是.htaccess)

  • ACCESS_CONF:允许在或内部的.conf 文件(不是.htaccess)

  • OR_OPTIONS:设置AllowOverride Options时允许在.conf 文件和.htaccess 中使用

  • OR_FILEINFO:设置AllowOverride FileInfo时允许在.conf 文件和.htaccess 中使用

  • OR_AUTHCFG:设置AllowOverride AuthConfig时允许在.conf 文件和.htaccess 中使用

  • OR_INDEXES:设置AllowOverride Indexes时允许在.conf 文件和.htaccess 中使用

  • OR_ALL:允许.conf 文件和.htaccess 中的任何位置

使用服务器分配配置插槽

Management 配置的一种更聪明的方法是让服务器帮助您创建配置。为此,我们必须首先通过更改我们的* name 标签*来让服务器知道,它应该有助于我们创建和 Management 我们的配置。由于我们已为模块配置选择了按目录(或按位置)上下文,因此将在标记中添加按目录创建者和合并功能引用:

module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    create_dir_conf, /* Per-directory configuration handler */
    merge_dir_conf,  /* Merge handler for per-directory configurations */
    NULL,            /* Per-server configuration handler */
    NULL,            /* Merge handler for per-server configurations */
    directives,      /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};

创建新的上下文配置

现在,我们已经告诉服务器帮助我们创建和 Management 配置,我们的第一步是创建一个用于创建新的空白配置的功能。为此,我们创建了刚刚在名称标签中引用的函数,作为每目录配置处理程序:

void *create_dir_conf(apr_pool_t *pool, char *context) {
    context = context ? context : "(undefined context)";
    example_config *cfg = apr_pcalloc(pool, sizeof(example_config));
    if(cfg) {
        /* Set some default values */
        strcpy(cfg->context, context);
        cfg->enabled = 0;
        cfg->path = "/foo/bar";
        cfg->typeOfAction = 0x11;
    }
    return cfg;
}

Merging configurations

创建上下文感知配置的下一步是合并配置。该过程的这一部分特别适用于具有父配置和子配置的方案,例如:

<Directory "/var/www">
    ExampleEnabled On
    ExamplePath "/foo/bar"
    ExampleAction file allow
</Directory>
<Directory "/var/www/subdir">
    ExampleAction file deny
</Directory>

在此示例中,自然会假定目录/var/www/subdir应该继承为/var/www目录设置的值,因为我们没有为此目录指定ExampleEnabledExamplePath。服务器不假定这是真的,但是巧妙地执行以下操作:

  • /var/www创建新配置

  • 根据为/var/www给出的指令设置配置值

  • /var/www/subdir创建新配置

  • 根据为/var/www/subdir给出的指令设置配置值

  • 建议将两种配置合并/var/www/subdir的新配置

该提案由我们在名称标签中引用的merge_dir_conf函数处理。此功能的目的是评估这两种配置并决定如何合并它们:

void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD) {
    example_config *base = (example_config *) BASE ; /* This is what was set in the parent context */
    example_config *add = (example_config *) ADD ;   /* This is what is set in the new context */
    example_config *conf = (example_config *) create_dir_conf(pool, "Merged configuration"); /* This will be the merged configuration */
    
    /* Merge configurations */
    conf->enabled = ( add->enabled == 0 ) ? base->enabled : add->enabled ;
    conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction;
    strcpy(conf->path, strlen(add->path) ? add->path : base->path);
    
    return conf ;
}

试用我们新的上下文感知配置

现在,让我们尝试将所有内容放在一起以创建一个具有上下文意识的新模块。首先,我们将创建一个配置,让我们测试模块的工作方式:

<Location "/a">
    SetHandler example-handler
    ExampleEnabled on
    ExamplePath "/foo/bar"
    ExampleAction file allow
</Location>

<Location "/a/b">
    ExampleAction file deny
    ExampleEnabled off
</Location>

<Location "/a/b/c">
    ExampleAction db deny
    ExamplePath "/foo/bar/baz"
    ExampleEnabled on
</Location>

然后,我们将汇编模块代码。请注意,由于我们现在在处理程序中获取配置时使用名称标签作为参考,因此我添加了一些原型以使编译器满意:

/*$6
 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * mod_example_config.c
 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */

#include <stdio.h>
#include "apr_hash.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Configuration structure
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

typedef struct
{
    char    context[256];
    char    path[256];
    int     typeOfAction;
    int     enabled;
} example_config;

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Prototypes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

static int    example_handler(request_rec *r);
const char    *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg);
const char    *example_set_path(cmd_parms *cmd, void *cfg, const char *arg);
const char    *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2);
void          *create_dir_conf(apr_pool_t *pool, char *context);
void          *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD);
static void   register_hooks(apr_pool_t *pool);

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Configuration directives
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

static const command_rec    directives[] =
{
    AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, ACCESS_CONF, "Enable or disable mod_example"),
    AP_INIT_TAKE1("examplePath", example_set_path, NULL, ACCESS_CONF, "The path to whatever"),
    AP_INIT_TAKE2("exampleAction", example_set_action, NULL, ACCESS_CONF, "Special action value!"),
    { NULL }
};

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Our name tag
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

module AP_MODULE_DECLARE_DATA    example_module =
{
    STANDARD20_MODULE_STUFF,
    create_dir_conf,    /* Per-directory configuration handler */
    merge_dir_conf,     /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    directives,         /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

/*
 =======================================================================================================================
    Hook registration function
 =======================================================================================================================
 */
static void register_hooks(apr_pool_t *pool)
{
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}

/*
 =======================================================================================================================
    Our example web service handler
 =======================================================================================================================
 */
static int example_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *config = (example_config *) ap_get_module_config(r->per_dir_config, &example_module);
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    ap_set_content_type(r, "text/plain");
    ap_rprintf(r, "Enabled: %u\n", config->enabled);
    ap_rprintf(r, "Path: %s\n", config->path);
    ap_rprintf(r, "TypeOfAction: %x\n", config->typeOfAction);
    ap_rprintf(r, "Context: %s\n", config->context);
    return OK;
}

/*
 =======================================================================================================================
    Handler for the "exampleEnabled" directive
 =======================================================================================================================
 */
const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *conf = (example_config *) cfg;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(conf)
    {
        if(!strcasecmp(arg, "on"))
            conf->enabled = 1;
        else
            conf->enabled = 0;
    }

    return NULL;
}

/*
 =======================================================================================================================
    Handler for the "examplePath" directive
 =======================================================================================================================
 */
const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *conf = (example_config *) cfg;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(conf)
    {
        strcpy(conf->path, arg);
    }

    return NULL;
}

/*
 =======================================================================================================================
    Handler for the "exampleAction" directive ;
    Let's pretend this one takes one argument (file or db), and a second (deny or allow), ;
    and we store it in a bit-wise manner.
 =======================================================================================================================
 */
const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *conf = (example_config *) cfg;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(conf)
    {
        {
            if(!strcasecmp(arg1, "file"))
                conf->typeOfAction = 0x01;
            else
                conf->typeOfAction = 0x02;
            if(!strcasecmp(arg2, "deny"))
                conf->typeOfAction += 0x10;
            else
                conf->typeOfAction += 0x20;
        }
    }

    return NULL;
}

/*
 =======================================================================================================================
    Function for creating new configurations for per-directory contexts
 =======================================================================================================================
 */
void *create_dir_conf(apr_pool_t *pool, char *context)
{
    context = context ? context : "Newly created configuration";

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *cfg = apr_pcalloc(pool, sizeof(example_config));
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(cfg)
    {
        {
            /* Set some default values */
            strcpy(cfg->context, context);
            cfg->enabled = 0;
            memset(cfg->path, 0, 256);
            cfg->typeOfAction = 0x00;
        }
    }

    return cfg;
}

/*
 =======================================================================================================================
    Merging function for configurations
 =======================================================================================================================
 */
void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *base = (example_config *) BASE;
    example_config    *add = (example_config *) ADD;
    example_config    *conf = (example_config *) create_dir_conf(pool, "Merged configuration");
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    conf->enabled = (add->enabled == 0) ? base->enabled : add->enabled;
    conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction;
    strcpy(conf->path, strlen(add->path) ? add->path : base->path);
    return conf;
}

Summing up

现在,我们研究了如何为 Apache HTTP Server 2.4 创建简单的模块并对其进行配置。接下来的工作完全由您决定,但是我希望阅读此文档后能得到一些有价值的东西。如果您对如何进一步开发模块有疑问,欢迎加入我们的mailing lists或查看我们的其他文档以获取更多提示。

一些有用的代码段

从 POST 表单数据中检索变量

typedef struct {
    const char *key;
    const char *value;
} keyValuePair;

keyValuePair *readPost(request_rec *r) {
    apr_array_header_t *pairs = NULL;
    apr_off_t len;
    apr_size_t size;
    int res;
    int i = 0;
    char *buffer;
    keyValuePair *kvp;

    res = ap_parse_form_data(r, NULL, &pairs, -1, HUGE_STRING_LEN);
    if (res != OK || !pairs) return NULL; /* Return NULL if we failed or if there are is no POST data */
    kvp = apr_pcalloc(r->pool, sizeof(keyValuePair) * (pairs->nelts + 1));
    while (pairs && !apr_is_empty_array(pairs)) {
        ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs);
        apr_brigade_length(pair->value, 1, &len);
        size = (apr_size_t) len;
        buffer = apr_palloc(r->pool, size + 1);
        apr_brigade_flatten(pair->value, buffer, &size);
        buffer[len] = 0;
        kvp[i].key = apr_pstrdup(r->pool, pair->name);
        kvp[i].value = buffer;
        i++;
    }
    return kvp;
}

static int example_handler(request_rec *r)
{
    /*~~~~~~~~~~~~~~~~~~~~~~*/
    keyValuePair *formData;
    /*~~~~~~~~~~~~~~~~~~~~~~*/

    formData = readPost(r);
    if (formData) {
        int i;
        for (i = 0; &formData[i]; i++) {
            if (formData[i].key && formData[i].value) {
                ap_rprintf(r, "%s = %s\n", formData[i].key, formData[i].value);
            } else if (formData[i].key) {
                ap_rprintf(r, "%s\n", formData[i].key);
            } else if (formData[i].value) {
                ap_rprintf(r, "= %s\n", formData[i].value);
            } else {
                break;
            }
        }
    }
    return OK;
}

打印出收到的每个 HTTP Headers

static int example_handler(request_rec *r)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    const apr_array_header_t    *fields;
    int                         i;
    apr_table_entry_t           *e = 0;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    fields = apr_table_elts(r->headers_in);
    e = (apr_table_entry_t *) fields->elts;
    for(i = 0; i < fields->nelts; i++) {
        ap_rprintf(r, "%s: %s\n", e[i].key, e[i].val);
    }
    return OK;
}

将请求正文读入内存

static int util_read(request_rec *r, const char **rbuf, apr_off_t *size)
{
    /*~~~~~~~~*/
    int rc = OK;
    /*~~~~~~~~*/

    if((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
        return(rc);
    }

    if(ap_should_client_block(r)) {

        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
        char         argsbuffer[HUGE_STRING_LEN];
        apr_off_t    rsize, len_read, rpos = 0;
        apr_off_t length = r->remaining;
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

        *rbuf = (const char *) apr_pcalloc(r->pool, (apr_size_t) (length + 1));
        *size = length;
        while((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) {
            if((rpos + len_read) > length) {
                rsize = length - rpos;
            }
            else {
                rsize = len_read;
            }

            memcpy((char *) *rbuf + rpos, argsbuffer, (size_t) rsize);
            rpos += rsize;
        }
    }
    return(rc);
}

static int example_handler(request_rec *r) 
{
    /*~~~~~~~~~~~~~~~~*/
    apr_off_t   size;
    const char  *buffer;
    /*~~~~~~~~~~~~~~~~*/

    if(util_read(r, &buffer, &size) == OK) {
        ap_rprintf(r, "We read a request body that was %" APR_OFF_T_FMT " bytes long", size);
    }
    return OK;
}