Nginx实战之autoindex模块源码解析

1、前言

之前在这篇文章《CentOS 7使用源码编译安装Nginx,以及配置使用autoindex模块》中有提到过如何使用autoindex模块来配置一个网页版的文件服务器,本文将对其源码进行分析,看看里面的代码逻辑是什么样的。

2、查看网页显示的html文件的内容

配置完autoindex服务后,启动Nginx,打开浏览器访问autoindex服务的url,如图所示:

在这里插入图片描述
点击鼠标右键选择查看网页源代码,可得到如下所示的源代码:

<html>
<head><title>Index of /autoindex/</title></head>
<body>
<h1>Index of /autoindex/</h1><hr><pre><a href="../">../</a>
<a href="bifang/">bifang/</a>                                            31-May-2021 10:55       -
<a href="cppjieba/">cppjieba/</a>                                          31-May-2021 09:45       -
<a href="doxygen/">doxygen/</a>                                           27-May-2021 16:34       -
<a href="gude/">gude/</a>                                              08-Jun-2021 20:17       -
<a href="sql_insert/">sql_insert/</a>                                        08-Jun-2021 20:17       -
</pre><hr></body>
</html>

从上图中可以看出名称后面的日期是自动对齐的,但是从html文件中可以看到并没有什么对齐的语法,所以可以判定是程序中用补空格的方式去作对齐的,目录跳转的话也仅仅只是是用了href而已,没有什么特别的东西,接下来就直接通过源码来进行分析。

3、ngx_http_autoindex_module.c文件解析

ngx_http_autoindex_module.c文件位于源代码的src/http/modules目录下

3.1、ngx_http_autoindex_module结构体解析

这个结构体一眼看去就有一种很熟悉的感觉,哦,原来是这篇文章《Nginx编译配置脚本篇(8)- 模块配置脚本auto/modules》中曾经讲到的内容,Nginx是通过ngx_modules.c文件中的ngx_modules数组来统一管理各个模块的,换句话说你的模块要想生效就必须出现在这个数组里,不然Nginx也不知道去哪里载入你的模块。Nginx就是通过这样的方式使得新增模块的难度大大地降低了,有不熟悉的小伙伴可以回去看看我之前的几篇文章。我们可以去看一下编译后的ngx_modules数组中也确实有ngx_http_autoindex_module

接下来稍微看一下ngx_http_autoindex_module结构体里面都有啥:

  • NGX_MODULE_V1:这是一个宏,里面放着一些结构体的信息,这里不分析这个,在以后的文章中再对其进行说明
  • ngx_http_autoindex_module_ctx:从注释中可以看出这个是当前模块的上下文信息,下一小节再进行分析
  • ngx_http_autoindex_commands:从注释中可以看出这个是模块的指令,后一小节再进行分析
  • NGX_HTTP_MODULE:这个是模块的类型,这里指的是HTTP的模块
  • NGX_MODULE_V1_PADDING:这个是一个宏,也是一些结构体的信息,这里先不管他是干嘛用的,以后的文章再讲

源代码如下:

ngx_module_t  ngx_http_autoindex_module = {
    NGX_MODULE_V1,
    &ngx_http_autoindex_module_ctx,        /* module context */
    ngx_http_autoindex_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
};

3.2、ngx_http_autoindex_module_ctx结构体解析

从注释上看,这些好像都是一些配置函数,区别应该是调用的时间点不同,这里只填了三个函数,下面对这三个函数进行分析。

源代码如下:

static ngx_http_module_t  ngx_http_autoindex_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_autoindex_init,               /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_autoindex_create_loc_conf,    /* create location configuration */
    ngx_http_autoindex_merge_loc_conf      /* merge location configuration */
};

3.2.1、ngx_http_autoindex_init函数解析

