当前位置: 首页 > 编程日记 > 正文

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

随着项目工程的发展,多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求,讲解如何在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更配哦”。

相关文章:

JSP学习笔记(七):使用JavaBean

bean.java publicclassB1 { publicString getString() { return"content"; } }page.jsp <%B1 b1 newB1(); out.print(b1.getString());%>

折返(Reentrancy)VS线程安全(Thread safety)

在Wiki上&#xff0c;折返例如&#xff0c;下面的定义&#xff08;接&#xff09; In computing, a computer program or subroutine is called reentrant if it can be interrupted in the middle of its execution and then safely called again ("re-entered") be…

服务器架设笔记——httpd插件支持mysql字符集选择

mysql数据库默认的字符集是latin1。默认情况下&#xff0c;我们编译的httpd插件是可以正常读取该类型的数据库&#xff0c;并且不会出现乱码。但是&#xff0c;如果我们的数据库变成其他格式&#xff0c;比如UTF8&#xff0c;那么默认读取出来的数据就是乱码&#xff0c;且无论…

只需3行代码自动生成高性能模型,支持4项任务,亚马逊发布开源库AutoGluon

作者 | KYLE WIGGERS编译 | AI科技大本营&#xff08;ID:rgznai100&#xff09;构建涉及图像、文本和表格数据集的机器学习应用并不容易。它需要特征工程或使用数据领域知识来创建使AI算法起作用的特征&#xff0c;还需要进行大量数据预处理&#xff0c;以确保训练模型时不会出…

在客户端(IE中)无法登录Citrix MetaFrame server的原因

当在IE中登录服务器时如果出现这面的错误提示&#xff1a; ERROR: The Citrix MetaFrame servers cannot process your request at this time. The Citrix XML Service object was not found. [404 Not Found] 我的原因是IE使用了8080端口&#xff0c;而我的Citrix XML servic…

虚拟化--015 配置VMware View Event database失败:

015 配置VMware View Event database失败&#xff1a;参考链接http://url.cn/VTq4zN 转载于:https://blog.51cto.com/williamliuwen/1686536

以金山界面库(openkui)为例思考和分析界面库的设计和实现——代码结构(完)

三年前&#xff0c;准备将金山界面库做一个全面的剖析。后来由于种种原因&#xff0c;这个系列被中断而一直没有更新。时过境迁&#xff0c;现在在windows上从事开发的人员越来越少&#xff0c;关注这块的技术的朋友也很少了。本以为这系列也随着技术的没落而不再被人所关注&am…

一包烟钱买到电动剃须刀,小米有品告诉你什么叫性价比

男人身上长得最快的是什么&#xff1f;答案是胡须。一名健康男性的胡须每天都要生长超过 0.4mm&#xff0c;比咱们头发的生长速度还快&#xff0c;这也是男人隔三差五就要剃须的原因之一。男人的一生是与胡子战斗一生&#xff0c;也是被剃须刀拖累的一生。出差办事儿&#xff0…

Label控件属性AssociatedControlID

可以使用Label控件来标注一个HTML表单字段。Label控件拥有属性AssociatedControlID,可以设置此属性来指向表示表单字段的ASP.NET控件。 例如,代码清单2-3中的页面含有一个简单的表单,表单包含两个字段用于输入名和姓。Label控件用于标注这两个TextBox控件。 Code<% Page Lan…

2015_8_21作业——有自翻译有复制他人的英语太差

date作用&#xff1a;打印或设置系统日期和时间格式:date [OPTION]...[FORMAT]date [u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]|是或 多选一的选项 ...代表同类内容可多次选项&#xff1a;注意短选项后不可加号-d,--dateSTRING显示时间字符串但不是立即&#xff1a;如date…

WMI技术介绍和应用——接收事件

时隔两三年&#xff0c;再次更新WMI系列博文。好在功能在三年前就已经实现了&#xff0c;现在只要补充些实例即可。 之前介绍的基本都是查询静态数据&#xff0c;而本文将要介绍非常有意思的事件接收功能。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 监控…

WML标签速查手册

WML标签速查手册 结构相关标签语法及属性<wml><wml xml:lang"lang" >    content</wml> WML元素的共有属性主要有3个&#xff0c;即id、class和xml:lang属性。WML的所有元素都有两个核心属性&#xff0c;即标识(id)和类(class)属性。它们主要用…

Python 三十大实践、建议和技巧

所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项&#xff0c;还有更多福利赠送作者 | Erik-Jan van Baaren译者 | 凯隐编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;【导读】2020年&#xff0c;你又立了什么新的 Flag&#xff1f;新一年…

点击通知栏后打开Activity,并传参

为什么80%的码农都做不了架构师&#xff1f;>>> Reciver中intent new Intent(context, MessageDetailsaActivity_.class);intent.putExtra("freshMessageId", String.valueOf(push.getObid()));intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Inten…

WMI技术介绍和应用——执行方法

在之前的博文中&#xff0c;我们主要介绍了如何使用WMI查询信息和接收事件。本文将介绍WMI的另一种用法——执行方法。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 这块的内容在msdn中有详细的介绍&#xff0c;如果想看原版的可以参阅《Example: Calling a…

动态指定超链接参数的几种方法(Passing a JavaScript variable into href of )

