随着项目工程的发展,多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求,讲解如何在Apache的Httpd插件体系中实现这些功能。(转载请指明出于breaksoftware的csdn博客)
之前我碰到两个需求:
对于需求1,我们最简单的办法就是:每次请求过来都去查询一下映射关系数据表,然后替换相关字符。但是这个方法对于一个优秀的实现来说,还是挺low的。我们可以注意到这个需求的特点——几乎不变动、且数据量少,那我们应该可以把他们放到我们内存里。
对于需求2,可以想到的最简单的办法就是:在代码中硬编码,将可配置的字符串写死在代码里。然后如果一旦有修改,那么我们就需要修改代码文件中的硬编码字段,然后编译后上线。这种方式非常麻烦,且可能会带来不稳定因素——说不定谁谁忘记了给待转义字符增加转义符呢。而且代码中字符串一堆双引号、单引号或者转义符看着实在令人难受。我们还是通过动态加载配置文件的形式,将这段配置加载进来比较靠谱。
那么我就想,我需要设计一个模块,用于预处理以上的需求——将数据加载到内存中。我给这个模块取名为prepare。至于插件模块的创建可以参见《服务器架设笔记——编译Apache及其插件》,本文我不在赘述。
prepare中的处理handler需要执行于其他业务handler之前。我们需要对httpd.conf做如下配置
SetHandler prepare
SetHandler query_board_list
SetHandler prepare
SetHandler query_project_info
SetHandler prepare
SetHandler query_board_info
上例的写法,便将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;
}
这段代码,需要注意的有四个部分:
以下是代码的罗列
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;
}
不可否认的一点是,在插件中写数据库访问的逻辑还是挺麻烦的。因为总是会遇到一些意想不到的问题,比如在上例中:
当然可能是我哪儿不得要领,但是从快速开发的角度来说,或许“下雪天,PHP和httpd更配哦”。