专栏首页方亮服务器架设笔记——多模块和全局数据

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

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

        之前我碰到两个需求:

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

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

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

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

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

<Location /query_board_list>
    SetHandler prepare
    SetHandler query_board_list
</Location>

<Location /query_project_info>
    SetHandler prepare
    SetHandler query_project_info
</Location>

<Location /query_board_info>
    SetHandler prepare
    SetHandler 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更配哦”。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

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

            之前介绍的Apache Httpd相关内容,都是些零散的知识点。而实际运用中,我们要根据不同的业务,将这些知识点连接起来以形成各种组合,来满足我...

    方亮
  • 浅析GPU计算——CPU和GPU的选择

            目前市面上介绍GPU编程的博文很多,其中很多都是照章宣科,让人只能感受到冷冷的技术,而缺乏知识的温度。所以我希望能写出一篇可以体现技术脉络感的文...

    方亮
  • bug诞生记——不定长参数隐藏的类型问题

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/breaksoftware/article/detai...

    方亮
  • 图解 Android 事件分发机制

    在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功...

    非著名程序员
  • Mozilla Firefox UAF 漏洞 - Seebug 每周一洞-2016-04-15

    image.png 漏洞概要 这个模块利用了一个 Mozilla Firefox 3.6.16use-after-free 漏洞。 一个对象元素, mChan...

    Seebug漏洞平台
  • 微信支付兴起,万亿级用户交易记录存储的挑战

    微信红包的火热带动微信支付的迅猛发展,每天的微信支付交易记录会达到 20 亿。本文将就微信支付背后的交易记录系统的重构优化历程进行一次全面的呈现。

    TEG云端专业号
  • webshell 常见 Bypass waf 技巧总结

    对于很多,和我一样刚刚入门,或者还在门边徘徊的小伙伴们,在渗透学习的过程中,总会遇到各种情况,例如 php 大马被 waf 拦截的时候,那么如何制作免杀 php...

    信安之路
  • 利用人脸识别与神经网络技术,这款app可让真实表情实时转为表情包

    镁客网
  • JavaWeb20-文件上传;下载(Java真正的全栈开发)

    文件上传&下载一.文件上传 1. 文件上传介绍 要将客户端(浏览器)大数据存储到服务器端,不将数据直接存储到数据库中,而是要将数据存储到服务器所在的磁盘上,这就...

    奋斗蒙
  • 包银消费金融总经理助理汤向军:消费金融行业的大数据

    数据猿报道,2017年10月25日,由 数据猿 联合《清华金融评论》共同主办的“2017金融科技价值峰会——数据驱动金融商业裂变”在北京隆重召开。本文为数据猿现...

    数据猿

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动