随着项目工程的发展,多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求,讲解如何在Apache的Httpd插件体系中实现这些功能。(转载请指明出于breaksoftware的csdn博客)

之前我碰到两个需求:

  1. 需要将从数据库中查询到的某些字段替换成另一个字符,替换的映射关系在另外一张表里面(这张表很小,且几乎不变动)。
  2. 需要返回一个可配置的字符串(基本不变动)。

对于需求1,我们最简单的办法就是:每次请求过来都去查询一下映射关系数据表,然后替换相关字符。但是这个方法对于一个优秀的实现来说,还是挺low的。我们可以注意到这个需求的特点——几乎不变动、且数据量少,那我们应该可以把他们放到我们内存里。

对于需求2,可以想到的最简单的办法就是:在代码中硬编码,将可配置的字符串写死在代码里。然后如果一旦有修改,那么我们就需要修改代码文件中的硬编码字段,然后编译后上线。这种方式非常麻烦,且可能会带来不稳定因素——说不定谁谁忘记了给待转义字符增加转义符呢。而且代码中字符串一堆双引号、单引号或者转义符看着实在令人难受。我们还是通过动态加载配置文件的形式,将这段配置加载进来比较靠谱。

那么我就想,我需要设计一个模块,用于预处理以上的需求——将数据加载到内存中。我给这个模块取名为prepare。至于插件模块的创建可以参见《服务器架设笔记——编译Apache及其插件》,本文我不在赘述。

prepare中的处理handler需要执行于其他业务handler之前。我们需要对httpd.conf做如下配置

<Location /query_board_list>SetHandler prepareSetHandler query_board_list
</Location><Location /query_project_info>SetHandler prepareSetHandler query_project_info
</Location><Location /query_board_info>SetHandler prepareSetHandler query_board_info
</Location>

上例的写法,便将prepare的执行于其他handler之前。但是仅仅如此是不够的,还有个隐藏的配置困扰了我很久,最后我开始“迷性”顺序关系才找到问题的所在。见httpd.conf的模块加载配置段

LoadModule prepare_module     modules/mod_prepare.so
LoadModule query_board_list_module modules/mod_query_board_list.so
LoadModule query_project_info_module modules/mod_query_project_info.so
LoadModule query_board_info_module modules/mod_query_board_info.so

这一点一定要切记:要把需要起始执行的模块,在之后处理的模块之前加载。如果我们把mod_prepare.so加载于mod_query_board_list.so之后,那么prepare将不会在query_board_list之前执行。

然后我们来看下prepare内部的书写。

static int prepare_handler(request_rec *r)
{apr_status_t rv;const char* user_data = NULL;const char* front_page_key = "front_page";const char* front_page_conf_path = "/usr/local/apache2/conf/front_page.template";const char* select_page_key = "select_page";const char* select_page_conf_path = "/usr/local/apache2/conf/select_page.template";apr_pool_userdata_setn(r, "request_rec_ptr", NULL, r->pool);rv = prepare_data(r->server->process->pool, front_page_key, front_page_conf_path);rv = prepare_data(r->server->process->pool, select_page_key, select_page_conf_path);prepare_map_from_db(r->server->process->pool, "LocationTable", "location");prepare_map_from_db(r->server->process->pool, "SourceTable", "source");prepare_map_from_db(r->server->process->pool, "ScopeTable", "scope");prepare_map_from_db(r->server->process->pool, "StageTable", "stage");return DECLINED;
}

这段代码,需要注意的有四个部分:

  1. 将request_rec指针r保存到r->pool的内存池中,从而实现了在请求级别的“全局数据”——之后的一些模块,可能没有传入request_rec指针。
  2. 通过prepare_data将配置文件内存保存到进程级别的内存池中,这样一个进程只加载一次。之后通过判断key是否存在来知道是否已经加载。
  3. 通过prepare_map_from_db将数据库中不同表的数据保存到内存中。这样的操作也是进程级别的。
  4. 返回DECLINED。返回这个值,告诉httpd还需要继续向后执行其他handler。

以下是代码的罗列