这个函数应该是初始化时被调用的,看不懂,看不懂的就是好代码。虽然看不懂,但是大概意思应该是从不知道哪个地方取出一段内存空间,将ngx_http_autoindex_handler函数放进去,这样在其他地方才能调用它。ngx_http_autoindex_handler函数也是处理autoindex服务逻辑的一个函数,后续我们再对其进行分析。
源代码如下:

static ngx_int_t
ngx_http_autoindex_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_autoindex_handler;

    return NGX_OK;
}

3.2.2、ngx_http_autoindex_create_loc_conf函数解析

这个就比较有意思了,就管这个结构体叫做逻辑控制块吧(下文都是这么叫的,具体真实的叫法我也不懂)。首先看一下ngx_http_autoindex_loc_conf_t结构体里的四个参数,其实就是我们配置Nginx配置文件时那四个与autoindex有关的参数。这里的功能是申请一块内存存放该结构体,然后初始化里面的值,最后将其作为返回值返回
源代码如下:

typedef struct {
    ngx_flag_t     enable;
    ngx_uint_t     format;
    ngx_flag_t     localtime;
    ngx_flag_t     exact_size;
} ngx_http_autoindex_loc_conf_t;

static void *
ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_autoindex_loc_conf_t  *conf;

    conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->enable = NGX_CONF_UNSET;
    conf->format = NGX_CONF_UNSET_UINT;
    conf->localtime = NGX_CONF_UNSET;
    conf->exact_size = NGX_CONF_UNSET;

    return conf;
}

3.2.3、ngx_http_autoindex_merge_loc_conf函数解析

这个函数就是就是将child里参数的值设置为parent的,从宏中可以看出并不一定会真的设置,是有条件判断的,具体读者可以自行看一下宏的内容,比较简单这里就不展开讲了。值得注意的是这里的childparent的真实类型其实就是前面提到的ngx_http_autoindex_loc_conf_t结构体

源代码如下:

#define ngx_conf_merge_value(conf, prev, default)                            \
    if (conf == NGX_CONF_UNSET) {                                            \
        conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \
    }

#define ngx_conf_merge_uint_value(conf, prev, default)                       \
    if (conf == NGX_CONF_UNSET_UINT) {                                       \
        conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev;               \
    }

static char *
ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_autoindex_loc_conf_t *prev = parent;
    ngx_http_autoindex_loc_conf_t *conf = child;

    ngx_conf_merge_value(conf->enable, prev->enable, 0);
    ngx_conf_merge_uint_value(conf->format, prev->format,
                              NGX_HTTP_AUTOINDEX_HTML);
    ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
    ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);

    return NGX_CONF_OK;
}

3.3、ngx_http_autoindex_commands结构体解析

这个结构体是配置那些模块指令信息的,我们之前配置过autoindex服务就知道,里面的四个配置参数,除了autoindex_format之外其他三个都是一样的,都是以onoff作为参数来控制功能的开关的,而autoindex_format的参数值则是字符串。从下面的配置也可以看出,除了autoindex_format以外其他三个的配置基本是一样的。

下面直接看一下ngx_command_t结构体的内容,可以看到ngx_command_t是个别名,实际是ngx_command_sngx_command_s的定义在src/core/ngx_conf_file.h文件中),结构体各个字段的作用见表3-1

表3-1 ngx_conf_enum_t结构体各字段含义解析

成员类型作用
name字符串指令名称,解析配置文件时按照名称去匹配
type无符号整型
该配置指令属性的集合。nginx提供了很多预定义的属性值(一些宏定义),通过逻辑或运算符可组合在一起,形成对这个配置指令的详细的说明。详细说明见表3-2
set函数指针
该函数是解析指令的函数,当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理。

