ngx_http_index_handler

📅 2026/6/29 19:38:57
ngx_http_index_handler
1 定义ngx_http_index_handler 函数 定义在 src/http/modules/ngx_http_index_module.c2 作用ngx_http_index_handler 函数 它处理以 / 结尾的目录请求 根据 index 指令指定的文件列表依次尝试找到存在的索引文件如果找到则内部重定向到该文件 若所有尝试失败则返回 NGX_DECLINED 让其他模块继续处理。3 详解1 函数签名staticngx_int_tngx_http_index_handler(ngx_http_request_t*r)1. 返回值类型ngx_int_t用于统一表示操作状态码。2. 函数名ngx_http_index_handler前缀ngx_http_表明该函数属于 HTTP 子系统处理的是 HTTP 协议相关的逻辑。模块名index直接指明它是ngx_http_index_module的一部分该模块实现了index指令的功能。后缀handlerNginx 的命名惯例表示该函数是一个阶段处理器会被注册到某个 HTTP 处理阶段。对于index模块它注册在NGX_HTTP_CONTENT_PHASE阶段负责为目录请求寻找默认索引文件。整体语义该函数是 Nginx 中处理目录请求的核心入口它根据index指令指定的文件列表依次在文件系统中查找第一个存在的索引文件并通过内部重定向让该文件的内容处理器生成最终响应。函数名准确地反映了它的功能和在请求处理流程中的角色。3. 参数ngx_http_request_t *r指向 当前 HTTP 请求上下文结构体2 逻辑流程1 前置检查只处理合法的目录请求 请求 URI 必须以 / 结尾 这是判断“目录请求”的唯一标准 如果不满足直接返回 NGX_DECLINED 表明本模块不处理避免了不必要的文件探测。 仅允许 GET、HEAD、POST 方法。 限制只有这三种方法才允许使用索引文件 对于 PUT、DELETE 等方法索引功能无意义 提早退出也能防止资源浪费和潜在的安全问题。 这两步过滤保证了只有真正需要索引服务的请求才会进入后续流程使模块职责清晰执行效率高。 2 获取配置与初始化工作变量 3 遍历索引文件列表 3-1 计算当前文件名所需的长度len和预留空间reserve 3-2 确保文件系统路径缓冲区有足够空间 3-3 拼接完整的文件系统路径 3-4 测试文件是否存在 3-5 构造内部重定向 URI 并执行重定向 当文件存在且类型匹配后函数需要将请求以新 URI 重新投入处理流水线。 4 全部未命中 循环结束返回 NGX_DECLINED 如果 for 循环遍历完所有索引文件没有一个文件存在且满足条件则退出循环 执行最后的 return NGX_DECLINED;。这表示索引模块无法处理该请求 内容检查器会继续调用下一个内容模块或最终由核心模块返回 403/404。 这种“尝试-重定向”的设计将“决定用哪个文件”和“如何发送该文件”彻底解耦。 索引模块只负责路由选择实际内容生成完全委托给后续阶段符合 Nginx 的模块化架构。{u_char*p,*name;size_tlen,root,reserve,allocated;ngx_int_trc;ngx_str_tpath,uri;ngx_uint_ti,dir_tested;ngx_http_index_t*index;ngx_open_file_info_tof;ngx_http_script_code_pt code;ngx_http_script_engine_te;ngx_http_core_loc_conf_t*clcf;ngx_http_index_loc_conf_t*ilcf;ngx_http_script_len_code_pt lcode;局部变量 声明1 前置检查只处理合法的目录请求if(r-uri.data[r-uri.len-1]!/){returnNGX_DECLINED;}if(!(r-method(NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))){returnNGX_DECLINED;}2 获取配置与初始化工作变量ilcfngx_http_get_module_loc_conf(r,ngx_http_index_module);clcfngx_http_get_module_loc_conf(r,ngx_http_core_module);allocated0;root0;dir_tested0;nameNULL;/* suppress MSVC warning */path.dataNULL;3 遍历索引文件列表indexilcf-indices-elts;for(i0;iilcf-indices-nelts;i){3-1 计算当前文件名所需的长度if(index[i].lengthsNULL){if(index[i].name.data[0]/){returnngx_http_internal_redirect(r,index[i].name,r-args);}reserveilcf-max_index_len;lenindex[i].name.len;静态文件名index[i].lengths NULL如果文件名是普通字符串不含变量直接取index[i].name.len作为长度该长度通常包含结尾的 ‘\0’。如果该字符串的第一个字符是 ‘/’说明它是一个绝对路径此时不需要在文件系统中查找直接调用ngx_http_internal_redirect进行内部重定向并返回。reserve被设置为ilcf-max_index_len——所有静态索引名的最大长度。这样做的好处是只要第一次路径映射时预留了最大空间后续所有静态名都可以复用同一个缓冲区不必每次都重新映射路径。}else{ngx_memzero(e,sizeof(ngx_http_script_engine_t));e.ipindex[i].lengths-elts;e.requestr;e.flushed1;/* 1 is for terminating \0 as in static names */len1;while(*(uintptr_t*)e.ip){lcode*(ngx_http_script_len_code_pt*)e.ip;lenlcode(e);}/* 16 bytes are preallocation */reservelen16;}动态文件名含有变量index[i].lengths ! NULL需要利用脚本引擎计算实际长度。首先初始化引擎 e将编译好的长度计算指令序列index[i].lengths-elts交给引擎循环执行每一条指令累加得到总长度 len。初始 len 1 是为结尾的 ‘\0’ 预留。然后预留空间 reserve len 16其中 16 字节是额外安全边界。3-2 确保文件系统路径缓冲区有足够空间if(reserveallocated){namengx_http_map_uri_to_path(r,path,root,reserve);if(nameNULL){returnNGX_HTTP_INTERNAL_SERVER_ERROR;}allocatedpath.datapath.len-name;}确保文件系统路径缓冲区有足够空间ngx_http_map_uri_to_path函数分配一个缓冲区用来 拼接文件完整路径缓冲区 首地址是path.data, 缓冲区长度是path.lenpath.data与name之间是 根据root/alias指令设置的根路径而映射得到的 系统绝对路径allocated是缓冲区剩余的空闲空间是在之后可以用来拼接index[i].name文件名的剩余空间reserve是需要为文件名预留的空间大小实际剩余空间小于这个大小就需要重新分配空间失败返回 NULL直接返回 500成功更新allocated实际剩余大小这个机制使得在整个索引文件列表的遍历过程中路径映射只会在必要时执行极大地减少了系统调用开销。3-3 拼接完整的文件系统路径if(index[i].valuesNULL){/* index[i].name.len includes the terminating \0 */ngx_memcpy(name,index[i].name.data,index[i].name.len);path.len(nameindex[i].name.len-1)-path.data;}else{e.ipindex[i].values-elts;e.posname;while(*(uintptr_t*)e.ip){code*(ngx_http_script_code_pt*)e.ip;code((ngx_http_script_engine_t*)e);}if(*name/){uri.lenlen-1;uri.dataname;returnngx_http_internal_redirect(r,uri,r-args);}path.lene.pos-path.data;*e.pos\0;}拼接完整的文件系统路径静态文件名index[i].values NULL直接将静态文件名包含结尾 ‘\0’拷贝到 name 位置然后调整 path.len使其表示完整的文件路径长度不包括结尾 ‘\0’。动态文件名需要执行脚本生成将脚本引擎的值生成指令序列交给引擎执行后将生成的字符串写入 name。如果生成的字符串以 ‘/’ 开头同样视为绝对路径直接内部重定向返回。生成结束后更新 path.len 并在 *e.pos ‘\0’ 添加字符串结束符形成完整的 C 字符串路径。3-4 测试文件是否存在ngx_log_debug1(NGX_LOG_DEBUG_HTTP,r-connection-log,0,open index \%V\,path);ngx_memzero(of,sizeof(ngx_open_file_info_t));of.read_aheadclcf-read_ahead;of.directioclcf-directio;of.validclcf-open_file_cache_valid;of.min_usesclcf-open_file_cache_min_uses;of.test_only1;of.errorsclcf-open_file_cache_errors;of.eventsclcf-open_file_cache_events;if(ngx_http_set_disable_symlinks(r,clcf,path,of)!NGX_OK){returnNGX_HTTP_INTERNAL_SERVER_ERROR;}初始化ngx_open_file_info_t结构体of并清零设置of.test_only 1表示仅测试文件是否存在及属性不真正打开文件从而节省文件描述符。设置 of.read_ahead、of.directio、of.valid 等字段以便测试时遵循与正式文件传输相同的 I/O 策略和文件缓存行为。调用ngx_http_set_disable_symlinks根据配置调整符号链接处理方式。if(ngx_open_cached_file(clcf-open_file_cache,path,of,r-pool)!NGX_OK){if(of.err0){returnNGX_HTTP_INTERNAL_SERVER_ERROR;}ngx_log_debug2(NGX_LOG_DEBUG_HTTP,r-connection-log,of.err,%s \%s\ failed,of.failed,path.data);#if(NGX_HAVE_OPENAT)if(of.errNGX_EMLINK||of.errNGX_ELOOP){returnNGX_HTTP_FORBIDDEN;}#endifif(of.errNGX_ENOTDIR||of.errNGX_ENAMETOOLONG||of.errNGX_EACCES){returnngx_http_index_error(r,clcf,path.data,of.err);}if(!dir_tested){rcngx_http_index_test_dir(r,clcf,path.data,name-1);if(rc!NGX_OK){returnrc;}dir_tested1;}if(of.errNGX_ENOENT){continue;}ngx_log_error(NGX_LOG_CRIT,r-connection-log,of.err,%s \%s\ failed,of.failed,path.data);returnNGX_HTTP_INTERNAL_SERVER_ERROR;}调用ngx_open_cached_file实际进行测试。该函数会先检查文件缓存若无缓存或已过期则进行stat等系统调用。ngx_open_cached_file测试失败处理如果 of.err 0说明未知错误返回 500。如果错误是 NGX_ENOENT文件不存在这是最常见的情况continue 尝试下一个索引文件。对于 NGX_EMLINK链接数过多或 NGX_ELOOP符号链接循环返回 403。对于 NGX_ENOTDIR路径成分不是目录、NGX_ENAMETOOLONG名称太长或 NGX_EACCES权限不足调用 ngx_http_index_error 返回适当的错误响应。在首次遇到错误且尚未测试过父目录时调用 ngx_http_index_test_dir 检查父目录是否存在若不存在则返回相应错误如 404避免后续对同一目录的无意义尝试。其他错误记录 CRIT 级别日志返回 500。文件存在但类型不匹配如果 of.is_dir ! test_dir例如期望文件但实际是目录或反之则 continue 尝试下一个索引文件。3-5 构造内部重定向 URI 并执行重定向uri.lenr-uri.lenlen-1;if(!clcf-alias){uri.datapath.dataroot;}else{uri.datangx_pnalloc(r-pool,uri.len);if(uri.dataNULL){returnNGX_HTTP_INTERNAL_SERVER_ERROR;}pngx_copy(uri.data,r-uri.data,r-uri.len);ngx_memcpy(p,name,len-1);}returnngx_http_internal_redirect(r,uri,r-args);}当文件存在且类型匹配后函数需要将请求以新 URI 重新投入处理流水线。计算新 URI 长度uri.len r-uri.len len - 1; 其中 len 包含结尾 ‘\0’减 1 得到实际文件名字符数加上原请求 URI 长度即目录路径长度即为新 URI 总长度。构造 uri.data未使用 alias即 root 模式直接利用路径缓冲区中的指针偏移 uri.data path.data root; 获得新 URI 的起始地址无需额外内存分配零拷贝。使用了 alias因为文件系统路径与请求 URI 不是简单的前缀对应关系需要从内存池分配空间先拷贝原请求 URI目录部分再拼接文件名不含结尾 ‘\0’构成新 URI。调用 return ngx_http_internal_redirect(r, uri, r-args); 执行内部重定向。该函数会修改 r-uri重置 phase_handler让请求重新进入 NGX_HTTP_SERVER_REWRITE_PHASE之后会再次经历 location 匹配和内容处理。函数直接返回该重定向的结果结束索引处理。4 全部未命中returnNGX_DECLINED;}