首页 > 代码库 > 【Nginx】将磁盘文件作文包体发送
【Nginx】将磁盘文件作文包体发送
接上一篇文章,当Nginx以文件作为用户的请求时,它不会把文件内容读入内存,然后再从内存发送出去,而是会调用sendfile系统调用在不经过用户空间的情况下,从内核直接发送出去。这样做显然效率要更高,Nginx也为我们封装好了一系列的接口,下面就来说明如何发送一个磁盘文件给客户端。
和从内存直接发送数据最大的不同在于ngx_buf_t缓冲区的设置方法,ngx_buf_t结构体的定义如下:
struct ngx_buf_s { .... off_t file_pos; // 文件起始位置 off_t file_last; // 文件结束位置 .... ngx_file_t *file; // 引用的文件 .... };
ngx_file_t表示一个文件:
typedef struct ngx_file_s ngx_file_t; struct ngx_file_s { ngx_fd_t fd; // 文件描述符 ngx_str_t name; // 文件名 ngx_file_info_t info; // 文件相关信息,相当于stat结构体 off_t offset; // 告诉Nginx处理到文件何处了,一般不使用 off_t sys_offset; // 文件偏移量,一般不使用 ngx_log_t *log; // 日志对象 #if (NGX_HAVE_FILE_AIO) ngx_event_aio_t *aio; #endif unsigned valid_info:1; // 未使用 unsigned directio:1; // 发送大文件时设为1 };
发送响应包体之前就需要对上述部分成员进行设置,以确定需要发送的文件和相关信息。
为了防止内存泄漏,HTTP框架需要在发送响应结束之后关闭文件描述符,这是需要定义一个ngx_pool_cleanup_t结构体:
struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; // 执行实际清理工作的回调方法 void *data; // 回调方法的参数 ngx_pool_cleanup_t *next; // 下一个清理对象 };
清理文件句柄的完整代码如下:
// 用于告诉HTTP框架,请求结束时调用cln->handler成员函数 ngx_pool_cleanup_t* cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t)); if (cln == NULL) return NGX_ERROR; cln->handler = ngx_pool_cleanup_file; // ngx_pool_cleanup_file专用于关闭文件句柄 ngx_pool_cleanup_file_t *clnf = cln->data; // cln->data为上述回调函数的参数 clnf->fd = b->file->fd; clnf->name = b->file->name.data; clnf->log = r->pool->log;
handler成员设为了ngx_pool_cleanup_file函数,这是Nginx本身内置的一个函数:
void ngx_pool_cleanup_file(void *data) { ngx_pool_cleanup_file_t *c = data; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d", c->fd); if (ngx_close_file(c->fd) == NGX_FILE_ERROR) { // 关闭文件描述符 ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, ngx_close_file_n " \"%s\" failed", c->name); } }
清理函数主要工作就是关闭文件描述符,它接受的参数为一个ngx_pool_cleanup_file_t指针,该结构体定义如下:
typedef struct { ngx_fd_t fd; // 文件描述符 u_char *name; // 文件名 ngx_log_t *log; // 日志对象 } ngx_pool_cleanup_file_t;
这三个成员和ngx_buf_t.ngx_file_t中的以下三个成员一一对应:
- ngx_buf_t.ngx_file_t.df
- ngx_buf_t.ngx_file_t.name
- ngx_buf_t.ngx_file_t.log
所以我们只需要用ngx_buf_t内的成员对ngx_pool_cleanup_file_t结构体赋值即可。
程序整体框架和从内存发送数据相同,下面是完整的代码:
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r); static ngx_command_t ngx_http_mytest_commands[] = { { ngx_string("mytest"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS, ngx_http_mytest, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_mytest_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_mytest_module = { NGX_MODULE_V1, &ngx_http_mytest_module_ctx, /* module context */ ngx_http_mytest_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; // 首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据 // 结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个 // http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体 clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); // http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果 // 请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们 // 实现的ngx_http_mytest_handler方法处理这个请求 clcf->handler = ngx_http_mytest_handler; return NGX_CONF_OK; } static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) { // 必须是GET或者HEAD方法,否则返回405 Not Allowed if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) return NGX_HTTP_NOT_ALLOWED; // 丢弃请求中的包体 ngx_int_t rc = ngx_http_discard_request_body(r); if (rc != NGX_OK) return rc; ngx_buf_t *b; b = ngx_palloc(r->pool, sizeof(ngx_buf_t)); u_char* filename = (u_char*)"/tmp/test.txt"; // 要打开的文件名 b->in_file = 1; // 设置为1表示缓冲区中发送的是文件 // 分配代表文件的结构体空间,file成员表示缓冲区引用的文件 b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); b->file->fd = ngx_open_file(filename, NGX_FILE_RDONLY | NGX_FILE_NONBLOCK, NGX_FILE_OPEN, 0); b->file->log = r->connection->log; // 日志对象 b->file->name.data = http://www.mamicode.com/filename; // name成员表示文件名称>
根据程序的指示,在/tmp目录内创建test.txt文件,内容如下:
运行结果:
Nginx服务器成功返回了test.txt文件内的内容。
参考:
《深入理解Nginx》 P107-P112.
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。