int prepare_data_from_db(apr_pool_t* pool, const char* database_table, pchar_ptr_map ptr_char_ptr_map) {const apr_dbd_driver_t* driver = NULL;apr_dbd_t* handle = NULL;apr_dbd_results_t* res = NULL;char* sql_cmd = NULL;apr_dbd_row_t* row = NULL;apr_status_t status = APR_SUCCESS;const char* value = NULL;apr_pool_t* pool_db = NULL;int rows_count = 0;pchar_item ptr_char_item = NULL;int index = 0;do {if (!pool || !database_table) {status = 41;break;}apr_pool_create(&pool_db, pool);if (!pool_db) {status = 42;break;}apr_dbd_init(pool_db);status = apr_dbd_get_driver(pool_db, "mysql", &driver);if (APR_SUCCESS != status) {status = 43;break;}status = apr_dbd_open(driver, pool_db, "host=localhost;user=root;pass=password;dbname=database_name", &handle);if (APR_SUCCESS != status) {status = 44;break;}sql_cmd = apr_psprintf(pool, "select * from %s", database_table);status = apr_dbd_select(driver, pool_db, handle, &res, sql_cmd, 0);if (APR_SUCCESS != status || !res) {status = 45;break;}rows_count = 64;ptr_char_ptr_map->array = apr_palloc(pool, rows_count *  sizeof(pchar_item));while (0 == apr_dbd_get_row(driver, pool_db, res, &row, -1) && row) {ptr_char_item = apr_palloc(pool, sizeof(char_item));status = 0;value = apr_dbd_get_entry(driver, row, 0);if (value) {ptr_char_item->key = apr_psprintf(pool, "%s", value);}else {ptr_char_item->key = apr_psprintf(pool, "%s", "");}value = apr_dbd_get_entry(driver, row, 1);if (value) {ptr_char_item->value = apr_psprintf(pool, "%s", value);}else {ptr_char_item->value = apr_psprintf(pool, "%s", "");}ptr_char_ptr_map->array[index] = ptr_char_item;index++;}ptr_char_ptr_map->count = index;} while(0);if (driver && handle) {apr_dbd_close(driver, handle);}if (pool_db) {apr_pool_destroy(pool_db);}return status;
}int prepare_map_from_db(apr_pool_t* pool, const char* table_name, const char* key) {pchar_ptr_map ptr_char_ptr_map = NULL;  if (APR_SUCCESS != apr_pool_userdata_get((void**)&ptr_char_ptr_map, key, pool) || !ptr_char_ptr_map) {ptr_char_ptr_map = apr_palloc(pool, sizeof(char_ptr_map));  prepare_data_from_db(pool, table_name, ptr_char_ptr_map);apr_pool_userdata_setn(ptr_char_ptr_map, key, NULL, pool);}return APR_SUCCESS;
}static apr_status_t save_file_to_mem(apr_pool_t* pool, const char* key, const char* file_path) {apr_status_t rv;apr_size_t buf_size = 0;apr_file_t* file_in = NULL;const char* file_buf = NULL;apr_size_t real_size = 0;apr_off_t offset = 0;if (!pool || !key || !file_path) {return 10;}rv = apr_file_open(&file_in, file_path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, pool);if (APR_SUCCESS != rv) {return rv;}do {buf_size = apr_file_buffer_size_get(file_in);if (0 == buf_size) {rv = 11;break;}real_size = buf_size;file_buf = apr_palloc(pool, buf_size);if (!file_buf) {rv = 12;break;}rv = apr_file_read(file_in, (void*)file_buf, &real_size);if (APR_SUCCESS != rv) {break;}apr_pool_userdata_setn(file_buf, key, NULL, pool);} while(0);apr_file_close(file_in);return rv;
}static apr_status_t prepare_data(apr_pool_t* pool, const char* key, const char* file_path) {const char* user_data = NULL;if (APR_SUCCESS != apr_pool_userdata_get((void**)&user_data, key, pool) || !user_data) {return save_file_to_mem(pool, key, file_path);}return APR_SUCCESS;
}

不可否认的一点是,在插件中写数据库访问的逻辑还是挺麻烦的。因为总是会遇到一些意想不到的问题,比如在上例中:

  1. 直接使用传入的pool操作数据库——虽然已经apr_dbd_init了,可能会导致进程意外退出。
  2. 调用apr_dbd_select最后一个参数传1,可能会导致进程意外退出。
  3. 调用apr_dbd_select最后一个参数传0,计算结果个数的apr_dbd_num_tuples函数将错误。这个问题与2结合导致我只能硬编码结果上线——low了一下。

当然可能是我哪儿不得要领,但是从快速开发的角度来说,或许“下雪天,PHP和httpd更配哦”。

