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

本文我将以用户注册、登陆和免登等这些业务需求,将之前四篇介绍的知识点串起来,形成一组可用的功能。但是,本例子只是为了完成功能,而不涉及相关优化——比如数据库的访问,我觉得是可以优化的——但是优化不是本文的主题。

网上有很多Apache+PHP的方案,诚然这个组合可以方便快速的搭建业务性功能,但是我不会写PHP,所以我还是用老掉牙的C去写相关模块。

用户注册和登陆这个大家一般都明白。但是什么叫免登,可能有些同学还不清楚。举个例子,比如我们登陆某网站后,我们再在其子页面中跳转,往往还是处于登陆状态。但是服务器如何确定这个用户的登陆状态,除了像长连接等方案外,通过协议约定也是一种方案。我们约定:在用户成功注册和登陆后,会访问给客户端请求一个加密字段。用户之后的请求都需要带上这个加密字段,以供服务器验证。

接口定义

注册

路径:login

参数:

  • uid        字符串,用户ID
  • pwd      字符串,密码
  • did        字符串,设备唯一标志
  • action   字符串,行为。注册时该值为new

返回:

  • res          整型,0 成功 ,1 用户名已存在, 2 其他失败
  • session  字符串,如果res为0, 则该字段有值,否则为空串

登陆

路径:login

参数:

  • uid        字符串,用户ID
  • pwd      字符串,密码
  • did        字符串,设备唯一标志

返回:

  • res          整型,0 成功, 其他 失败
  • session  字符串,如果res为0, 则该字段有值,否则为空串

免登

路径:任何路径(包括login)

参数:

  • uid        字符串,用户ID
  • ss          字符串,用于免登的校验标志
  • did        字符串,设备唯一标志

返回:根据不同业务,有不同的返回值。免登只是这个请求的一种协助方式。

模块划分

根据我们的业务特点,可以拆分出如下4个独立模块(so):

  1. 注册和登陆;
  2. 免登;
  3. 公用库;
  4. json库;这儿需要说明下,我们将使用json作为返回参数的格式。虽然XML是apache apr库中一个可用模块,也是可以用来组织返回数据,但是json还是更加主流。

Json库

因为apache httpd是C语言写的,所以为了统一风格以及免除之后一切编译相关的问题,我选在了同样的C写的json库——CJson。我们只选用cJSON.h和cJson.c两个文件作为库的原始文件,并编写一个编译脚本

gcc cJSON.c -fPIC -lm -shared -o libcjson.so
cp libcjson.so /usr/local/apache2/modules/

基础库——utils

我将基础库分为如下几个功能集:

  1. 加解密;在登陆校验等业务中会使用到。
  2. 编码;base64编码和解码是服务的基础功能。
  3. hash;md5等是必要功能。
  4. 其他辅助函数;一些函数比较复杂,在多个模块中都要被使用到,所以把他们放到基础库中,供各个模块使用。

编码和Hash没什么好说的,apr库里提供了便捷的方法。加解密在apr-util里也有相应的封装。这儿需要指出的是,我们在编译apr-util时需要指定参数--with-crypto。有的文章上说,还要通过--with-openssl来指定使用openssl库。而我试验发现通过指定该参数,反而会导致加解密模块不可用。因为我们还要使用数据库,所以我们如此编译apr-util

./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr --with-crypto --with-mysql

我们通过查看/usr/local/apr-util/include/apr-1/apu.h文件中相应宏的定义来知晓相应功能是否已被启用

#define APU_HAVE_PGSQL         0
#define APU_HAVE_MYSQL         1
#define APU_HAVE_SQLITE3       0
#define APU_HAVE_SQLITE2       0
#define APU_HAVE_ORACLE        0
#define APU_HAVE_FREETDS       0
#define APU_HAVE_ODBC          0#define APU_HAVE_CRYPTO        1
#define APU_HAVE_OPENSSL       1
#define APU_HAVE_NSS           0

但是非常不幸的是,我参考例子写的一段加解密代码,在测试代码中可以正确运行,而在请求线程中却出现了一些诡异的现象。于是我只能直接使用openssl中的API进行加解密。
        使用如下指令编译openssl,将产出动态链接库,我们将libcrypto.so拷贝到apache httpd的module目录下。

