之前介绍的Apache Httpd相关内容,都是些零散的知识点。而实际运用中,我们要根据不同的业务,将这些知识点连接起来以形成各种组合,来满足我们的需求。(转载请指明出于breaksoftware的csdn博客)
本文我将以用户注册、登陆和免登等这些业务需求,将之前四篇介绍的知识点串起来,形成一组可用的功能。但是,本例子只是为了完成功能,而不涉及相关优化——比如数据库的访问,我觉得是可以优化的——但是优化不是本文的主题。
网上有很多Apache+PHP的方案,诚然这个组合可以方便快速的搭建业务性功能,但是我不会写PHP,所以我还是用老掉牙的C去写相关模块。
用户注册和登陆这个大家一般都明白。但是什么叫免登,可能有些同学还不清楚。举个例子,比如我们登陆某网站后,我们再在其子页面中跳转,往往还是处于登陆状态。但是服务器如何确定这个用户的登陆状态,除了像长连接等方案外,通过协议约定也是一种方案。我们约定:在用户成功注册和登陆后,会访问给客户端请求一个加密字段。用户之后的请求都需要带上这个加密字段,以供服务器验证。
路径:login
参数:
参数:
因为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/
./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进行加解密。
make clean ./config --prefix=/usr/local/openssl ./config shared --prefix=/usr/local/openssl make depend make install
LoadFile modules/libcjson.so LoadFile modules/libutils.so LoadFile modules/libcrypto.so LoadModule user_check_module modules/mod_user_check.so LoadModule login_module modules/mod_login.so如此,我们就可以在用户注册登录模块——mod_login.so和免登模块——mod_user_check.so中使用这些API了。
<Location /login> SetHandler user_check SetHandler 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。这样内容生成器,才会传导到下一个内容生成器中。
/** * @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的组合)。
1.关闭mysql # service mysqld stop 2.屏蔽权限 # mysqld_safe --skip-grant-table 屏幕出现: Starting demo from ..... 3.新开起一个终端输入 # mysql -u root mysql mysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root'; mysql> FLUSH PRIVILEGES;//记得要这句话,否则如果关闭先前的终端,又会出现原来的错误 mysql> \q
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; }
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; };
#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