服务器架设笔记——多模块和全局数据相关推荐

  1. 服务器架设笔记——Apache模块开发基础知识

    通过上节的例子,我们发现Apache插件开发的一个门槛便是学习它自成体系的一套API.虽然Apache的官网上有对这些API的详细介绍,但是空拿着一些零散的说明书,是很难快速建立起一套可以运行的系统. ...

  2. 服务器架设笔记——使用Apache插件解析简单请求

    一般来说,对于一个请求,服务器都会对其进行解析,以确定请求的合法性以及行进的路径.于是本节将讲解如何获取请求的数据.(转载请指明出于breaksoftware的csdn博客) 我们使用<服务器架 ...

  3. 服务器架设笔记——打通MySQL和Apache

    在<服务器架设笔记--使用Apache插件解析简单请求>一文中,我们已经可以获取请求内容.这只是万里长征的第一步.因为一般来说,客户端向服务器发起请求,服务器会有着复杂的业务处理逻辑.举个 ...

  4. CentOS4.4下邮件服务器架设笔记之windows AD整合功能实现

    1.通过"CentOS4.4下邮件服务器架设笔记之邮件网关功能实现"这一篇文章,我们已经实现了邮件网关功能,但是对于microsoft ad 平台下exchange邮件系统用户来说 ...

  5. nodejs linux模块全局,nodejs笔记一--模块,全局process对象;

    一.os模块可提供操作系统的一些基本信息,它的一些常用方法如下: var os = require("os"); var result = os.platform(); //查看操 ...

  6. RHCE课程-RH253Linux服务器架设笔记五-DNS服务器配置(2)

    上季我们学习了,DNS的原理和bind软件的相关简介,还有安装架设了BIND软件的DNS服务器,还有就是正向区域和反向区域的一些技巧,今天我们要学的就是DNS的辅助服务器的架设,还有DNS的缓存域名服 ...

  7. 服务器架设笔记——搭建用户注册和验证功能

    之前介绍的Apache Httpd相关内容,都是些零散的知识点.而实际运用中,我们要根据不同的业务,将这些知识点连接起来以形成各种组合,来满足我们的需求.(转载请指明出于breaksoftware的c ...

  8. 服务器架设笔记——编译Apache及其插件

    之前一直从事Windows上的客户端软件开发,经常会处理和服务器交互相关的业务.由于希望成为一个全栈式的工程师,我对Linux上服务器相关的开发也越来越感兴趣.趁着年底自由的时间比较多,我可以对这块做 ...

  9. RHCE课程-RH253Linux服务器架设笔记三-Samba服务器配置(1)

    今天我们要架设的samba服务器,功能主要就是类unix机器与windows机器的文件共享,也可以是共享打印机,samba软件整合了SMB协议及Netbios协议,使其运行在TCP/IP上 SMB协议 ...

最新文章

  1. mysql怎么防止误操作_MySQL数据库防止人为误操作的实例讲解
  2. 2013上半年-学习目录
  3. AI+AR如何提升花椒直播的体验?
  4. idea 修改Git密码和账号方法
  5. mysql explain 派生表_MySQL的Explain命令
  6. 电钻有刷好还是无刷好_高中物理好的来看看,永磁同步直流电机是怎样实现无刷驱动的?...
  7. window.open怎么设置title_企业seo怎么优化 都有那些策略?
  8. Aho-Corasick算法
  9. android listview 不重绘,重绘listview
  10. IMP导入数据 报错 IMP-00058 ORA-01691 IMP-00028
  11. $ is not defined与SpringMVC访问静态资源
  12. c# zxing条形码设置密度_C# 中 ZXing.Net 怎样突破 条形码 多识别 数量限制
  13. linux下dhcp配置(二)
  14. 怎样帮宝宝起个好名字?起名必备的五大招在这里了
  15. 2022-04-24 表单设计器动态插入脚本【低代码平台】
  16. java中级程序员需要掌握的一些必备知识
  17. 强收红包漫天要价偷转黑车……滴滴网约车被指太任性
  18. 数学建模——MATLAB基础知识
  19. 一男老师每日百词转载+连载(3)
  20. ue4 改变枢轴位置_[UE4蓝图][Materials]虚幻4中可互动的雪地材质完整实现(一)

热门文章

  1. catv系统主要有哪三部分组成_你了解买车“三大件”之一的悬挂系统吗?家用车用什么悬挂系统?...
  2. Python Qt GUI设计:多线程中信号与槽的使用(基础篇—9)
  3. tensorflow-gpu
  4. 深入理解 wpa_supplicant(四)
  5. 【对接】《前后端对接联调数据、调试接口过程乌龙大合集》
  6. 在Ubuntu 14.04 64bit上使用pycURL模块示例
  7. Ubuntu 14.04 64位上安装wps office软件
  8. 交互两个数(不引入第三个变量)
  9. Excel常用公式记录
  10. docker redis 多个实例