情景&#xff1a;有些环境下我们需要根据页面中JavaScript变量的值来确定某个&#xff08;某些&#xff09;超链接的参数&#xff0c;如“http://www.bla.com/test.aspx?var1”中&#xff0c;究竟var1等于多少&#xff0c;要根据JavaScript变量来判定 方法一&#xff1a;很简单…

知乎「致知计划之科学季」颁奖,创作者分享80万元奖金

1月11日&#xff0c;「致知计划之科学季」颁奖典礼在北京798艺术中心举行&#xff0c;近500名创作者到场参加。 为了激励创作者&#xff0c;推动优质内容持续产出&#xff0c;知乎的「致知计划」从四个方面对创作者进行了扶持。一是流量扶持&#xff0c;让专业、优质的内容获得…

Inno Setup制作应用程序安装包

我最近写了一个MFC应用程序&#xff0c;想发给其他的小伙伴玩一玩&#xff0c;直接发了个exe文件过去&#xff0c;结果发现小伙伴那边打不开。原来这个exe文件虽然是MFC静态编译的&#xff0c;但是还依赖了其他几个.dll文件&#xff0c;需要把这几个dll文件和exe文件放在同一个…

WMI技术介绍和应用——事件通知

在《WMI技术介绍和应用——WMI概述》中&#xff0c;我们使用了下图介绍WMI构架&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 我们之前介绍的使用WMI查询系统、硬件等信息的功能&#xff0c;是通过查询WMI静态数据的空间实现的。这个功能的核心是在上图中2&a…

OpenWebSpider 安装使用

OpenWebSpider 是一个很好用的网络爬虫,也可以叫做“网络蜘蛛”. 安装&#xff1a; 1.使用Vs.net 2003 编译&#xff0c; 记得要copy libmysql.dll到工程里 2.配置文件openwebspider.conf 将已经启动了的 mysql数据库用户名&#xff0c;密码填写好 3.数据库建立 执行 sql_struc…

杂谈 | 当前知识蒸馏与迁移学习有哪些可用的开源工具?

所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项&#xff0c;还有更多福利赠送作者&编辑 | 言有三来源 | 有三AI&#xff08;ID:yanyousan_ai&#xff09;【导读】知识蒸馏与迁移学习不仅仅属于模型优化的重要技术之一&#xff0c;也是提升模型跨领域泛化能力的重要技…

对 Thinking in java 4th Edition I/O DirList.java的疑问

2019独角兽企业重金招聘Python工程师标准>>> 以下原文代码&#xff1a; //: io/DirList.java // Display a directory listing using regular expressions. // {Args: "D.*\.java"} import java.util.regex.*; import java.io.*; import java.util.*; pub…

WMI技术介绍和应用——Instance/Method Provider

在《WMI技术介绍和应用——事件通知》一文中&#xff0c;我们提到了提供者&#xff08;Provider&#xff09;这个概念。我们还是要引入WMI的结构图&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 我们在1这层的Native C/C里可以看到若干Provider&#xff0c;这…

SSH 组建轻量级架构 附录 -- 遇到的问题和解答

action: nulljava.lang.ClassNotFoundException: org.springframework.web.struts.ContextLoaderPlugIn解决方法&#xff1a;加载 spring.jar 包 报 无法初始化at org.apache.struts.action.ActionServlet.initModulePlugIns(解决方法&#xff1a;删除 asm-2.2.3.jar springda…

TIOBE 1月编程语言排行榜:C语言再度「C 位」出道,Python惜败

整理 | 屠敏来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;【导读】在 2020 年初雪来临之际&#xff0c;TIOBE 官方在最新发布的 1 月编程语言榜单中为我们最终揭开了「 2019 年度编程语言」的神秘面纱&#xff0c;然意料之外情理之中&#xff0c;获此殊荣的并非是…

my项目的总结2015.8.26编

这已经是上上个星期的事了&#xff0c;现在回顾一下&#xff1a; 负责的模块是"my"&#xff0c;更精准的说应该是my里面的个人信息管理 由于项目分域&#xff0c;模块已经分好了&#xff0c;涉及到的只是在现有的基础上解决分域后遗留的历史问题 上点图吧&#xff1a…

WMI技术介绍和应用——Event Provider

在《WMI技术介绍和应用——Instance/Method Provider》一文中&#xff0c;我们介绍了Instance和Method Provider的编写方法。本文我们将介绍更有意思的“事件提供者”。在《WMI技术介绍和应用——事件通知》中&#xff0c;我们曾经提到事件是分为两种&#xff1a;intrinsic eve…

Windows server 2003 IP路由配置

1、静态路由&#xff1a;在静态路由中必须明确指出从源到目标所经过的路径2、默认路由&#xff1a;默认路由是一种特殊的静态路由&#xff0c;为那些在路由表中没有找到明确匹配的路由信息的数据包指定下一步跳地址。在Windows server 2003的计算机上配置默认网关时就为该计算机…

人工智能的下一个前沿:识别“零”和“无”

所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项&#xff0c;还有更多福利赠送作者 | Max Versace译者 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;声明&#xff1a;本文为客座文章&#xff0c;仅是为作者的观点&#xff0c;不代表 IEEE Spectrum 或 …

File Operations In Java

2019独角兽企业重金招聘Python工程师标准>>> The “File” class in Java defines many useful methods, here is a program which demonstrates some of these methods. import java.io.*;public class streams {public static void main(String []args){File f1ne…