函数指针原型:char *(*set)(ngx_conf_t *cf, ngx_command_t cmd, voidconf)
传入参数
cf:该参数里面保存从配置文件读取到的原始字符串以及相关的一些信息。特别注意的是这个参数的args字段是一个ngx_str_t类型的数组,该数组的首个元素是这个配置指令本身,第二个元素是指令的第一个参数,第三个元素是第二个参数,依次类推
cmd:这个配置指令对应的ngx_command_t结构
conf:就是定义的存储这个配置值的结构体,比如在上面展示的那个ngx_http_autoindex_loc_conf_t。当解析这个arg_str变量的时候,传入的conf就指向一个ngx_http_autoindex_loc_conf_t类型的变量。用户在处理的时候可以使用类型转换,转换成自己知道的类型,再进行字段的赋值。
返回值:处理成功时,返回NGX_OK,失败则返回NGX_CONF_ERROR或者一个自定义的错误信息的字符串

为了更加方便的实现对配置指令参数的读取,Nginx已经默认提供了对一些标准类型的参数进行读取的函数,可以直接赋值给set字段使用。这些函数的说明见表3-3
conf无符号整型
该字段被NGX_HTTP_MODULE类型模块所用 (我们编写的基本上都是NGX_HTTP_MOUDLE,只有一些nginx核心模块是非NGX_HTTP_MODULE),该字段指定当前配置项存储的内存位置。因为http模块对所有http模块所要保存的配置信息,划分了main, server和location三个地方进行存储,每个地方都有一个内存池用来分配存储这些信息的内存。这里可能的值为NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET
offset无符号整型
当前指令在逻辑控制块中的偏移量(比如说这里指的是在前面提到的ngx_http_autoindex_loc_conf_t中的偏移量)
postvoid类型指针
该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理

表3-2 type的各个参数及意义

参数名参数值说明
限制配置项的参数个数
NGX_CONF_NOARGS0x00000001配置项不携带任何参数
NGX_CONF_TAKE10x00000002配置项可以携带1个参数
NGX_CONF_TAKE20x00000004配置项可以携带2个参数
NGX_CONF_TAKE30x00000008配置项可以携带3个参数
NGX_CONF_TAKE40x00000010配置项可以携带4个参数
NGX_CONF_TAKE50x00000020配置项可以携带5个参数
NGX_CONF_TAKE60x00000040配置项可以携带6个参数
NGX_CONF_TAKE70x00000080配置项可以携带7个参数
NGX_CONF_TAKE12(NGX_CONF_TAKE1|NGX_CONF_TAKE2)配置项可以携带1个或2个参数
NGX_CONF_TAKE13(NGX_CONF_TAKE1|NGX_CONF_TAKE3)配置项可以携带1个或3个参数
NGX_CONF_TAKE23(NGX_CONF_TAKE2|NGX_CONF_TAKE3)配置项可以携带2个或3个参数
NGX_CONF_TAKE123(NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)配置项可以携带1~3个参数
NGX_CONF_TAKE1234(NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3|NGX_CONF_TAKE4)配置项可以携带1~4个参数
限制配置项后的参数出现的形式
NGX_CONF_ARGS_NUMBER0x000000ff目前未使用,无意义
NGX_CONF_BLOCK0x00000100配置项可以接受的值是一个配置信息块,也就是一对大括号括起来的内容,里面可以再包括很多的配置指令,比如常见的server指令就是这个属性的
NGX_CONF_FLAG0x00000200配置项携带的参数只能是1个,并且参数的值只能是on或者off
NGX_CONF_ANY0x00000400配置项可以接受的任意数量、类型的参数值,比如on、off或者是配置块等等
NGX_CONF_1MORE0x00000800配置项携带的参数个数必须超过1个
NGX_CONF_2MORE0x00001000配置项携带的参数个数必须超过2个
限制配置项可以出现的位置
NGX_DIRECT_CONF0x00010000配置项可以出现在配置文件中最外层。例如已经提供的配置项daemon、master_process等
NGX_MAIN_CONF0x01000000配置项可以出现在全局配置中,既不属于任何{}配置块
NGX_ANY_CONF0xFF000000配置项可以出现在任意配置级别上
NGX_EVENT_CONF0x02000000配置项可以出现在events{}块内
NGX_MAIL_MAIN_CONF0x02000000配置项可以出现在mail{}块或者imap{}块内
NGX_MAIL_SRV_CONF0x04000000配置项可以出现在server{}块内,然而该server{}块必须属于mail{}块或者imap{}块
NGX_HTTP_MAIN_CONF0x02000000配置项可以出现在http{}块
NGX_HTTP_SRV_CONF0x04000000配置项可以出现在server{}块内,然而server块必须属于http{}块
NGX_HTTP_LOC_CONF0x08000000配置项可以出现在location{}块内,然而该location块必须属于http{}块
NGX_HTTP_UPS_CONF0x10000000配置项可以出现在upstream{}块内,然而该upstream块必须属于http{}块
NGX_HTTP_SIF_CONF0x20000000配置项可以出现在server块内的if{}块中,该if{}块必须属于http{}块
NGX_HTTP_LIF_CONF0x40000000配置项可以出现在location块内的if{}块中,该if{}块必须属于http{}块
NGX_HTTP_LMT_CONF0x80000000配置项可以出现在limit_except{}块内,然而该limit_except块必须属于http{}块