make clean
./config --prefix=/usr/local/openssl
./config shared --prefix=/usr/local/openssl
make depend
make install

其他模块

我们其它模块,都会使用到libcjson.so、libutils.so和libcrypto.so。对于这些第三方动态链接文件,我们需要在使用到他们的模块加载之前就加载它们。于是我们配置httpd.conf文件:

LoadFile modules/libcjson.so
LoadFile modules/libutils.so
LoadFile modules/libcrypto.soLoadModule user_check_module  modules/mod_user_check.so
LoadModule login_module       modules/mod_login.so

如此,我们就可以在用户注册登录模块——mod_login.so和免登模块——mod_user_check.so中使用这些API了。

处理流程

在之前,我们说到,我们要是的免登逻辑位于所有请求之前。于是我们以注册和登录模块为例子,我们需要在httpd.conf中做如下配置

<Location /login>SetHandler user_checkSetHandler login
</Location>

user_check模块的代码如下

static int user_check_handler(request_rec *r)
{char* uid;char* ss;char* did;user_define_data_ptr = apr_palloc(r->pool, sizeof(user_data));user_data* user_data_ptr = (user_data*)r->user_define_data_ptr;user_data_ptr->login = 0;uid = get_args_param(r, "uid");ss = get_args_param(r, "ss");did = get_args_param(r, "did");if (!uid || !ss || !did) {return DECLINED;}user_data_ptr->login = !user_login_ok(r->pool, uid, ss, did); return DECLINED;
}

注意所有的返回值都是DECLINED。这样内容生成器,才会传导到下一个内容生成器中。

如果熟悉request_rec结构的同学,可能会马上对上面的代码产生疑问——哪儿来的user_define_data_ptr参数?是的,这个参数不是request_rec默认结构体的成员,是我为了贯通各个内容生成器自行加入的一个变量——修改/usr/local/apache2/include/httpd.h

/*** @brief A structure that represents the current request*/
struct request_rec {/** The pool associated with the request */apr_pool_t *pool;……/** MIME trailer environment from the response */apr_table_t *trailers_out; /** define by fangliang*/void* user_define_data_ptr;
};

请求通过免登模块检测后,便已经确认该用户是否已经登录了。然后在其他内容生成其中,通过user_define_data_ptr所指向的结构体对象得知其状态——上下文(如果不想修改源码,可以考虑使用apr_pool_userdata_setn和apr_pool_userdata_get的组合)。

以上便将所有要点讲解完了,我们可以通过请求相关接口测试相应功能。

特殊问题

我在链接Mysql数据库时,遇到了Access denied for user ''@'localhost'”的问题。在网上找到一个可行的解决方案,在此做以记录

1.关闭mysql# service mysqld stop
2.屏蔽权限# mysqld_safe --skip-grant-table屏幕出现: Starting demo from .....
3.新开起一个终端输入# mysql -u root mysqlmysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';mysql> FLUSH PRIVILEGES;//记得要这句话,否则如果关闭先前的终端,又会出现原来的错误mysql> \q

代码片段

以下列出比较有用的代码片段,方便大家使用。

获取get请求中的参数

char* get_args_param(request_rec* r, const char* name) {const char* args = r->args;const char* start_args;if (!args) {return NULL;}for (start_args = ap_strstr_c(args, name); start_args;start_args = ap_strstr_c(start_args + 1, name)){if (start_args == args || start_args[-1] == '&' || isspace(start_args[-1])) {start_args += strlen(name);while (*start_args && isspace(*start_args)) {++start_args;}if (*start_args == '=' && start_args[1]) {char* end_args;char* arg;++start_args;arg = apr_pstrdup(r->pool, start_args);if ((end_args = strchr(arg, '&')) != NULL) {*end_args = '\0';}return arg;}}}return NULL;
}

使用apr库实现md5算法

