本文档说明了如何为Apache HTTP Server 2.4开发模块
本文档将通过探索名为的示例模块,讨论如何为Apache HTTP Server 2.4创建模块
mod_example
。在本文档的第一部分中,每当我们访问URL时,本模块的目的就是为您的Web服务器上的现有文件计算并打印出各种摘要值
http://hostname/filename.sum
。例如,如果我们想知道位于的文件的MD5摘要值
http://www.example.com/index.html
,则可以访问
http://www.example.com/index.html.sum
。
在本文档的第二部分中,它涉及配置指令和上下文感知,我们将研究一个简单地将其自身配置写入客户端的模块。
首先,最重要的是,您应该具有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 example_module modules/mod_example.so
从本质上讲,这告诉服务器打开mod_example.so
并寻找名为的模块example_module
。
在我们的这个名称标签中,还包含一堆关于我们希望如何处理事情的引用:我们在配置文件或.htaccess中响应哪些指令,我们如何在特定上下文中操作,以及我们对哪些处理程序感兴趣在Apache HTTP服务中注册。在本文档的后面,我们将返回所有这些元素。
在Apache HTTP Server 2.4中处理请求时,您需要做的第一件事是在请求处理过程中创建一个钩子。挂钩实际上是一条消息,告诉服务器您愿意服务或至少看一眼客户端发出的某些请求。所有处理程序,无论是mod_rewrite,mod_authn _ *,mod_proxy等,都被挂接到请求过程的特定部分。如您所知,模块有不同的用途。一些是身份验证/授权处理程序,其他是文件或脚本处理程序,而其他一些第三模块则重写URI或代理内容。此外,最终,服务器的用户将取决于如何以及何时安装每个模块。因此,服务器本身并不假定知道哪个模块负责处理特定请求,并询问每个模块是否对给定的请求感兴趣。然后,由每个模块决定是否像身份验证/授权模块那样,轻轻地拒绝服务请求,接受服务请求或拒绝服务请求。
为了使诸如mod_example之类的处理程序更容易知道客户端是否在请求我们应处理的内容,服务器具有用于向模块提示是否需要其协助的指令。其中两个是AddHandler
和SetHandler
。让我们来看一个使用的示例AddHandler
。在我们的示例案例中,我们希望每个以.sum结尾的请求都由提供
mod_example
,因此我们将添加一个配置指令,该指令指示服务器执行此操作:
AddHandler example-handler .sum
这告诉服务器的是以下内容:每当我们收到以.sum结尾的URI请求时,我们都要让所有模块知道我们正在寻找名称为“ example-handler”的对象。因此,当服务请求以.sum结尾时,服务器将通知所有模块,该请求应由“ example-handler”服务。就像您稍后将看到的那样,当我们开始构建mod_example时,我们将检查该处理程序标记是否中继AddHandler
并基于该标记的值回复服务器。
首先,我们只想创建一个简单的处理程序,当请求一个特定的URL时,该处理程序会回复到客户端浏览器,因此,我们现在就不会花时间设置配置处理程序和指令。我们的初始模块定义将如下所示:
module AP_MODULE_DECLARE_DATA example_module = { STANDARD20_MODULE_STUFF, NULL, NULL, NULL, NULL, NULL, register_hooks /* Our hook registering function */ };
这使服务器知道我们对任何花哨的内容都不感兴趣,我们只想挂接到请求并可能处理其中的一些请求。
示例声明中的引用register_hooks
是将创建的函数的名称,该函数将管理如何挂钩到请求流程。在此示例模块中,该功能仅具有一个目的。为了创建一个简单的钩子,在所有重写,访问控制等都已处理之后被调用。因此,我们将让服务器知道我们希望作为最后一个模块之一加入到其进程中:
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
:放置一个在服务器将要添加当前请求的日志条目时执行的钩子处理程序本质上是一个向服务器发出请求时接收回调的函数。它将当前请求的记录(如何进行,传递了哪些标头和请求,谁发出请求等等)传递给它,并负责告诉服务器对请求不感兴趣或使用提供的工具处理请求。
让我们从制作一个简单的请求处理程序开始,该请求处理程序执行以下操作:
text/html
在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*
与每次调用一起传递的结构表示。这个结构(通常r
在模块中简称为)包含模块完全处理任何HTTP请求并做出相应响应所需的所有信息。
该
request_rec
结构的一些关键要素是:
r->handler (char*):
包含服务器当前要求处理此请求的处理程序的名称r->method (char*):
包含正在使用的HTTP方法,fx GET或POSTr->filename (char*):
包含客户端请求的翻译文件名r->args (char*):
包含请求的查询字符串(如果有)r->headers_in (apr_table_t*):
包含客户端发送的所有标头r->connection (conn_rec*):
包含有关当前连接信息的记录r->user (char*):
如果URI需要身份验证,则将其设置为提供的用户名r->useragent_ip (char*):
连接到我们的客户端的IP地址r->pool (apr_pool_t*)
:此请求的内存池。我们将在“ 内存管理 ”一章中对此进行讨论。request_rec
在httpd.h
头文件中或在http://ci.apache.org/projects/httpd/trunk/doxygen/structrequest__rec.html中,可以找到结构中
包含的所有值的完整列表。
让我们在另一个示例处理程序中尝试其中一些变量:
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!<br/>", r); } else { ap_rputs("You did not use POST or GET, that makes us sad :(<br/>", 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; }
Apache依赖于处理程序的返回值来表示是否处理了请求,如果是,则表示请求是否正常。如果模块对处理特定请求不感兴趣,则应始终返回value DECLINED
。如果正在处理请求,则应返回通用值OK
或特定的HTTP状态代码,例如:
static int example_handler(request_rec *r) { /* Return 404: Not found */ return HTTP_NOT_FOUND; }
返回OK
或HTTP状态代码并不一定意味着请求将结束。服务器可能仍然具有对该请求感兴趣的其他处理程序,例如日志记录模块,在成功请求之后,该日志记录模块将记录所请求内容及其进行方式的摘要。要完全停止并防止模块完成后的任何进一步处理,您可以返回该值,DONE
以使服务器知道它应停止此请求上的所有活动并继续执行下一个,而无需通知其他处理程序。
通用响应代码:
DECLINED
:我们未处理此请求OK
:我们处理了此请求,进展顺利DONE
:我们处理了此请求,服务器应关闭该线程,而无需进一步处理HTTP特定的返回码(节选):
HTTP_OK (200)
:请求还可以HTTP_MOVED_PERMANENTLY (301)
:资源已移至新网址HTTP_UNAUTHORIZED (401)
:客户无权访问此页面HTTP_FORBIDDEN (403)
: 没有权限HTTP_NOT_FOUND (404)
: 文件未找到HTTP_INTERNAL_SERVER_ERROR (500)
:内部服务器错误(自我解释)ap_rputs(const char *string, request_rec *r)
:ap_rputs("Hello, world!", r);
ap_rprintf
:printf
,不同之处在于它将结果发送到客户端。
ap_rprintf(r, "Hello, %s!", r->useragent_ip);
ap_set_content_type(request_rec *r, const char *type)
:ap_set_content_type(r, "text/plain"); /* force a raw text output */
得益于内存池系统,在Apache HTTP Server 2.4中管理资源非常容易。本质上,每个服务器,连接和请求都有自己的内存池,该内存池在其作用域结束时(例如,请求完成或服务器进程关闭时)会被清理。您的模块所需要做的就是将其锁存到该内存池中,而您不必担心必须自己清理一下-非常整洁,是吗?
在我们的模块中,我们将主要为每个请求分配内存,因此r->pool
在创建新对象时使用引用是适当的。在池中分配内存的一些功能是:
void* apr_palloc(
apr_pool_t *p, apr_size_t size)
:size
为您分配池中的字节数void* apr_pcalloc(
apr_pool_t *p, apr_size_t size)
:size
为您分配池中的字节数并将所有字节设置为0char* 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); }
在此请求前初始化函数中,我们不会使用与为基于请求的函数分配资源时使用的池相同的池。取而代之的是,我们将使用服务器给我们的池在基于每个进程的级别上分配内存。
在示例模块中,我们想添加一个功能,该功能可以检查客户端希望查看的摘要类型,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数据中获取值而不是查询字符串,则必须诉诸更多行,如本示例中的概述所示。本文档的最后一章。
现在,我们已经了解了如何解析表单数据和管理资源,接下来我们可以继续创建模块的高级版本,该高级版本会散发出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<br/>", 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("<br/><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("<br/><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文件来更改配置,该怎么办?现在是时候让服务器知道我们希望做到这一点了。为此,我们必须首先更改名称标签,以包含对我们要向服务器注册的配置指令的引用:
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 } };
如您所见,每个指令至少需要设置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
,即value ,以便我们使用<Directory>和<Location>块中的指令。为了更好地控制指令的位置,可以将以下限制组合在一起以形成特定的规则:
RSRC_CONF
:允许在<Directory>或<Location>之外的.conf文件(不是.htaccess)ACCESS_CONF
:允许在<Directory>或<Location>内部的.conf文件(不是.htaccess)OR_OPTIONS
:当允许在conf文件和htaccess的AllowOverride Options
是集OR_FILEINFO
:当允许在conf文件和htaccess的AllowOverride FileInfo
是集OR_AUTHCFG
:当允许在conf文件和htaccess的AllowOverride AuthConfig
是集OR_INDEXES
:当允许在conf文件和htaccess的AllowOverride Indexes
是集OR_ALL
:允许在.conf文件和.htaccess中的任何位置管理配置的一种更聪明的方法是让服务器帮助您创建配置。为此,我们必须首先通过更改 名称标签来让服务器知道,它应该有助于我们创建和管理我们的配置。由于我们为模块配置选择了按目录(或按位置)上下文,因此将在标记中添加按目录创建者和合并功能引用:
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 */ };
现在,我们已经告诉服务器帮助我们创建和管理配置,我们的第一步是创建一个用于创建新的空白配置的功能。为此,我们创建了刚刚在名称标签中引用的函数,作为每目录配置处理程序:
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; }
创建上下文感知配置的下一步是合并配置。该过程的这一部分特别适用于具有父配置和子配置的方案,例如:
<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
目录设置的值,因为我们没有为此目录指定ExampleEnabled
或ExamplePath
。服务器不会假定这是否成立,但是会巧妙地执行以下操作:
/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; }
现在,我们研究了如何为Apache HTTP Server 2.4创建简单的模块并进行配置。接下来的工作完全由您决定,但我希望阅读本文档后能得到一些有价值的东西。如果您对如何进一步开发模块有疑问,欢迎加入我们的邮件列表 或查看我们的其他文档以获取更多提示。
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; }
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; }
静态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))){