表3-3 系统提供的set函数

函数名作用
ngx_conf_set_flag_slot读取NGX_CONF_FLAG类型的参数(解析出on、off)
ngx_conf_set_str_slot读取字符串类型的参数
ngx_conf_set_str_array_slot读取字符串数组类型的参数
ngx_conf_set_keyval_slot读取键值对类型的参数
ngx_conf_set_num_slot读取整数类型(有符号整数ngx_int_t)的参数
ngx_conf_set_size_slot读取size_t类型的参数,也就是无符号数
ngx_conf_set_off_slot读取off_t类型的参数
ngx_conf_set_msec_slot读取毫秒值类型的参数
ngx_conf_set_sec_slot读取秒值类型的参数
ngx_conf_set_bufs_slot读取的参数值是2个,一个是buf的个数,一个是buf的大小
ngx_conf_set_enum_slot读取枚举类型的参数,将其转换成整数ngx_uint_t类型
ngx_conf_set_bitmask_slot读取参数的值,并将这些参数的值以bit位的形式存储

接下来我们就只分析autoindexautoindex_format指令的配置信息,其余两个和autoindex是一样的:

  • autoindex指令的第二个参数使用的是NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,结合表3-2我们可以知道前三个是限制autoindex这个配置项可以出现的位置的,而第四个则是限制配置项只能有一个参数,且值只能是on或者off,这个情况和我们之前配置的时候是一致的。第三个参数使用的是ngx_conf_set_flag_slot函数,这个就不用多解释了,是配合NGX_CONF_FLAG使用的,其它的也就没什么好分析了,看前面三张表基本就能明白含义了
  • autoindex_format指令的第二个参数使用的是NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,前面的和其他几个指令是一样的,都是限制作用域的,唯一不同的就是使用了NGX_CONF_TAKE1,说明autoindex_format指令只能且必须输入一个参数。然后第三个参数使用的是ngx_conf_set_enum_slot函数,该函数的介绍可以看表表3-3,从autoindex_format指令的最后一个参数也可以看出其与其他几个指令是不同的,第六个参数将会被ngx_conf_set_enum_slot函数使用到,可以看到其类型是ngx_conf_enum_t(后面代码部分有给出),里面有两个成员,namevalue,作用就是ngx_conf_set_enum_slot函数里会拿name去匹配,匹配成功之后就用value的值去填进去相应的逻辑控制块,这里需要注意的是ngx_http_autoindex_format里的四个值也就是我们能填进去autoindex_format里的值了,也就是说这里的autoindex_format指令虽然可以接受字符串的参数,但这个参数是不可以胡乱填的,是有选择范围的。其它部分就没什么可讲的了

本节涉及到的源代码如下(这些源代码并不是都在同一个文件中的,是从各个地方摘录出来方便读者阅读的):

typedef struct ngx_command_s         ngx_command_t;

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

typedef struct {
    ngx_str_t                 name;
    ngx_uint_t                value;
} ngx_conf_enum_t;

