c apache2模块开发--根据自定义业务逻辑实现文件下载
来源:互联网 发布:js验证用户名是否存在 编辑:程序博客网 时间:2024/05/18 04:00
1.需求概述
最近和公司其他项目平台对接,有这样一个需求:提供一个HTTP Server,从URL中解析出文件ID等信息,然后调用我方项目开发的接口,从我方平台中下载这个文件,根据URL中的参数再对其做一些简单处理,然后再将文件以HTTP方式发送给对方平台。由于只用到一个查询接口,get即可满足,因此不用rest库。且受限于软硬件条件,不用java,需使用c/c++开发。
2.总体思路:
使用apache2搭建http server,然后开发一个模块处理http请求,在该模块中解析URL、调用我方平台接口下载文件、对文件做二次处理、封装http响应报文,将请求返回给客户端。
3.apache2 模块开发
关于apache2的安装,这里不做赘述,请自行百度。
apache2 模块开发步骤,网上资料也比较多,请主要参考 《将 Apache httpd 作为应用开发平台》。
简单的说,就是通过apache2提供的apxs工具,生成一套框架代码、Makefile以及部署脚本,然后基于该框架代码进一步添加自己的业务逻辑:
1) apxs -g -n mymodule 生成模块代码框架(mymodule 是自己的模块名): apache2会生成名为 mod_mymodule的目录,其中包含 mod_mymodule.c以及Makefile等文件;
2) 修改 mod_mymodule.c ,添加自己的业务逻辑(稍后再详细介绍)
3) apxs -i -c -a mod_mymodule.c 会将so文件释放到apache2的lib目录,例如:
/usr/lib64/httpd/modules
4) 修改apache2 配置,加载 mymodule模块,并执行 apachectrl -k restart 重启apache2服务。
LoadModule mymodule_module /usr/lib64/httpd/modules/mod_mymodule.so<Location /mymodule> SetHandler mymodule</Location>
4. 下面主要介绍如何修改自己的模块代码,实现文件下载
4.1 检查参数,解析URL
URL 格式定义如下:
http://192.168.1.100:8088/mymodule?file=/home/test/abc.txt&type=2
/home/test/abc.txt 表示文件路径,这里仅仅是为了演示说明,因此用了本地目录。实际可能需要调用一些接口去获得这个文件;
type=2表示对原始文件如何处理,比如0-表示直接传输给客户端;1-表示将文件压缩后再传输,等等。
4.2、设置HTTP头
百度知,下载文件通常的http响应报文header要包含以下字段。因此需要在代码中设置这些信息,并读取文件数据。
header("Content-type: application/octet-stream"); //高速浏览器传递的是文件流
header("Accept-Length: 2048"); //文件大小
header("Content-Disposition: attachment; filename=abc.txt"); //指定文件名
4.3、 获取文件:
header("Accept-Length: 2048"); //文件大小
header("Content-Disposition: attachment; filename=abc.txt"); //指定文件名
对应代码修改如下:
static int mymodule_handler(request_rec *r){ if (strcmp(r->handler, "mymodule")) { return DECLINED; }/* r->content_type = "text/html"; */ /*这是apxs模板生成的代码 */ r->content_type = "application/octet-stream"; /*设置Content-type*/ /*request_rec 结构中没有定义与Content-Disposition直接对应的字段,但header_out包含了所有response的header信息,我们可以手动把这个字段add进来(注意:不能用apr_table_set,会把其他header信息覆盖掉)*/ apr_table_add(r->headers_out,"Content-Disposition","attachment;filename=abc.txt"); …… /* 获取(本地)文件长度 */ apr_finfo_t info; apr_stat(&info, r->filename, APR_FINFO_SIZE, r->pool); len = (apr_size_t)info.size; char file_len[64]; memset(file_len, 0, sizeof(file_len)); snprintf(file_len, sizeof(file_len)-1, "%d", (int)info.size); apr_table_add(r->headers_out,"Content-Length", file_len);
4.3、 获取文件:
本人所在用项目中,主要是调用自己项目里的一些接口将文件从远程下载到本地内存,因过程比较简单且不具有通用性,不再赘述。假设两种比较典型的情况:
4.3.1 文件在磁盘(文件系统中),调用apache接口直接读取、发送文件
通过
apr_file_open 打开文件,
ap_send_fd
发送文件,apr_file_close
关闭文件。需要主要send调用应该是一个循环,代码比较简单:/* call apr_file_open,ap_send_fd to open and send file from local file system */apr_file_t *f = NULL;apr_status_t rv;apr_off_t offset = 0;apr_size_t bytes = 0;apr_size_t len = 0;rv = apr_file_open( &f, file_path, APR_READ | APR_SENDFILE_ENABLED, APR_OS_DEFAULT, r->pool );if( NULL == f ){ap_log_error( APLOG_MARK, APLOG_ERR, 0, r->server, "file(%s) permissions deny server access", file_path );return -1;}if( !r->header_only ){while( offset < len ){ /*ap_flush_conn(r->connection);*/ap_send_fd( f, r, offset, len, &bytes );offset += bytes;}}apr_file_close( f );
4.3.2 文件信息内存中
以本人实际遇到的项目为例,收到http请求后,会调用自己的sdk接口,远程下载文件数据,为了提高效率,所以文件肯定是先到内存,再落文件。为了提高效率,可以不落文件,当数据还在内存中的时候就直接返回。
这时需要用到的是
ap_rwrite
接口。 为了演示,这里自己调用fopen,fread 打开本地文件并读取数据到内存,然后调用 ap_write 将其发送出去。
/* suppose that we have already downloaded files from other platform, and all the file datas are in the memory. * so just return the memory data to client */ FILE* fp = fopen(file_path, "r"); if ( NULL == fp ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server,"failed to open file %s", file_path); return -1; } int read_ret = 0; char read_buf[FILE_BUF_SIZE];while( !feof( fp ) ){memset( read_buf, 0, sizeof( read_buf ) );read_ret = fread( read_buf, 1, 1024, fp );if( ferror( fp ) ){/* todo log error */return -1;}/*send data to client*/int send_bytes = 0;while( send_bytes < read_ret ){/*ap_flush_conn(r->connection);*/int send_ret = ap_rwrite( read_buf, read_ret - send_bytes, r );if( send_ret >= 0 ) {send_bytes += send_ret;} else {/* todo log error */return -1;}}}fclose(fp);
4.3.3 其他发送接口
如apxs框架生成的代码中用到的ap_rputs,可以在http响应报文中设置一个字符串。 类似这些交口比较简单,可以直接查看头文件中的定义。
附件: 完整的示例代码如下
/* ** mod_helloworld.c -- Apache sample helloworld module** [Autogenerated via ``apxs -n helloworld -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_helloworld.c**** Then activate it in Apache's httpd.conf file for instance** for the URL /helloworld in as follows:**** # httpd.conf** LoadModule helloworld_module modules/mod_helloworld.so** <Location /helloworld>** SetHandler helloworld** </Location>**** Then after restarting Apache via**** $ apachectl restart**** you immediately can request the URL /helloworld and watch for the** output of this module. This can be achieved for instance via:**** $ lynx -mime_header http://localhost/helloworld **** 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_helloworld.c*/ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" /*#include "http_connection.h"*/#include "ap_config.h" #include "ap_regex.h" #include "http_log.h" #include <stdio.h>#define MAX_PATH_LEN 256#define MAX_FILE_LEN_DIGITS 64#define FILE_BUF_SIZE 1024/*#define RETURN_FROM_MEMORY*//* 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 helloworld_handler(request_rec *r){ if (strcmp(r->handler, "helloworld")) { 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://172.25.3.121:8088/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; } /* parse file name from uri param */ char file_path[MAX_PATH_LEN]; memset(file_path, 0, sizeof(file_path)); int file_type=0; int ret = sscanf(r->parsed_uri.query, "file=%[^&]&type=%d", file_path, &file_type); if ( ret != 2 ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server, "failed to parse file path and type from uri:%s,ret:%d", r->parsed_uri.query, ret); 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); /* Content-Length:xxxx */ char file_len[MAX_FILE_LEN_DIGITS]; memset(file_len, 0, sizeof(file_len)); int file_length = get_file_length(file_path, r); snprintf(file_len, sizeof(file_len)-1, "%d", file_length); apr_table_add(r->headers_out,"Content-Length", file_len);#ifdef RETURN_FROM_MEMORY /* suppose that we have already downloaded files from other platform, and all the file datas are in the memory. * so just return the memory data to client */ FILE* fp = fopen(file_path, "r"); if ( NULL == fp ) { ap_log_error(APLOG_MARK, APLOG_ERR, 0,r->server,"failed to open file %s", file_path); return -1; } int read_ret = 0; char read_buf[FILE_BUF_SIZE]; while( !feof( fp ) ){ memset( read_buf, 0, sizeof( read_buf ) ); read_ret = fread( read_buf, 1, 1024, fp ); if( ferror( fp ) ){ /* todo log error */ return -1; } /*send data to client*/ int send_bytes = 0; while( send_bytes < read_ret ){ /*ap_flush_conn(r->connection);*/ int send_ret = ap_rwrite( read_buf, read_ret - send_bytes, r ); if( send_ret >= 0 ) { send_bytes += send_ret; } else { /* todo log error */ return -1; } } } fclose(fp);#else /* call apr_file_open,ap_send_fd to open and send file from local file system */ apr_file_t *f = NULL; apr_status_t rv; apr_off_t offset = 0; apr_size_t bytes = 0; apr_size_t len = file_length; rv = apr_file_open( &f, file_path, APR_READ | APR_SENDFILE_ENABLED, APR_OS_DEFAULT, r->pool ); if( NULL == f ){ ap_log_error( APLOG_MARK, APLOG_ERR, 0, r->server, "file(%s) permissions deny server access", file_path ); return -1; } if( !r->header_only ){ while( offset < len ){ ap_flush_conn(r->connection); ap_send_fd( f, r, offset, len, &bytes ); offset += bytes; } } apr_file_close( f );#endif return OK;}static void helloworld_register_hooks(apr_pool_t *p){ ap_hook_handler(helloworld_handler, NULL, NULL, APR_HOOK_MIDDLE);}/* Dispatch list for API hooks */module AP_MODULE_DECLARE_DATA helloworld_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 */ helloworld_register_hooks /* register hooks */};
0 0
- c apache2模块开发--根据自定义业务逻辑实现文件下载
- apache2 开发C++模块 —— 基于cximage实现图片、缩略图下载功能
- LINQ : 如何为LINQ TO SQL实现自定义业务逻辑
- 开发业务逻辑
- 用C语言开发一个BT下载软件 (四) ------ 代码实现-1-种子文件解析模块
- Android新闻客户端开发2--主界面业务逻辑实现
- APP开发实战12-业务逻辑的实现
- SSH物流开发系统设计:业务受理逻辑实现
- 根据业务逻辑线做修改
- 软件开发之业务逻辑
- 自定义ClassLoader实现java应用核心逻辑模块热部署
- JDBC的业务逻辑流程和模块开发的原理分析
- 业务逻辑实现方式选择
- apache2 服务器模块文件配置
- Apache2.0模块开发基础
- apache2.4模块开发学习
- 软件行业 业务 模块 业务逻辑的理解
- js实现文件下载自定义下载路径
- Matlab GUI设计——文件读取和保存uigetfile,uiputfile
- hdu5073 Galaxy(暴力)
- 二叉树遍历
- 使用insert和select选项,可以把数据从一张表复制到另外一张表
- swif通过代码生成子视图
- c apache2模块开发--根据自定义业务逻辑实现文件下载
- uva 1636 概率
- access 删除字段为空的值
- hdu2079选课时间(动态规划&&母函数)
- tarjan算法入门整理专题(判断是否是一个强连通、通过缩点求至少加几条边让整个图变成强连通和传递的最小费用)
- 代码实现Lable 、textField创建界面以及键盘的处理
- UI_数据处理(model)
- POJ 1001 Exponentiation(java+处理字符串)
- ubuntu下Android开发环境的搭建:eclipse+SDK详细安装教程+常见问题及其解决方案