apache2 开发C++模块 —— 基于cximage实现图片、缩略图下载功能
来源:互联网 发布:excle怎么做数据分析 编辑:程序博客网 时间:2024/04/30 11:15
之前的博客中有提到基于apache2的模块开发,提供了下载文件的demo。但后来发现不仅仅是文件(准确说是图片)下载,还需要根据相关参数,提供图片缩略图下载、裁剪图下载或原图下载等功能。
一、编译问题:
种种原因,选用cximage库来提供图片处理功能。但坑爹的是这个库是C++的,而apache是c的,apxs怎么才能编译C++代码呢? 当然可以把cximage封装一下,提供一个C接口的库。但本人比较懒,还是希望直接将cximage编译到apache2模块中。主要解决步骤如下:
1、 模块代码中包含cximage的头文件、直接定义cximage类对象,以C++方式调用相关接口。
2、 代码编写完成后通过apxs编译。
#apxs编译代码的命令中需要指定c++头文件,并通过-S参数将gcc替换成g++/usr/local/apache2/bin/apxs -L /usr/include/c++/ -L /usr/include/c++/3.4.6/x86_64-redhat-linux/ -i -c -a -S CC=g++ mod_api.c
libCxImage.a(ximatran.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC/home/apache-module/api/libCxImage.a: could not read symbols: Bad value
这应该不是一个普遍问题,主要是编译cximage时没有指定-fPIC,添加-fPIC选项重新编译cximage库,就可以了。
4、编译成功后,修改httpd.conf,加载mod_api.so,并设置handler.
实际上通过apxs编译的时候,应该会自动添加LoadModule的配置,但是 SetHandler还是需要自己手动加的。
<Location /api> SetHandler api </Location>
httpd: Syntax error on line 56 of /usr/local/apache2/conf/httpd.conf: Cannot load /usr/local/apache2/modules/mod_api.so into server: /usr/local/apache2/modules/mod_api.so: undefined symbol: jpeg_resync_to_restart
解决方法:应该是编译mod_api.so是缺少cximage相关的.a文件。用find cximage -name *.a 找出所有的.a文件,然后逐个添加到编译选项中测试,最终确定需要以下几个.a(顺序上被依赖的库放在后面,需要依赖别人的库放在前面):
-lCxImage -ljasper -lpng -ltiff -lzlib -ljpeg
所以最终的编译命令如下:
/usr/local/apache2/bin/apxs -L /usr/include/c++/ -L /usr/include/c++/3.4.6/x86_64-redhat-linux/ -i -c -a -S CC=g++ mod_api.c -L./lib -lCxImage -ljasper -lpng -ltiff -lzlib -ljpeg
PS:
另外网上还有说需要在httpd.conf的LoadModule之前加上一句LoadFile,已加载C++模块:
LoadFile /usr/lib64/libstdc++.so.6LoadModule api_module modules/mod_api.so但实际测试了一下,不加这句似乎也没有什么问题。
二、模块代码编写
概括的说,主要有三类需求:首先从调用我们内部的SDK从其他内部平台下载图片原始文件,然后根据参数
1) 返回原图;2)返回缩略图;3)裁剪指定范围的图片
这里忽略了调用sdk的步骤,假设图片就在服务器本地,且直接在url路径中表示本地路径,设计的url规则如下:
url参数定义: 以下参数中 file、mode 是必须参数,其他参数是否需要视mode取值而定。 格式为: file=xxx&mode=xxx&ratio=xxx 参数组合的顺序可以任意。file 文件路径和名称mode 图片下载模式,有以下三种取值:normal 原图下载,忽略除file、mode之外的其他参数;thumbnail 缩略图,还需要提供压缩比例: ratio,此时忽略图片裁剪相关的参数;crop 图片裁剪,还需提供参裁剪范围参数: left,top,right,bottom, 此时忽略缩略图相关参数ratio 图片压缩比例left 图片裁剪区域左上角x坐标top 图片裁剪区域左上角y坐标right 图片裁剪区域右下角x坐标bottom 图片裁剪区域右下角y坐标
原图
http://192.168.1.100:8080/api?file=/home/tmp/test-img.jpeg&mode=normal缩略图http://192.168.1.100:8080/api?file=/home/tmp/test-img.jpeg&mode=thumbnail&ratio=2图片裁剪http://192.168.1.100:8080/api?file=/home/tmp/test-img.jpeg&mode=crop&left=50&top=80&right=300&bottom=600
2.1 参数解析
参考apache2.4官方文档 ,可以使用 ap_args_to_table 和 apr_table_get 解析参数,前者将query中的参数都解析到一个列表中,后者从该列表中查找指定的参数。
示例代码:
/* parse args from uri */ apr_table_t *GET; apr_array_header_t *POST; ap_args_to_table(r, &GET); ap_parse_form_data(r, NULL, &POST, -1, MAX_ARGS_BUF); /*http://192.168.1.100:8080/api?file=/home/tmp/test-img.jpeg&mode=crop&left=50&top=80&right=300&bottom=600*/ const char* file_path = apr_table_get(GET, "file"); const char* mode = apr_table_get(GET, "mode");
另外,如果url中还有子目录,例如 192.168.1.100:8080/api/downloads?XXXXXX ,那么downloads 字段会体现在request_rec结构的uri和path_info中:(以下是gdb调试时打印出来的request_rec结构体部分字段)
unparsed_uri = 0x8ed008 "/api/downloads?file=abc.txt&type=2", uri = 0x8ed028 "/api/downloads", filename = 0x8e38a8 "/usr/local/apache2/htdocs/api", canonical_filename = 0x8e38a8 "/usr/local/apache2/htdocs/api", path_info = 0x8e37ec "downloads", args = 0x8ed038 "file=/home/abc.txt&type=2",
PS:如果用的是apache 2.2 的版本,那么是没有这两个函数的。可以参照2.4的代码,将这两个函数以及其依赖的代码移植到2.2中。
2.2 图片处理:缩略图提取或裁剪
由于这部分是比较定制化的需求,与apache2没有必然关系,这里仅为了逻辑的完整性贴出调用cximage处理图片的示例代码,不做过多解释。
CxImage src_img;CxImage dest_img;long img_size = 0;BYTE* img_buf = NULL; if ( 0 == strcmp("normal", mode)) { // 原图下载img_buf = (BYTE *)read_buf;img_size = len; }else if ( 0 == strcmp("thumbnail", mode) ) { // 缩略图下载 /* parse resample rate from uri */ const char* ratio_str = apr_table_get(GET, "ratio");if ( NULL == ratio_str ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to parse resample ratio from uri query : %s", query); return HTTP_BAD_REQUEST;} int ratio = atoi(ratio_str);if ( 0 == ratio ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "invalid resample rate : %s", ratio_str); return HTTP_BAD_REQUEST;}if ( !src_img.Decode((BYTE *)read_buf, sizeof(read_buf), CXIMAGE_FORMAT_JPG) ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to decode picture");return HTTP_INTERNAL_SERVER_ERROR;}if (src_img.Resample(src_img.GetWidth()/ratio,src_img.GetHeight()/ratio, 1, &dest_img)) {dest_img.Encode( img_buf, img_size, CXIMAGE_FORMAT_JPG );} else {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to resample picture : %s, ratio : %d", file_path, ratio);return HTTP_INTERNAL_SERVER_ERROR;} }else if ( 0 == strcmp("crop", mode) ) { // 图片裁剪src_img.Decode((BYTE *)read_buf, sizeof(read_buf) ,CXIMAGE_FORMAT_JPG); const char* left_str = apr_table_get(GET, "left"); const char* top_str = apr_table_get(GET, "top"); const char* right_str = apr_table_get(GET, "right"); const char* bottom_str = apr_table_get(GET, "bottom"); if (NULL == left_str || NULL == top_str || NULL == right_str || NULL == bottom_str ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to parse crop region from uri query : %s", query); return HTTP_BAD_REQUEST; }int left = atoi(left_str);int top = atoi(top_str);int right = atoi(right_str);int bottom = atoi(bottom_str);if (left < 0 || top < 0 || right < 0 || bottom < 0) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "invalid crop region: (%d, %d), (%d, %d)", left, top, right, bottom); return HTTP_BAD_REQUEST;}if ( !src_img.Decode((BYTE *)read_buf, sizeof(read_buf), CXIMAGE_FORMAT_JPG) ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to decode picture");return HTTP_INTERNAL_SERVER_ERROR;}if (src_img.Crop( left, top, right, bottom, &dest_img )) {dest_img.Encode( img_buf, img_size, CXIMAGE_FORMAT_JPG );}else {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to crop picture : %s, (%s, %s), (%s, %s)", file_path, left_str, top_str, right_str, bottom_str);return HTTP_INTERNAL_SERVER_ERROR;} }
2.3 下载:将图片数据发送给客户端
这一部分与上一篇《c apache2模块开发--根据自定义业务逻辑实现文件下载》中相同:
调用 ap_rwrite 接口循环发送数据。 稍微有点区别,我们改成在发送数据之前才设置Content-Length,因为结合前面的逻辑,数据的长度只有待缩略图/剪切图生成后才能最终确定。其他的就没什么变化了。
/* we can get file length only after converted picture format */ /* Content-Length:xxxx */ char file_len_str[MAX_FILE_LEN_DIGITS]; memset(file_len_str, 0, sizeof(file_len_str)); snprintf(file_len_str, sizeof(file_len_str)-1, "%d", img_size); apr_table_add(r->headers_out,"Content-Length", file_len_str);int send_bytes = 0;while( send_bytes < img_size ){/*ap_flush_conn(r->connection);*/int send_ret = ap_rwrite( img_buf, img_size - send_bytes, r );if( send_ret >= 0 ) {send_bytes += send_ret;} else {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to send buffer to client");return HTTP_INTERNAL_SERVER_ERROR;}}
完整示例代码如下:
/* ** mod_api.c -- Apache sample api module** [Autogenerated via ``apxs -n api -g'']**** To play with this sample module first compile it into a** DSO file and install it into Apache's modules directory ** by running:**** $ apxs -c -i mod_api.c**** Then activate it in Apache's httpd.conf file for instance** for the URL /api in as follows:**** # httpd.conf** LoadModule api_module modules/mod_api.so** <Location /api>** SetHandler api** </Location>**** Then after restarting Apache via**** $ apachectl restart**** you immediately can request the URL /api and watch for the** output of this module. This can be achieved for instance via:**** $ lynx -mime_header http://localhost/api **** The output should be similar to the following one:**** HTTP/1.1 200 OK** Date: Tue, 31 Mar 1998 14:42:22 GMT** Server: Apache/1.3.4 (Unix)** Connection: close** Content-Type: text/html** ** The sample page from mod_api.c*/ #include "httpd.h"#include "http_config.h"#include "http_protocol.h"#include "ap_config.h"/*#include "http_connection.h"*/#include "ap_regex.h"#include "http_log.h"#include "util_script.h"#include "ximage.h"#include <stdio.h>#define MAX_PATH_LEN 256#define MAX_FILE_LEN_DIGITS 64#define FILE_BUF_SIZE 1024#define MAX_ARGS_BUF 8192/* get file name from the abolute path * eg: input /home/downloads/test.so * output test.so */const char* get_file_name(const char* path){ if (NULL == path) { return NULL; } int path_len = strlen(path); const char *pos = path + path_len; while (*pos != '/' && pos != path) { pos--; } if (pos == path) { return path+1; }else { int len = len - (pos - path); return (pos + 1); }}int get_file_length(const char* file_path, request_rec *r){ int len = 0; apr_finfo_t info; apr_stat(&info, file_path, APR_FINFO_SIZE, r->pool); len = (apr_size_t)info.size; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,r->server, "file :%s, len:%d", file_path, len); return len;}/* The sample content handler */static int api_handler(request_rec *r){ if (strcmp(r->handler, "api")) { return DECLINED; } /* only support GET or POST request */ if ((r->method_number != M_GET) && (r->method_number != M_POST)) { return HTTP_METHOD_NOT_ALLOWED; } /* full url : http://192.168.1.100:8080/helloworld?file=/home/test.txt&type=2*/ /* r->parsed_uri.query : file=/home/test.txt&type=2 */ if ( NULL == r->parsed_uri.query ){ ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server,"uri param is empty"); return HTTP_BAD_REQUEST; }const char* query = r->parsed_uri.query; /* parse args from uri */ apr_table_t *GET; apr_array_header_t *POST; ap_args_to_table(r, &GET); ap_parse_form_data(r, NULL, &POST, -1, MAX_ARGS_BUF); /* http://192.168.1.100:8080/api?file=/home/tmp/test-img.jpeg&type=1&left=0&top=0&right=100&bottom=100&resizerate=3 */ //const char* bucket = apr_table_get(GET, "bucket"); const char* file_path = apr_table_get(GET, "file"); const char* mode = apr_table_get(GET, "mode"); /* file,mode should not be empty */ if ( NULL == file_path || NULL == mode ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to parse file/mode from uri query: %s", query); return HTTP_BAD_REQUEST; } /* set response headers */ /* Content-Type:application/octet-stream */ r->content_type = "application/octet-stream"; /* "text/html" */ /* Content-Disposition:attachment;filename=test.txt */ char file_name[24 + (MAX_PATH_LEN)] = {0}; /* length of "attachment;filename=" is 20 */ sprintf(file_name, "attachment;filename=%s", get_file_name(file_path)); apr_table_add(r->headers_out,"Content-Disposition", file_name);/* we can get file length only after converted picture format */ /* Content-Length:xxxx *//* TODO: call our SDK to get picture */ /* load picture from file to memory *//* suppose that we have already downloaded files from other platform, and all the files are in the memory. * so just return the memory data to client */ FILE* fp = fopen(file_path, "rb"); if ( NULL == fp ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server,"failed to open file %s", file_path); return HTTP_NON_AUTHORITATIVE; } int read_ret = 0;int len = get_file_length(file_path, r); char read_buf[len];//while( !feof( fp ) ){memset( read_buf, 0, sizeof( read_buf ) );read_ret = fread( read_buf, 1, len, fp );if( ferror( fp ) ){/* todo log error */fclose(fp);return HTTP_INTERNAL_SERVER_ERROR;}//}fclose(fp); // for testCxImage src_img;CxImage dest_img;long img_size = 0;BYTE* img_buf = NULL; if ( 0 == strcmp("normal", mode)) { // 原图下载img_buf = (BYTE *)read_buf;img_size = len; }else if ( 0 == strcmp("thumbnail", mode) ) { // 缩略图下载 /* parse resample rate from uri */ const char* ratio_str = apr_table_get(GET, "ratio");if ( NULL == ratio_str ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to parse resample ratio from uri query : %s", query); return HTTP_BAD_REQUEST;} int ratio = atoi(ratio_str);if ( 0 == ratio ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "invalid resample rate : %s", ratio_str); return HTTP_BAD_REQUEST;}if ( !src_img.Decode((BYTE *)read_buf, sizeof(read_buf), CXIMAGE_FORMAT_JPG) ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to decode picture");return HTTP_INTERNAL_SERVER_ERROR;}if (src_img.Resample(src_img.GetWidth()/ratio,src_img.GetHeight()/ratio, 1, &dest_img)) {dest_img.Encode( img_buf, img_size, CXIMAGE_FORMAT_JPG );} else {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to resample picture : %s, ratio : %d", file_path, ratio);// todo :失败的情况下是否需要调用 dest_img.FreeMemory(img_buf);return HTTP_INTERNAL_SERVER_ERROR;} }else if ( 0 == strcmp("crop", mode) ) { // 图片裁剪src_img.Decode((BYTE *)read_buf, sizeof(read_buf) ,CXIMAGE_FORMAT_JPG); const char* left_str = apr_table_get(GET, "left"); const char* top_str = apr_table_get(GET, "top"); const char* right_str = apr_table_get(GET, "right"); const char* bottom_str = apr_table_get(GET, "bottom"); if (NULL == left_str || NULL == top_str || NULL == right_str || NULL == bottom_str ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to parse crop region from uri query : %s", query); return HTTP_BAD_REQUEST; }int left = atoi(left_str);int top = atoi(top_str);int right = atoi(right_str);int bottom = atoi(bottom_str);if (left < 0 || top < 0 || right < 0 || bottom < 0) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "invalid crop region: (%d, %d), (%d, %d)", left, top, right, bottom); return HTTP_BAD_REQUEST;}if ( !src_img.Decode((BYTE *)read_buf, sizeof(read_buf), CXIMAGE_FORMAT_JPG) ) {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to decode picture");return HTTP_INTERNAL_SERVER_ERROR;}if (src_img.Crop( left, top, right, bottom, &dest_img )) {dest_img.Encode( img_buf, img_size, CXIMAGE_FORMAT_JPG );}else {// todo :失败的情况下是否需要调用 dest_img.FreeMemory(img_buf);ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to crop picture : %s, (%s, %s), (%s, %s)", file_path, left_str, top_str, right_str, bottom_str);return HTTP_INTERNAL_SERVER_ERROR;} } /* we can get file length only after converted picture format */ /* Content-Length:xxxx */ char file_len_str[MAX_FILE_LEN_DIGITS]; memset(file_len_str, 0, sizeof(file_len_str)); snprintf(file_len_str, sizeof(file_len_str)-1, "%d", img_size); apr_table_add(r->headers_out,"Content-Length", file_len_str);int send_bytes = 0;while( send_bytes < img_size ){/*ap_flush_conn(r->connection);*/int send_ret = ap_rwrite( img_buf, img_size - send_bytes, r );if( send_ret >= 0 ) {send_bytes += send_ret;} else {ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to send buffer to client");return HTTP_INTERNAL_SERVER_ERROR;}}if (img_buf != (BYTE*)read_buf) { /* (img_buf == read_buf) means download the original picture, and haven't call dest_img.Encode() */dest_img.FreeMemory(img_buf);img_buf = NULL;} return OK;}static void api_register_hooks(apr_pool_t *p){ ap_hook_handler(api_handler, NULL, NULL, APR_HOOK_MIDDLE);}/* Dispatch list for API hooks */module AP_MODULE_DECLARE_DATA api_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ api_register_hooks /* register hooks */};
- apache2 开发C++模块 —— 基于cximage实现图片、缩略图下载功能
- VC++基于CXImage库实现缩略图
- VC++基于CXImage库实现缩略图
- c apache2模块开发--根据自定义业务逻辑实现文件下载
- cximage缩略图
- 基于fileInput实现下载功能
- 基于MT7688模块的开发笔记12——给MT7688开发板添加WiFi功能
- Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能
- Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能
- Android开发——下载功能的逻辑和实现
- 利用PHP结合GD实现图片裁剪和缩略图功能
- js 实现前端图片上传,展示缩略图功能
- FL2440——基于安信可A7模块编程实现GPS定位功能
- 下载图片并产生缩略图
- CxImage下载
- 基于MT7688模块的开发笔记13——给MT7688开发板添加tftp等功能
- 基于RandomAccessFile实现断点文件下载功能
- Apache2.0模块开发基础
- java设计模式之单例模式
- iOS开发学习之C语言---C08 高级指针-1
- 找两条单链表的公共结点
- Altium Designer 对覆铜的切割和挖孔
- iOS开发学习之C语言---C08 高级指针-12
- apache2 开发C++模块 —— 基于cximage实现图片、缩略图下载功能
- pdg转pdf的正确方法!!
- iOS开发学习之C语言---C09 动态内存分配
- 关于字符串(1)
- 敏捷实况1:项目概况
- java中的匿名内部类总结
- iOS开发学习之C语言---C10 函数指针-1
- Android 怎样把底部栏顶上去
- 【转】java基础:堆&栈