#define NGX_HTTP_AUTOINDEX_HTML         0
#define NGX_HTTP_AUTOINDEX_JSON         1
#define NGX_HTTP_AUTOINDEX_JSONP        2
#define NGX_HTTP_AUTOINDEX_XML          3

static ngx_conf_enum_t  ngx_http_autoindex_format[] = {
    { ngx_string("html"), NGX_HTTP_AUTOINDEX_HTML },
    { ngx_string("json"), NGX_HTTP_AUTOINDEX_JSON },
    { ngx_string("jsonp"), NGX_HTTP_AUTOINDEX_JSONP },
    { ngx_string("xml"), NGX_HTTP_AUTOINDEX_XML },
    { ngx_null_string, 0 }
};

static ngx_command_t  ngx_http_autoindex_commands[] = {

    { ngx_string("autoindex"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_autoindex_loc_conf_t, enable),
      NULL },

    { ngx_string("autoindex_format"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_enum_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_autoindex_loc_conf_t, format),
      &ngx_http_autoindex_format },

    { ngx_string("autoindex_localtime"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_autoindex_loc_conf_t, localtime),
      NULL },

    { ngx_string("autoindex_exact_size"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_autoindex_loc_conf_t, exact_size),
      NULL },

      ngx_null_command
};

3.4、ngx_http_autoindex_handler函数详解

前面介绍了一大堆的内容都是关于配置项的,可以看到里面并没有什么奇特的技巧,都是使用了Nginx模块配置文件的框架去实现的,从侧面也可以看出Nginx具有极强的扩展性。但是,光有配置是不行的,当http请求过来的时候,还需要有相应的函数去处理,这个函数也就是之前提到过的ngx_http_autoindex_handler,下面对该函数进行分析。

  1. 首先这里是定义一堆变量,然后判断一下,然后对请求的url和请求方法进行判断,不符合要求则退出,代码如下:
static ngx_int_t
ngx_http_autoindex_handler(ngx_http_request_t *r)
{
    u_char                         *last, *filename;
    size_t                          len, allocated, root;
    ngx_err_t                       err;
    ngx_buf_t                      *b;
    ngx_int_t                       rc;
    ngx_str_t                       path, callback;
    ngx_dir_t                       dir;
    ngx_uint_t                      level, format;
    ngx_pool_t                     *pool;
    ngx_chain_t                     out;
    ngx_array_t                     entries;
    ngx_http_autoindex_entry_t     *entry;
    ngx_http_autoindex_loc_conf_t  *alcf;

    if (r->uri.data[r->uri.len - 1] != '/') {
        return NGX_DECLINED;
    }

    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_DECLINED;
    }
  1. 从传入的ngx_http_request_t结构体指针中取出逻辑控制块,然后判断autoindex是否被开启,代码如下:
    alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);

    if (!alcf->enable) {
        return NGX_DECLINED;
    }
  1. 丢弃掉请求体,很显然,autoindex这个模块根本不需要理会请求体,所以调用ngx_http_discard_request_body函数将请求体丢掉了(丢掉并不是传统意义上的丢掉,碍于篇幅,有关ngx_http_discard_request_body函数的具体实现就放到以后的文章中讲吧)
    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }
  1. 我们之前在配置autoindex服务时,曾经使用了一个alias的语法,不知道各位读者是否还记得。大家都知道,很多情况下urlpath和我们本地需要映射的path是不同的,如果是这样的话,这中间必定有一个映射关系,比如,我们使用alias指定了一个本地的路径/home/bifang/,然后我们需要让他对应上/autoindex这个url,在Nginx的配置文件中可以这么写:
location /autoindex {
    alias /home/bifang/;
}

