1.需求概述
最近和公司其他项目平台对接,有这样一个需求:提供一个HTTP Server,从URL中解析出文件ID等信息,然后调用我方项目开发的接口,从我方平台中下载这个文件,根据URL中的参数再对其做一些简单处理,然后再将文件以HTTP方式发送给对方平台。由于只用到一个查询接口,get即可满足,因此不用rest库。且受限于软硬件条件,不用java,需使用c/c++开发。
LoadModule mymodule_module /usr/lib64/httpd/modules/mod_mymodule.so
SetHandler mymodule
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);
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 );
ap_rwrite
接口。
/* 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);
/*
** 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
**
** SetHandler helloworld
**
**
** 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
#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 */
};