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
的,从宏中可以看出并不一定会真的设置,是有条件判断的,具体读者可以自行看一下宏的内容,比较简单这里就不展开讲了。值得注意的是这里的child
和parent
的真实类型其实就是前面提到的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
之外其他三个都是一样的,都是以on
、off
作为参数来控制功能的开关的,而autoindex_format
的参数值则是字符串。从下面的配置也可以看出,除了autoindex_format
以外其他三个的配置基本是一样的。
下面直接看一下ngx_command_t
结构体的内容,可以看到ngx_command_t
是个别名,实际是ngx_command_s
(ngx_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中的偏移量)
|
post | void类型指针 |
该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理
|
表3-2 type的各个参数及意义
参数名 | 参数值 | 说明 |
限制配置项的参数个数 | ||
NGX_CONF_NOARGS | 0x00000001 | 配置项不携带任何参数 |
NGX_CONF_TAKE1 | 0x00000002 | 配置项可以携带1个参数 |
NGX_CONF_TAKE2 | 0x00000004 | 配置项可以携带2个参数 |
NGX_CONF_TAKE3 | 0x00000008 | 配置项可以携带3个参数 |
NGX_CONF_TAKE4 | 0x00000010 | 配置项可以携带4个参数 |
NGX_CONF_TAKE5 | 0x00000020 | 配置项可以携带5个参数 |
NGX_CONF_TAKE6 | 0x00000040 | 配置项可以携带6个参数 |
NGX_CONF_TAKE7 | 0x00000080 | 配置项可以携带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_NUMBER | 0x000000ff | 目前未使用,无意义 |
NGX_CONF_BLOCK | 0x00000100 | 配置项可以接受的值是一个配置信息块,也就是一对大括号括起来的内容,里面可以再包括很多的配置指令,比如常见的server指令就是这个属性的 |
NGX_CONF_FLAG | 0x00000200 | 配置项携带的参数只能是1个,并且参数的值只能是on或者off |
NGX_CONF_ANY | 0x00000400 | 配置项可以接受的任意数量、类型的参数值,比如on、off或者是配置块等等 |
NGX_CONF_1MORE | 0x00000800 | 配置项携带的参数个数必须超过1个 |
NGX_CONF_2MORE | 0x00001000 | 配置项携带的参数个数必须超过2个 |
限制配置项可以出现的位置 | ||
NGX_DIRECT_CONF | 0x00010000 | 配置项可以出现在配置文件中最外层。例如已经提供的配置项daemon、master_process等 |
NGX_MAIN_CONF | 0x01000000 | 配置项可以出现在全局配置中,既不属于任何{}配置块 |
NGX_ANY_CONF | 0xFF000000 | 配置项可以出现在任意配置级别上 |
NGX_EVENT_CONF | 0x02000000 | 配置项可以出现在events{}块内 |
NGX_MAIL_MAIN_CONF | 0x02000000 | 配置项可以出现在mail{}块或者imap{}块内 |
NGX_MAIL_SRV_CONF | 0x04000000 | 配置项可以出现在server{}块内,然而该server{}块必须属于mail{}块或者imap{}块 |
NGX_HTTP_MAIN_CONF | 0x02000000 | 配置项可以出现在http{}块 |
NGX_HTTP_SRV_CONF | 0x04000000 | 配置项可以出现在server{}块内,然而server块必须属于http{}块 |
NGX_HTTP_LOC_CONF | 0x08000000 | 配置项可以出现在location{}块内,然而该location块必须属于http{}块 |
NGX_HTTP_UPS_CONF | 0x10000000 | 配置项可以出现在upstream{}块内,然而该upstream块必须属于http{}块 |
NGX_HTTP_SIF_CONF | 0x20000000 | 配置项可以出现在server块内的if{}块中,该if{}块必须属于http{}块 |
NGX_HTTP_LIF_CONF | 0x40000000 | 配置项可以出现在location块内的if{}块中,该if{}块必须属于http{}块 |
NGX_HTTP_LMT_CONF | 0x80000000 | 配置项可以出现在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位的形式存储 |
接下来我们就只分析autoindex
和autoindex_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
(后面代码部分有给出),里面有两个成员,name
和value
,作用就是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
,下面对该函数进行分析。
- 首先这里是定义一堆变量,然后判断一下,然后对请求的
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;
}
- 从传入的
ngx_http_request_t
结构体指针中取出逻辑控制块,然后判断autoindex
是否被开启,代码如下:
alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
if (!alcf->enable) {
return NGX_DECLINED;
}
- 丢弃掉请求体,很显然,
autoindex
这个模块根本不需要理会请求体,所以调用ngx_http_discard_request_body
函数将请求体丢掉了(丢掉并不是传统意义上的丢掉,碍于篇幅,有关ngx_http_discard_request_body
函数的具体实现就放到以后的文章中讲吧)
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
- 我们之前在配置
autoindex
服务时,曾经使用了一个alias
的语法,不知道各位读者是否还记得。大家都知道,很多情况下url
的path
和我们本地需要映射的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);
- 和跨域读取数据相关的内容,代码如下:
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;
}
}
- 打开本地资源目录和处理一些杂项信息,代码如下:
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);
}
- 这里是根据配置文件中设置的格式来设置响应头的参数
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;
}
- 接下来这段代码就是读取目标目录中的内容,然后将其一个一个存储到前面定义的
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);
}
- 最后这里就是根据文件格式的不同,调用对应的函数生成对应的文件内容,然后作为报文体发送出去。代码如下:
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
支持三种格式,分别是json
、xml
和html
,我们这里就以html
格式的生成函数ngx_http_autoindex_html
为例来讲解一下吧,因为之前配置的也是html
格式的。
- 首先这里是定义了几个
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;
}
ngx_escape_html
函数是算出需要额外增加的内存长度,为什么需要额外增加呢,因为在html
中有些符号是需要转义的,比如<
符号需要转义为<
,这样长度就增加了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(">") - 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;
}
- 这一段的作用就是把
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);
- 这段程序是把逻辑控制块给取出来了,为了获取到我们配置的参数(比如显示的文件大小是要准确到字节还是带单位的非精确模式等等)
alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
- 后面的代码太长了就不说明了,里面做得工作无非就是把目录的信息按照格式写进去分配的内存中,里面确实也有处理对齐的情况,并且也会在文件或目录名过长时对显示的内容进行省略,不过这里的写法并不兼容中文名情况,一旦路径或者文件有中文名就无法对齐了。
4、总结
本文主要讲解了Nginx
的autoindex
模块的源码,并补充了一些关于Nginx
模块配置的内容。碍于篇幅,有关Nginx
的工作流程之类的内容这里并没有讲解(比如它是如何去调用到ngx_http_autoindex_handler
函数),本文提到的但是没有详细说明的内容在后续的文章中会逐渐进行补充,大家觉得感兴趣的可以关注我或者该专栏,这样就能看到我最新发布的文章了。
最后,我自己也写了一个服务器的框架,里面也有实现和autoindex
类似的功能,就是框架里的file_server
模块,不过我做的是支持中文对齐的,功能上也略有调整,点击目录时就是普通的跳转,单击文件是直接在网页打开该文件(文档、音频、视频等,必须是H5支持的才行),双击就是和autoindex
一样将文件下载到本地了,大家有兴趣的可以看一下这篇文章《一个C++编写的服务器框架 - bifang》,去我的github上面把源码下载下来,编译调试一下。觉得有用的小伙伴记得帮我点个star,谢谢