unsigned char* md5hex(apr_pool_t* pool, const char* in, apr_size_t in_len) {unsigned char* out;apr_md5_ctx_t context;out = apr_palloc(pool, APR_MD5_DIGESTSIZE + 1);if (!out) {return NULL;}if (0 != apr_md5_init(&context)) {return NULL;}if (0 != apr_md5_update(&context, in, in_len)) {return NULL;}if (0 != apr_md5_final(out, &context)) {return NULL;}out[APR_MD5_DIGESTSIZE] = '\0';return out;
};char hex2char(int hex) {char result = '\0';if(hex >= 0 && hex <= 9) {result = (char)(hex + 48);}else if(hex >= 10 && hex <= 15) {result = (char)(hex - 10 + 65);}else {result = (char)hex;}return result;
};char* md5(apr_pool_t* pool, const char* in, apr_size_t in_len) {char* out;unsigned char* md5buffer;out = apr_palloc(pool, APR_MD5_DIGESTSIZE * 2 + 1);if (!out) {return NULL;}md5buffer = md5hex(pool, in, in_len);if (!md5buffer) {return NULL;}for (apr_size_t index = 0; index < APR_MD5_DIGESTSIZE; index++) {unsigned char high;unsigned char low;unsigned char tmp;high = md5buffer[index] >> 4;tmp = md5buffer[index] << 4;low = tmp >> 4;out[2 * index] = hex2char(high);out[2 * index + 1] = hex2char(low);}out[APR_MD5_DIGESTSIZE * 2] = '\0';return out;
};

aes128加解密算法

#include "openssl/evp.h"apr_size_t aes_128_encrypt(apr_pool_t* pool,const unsigned char* in,apr_size_t in_len,const unsigned char* key,const unsigned char* iv,unsigned char** out)
{EVP_CIPHER_CTX *ctx;int len;apr_size_t out_len = 0;if (!(ctx = EVP_CIPHER_CTX_new())) {return 0;}if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {return 0;}len = (in_len / 16 + 1) *16;*out = apr_palloc(pool, len);if(1 != EVP_EncryptUpdate(ctx, *out, &len, in, in_len)) {return 0;}out_len = len;if(1 != EVP_EncryptFinal_ex(ctx, *out + len, &len)) {return 0;}out_len += len;EVP_CIPHER_CTX_free(ctx);return out_len;
};apr_size_t aes_128_decrypt(apr_pool_t* pool,const unsigned char* in,apr_size_t in_len,const unsigned char* key,const unsigned char* iv,unsigned char** out)
{EVP_CIPHER_CTX *ctx;int len;apr_size_t out_len = 0;if (!(ctx = EVP_CIPHER_CTX_new())) {return 0;}if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {return 0;}len = (in_len / 16 + 1) *16;*out = apr_palloc(pool, len);if(1 != EVP_DecryptUpdate(ctx, *out, &len, in, in_len)) {return 0;}out_len = len;if(1 != EVP_DecryptFinal_ex(ctx, *out + len, &len)) {return 0;}out_len += len;EVP_CIPHER_CTX_free(ctx);return out_len;
};

更新数据库中的项

apr_status_t updata_db(apr_pool_t* pool, const char* table_name,const char* uid, const char* col_name, const char* value)
{const apr_dbd_driver_t* driver = NULL;apr_dbd_t* handle = NULL;apr_dbd_results_t* res = NULL;char* sql_cmd;apr_status_t status;int nrows;if (!pool || !uid || !table_name || !col_name || !value) {return 1;}status = apr_dbd_get_driver(pool, "mysql", &driver);if (APR_SUCCESS != status) {return 1;}status = apr_dbd_open(driver, pool, "host=localhost;user=root;pass=password;dbname=database_name", &handle);if (APR_SUCCESS != status) {return 1;}sql_cmd = apr_psprintf(pool, "update %s set %s=%s where userid='%s'", table_name, col_name, value, uid);status = apr_dbd_query(driver, handle, &nrows, sql_cmd);if (APR_SUCCESS != status && 1 != nrows) {status = 1;}else {status = 0;}apr_dbd_close(driver, handle);return status;
}

获取数据库中某项

