【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 = filename;      // name成员表示文件名称
    b->file->name.len = sizeof(filename) - 1;
    if (b->file->fd <= 0)
        return NGX_HTTP_NOT_FOUND;

    r->allow_ranges = 1;    //支持断点续传

    // 获取文件长度,ngx_file_info方法封装了stat系统调用
    // info成员就表示stat结构体
    if (ngx_file_info(filename, &b->file->info) == NGX_FILE_ERROR)
        return NGX_HTTP_INTERNAL_SERVER_ERROR;

    // 设置缓冲区指向的文件块
    b->file_pos = 0;                        // 文件起始位置
    b->file_last = b->file->info.st_size;   // 文件结束为止

    // 用于告诉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;

    // 设置返回的Content-Type
    // 注意,ngx_str_t有一个很方便的初始化宏
    // ngx_string,它可以把ngx_str_t的data和len成员都设置好
    ngx_str_t type = ngx_string("text/plain");

    //设置返回状态码
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->file->info.st_size;    // 正文长度
    r->headers_out.content_type = type;

    // 发送http头部
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
        return rc;

    // 构造发送时的ngx_chain_t结构体
    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;

    //最后一步发送包体,http框架会调用ngx_http_finalize_request方法
    return ngx_http_output_filter(r, &out);
}

根据程序的指示,在/tmp目录内创建test.txt文件,内容如下:

运行结果:

Nginx服务器成功返回了test.txt文件内的内容。

参考:

《深入理解Nginx》 P107-P112.

【Nginx】将磁盘文件作文包体发送

时间: 2024-09-29 01:37:32

【Nginx】将磁盘文件作文包体发送的相关文章

nginx丢弃http包体处理

http框架丢弃http请求包体和上一篇文章http框架接收包体, 都是由http框架提供的两个方法,供http各个模块调用,从而决定对包体做什么处理.是选择丢弃还是接收,都是由模块决定的.例如静态资源模块,如果接收到来自浏览器的get请求,请求某个文件时,则直接返回这个文件内容给浏览器就可以了.没有必要再接收包体数据,get请求实际上也不会有包体.因此静态资源模块将调用http框架提供的丢弃包体函数进行丢包处理. 相比接收包体过程, 丢弃包体操作就简单很多了,至少不需要把包体存放到http结构

【Nginx】磁盘文件写入飞地发

文章继续.什么时候Nginx当用户请求一个文件,这将无法读取该文件的内容加载到内存,然后从内存发送,但电话sendfile况下,从内核直接发送出去.这样做显然效率要更高.Nginx也为我们封装好了一系列的接口.以下就来说明怎样发送一个磁盘文件给client. 和从内存直接发送数据最大的不同在于ngx_buf_t缓冲区的设置方法.ngx_buf_t结构体的定义例如以下: struct ngx_buf_s { .... off_t file_pos; // 文件起始位置 off_t file_las

Nginx学习之三-ngx_http_request_t结构体

ngx_http_request_s是nginx中非常重要的一个结构体,贯穿于htpp请求处理的整个过程中. 下面解释了ngx_http_request_s结构体中与HTTP框架相关的重要的成员变量. [cpp] view plaincopyprint? struct ngx_http_request_s { uint32_t                          signature;         /* "HTTP" */ //请求对应的客户端连接 ngx_connec

Linux网络 - 数据包的发送过程【转】

转自:https://segmentfault.com/a/1190000008926093 继上一篇介绍了数据包的接收过程后,本文将介绍在Linux系统中,数据包是如何一步一步从应用程序到网卡并最终发送出去的. 如果英文没有问题,强烈建议阅读后面参考里的文章,里面介绍的更详细. 本文只讨论以太网的物理网卡,并且以一个UDP包的发送过程作为示例,由于本人对协议栈的代码不熟,有些地方可能理解有误,欢迎指正 socket层 +-------------+ | Application | +-----

[转]Linux网络 - 数据包的发送过程

转, 原文:https://segmentfault.com/a/1190000008926093 -------------------------------------------------------------------------------------------------------------------- 继上一篇介绍了数据包的接收过程后,本文将介绍在Linux系统中,数据包是如何一步一步从应用程序到网卡并最终发送出去的. 如果英文没有问题,强烈建议阅读后面参考里的文章

自定义协议封装包头、包体

底层通信消息类,定义消息ID.消息体,和初始化 1 using System; 2 3 /// <summary> 4 /// 底层通信消息 5 /// </summary> 6 public class TSocketMessage : IDisposable 7 { 8 /// <summary> 9 /// 消息ID 10 /// </summary> 11 public int MsgID; 12 /// <summary> 13 ///

在Java语言中调用存储过程、存储函数、包头、包体

需要拷贝连接Oracle的jar包,路径如下图所示: 连接Oracle数据库的代码: package demo.utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JDBCUtils { private static Stri

PL/SQL 编程(三 )程序包和包体,触发器,视图,索引

一.程序包和包体 程序包(package):存储在数据库中的一组子程序.变量定义.在包中的子程序可以被其它程序包或子程序调用.但如果声明的是局部子程序,则只能在定义该局部子程序的块中调用该局部子程序. 它具有面向对象程序设计语言的特点,是对这些PL/SQL 程序设计元素的封装.包类似于JAVA语言中的类,其中变量相当于类中的成员变量,过程和函数相当于类方法. create or replace package stuinfo as type stucur is ref cursor; proce

解决编译nginx模块与rpm包安装的nginx不兼容问题

环境:centos  nginx-10.0.1 现象: 自己开发一个nginx模块,放到rpm包安装的nginx服务器上启动服务时报错如下: 30490#0: module "/usr/lib64/nginx/modules/ndk_http_module.so" is not binary compatible in /usr/share/nginx/modules/route.conf:1 解决方法: 查看nginx -V 所编译使用的选项 重新编译nginx源码生成.so模块 网