但是配置文件写了程序要怎么识别呢?这就需要用到ngx_http_map_uri_to_path函数了,这个函数的具体内容这里就不分析了,他的作用就是获取资源的绝对路径,例如我们输入的path/autoindex/src/,则这里得出的路径为/home/bifang/src/。代码如下:

    last = ngx_http_map_uri_to_path(r, &path, &root,
                                    NGX_HTTP_AUTOINDEX_PREALLOCATE);
    if (last == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    allocated = path.len;
    path.len = last - path.data;
    if (path.len > 1) {
        path.len--;
    }
    path.data[path.len] = '\0';

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http autoindex: \"%s\"", path.data);
  1. 和跨域读取数据相关的内容,代码如下:
    format = alcf->format;

    if (format == NGX_HTTP_AUTOINDEX_JSONP) {
        if (ngx_http_autoindex_jsonp_callback(r, &callback) != NGX_OK) {
            return NGX_HTTP_BAD_REQUEST;
        }

        if (callback.len == 0) {
            format = NGX_HTTP_AUTOINDEX_JSON;
        }
    }
  1. 打开本地资源目录和处理一些杂项信息,代码如下:
    if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
        err = ngx_errno;

        if (err == NGX_ENOENT
            || err == NGX_ENOTDIR
            || err == NGX_ENAMETOOLONG)
        {
            level = NGX_LOG_ERR;
            rc = NGX_HTTP_NOT_FOUND;

        } else if (err == NGX_EACCES) {
            level = NGX_LOG_ERR;
            rc = NGX_HTTP_FORBIDDEN;

        } else {
            level = NGX_LOG_CRIT;
            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_log_error(level, r->connection->log, err,
                      ngx_open_dir_n " \"%s\" failed", path.data);

        return rc;
    }

#if (NGX_SUPPRESS_WARN)

    /* MSVC thinks 'entries' may be used without having been initialized */
    ngx_memzero(&entries, sizeof(ngx_array_t));