char* get_value(apr_pool_t* pool, const char* uid, const char* col_name) {const apr_dbd_driver_t* driver = NULL;apr_dbd_t* handle = NULL;apr_dbd_results_t* res = NULL;char* sql_cmd;apr_dbd_row_t* row;apr_status_t status;char* value = NULL;const char* value_tmp = NULL;if (!pool || !uid) {return NULL;}status = apr_dbd_get_driver(pool, "mysql", &driver);if (APR_SUCCESS != status) {return NULL;}status = apr_dbd_open(driver, pool, "host=localhost;user=root;pass=password;dbname=database_name", &handle);if (APR_SUCCESS != status) {return NULL;}sql_cmd = apr_psprintf(pool, "select %s from userlogin where userid='%s'", col_name, uid);status = apr_dbd_select(driver, pool, handle, &res, sql_cmd, 0);if (APR_SUCCESS != status) {value = NULL;}if (0 == apr_dbd_get_row(driver, pool, res, &row, 1)) {value_tmp = apr_dbd_get_entry(driver, row, 0);value = apr_palloc(pool, 128);strcpy(value, value_tmp);}else {value = NULL;}apr_dbd_close(driver, handle);return value;
};

最后附上模块的代码地址链接: http://pan.baidu.com/s/1dDmAmvZ 密码: c28d

服务器架设笔记——搭建用户注册和验证功能相关推荐

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

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

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

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

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

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

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

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

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

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

  6. android网络编程登录和验证,ASP.NET实现用户注册和验证功能(第4节)

    在前几篇文章中小编已经为大家分享几篇关于验证控件实现的文章,今天我们再具体为用户登录页面添加输入数据验证功能和验证码功能. 学习内容 1.创建ASP.NET应用程序. 在E:\Solution1目录下 ...

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

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

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

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

  9. 用户注册信息验证功能(前端+后台)

    一.写在前面罗里吧嗦的话 最近在写自己的第一个个人项目,开始写了很久了,但是每天没啥时间记录,趁着周末先记录一些思路和踩过的坑. 项目预计是做一个关于家装的电商网站,样式参考自土巴兔网站(我不是打广告 ...

最新文章

  1. 送100本书!涵盖Java 、大数据、推荐系统、机器学习、黑客、数据库、手游、少儿编程等!免费包邮!...
  2. Xen的内存布局及其启动
  3. kalman 滤波 演示与opencv代码
  4. 第二十八章 springboot + zipkin(brave定制-AsyncHttpClient)
  5. 【报告分享】中国都市圈发展报告:城镇化白皮书5.0.pdf(附下载链接)
  6. 【翻译】AdaIN:Arbitrary Style Transfer in Real-time with Adaptive Instance Normalization
  7. 解决网站开通https后地址栏图标小锁变成灰色警告图标教程
  8. 选课系统服务器,选课系统概要设计
  9. 数学分析教程(科大)——1-(1~10)小节
  10. JavaScript 全栈工程师培训教程(React.js + Node.js)
  11. VMware虚拟机安装ubuntu后无法复制文件的解决办法
  12. 编写程序,先将输入的一系列整数中的最小值与第一个数交换,然后将最大值与最后一个数交换,最后输出交换后的序列。
  13. Idea的GenerateAllSetter插件,快速填充对象属性
  14. liunx 全盘查找_Linux中查找文件夹的命令
  15. 【P4】 查看库文件两个历史版本的区别
  16. 2022 ACM杰出会员揭榜:清华黄隆波、刘世霞,北大郝丹、刘譞哲等23位华人学者入选!...
  17. 计算机策略组无法打开怎么办,电脑打不开gpedit.msc组策略怎么办
  18. C++ —— 到底还能做什么?
  19. 一种全新的指令集架构RISC-V
  20. 中文编程最高境界,不用编程,会用excel就会用,香不香?

热门文章

  1. Docker容器化部署config-server无法直接访问
  2. Python Qt GUI设计:QComboBox下拉列表框类(基础篇—14)
  3. 基于yolov5的行人检测跟踪与社交距离预测 (pedestrian detection and social distance prediction)
  4. 基于自适应逆透视变换的车道线SLAM
  5. 基于消失点的相机自标定(2)
  6. 在Ubuntu 16.4.3 LTS x86_64上安装PyCharm社区版笔记
  7. C4D灯光照明技术学习教程
  8. leetcode-142 环形链表II
  9. socket编程和并发服务器
  10. python高级-模块(14)