1、Apache模块开发指南 Apache 可移植运行时库 TheApache Portable Runtime Apache 可移植运行时库(Apache Portable Runtime,APR)和 APR 的实用库(APR Utilities、APRUTILS 或者 APU)是 Apache 软件基金会旗下两个自主开发和维护的库, 为 Apache的 httpd 程序所使用。尽管很多的内核开发者要同时涉及开发 httpd(即Web 服 务器)和 APR,然而这两个项目却是相互独立的。APR 和APU 提供了内核函数,这些函 数不仅仅只与 Web 服务相关,对于更加普遍的应用开发也很有用。 除
2、了 Web 服务器之外,最有名的 APR 应用就是 Subversion,它是一个版本管理和变 更管理系统。另外一个就是 Site Valet,一个在网上进行质量保证(QA)和易用性审计的 软件套件。Site Valet 是由笔者开发的。 本章介绍 APR,并探讨如何把 APR 应用到Apache模块中。本章并没有深入介绍类似 CHAPTER 第 3 章第3章 使用Apache进行应用程序开发 Apache模块开发指南 54 于应用程序初始化之类的主题,虽然这些也是必要的,不过它们由 Apache 内核代码在内 部进行处理。对于那些Web 服务器内核之外的应用程序开发者,想更深入了解 APR
3、可以 参考其自身文档,它介绍得很翔实,可以在 http:/ html/ aprtutorial.html找到APR的教程。 3.1 APR APR的主要目的是为应用提供一个可移植的、平台无关的层。它使用底层的、交叉平 台的库来提供文件系统访问、网络编程、进程和线程管理以及共享内存等功能。那些使用 Apache 专有 APR、而不是使用本地系统功能的模块在平台之间只可以移植的,并且能够 在所有被 Apache所支持的平台上被干净地(最坏的情况也是需要很小程度修改)编译。 每一个 APR 模块由一个所有平台共享的应用编程接口(Application Programming Interface,AP
4、I)和该 API 所定义的所有函数实现组成。对该 API 的函数实现基本上(至 少一部分)是与平台相关的,尽管这些对于应用程序没有任何影响。 APR 的核心就是Apache的资源管理(池),我们将在本章的后面部分进行更加详细 的介绍。表 31 列出了 APR 中的所有模块。 表31 APR模块 名称 目的 apr_allocator 内存分配,内部使用 apr_atomic 原子操作 apr_dso 动态加载代码(.so/.dll) apr_env 读取/设定环境变量 apr_errno 定义错误条件和宏 apr_file_info 文件系统对象和路径的属性 apr_file_io 文件系统输
5、入/输出 apr_fnmatch 文件系统模式匹配 apr_general 初始化/终结,有用的宏3.1 APR Apache模块开发指南 55 续表 名称 目的 apr_getopt 命令参数 apr_global_mutex 全局锁 apr_hash 哈希表 apr_inherit 文件句柄继承助手 apr_lib 奇数和末端 apr_mmap 内存映射 apr_network_io 网络输入/输出(套接字) apr_poll 投票 apr_pools 资源管理 apr_portable APR到本地映射转换 apr_proc_mutex 进程锁 apr_random 随机数 apr_ri
6、ng 环数据结构和宏 apr_shm 共享内存 apr_signal 信号处理 apr_strings 字符串操作 apr_support 内部支持函数 apr_tables 表格和数组函数 apr_thread_cond 线程条件 apr_thread_mutex 线程锁 apr_thread_proc 线程和进程函数 apr_thread_rwlock 读写锁 apr_time 时间/日期函数 apr_user 用户和组ID服务 apr_version APR版本 apr_want 标准头文件支持第3章 使用Apache进行应用程序开发 Apache模块开发指南 56 3.2 APR实用库
7、 APR实用库(APRUTIL,或者 APU)是 APR 项目的第二个库。它在APR基础上, 使用统一标准的编程接口, 提供了一部分功能函数集。 APU并不是每在一个平台上都有一 个单独的模块,但是它为某些其他常用的资源例如数据库提供了一个类似的方法。 表 32 列出了APU的所有模块。 表32 APU模块 名称 目的 apr_anylock 透明的、任何锁的封装 apr_base64 Base64编码 apr_buckets Buckets/Bucket brigade apr_date 时间字符串解析 apr_dbd 针对SQL数据库的常用API apr_dbm 针对DBM数据库的常用AP
8、I apr_hooks 钩子实现宏 apr_ldap LDAP授权 API apr_ldap_init LDAP初始化API,主要应用在和LDAP服务器的初始安全连接 apr_ldap_option 设置LDAP选项的API apr_ldap_url 解析和处理LDAP URL的 API apr_md4 MD4编码 apr_md5 MD5编码 apr_optional 可选函数 apr_optional_hooks 可选钩子 apr_queue 线程安全的FIFO队列 apr_reslist 资源池 apr_rmm 可再定位地址的内存3.3 基本的约定 Apache模块开发指南 57 续表 名
9、称 目的 apr_sdbm SDBM库 apr_sha1 SHA1编码 apr_strmatch 字符串模式匹配 apr_uri URI解析/构造 apr_uuid 用户标识 apr_xlate 字符集转换(I18N) apr_xml XML解析 3.3 基本的约定 APR和 APRUTIL采用了一些约定,使得它们的 API具有同质性,并且易于使用。 3.3.1 参考手册:API 文档和 Doxygen APR 和 APU 在代码层都有非常好的文档。每一个公开函数和数据类型都在定义它们 的头文件中进行了注释,使用了 doxygen 1 友好的格式。那些头文件,或者 doxygen 生成的 文档
10、, 为程序员提供了完整的API 参考手册。 如果你安装了doxygen, 那么就可以通过 make dox 命令从源代码中生成你自己版本的 APR 参考手册。 3.3.2 命名空间 所有的APR和 APU 的公开接口都使用了字符串前缀“apr_”(数据类型和函数)和 “APR_”(宏),这就为 APR 定义了一个“保留”的命名空间。 在 APR 命名空间中,绝大部分的 APR 和 APU 模块使用了二级命名空间。这个约定 通常基于正在讨论的那个模块的名字。例如,模块 apr_dbd 中的所有函数使用字符串 “apr_dbd_”前缀。有时候使用一个明显的描述性的二级命名空间。例如,在模块 1 h
11、ttp:/ww.doxygen.org/。第3章 使用Apache进行应用程序开发 Apache模块开发指南 58 apr_network_io 中套接字操作使用“apr_socket_”前缀。 3.3.3 声明的宏 APR 和 APU 的公开函数使用类似于 APR_DECLARE、APU_DECLARE 和 APR_ DECLARE_NONSTD 的宏进行声明。例如: APR_DECLARE(apr_status_t) apr_initialize(void) 在很多的平台上,这是一个空声明,并且扩展为 apr_status_t apr_initialize(void) 例如在Windows
12、 的Visual C+平台上,需要使用它们自己的、非标准的关键字,例如 “_dllexport”来允许其他的模块使用一个函数,这些宏就需要扩展以适应这些需要的 关键字。 3.3.4 apr_status_t 和返回值 在 APR 和APU中广泛采用的一个约定是:函数返回一个状态值,用来为调用者指示 成功或者是返回一个错误代码。这个类型是 apr_status_t,在 apr_errno.h 中定义,并 赋予整数值。因此一个APR函数的常见原型就是: APR_DECLARE(apr_status_t) apr_do_something(function args) 返回值应当在逻辑上进行判断,并
13、且实现一个错误处理函数(进行回复或者对错误进 行进一步的描述)。返回值 APR_SUCCESS 意味着成功,我们通常可以用如下的方式进行 错误处理结构: apr_status_t rv . rv = apr_do_something(. args .) if (rv != APR_SUCCESS) /* 记录一个错误 */ return rv 有时候我们可能需要做得更多。例如,如果 do_something 是一个非闭塞的 I/O 操作 并且返回 APR_EAGAIN,我们可能需要重试这个操作。 有些函数返回一个字符串(char *或者 const char *)、一个 void *或者 vo
14、id。 这些函数就被认为在没有失败条件或者在错误发生时返回一个空指针。3.4 资源管理:APR池 Apache模块开发指南 59 3.3.5 条件编译 本质上说,APR 的一些特色可能并不是每个平台都支持的。例如,FreBSD 在 5.x版 本之前并没有适合Apache的本地线程实现,因此线程在APR 中就不被支持(除非编译时 手动设置相应的操作)。 为了在这种情况下应用程序依然能够工作, APR 为这些情况提供了 APR_HAS_*宏。 如 果一个应用处于这种情况,它应当使用这些宏进行条件编译。例如,一个模块执行了一个 操作,这个操作可能导致在多线程环境下的竞争条件,那么它就可能使用以下的方
15、式。 #if APR_HAS_THREADS rv = apr_thread_mutex_lock(mutex) if (rv != APR_SUCCESS) /* 记录一个错误 */ /* 放弃关键的操作*/ #endif /* . 在这里执行关键代码. */ #if APR_HAS_THREAD apr_thread_mutex_unlock(mutex) #endif 3.4 资源管理:APR 池 APR池是基础的构建块,是APR 和Apache的核心,它们作为所有资源管理的基础。 池进行内存分配,既有直接的方式(类似于 malloc 方式),也有间接的方式(例如,在 字符串操作过程中)
16、,最主要的是,它保证分配的内存在最恰当的时候被释放掉。由于池 扩展得很大,用来保证其他的资源例如文件或者互斥信号量能够被分配,并且总是能够被 正确地清理释放。它们甚至可以管理被第三方库透明管理的资源。 注意:在 Apache 中通常认为池的内存分配永远不会失败。这个假设成立的原因在于如果 内存分配失败,那么系统是不可恢复的,任何错误处理也将会失败。第3章 使用Apache进行应用程序开发 Apache模块开发指南 60 3.4.1 资源管理的问题 每一个程序员都了解,当你分配一个资源后,你必须保证在你结束使用该资源时将其 释放掉。例如: char* buf = malloc(n) . 检查它是
17、否为空 . . 使用该缓冲区 . free(buf) 或者 FILE* f = fopen(path, “r“) . 检查是否为空 . . 从 f 中读取 fclose(f) 很明显,如果没有释放 buf 或者关闭 f,那就是一个 Bug。在长时间运行的程序中, 例如 Apache,这个 Bug 将导致严重的后果,甚至导致整个服务器宕机。显然,正确地管 理分配的资源非常重要。 在比较简单的环境下,资源管理比较容易。但是如果在一个拥有多个错误路径的更加 复杂的情况下,资源被分配时甚至资源本身的范围都不确定,在这种情况下保证每个执行 路径上的资源都能够被释放是非常困难的。在这种环境下,我们需要管理
18、资源有更好的办 法。 Constructor/Destructor模型(构造/析构模型) 在 C+中, 每一个对象都有一个构造函数 (constructor) 和一个析构函数 (destructor), 这是资源管理的一种方法。 很多的 C+程序员让析构函数负责清理所有为这个对象所分配 的资源。这种让对象负责动态管理资源的方法运行得很好。不过,和简单的C 语言方法相 比,我们需要用大量精力去关注细节例如,当资源有条件地进行分配时,或者在多个 不同的对象之间共享时,这种方法很容易带来编程的 Bug。 Garbage Collection模型(垃圾回收模型) 垃圾回收模型是一个高层次的资源管理方法
19、,以 Lisp 语言和 Java 语言为代表。这种 方法的优势是将资源管理的问题交由语言本身负责,而不是由程序员负责,这样就完全避3.4 资源管理:APR池 Apache模块开发指南 61 免了由于编程的缺点导致错误发生的危险。它的缺点是,垃圾回收在它不需要时带来了额 外的负担,并且将程序员的某些对资源有用的控制权剥夺了,例如控制一个资源生命周期 的能力。它还需要所有的编程组件包括第三方的库建立在同一个系统上,那么这 在使用C语言开发的开放系统中很明显是不可满足的。 3.4.2 APR 池 APR 池为资源管理提供了一个可选的模型。和垃圾回收类似,APR 池将程序员从各 种可能的情况下进行清理
20、操作的复杂性中解放出来。除此之外,APR 池具有一些其他的优 势,包括对资源的生命周期内的完全控制和管理不同种类资源的能力。 APR池的基本理念和此类似:无论何时你分配了一个需要清理的资源,你必须使用一 个池来对其进行注册。然后这个池就负责对这个资源进行清理,清理操作将会在这个池本 身被清理的时候发生。使用这种方式,资源管理的问题被简化成为对一个单一资源的分配 和清除:池本身。如果 Apache 的池是被服务器来管理,那么就从应用编程的角度不去涉 及资源管理的复杂性。程序要做的事情就是为一个资源的生命周期选择一个合适的池。 基本的内存管理 池最基本的应用就是内存管理。我们不使用如下的方式: m
21、ytype* myvar = malloc(sizeof(mytype) /* 在任一个可能的执行路径上,该资源需要被释放 */ 而是采用如下的方式: mytype* myvar = apr_palloc(pool, sizeof(mytype) 不管在此期间发生了什么,池将自动地负责释放这个资源。第二个好处就是池的分配 在很多平台上都比 molloc 快得多。 基本的内存管理在APR 和Apache中已经成形,内存使用其他的函数进行分配。下面 是字符串操作和审计的例子,我们可以从中马上获益,使用 APR 版本的 sprintf()不需第3章 使用Apache进行应用程序开发 Apache模块
22、开发指南 62 要事先知道一个字符串的大小: char* result = apr_psprintf(pool, fmt, .) APR也提供了池内存的高层次抽象例如,用来向过滤链传送数据的 Bucket。 普遍适用的内存管理 APR提供了用来进行内存管理内建的函数,以及一些其他的基本资源,例如文件、套 接字和互斥信号量。但是,程序员不需要使用这些函数和资源。一个可选方案就是使用本 地的分配函数并通过池显式地注册一个清理操作。 mytype* myvar = malloc(sizeof(mytype) apr_pool_cleanup_register(pool, myvar, free, a
23、pr_pool_cleanup_null) 或者 FILE* f = fopen(filename, “r“) apr_pool_cleanup_register(pool, f, fclose, apr_pool_cleanup_null) 这个代码将清理的责任委托给池,因此对于程序员来说则不需要额外的步骤。不过, 相对于 APR 的池,本地的、功能和 APR 的 apr_pools 和 apr_file_io 类似的函数,其 移植性要差一些,而且 molloc 在绝大部分平台上比使用池要慢一些。 这种对任何资源普遍适用的内存管理方法对于 Apache和 APR 是透明的。例如,如果 想打开
24、一个MySQL数据库连接并在使用之后关闭,你可以写下面的代码。 MYSQL* sql = NULL sql = mysql_init(sql) if ( sql = NULL ) log error and return failure apr_pool_cleanup_register(pool, sql, mysql_close, apr_pool_cleanup_null) sql = mysql_real_connect(sql, host, user, pass, dbname, port, sock, 0) if ( sql = NULL ) log error and retur
25、n failure 注意,apr_dbd(我们将在第 11 章进行讨论)为管理数据库连接提供了一个总的来说 更好的方法。3.4 资源管理:APR池 Apache模块开发指南 63 作为第二个例子,考虑XML的处理。 xmlDocPtr doc = xmlReadFile(filename) apr_pool_cleanup_register(pool, doc, xmlFreeDoc, apr_pool_cleanup_null) /* 我们目前在操作 doc,将需要分配内存, * 该内存由 XML 库管理,不过将通过 xmlFreeDoc 释放。 */ 如果集成 C+的析构函数做资源释放,我
26、们提供了下面这个例子,假设我们定义了一 个类:class myclass public: virtual myclass() do cleanup / 我们定义了一个C 的封装: void myclassCleanup(void* ptr) delete (myclass*)ptr 然后我们在分配 myclass 时通过池注册这个封装: myclass* myobj = new myclass(.) apr_pool_cleanup_register(pool, (void*)myobj, myclassCleanup, apr_pool_cleanup_null) /我们已经为 C+的资源管
27、理在 Apache 中挂了钩子 /不再需要删除 myobj /池将为我们做这个清除操作 隐式和显式地清除 假设我们想要在请求结束之前显式地释放资源例如,因为我们正在做一些内存消 耗比较大的操作而有些对象我们可以释放。我们可能会想根据普通的代码域规则做一些事 情,或者仅仅使用基于池的清理作为后退(fallback)来处理错误路径。不过,由于我们已 经注册了清理,因此清理操作将不顾我们的意愿而运行。在最坏的情形下,它可能会导致 一个两次释放(doublefre)并产生段错误。 另外一个池函数,apr_pool_cleanup_kill,就是用来处理这种情形的。当我们运行 了显式的清除,我们从池中注
28、销清理操作。当然,我们对于如何进行这项任务有更加聪明 的选择。下面是一段代码摘要,一个 C+类基于一个池管理自己,不管是否显式删除了这 个池。第3章 使用Apache进行应用程序开发 Apache模块开发指南 64 class poolclass private: apr_pool_t* pool public: poolclass(apr_pool_t* p) : pool(p) apr_pool_cleanup_register(pool, (void*)this, myclassCleanup, apr_pool_cleanup_null) virtual poolclass() apr
29、_pool_cleanup_kill(pool, (void*)this, myclassCleanup) 如果你使用C+开发Apache(或者APR),你可以从 poolclass 类继承。大部分的 APR函数做的和这个一样,随时在资源被分配或者清除时进行注册和清理操作。 在 C语言中,我们可以使用以下的通用格式。 /* 分配一些资源 */ my_type* my_res = my_res_alloc(args) /* 处理错误 */ if (my_res = NULL) /* 记录错误并跳出 */ /* 通过注册清理保证资源不会泄漏 */ apr_pool_cleanup_register
30、(pool, my_res, my_res_free, apr_pool_cleanup_null) /* . 现在就按照需求使用这个资源 . */ /* OK,我们已经完成任务,现在要尽快释放资源了 */ rv = my_res_free(my_res) /* 既然我们已经释放了,我们必须 kill 掉清除操作 */ apr_pool_cleanup_kill(pool, my_res, my_res_free) /* 现在进行错误处理并继续 */ if (rv != APR_SUCCESS) /* or whatever test may be appropriate */ /* . 记录
31、错误并跳出,或者尝试进行恢复 . */ 我们甚至可以将这个格式进行流水线作业,通过使用一个函数在池中进行清理和注 销。 apr_pool_cleanup_run(pool, my_res, my_res_free) 3.4 资源管理:APR池 Apache模块开发指南 65 3.4.3 资源的生命周期 当我们使用池进行资源分配时,我们就会确信这些资源将在某个时间点被清理释放。 但具体是什么时间呢?我们需要确保清理发生在正确的时间即,既不是资源正在使用 的时候,也不是在资源不再需要的很长时间之后。 Apache的池 庆幸的是,Apache 通过采取为不同类型的资源分配不同类型的池的方法,将这个过
32、 程变得非常简单。这些池和 httpd 的相关结构结合起来,和相应的结构具有同样的生命周 期。在Apache中,有四个通用意义的池可以使用。 l 请求池(request pool),生命周期为 HTP 请求的生命周期 l 进程池(proces pool),生命周期为一个服务进程的生命周期 l 连接池(connection pool),生命周期为一个 TCP 连接的生命周期 l 配置池(configuration pool) 前三个池结合 Apache 的相关数据结构 , 分别通过 requestpool , connectionpool 和 processpool 进行访问。第四个池也是和进程
33、结合,通过 processpconf 进行访问,不过和进程池不同的是它将在 Apache 再次读取配置文件时 进行清理。 进程池适合于长时间运行的资源,例如那些在服务器启动时被初始化的资源。请求池 适合于处理一个单一请求所使用的临时资源。 连接通常由一个或者多个请求组成,连接池的生命周期和连接相同。连接池对于不能 和一个请求相结合的临时资源来说非常有用,最值得一提的是在一个连接层过滤器中, request_rec 结构还没有定义,或者在一个非 HTP 协议的处理函数中。 除了以上这些标准的池, 还有一些为了某些原因特别创建的专用池, 例如配置和审计, 或者模块自己创建的、供自己使用的池。第3章
34、 使用Apache进行应用程序开发 Apache模块开发指南 66 在Apache中使用池:处理请求 所有的请求处理的钩子函数采用如下的形式: int my_func(request_rec* r) /* 在这里实现请求处理的钩子函数 */ 这个钩子将请求池 rpool 放在我们的处理函数中。 我们之前曾经介绍过, 这个请求 池对涉及处理请求的绝大部分操作都适合。我们将请求池传递给Apache和APR函数,这 些函数需要一个池作为参数,也需要我们自己的参数。 对于那些需要分配长时间运行资源的操作来说 , 进程池可以通过 rserverprocesspool 的方式进行访问例如,缓存一个资源,该
35、资源需要被计 算一次,并随后被其他的请求重用。不过,这个过程有一些复杂,通常我们更愿意从这个 进程池派生一个子池,我们将在第4 章和第 10 章进行介绍。 连接池通过 rconnectionpool 进行访问。 在Apache中使用池:初始化和配置 Apache 初始化的内部流程是比较复杂的。不过,就所涉及的模块而言,初始化通常 被当作是一个简单的流程:仅仅建立一个配置,然后所有的东西都是持久的。Apache 将 这个过程变得很简单,因为大部分的相关钩子都具有将相关的池作为第一个参数进行传递 的原型。 配置处理函数 static const char* my_cfg(cmd_parms* cm
36、d, void* cfg, /* 参数 */ ) 使用配置池 cmdpool 将给这个配置赋予指令的生命周期。 前向配置和后向配置钩子 这些钩子通常不传递几个池。 static int my_pre_config(apr_pool_t* pool, apr_pool_t* plog, apr_pool_t* ptemp) 对于大多数情况来说,仅仅使用第一个 pool 参数。ptemp 适用于在配置阶段使用的 资源,不过这些资源将在Apache进入运行阶段之前释放。plog 在服务器的整个生命周期3.4 资源管理:APR池 Apache模块开发指南 67 内保持激活,在每次读取配置文件时被释放。
37、 子池初始化(Child init) static void my_child_init(apr_pool_t* pool, server_rec* s) 子池是第一个参数。 监控器 static int my_monitor(apr_pool_t* pool) 监控器是一个特殊的情况: 它在父进程中运行, 而且没有绑定到任何时间受限的结构。 因此,在一个监控器函数中分配的资源应当被显式地释放。如果需要,监控器将创建、管 理和释放自己的子池,这将在第 4章进行讨论。很少的应用程序需要使用监控器钩子。 在Apache中使用池:其他的情形 我们已经介绍了大部分在初始化和请求处理中涉及的模块。不过,
38、还有两种其他的情 形:连接函数和过滤器函数。 连接函数 pre_connection 和 process_connection 这两个连接层的钩子将 conn_rec 作为第 一个参数进行传递;就所涉及的池资源而言,它们与请求函数非常类似。连接初始化的钩 子 create_connection 也将池作为它的第一个参数进行传递。任何实现这个钩子的模块 需要负责建立连接。 过滤器函数 过滤器函数接收一个 ap_filter_t 作为它们的第一个参数。这个对象模糊地包含一 个 request_rec 和一个 conn_rec 成员,不管它究竟是一个请求层的过滤器,还是一个连 接层的过滤器。内容过滤
39、器通常应当使用请求池。连接层的过滤器将在 fr 中获取一个 垃圾指针(请求不存在于协议层之外,参考第 8 章)并且必须使用连接池。一定要小心, 这可能是一个不经意的陷阱。第3章 使用Apache进行应用程序开发 Apache模块开发指南 68 3.4.4 池的局限性 到目前为止,我们已经看到了在资源管理中使用池的好处。不过池也有一些不足。 l 如果要管理的资源的生命周期和 Apache中主要对象的生命周期不能对应, 往往需 要做更多的工作。我们将会在第 4章对这个问题进行深入地讨论。 l 从池中分配资源不是线程安全(thread safe)的。当Apache运行在一个多线程基 础之上时,它使用
40、一个对象(HTP 请求或者 TCP 连接)所拥有的池,这个池在 使用时是线程私有的,大部分的池分配都是通过模块进行的,因此池中分配资源 不是线程安全的情况很少。第 4 章将讨论需要线程安全时的一些情况。 l APR池在它们被释放之前(当然,它们确实是复用内存,因此基于池的应用程序 不会无限增长)是不会向操作系统返回内存的。因此在分配非常大的内存块时, 有时需要使用 malloc 函数而不是使用池。相反地,在代码中使用 malloc 可能会 影响二进制兼容性。在 Windows系统中,考虑到和运行时库的不兼容性,系统将 使用其他不同版本的 Visual C+编译的二进制代码和你的代码进行链接。
41、3.5 精选的 APR 主题 系统中有些函数对我们来说非常熟悉,而且在不需要安装 APR 的情况下也可以在我 们的系统上使用。APR 为这些函数提供了 APR 版本的实现,作为普通系统函数的替代选 择。不过,使用这些函数的APR版本具有以下的特点: l APR函数是平台独立的,可以提供更好的移植性。 l APR函数可以自由地从 APR 的基于池的资源管理中获取优势。 我们将不再进行详细地讨论。如果你想获取更多的信息,请参考头文件中那些非常不 错的文档。3.5 精选的APR主题 Apache模块开发指南 69 3.5.1 字符串和格式 apr_string 模块提供了以下的APR实现。 l 常用
42、的字符串函数:字符串比较、字符串匹配、字符串拷贝和字符串连接。 l 类似stdio的函数: sprintf和printf家族函数, 包括变参的格式化 (vformatter)。 l 字符串解析,包括线程安全的 strtok。 l 字符串变换为其他的数据类型, 和从其他的数据类型变换为字符串 (例如: atoi)。 APR的字符串处理是基于池的。由于我们很少会去关注缓冲区的大小,因此基于池的 机制带来了实质性的简化。例如,如果要连接一个随意长度的字符串,我们可以使用: result = apr_pstrcat(pool, str1, str2, str3, ., NULL) 而不再需要计算 re
43、sult 的长度,并预先分配一个缓冲区。类似地: result = apr_psprintf(pool, fmt, .) 也不需要去关注单调乏味的字符串空间的大小,之前我们必须使用如下的方式: length = compute length here buf = malloc(length) sprintf(buf, fmt, .) APR 中不支持正则表达式(尽管在 Apache 中支持),不过 apr_strmatch 模块提供 了更快的字符串匹配机制,它使用不分大小写(以及分大小写)的查询和非空终结 (nonnullterminated)的方式进行处理。 3.5.2 国际化 apr_xl
44、ate 模块提供了在不同字符集之间转换的功能。 在本书写作之际, 由于Windows 缺乏本地国际化的支持, Windows 平台的 apr_xlate 还需要一个第三方的 APR库 apr_iconv,这个依赖将会在以后的版本中消除掉。第3章 使用Apache进行应用程序开发 Apache模块开发指南 70 3.5.3 时间和日期 apr_time 模块提供了一个微秒计时器和时钟。由于 APR 工作在微秒环境下,它的基 本数据类型 apr_time_t 是一个 64 比特的整型,与 time_t 是不能互换的。以下的宏用来 进行转换: /* return apr_time_t,它的单位为秒
45、*/ #define apr_time_sec(time) (time) / APR_USEC_PER_SEC) /* 将秒作为 an apr_time_t 返回*/ #define apr_time_from_sec(sec) (apr_time_t)(sec) * APR_USEC_PER_SEC) 其他的数据类型,包括时间间隔和一个与“struct tm”类似的类型 apr_time_exp_t。 APR的时间函数包括: l 当前时间 l 格林威治时间(Grenwich Mean Time,GMT),本地时间和任何时区 l 时间算法 l 休眠 l 时间格式化为 ctime 或者 RFC8
46、22字符串 apr_date 模块提供了一些辅助的函数进行常用的时间和日期格式的解析。 3.5.4 数据结构 Apache提供了 4 个数据结构模块。 l apr_table:提供表和数组 l apr_hash:提供了哈希表 l apr_queue:提供了先进先出队列(FIFO) l apr_ring:提供了一个环结构,该结构也是APR 的 Bucket brigade的基础 3.5.4.1 数组 APR 数组类型由 apr_array_header_t 类型提供,可以通过对象和指针两种方式进 行访问。数组数据类型也可以作为堆栈。一个数组拥有一个默认的大小,并在数组创建时 被设置。尽管数组在其
47、默认大小范围内工作效率最高,但它还是可以根据需要增长。数组3.5 精选的APR主题 Apache模块开发指南 71 最常用的操作就是增加(压栈)和迭代。 /* 分配一个 my_type 类型的数组 */ apr_array_header_t* arr = apr_array_make(pool, sz, sizeof(my_type) /* 在数组中分配一个未初始化的元素 */ my_type* newelt = apr_array_push(arr) /* 为 elt 进行赋值 */ neweltfoo = abc neweltbar = “foo“ /* 弹出最后面的元素 */ my_ty
48、pe* oldelt = apr_array_pop(arr) /* 迭代所有的元素 */ for (i = 0 i nelts i+) /* 一个 C+引用最能清晰地体现这个操作 */ my_type& elt = arreltsi 其他的数组操作,包括pop 出栈操作、复制(shallow copy,浅拷贝)、懒复制(lazy copy)、连接(concatenation)、增加(append)和转换为一个字符串变量(这个只有在 数组的内容都是字符串变量时才比较有意义)。 3.5.4.2 表apr_table_t 是一个建立在数组之上的、直观的高层数据结构,用来存贮键/值 (key/val
49、ue)对。它支持增加元素(几个变种)、删除元素(效率不高)、查找、迭代以 及清除整个表的操作。它也支持融合和覆盖操作,对数据进行融合或者清除冗余的元素。 表的键一般都是不分大小写的(这和 APR 哈希表的键不同)。 /* 新建一个表 */ apr_table_t* table = apr_table_make(pool, sz) /* 设置键/值对 */ apr_table_setn(table, key, val) apr_table_set 的几个变种包括 : apr_table_setn 、 apr_table_add 、 apr_table_addn、apr_table_merge、apr_table_mergen。 l apr_table_setn:设置一个值