#endif

    /* TODO: pool should be temporary pool */
    pool = r->pool;

    if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
        != NGX_OK)
    {
        return ngx_http_autoindex_error(r, &dir, &path);
    }
  1. 这里是根据配置文件中设置的格式来设置响应头的参数content_type参数的,可以看到响应头设置好之后就直接先发送出去了,并没有等报文体组好一起发送出去,事实上很多浏览器、服务器的设计也是如此的(目前个人了解到的只有Windows的网络磁盘会在报文体比较少的情况下一次性发送出去)。代码如下:
    r->headers_out.status = NGX_HTTP_OK;

    switch (format) {

    case NGX_HTTP_AUTOINDEX_JSON:
        ngx_str_set(&r->headers_out.content_type, "application/json");
        break;

    case NGX_HTTP_AUTOINDEX_JSONP:
        ngx_str_set(&r->headers_out.content_type, "application/javascript");
        break;

    case NGX_HTTP_AUTOINDEX_XML:
        ngx_str_set(&r->headers_out.content_type, "text/xml");
        ngx_str_set(&r->headers_out.charset, "utf-8");
        break;

    default: /* NGX_HTTP_AUTOINDEX_HTML */
        ngx_str_set(&r->headers_out.content_type, "text/html");
        break;
    }

    r->headers_out.content_type_len = r->headers_out.content_type.len;
    r->headers_out.content_type_lowcase = NULL;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        if (ngx_close_dir(&dir) == NGX_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
                          ngx_close_dir_n " \"%V\" failed", &path);
        }

        return rc;
    }
  1. 接下来这段代码就是读取目标目录中的内容,然后将其一个一个存储到前面定义的entries中,代码比较简单,这里就不详细讲了。代码如下:
    filename = path.data;
    filename[path.len] = '/';

    for ( ;; ) {
        ngx_set_errno(0);

        if (ngx_read_dir(&dir) == NGX_ERROR) {
            err = ngx_errno;

            if (err != NGX_ENOMOREFILES) {
                ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
                              ngx_read_dir_n " \"%V\" failed", &path);
                return ngx_http_autoindex_error(r, &dir, &path);
            }

            break;
        }

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http autoindex file: \"%s\"", ngx_de_name(&dir));

        len = ngx_de_namelen(&dir);

        if (ngx_de_name(&dir)[0] == '.') {
            continue;
        }

        if (!dir.valid_info) {

            /* 1 byte for '/' and 1 byte for terminating '\0' */

            if (path.len + 1 + len + 1 > allocated) {
                allocated = path.len + 1 + len + 1
                                     + NGX_HTTP_AUTOINDEX_PREALLOCATE;

                filename = ngx_pnalloc(pool, allocated);
                if (filename == NULL) {
                    return ngx_http_autoindex_error(r, &dir, &path);
                }

                last = ngx_cpystrn(filename, path.data, path.len + 1);
                *last++ = '/';
            }

            ngx_cpystrn(last, ngx_de_name(&dir), len + 1);

            if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
                err = ngx_errno;

                if (err != NGX_ENOENT && err != NGX_ELOOP) {
                    ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
                                  ngx_de_info_n " \"%s\" failed", filename);

                    if (err == NGX_EACCES) {
                        continue;
                    }

                    return ngx_http_autoindex_error(r, &dir, &path);
                }

                if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
                    ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
                                  ngx_de_link_info_n " \"%s\" failed",
                                  filename);
                    return ngx_http_autoindex_error(r, &dir, &path);
                }
            }
        }

        entry = ngx_array_push(&entries);
        if (entry == NULL) {
            return ngx_http_autoindex_error(r, &dir, &path);
        }

        entry->name.len = len;

        entry->name.data = ngx_pnalloc(pool, len + 1);
        if (entry->name.data == NULL) {
            return ngx_http_autoindex_error(r, &dir, &path);
        }

        ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);

        entry->dir = ngx_de_is_dir(&dir);
        entry->file = ngx_de_is_file(&dir);
        entry->mtime = ngx_de_mtime(&dir);
        entry->size = ngx_de_size(&dir);
    }

    if (ngx_close_dir(&dir) == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
                      ngx_close_dir_n " \"%V\" failed", &path);
    }

    if (entries.nelts > 1) {
        ngx_qsort(entries.elts, (size_t) entries.nelts,
                  sizeof(ngx_http_autoindex_entry_t),
                  ngx_http_autoindex_cmp_entries);
    }
  1. 最后这里就是根据文件格式的不同,调用对应的函数生成对应的文件内容,然后作为报文体发送出去。代码如下:
    switch (format) {

    case NGX_HTTP_AUTOINDEX_JSON:
        b = ngx_http_autoindex_json(r, &entries, NULL);
        break;

    case NGX_HTTP_AUTOINDEX_JSONP:
        b = ngx_http_autoindex_json(r, &entries, &callback);
        break;

    case NGX_HTTP_AUTOINDEX_XML:
        b = ngx_http_autoindex_xml(r, &entries);
        break;

    default: /* NGX_HTTP_AUTOINDEX_HTML */
        b = ngx_http_autoindex_html(r, &entries);
        break;
    }

    if (b == NULL) {
        return NGX_ERROR;
    }

    /* TODO: free temporary pool */

    if (r == r->main) {
        b->last_buf = 1;
    }

    b->last_in_chain = 1;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

3.5、ngx_http_autoindex_html函数详解

可以看到autoindex支持三种格式,分别是jsonxmlhtml,我们这里就以html格式的生成函数ngx_http_autoindex_html为例来讲解一下吧,因为之前配置的也是html格式的。

  1. 首先这里是定义了几个html的标签啥的,然后定义了一个日期数组,和判断是否使用utf8格式
    static u_char  title[] =
        "<html>" CRLF
        "<head><title>Index of "
    ;

    static u_char  header[] =
        "</title></head>" CRLF
        "<body>" CRLF
        "<h1>Index of "
    ;

    static u_char  tail[] =
        "</body>" CRLF
        "</html>" CRLF
    ;

    static char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

    if (r->headers_out.charset.len == 5
        && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
           == 0)
    {
        utf8 = 1;

    } else {
        utf8 = 0;
    }
  1. ngx_escape_html函数是算出需要额外增加的内存长度,为什么需要额外增加呢,因为在html中有些符号是需要转义的,比如<符号需要转义为&lt;,这样长度就增加了3个了,而ngx_escape_html函数就是为了来算这个额外的长度的。
    有了上面的介绍,我们可以很容易得出下面一段代码的作用就是提前算出最终生成的html文件的大小,然后分配对应的内容(从这里也可以看出Nginx确实是极致地省内存,居然先算出大小再来分配内存)
    escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);

    len = sizeof(title) - 1
          + r->uri.len + escape_html
          + sizeof(header) - 1
          + r->uri.len + escape_html
          + sizeof("</h1>") - 1
          + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
          + sizeof("</pre><hr>") - 1
          + sizeof(tail) - 1;

    entry = entries->elts;
    for (i = 0; i < entries->nelts; i++) {
        entry[i].escape = 2 * ngx_escape_uri(NULL, entry[i].name.data,
                                             entry[i].name.len,
                                             NGX_ESCAPE_URI_COMPONENT);

        entry[i].escape_html = ngx_escape_html(NULL, entry[i].name.data,
                                               entry[i].name.len);

        if (utf8) {
            entry[i].utf_len = ngx_utf8_length(entry[i].name.data,
                                               entry[i].name.len);
        } else {
            entry[i].utf_len = entry[i].name.len;
        }

        entry_len = sizeof("<a href=\"") - 1
                  + entry[i].name.len + entry[i].escape
                  + 1                                    /* 1 is for "/" */
                  + sizeof("\">") - 1
                  + entry[i].name.len - entry[i].utf_len
                  + entry[i].escape_html
                  + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
                  + sizeof("</a>") - 1
                  + sizeof(" 28-Sep-1970 12:00 ") - 1
                  + 20                                   /* the file size */
                  + 2;

        if (len > NGX_MAX_SIZE_T_VALUE - entry_len) {
            return NULL;
        }

        len += entry_len;
    }

    b = ngx_create_temp_buf(r->pool, len);
    if (b == NULL) {
        return NULL;
    }
  1. 这一段的作用就是把html文件的一部分通用信息给写入到刚刚分配的内存中
    b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);

    if (escape_html) {
        b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
        b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
        b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);

    } else {
        b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
        b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
        b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
    }

    b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);

    b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
                         sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
  1. 这段程序是把逻辑控制块给取出来了,为了获取到我们配置的参数(比如显示的文件大小是要准确到字节还是带单位的非精确模式等等)
alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
  1. 后面的代码太长了就不说明了,里面做得工作无非就是把目录的信息按照格式写进去分配的内存中,里面确实也有处理对齐的情况,并且也会在文件或目录名过长时对显示的内容进行省略,不过这里的写法并不兼容中文名情况,一旦路径或者文件有中文名就无法对齐了。

4、总结

本文主要讲解了Nginxautoindex模块的源码,并补充了一些关于Nginx模块配置的内容。碍于篇幅,有关Nginx的工作流程之类的内容这里并没有讲解(比如它是如何去调用到ngx_http_autoindex_handler函数),本文提到的但是没有详细说明的内容在后续的文章中会逐渐进行补充,大家觉得感兴趣的可以关注我或者该专栏,这样就能看到我最新发布的文章了。

最后,我自己也写了一个服务器的框架,里面也有实现和autoindex类似的功能,就是框架里的file_server模块,不过我做的是支持中文对齐的,功能上也略有调整,点击目录时就是普通的跳转,单击文件是直接在网页打开该文件(文档、音频、视频等,必须是H5支持的才行),双击就是和autoindex一样将文件下载到本地了,大家有兴趣的可以看一下这篇文章《一个C++编写的服务器框架 - bifang》,去我的github上面把源码下载下来,编译调试一下。觉得有用的小伙伴记得帮我点个star,谢谢

评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彼 方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值