ngx_http_init_connection
1 定义ngx_http_init_connection 函数 定义在 ./nginx-1.24.0/src/http/ngx_http_request.cvoidngx_http_init_connection(ngx_connection_t*c){ngx_uint_ti;ngx_event_t*rev;structsockaddr_in*sin;ngx_http_port_t*port;ngx_http_in_addr_t*addr;ngx_http_log_ctx_t*ctx;ngx_http_connection_t*hc;ngx_http_core_srv_conf_t*cscf;#if(NGX_HAVE_INET6)structsockaddr_in6*sin6;ngx_http_in6_addr_t*addr6;#endifhcngx_pcalloc(c-pool,sizeof(ngx_http_connection_t));if(hcNULL){ngx_http_close_connection(c);return;}c-datahc;/* find the server configuration for the address:port */portc-listening-servers;if(port-naddrs1){/* * there are several addresses on this port and one of them * is an *:port wildcard so getsockname() in ngx_http_server_addr() * is required to determine a server address */if(ngx_connection_local_sockaddr(c,NULL,0)!NGX_OK){ngx_http_close_connection(c);return;}switch(c-local_sockaddr-sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:sin6(structsockaddr_in6*)c-local_sockaddr;addr6port-addrs;/* the last address is * */for(i0;iport-naddrs-1;i){if(ngx_memcmp(addr6[i].addr6,sin6-sin6_addr,16)0){break;}}hc-addr_confaddr6[i].conf;break;#endifdefault:/* AF_INET */sin(structsockaddr_in*)c-local_sockaddr;addrport-addrs;/* the last address is * */for(i0;iport-naddrs-1;i){if(addr[i].addrsin-sin_addr.s_addr){break;}}hc-addr_confaddr[i].conf;break;}}else{switch(c-local_sockaddr-sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:addr6port-addrs;hc-addr_confaddr6[0].conf;break;#endifdefault:/* AF_INET */addrport-addrs;hc-addr_confaddr[0].conf;break;}}/* the default server configuration for the address:port */hc-conf_ctxhc-addr_conf-default_server-ctx;ctxngx_palloc(c-pool,sizeof(ngx_http_log_ctx_t));if(ctxNULL){ngx_http_close_connection(c);return;}ctx-connectionc;ctx-requestNULL;ctx-current_requestNULL;c-log-connectionc-number;c-log-handlerngx_http_log_error;c-log-datactx;c-log-actionwaiting for request;c-log_errorNGX_ERROR_INFO;revc-read;rev-handlerngx_http_wait_request_handler;c-write-handlerngx_http_empty_handler;#if(NGX_HTTP_V2)if(hc-addr_conf-http2){rev-handlerngx_http_v2_init;}#endif#if(NGX_HTTP_SSL){ngx_http_ssl_srv_conf_t*sscf;sscfngx_http_get_module_srv_conf(hc-conf_ctx,ngx_http_ssl_module);if(sscf-enable||hc-addr_conf-ssl){hc-ssl1;c-log-actionSSL handshaking;rev-handlerngx_http_ssl_handshake;}}#endifif(hc-addr_conf-proxy_protocol){hc-proxy_protocol1;c-log-actionreading PROXY protocol;}if(rev-ready){/* the deferred accept(), iocp */if(ngx_use_accept_mutex){ngx_post_event(rev,ngx_posted_events);return;}rev-handler(rev);return;}cscfngx_http_get_module_srv_conf(hc-conf_ctx,ngx_http_core_module);ngx_add_timer(rev,cscf-client_header_timeout);ngx_reusable_connection(c,1);if(ngx_handle_read_event(rev,0)!NGX_OK){ngx_http_close_connection(c);return;}}ngx_http_init_connection 函数的作用是 为刚建立的 TCP 连接初始化 HTTP 层处理环境。 它分配连接专属的 HTTP 上下文 根据本地 IP 与端口匹配对应的 server 配置块设置日志、读/写事件回调 添加客户端请求超时并将读事件注册到底层事件驱动模块 使连接正式进入等待接收客户端请求的状态。2 详解1 函数签名voidngx_http_init_connection(ngx_connection_t*c)无返回值参数 ngx_connection_t *c 指向要初始化的连接对象2 逻辑流程1 局部变量 2 分配并绑定 HTTP 连接上下文 3 根据本地地址匹配 server 配置 4 选定默认 server 配置 5 初始化 HTTP 日志上下文 6 设置事件回调 7 处理数据已就绪的情形 8 设置超时并注册读事件1 局部变量{ngx_uint_ti;ngx_event_t*rev;structsockaddr_in*sin;ngx_http_port_t*port;ngx_http_in_addr_t*addr;ngx_http_log_ctx_t*ctx;ngx_http_connection_t*hc;ngx_http_core_srv_conf_t*cscf;#if(NGX_HAVE_INET6)structsockaddr_in6*sin6;ngx_http_in6_addr_t*addr6;#endif2 分配并绑定 HTTP 连接上下文hcngx_pcalloc(c-pool,sizeof(ngx_http_connection_t));if(hcNULL){ngx_http_close_connection(c);return;}c-datahc;ngx_pcalloc 从连接内存池 c-pool 分配内存并清零。 分配失败处理 如果内存不足直接关闭连接并返回。 c-data hc 将 HTTP 私有数据挂载到通用连接对象上。 此后无论在哪个模块只要拿到 c就可以通过 hc c-data 取回 HTTP 层专有信息 这是 nginx 实现模块扩展的典型方式通用结构体里留一个 void* data 指针。3 根据本地地址匹配 server 配置/* find the server configuration for the address:port */portc-listening-servers;if(port-naddrs1){/* * there are several addresses on this port and one of them * is an *:port wildcard so getsockname() in ngx_http_server_addr() * is required to determine a server address */if(ngx_connection_local_sockaddr(c,NULL,0)!NGX_OK){ngx_http_close_connection(c);return;}switch(c-local_sockaddr-sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:sin6(structsockaddr_in6*)c-local_sockaddr;addr6port-addrs;/* the last address is * */for(i0;iport-naddrs-1;i){if(ngx_memcmp(addr6[i].addr6,sin6-sin6_addr,16)0){break;}}hc-addr_confaddr6[i].conf;break;#endifdefault:/* AF_INET */sin(structsockaddr_in*)c-local_sockaddr;addrport-addrs;/* the last address is * */for(i0;iport-naddrs-1;i){if(addr[i].addrsin-sin_addr.s_addr){break;}}hc-addr_confaddr[i].conf;break;}}else{switch(c-local_sockaddr-sa_family){#if(NGX_HAVE_INET6)caseAF_INET6:addr6port-addrs;hc-addr_confaddr6[0].conf;break;#endifdefault:/* AF_INET */addrport-addrs;hc-addr_confaddr[0].conf;break;}}#1 c-listening 是本次 accept 所在的监听对象 它的 servers 字段指向 ngx_http_port_t 其中包含了该端口上所有 listen 指令合并后的地址信息。 这是连接与具体 server 配置关联的入口。#2-1 情况1多地址port-naddrs 1 port-naddrs 1表示该端口绑定了多个不同的 IP 地址#2-2 调用 ngx_connection_local_sockaddr ngx_connection_local_sockaddr 作用是 获取并缓存连接的本端 socket 地址 即 accept 产生的 socket 实际绑定的本地 IP 和端口。 它内部会调用 getsockname() 系统调用。 第一个参数 c 当前的连接对象。 第二个参数 NULL 表示我们不关心对端地址 通常此函数也可用于同时获取对端地址这里传 NULL 忽略。 第三个参数 0 标志位通常为 0 表示默认行为。 返回值 成功时返回 NGX_OK失败返回 NGX_ERROR。 ! NGX_OK 条件判断如果函数未返回成功 说明获取本地地址失败进入 if 体进行错误处理。 意义 当 port-naddrs 1端口上配置了多个监听地址 不同的本地 IP 可能对应不同的虚拟主机配置 因此必须精确知道客户端究竟连接到了哪个 IP。 这个信息只能通过 getsockname() 获取 并且函数会将其保存到 c-local_sockaddr 中 供后续地址匹配循环使用。 ! NGX_OK 失败 调用 HTTP 模块的连接关闭函数 ngx_http_close_connection 该函数会执行一系列清理工作#2-3 根据连接的本地 IP 协议族 在端口的多地址数组中找到匹配的那一项 从而为连接选定正确的地址级配置 c-local_sockaddr 此前已通过 ngx_connection_local_sockaddr 填充 保存了本端服务器的 socket 地址。 sa_family socket 地址结构体中的协议族字段 用于区分 IPv4AF_INET或 IPv6AF_INET6。 switch 根据协议族分支处理 因为 IPv4 和 IPv6 的地址数据结构和长度不同 必须用不同的代码匹配。#2-3-1 当连接本地地址协议族为 AF_INET6 时 将通用的 sockaddr 指针强制转换为 sockaddr_in6 指针 以便访问 IPv6 地址字段 sin6_addr port-addrs 在 IPv6 场景下这是 ngx_http_in6_addr_t 数组 每个元素代表该端口上监听的一个 IPv6 地址及其配置。 addr6局部指针方便后续遍历。 循环遍历数组的前 naddrs - 1 个元素即除了最后一个通配符以外的所有具体地址。 意义精确匹配具体 IP 保证在多个具体地址共存时找到正确的那一个 若未匹配循环结束后 i 自动等于 naddrs - 1即通配符索引。 addr6[i].addr6数组中某个监听地址的 IPv6 地址struct in6_addr。 sin6-sin6_addr本端 socket 的实际 IPv6 地址。 ngx_memcmp按字节比较两块内存第三个参数 16 即 IPv6 地址长度128 位/16 字节。 0相等说明该连接实际到达的本地 IP 就是这个监听地址。 一旦找到匹配的地址立即跳出循环。此时 i 就是匹配项的索引 将匹配到的那一项地址配置块conf 字段的地址赋给 hc-addr_conf。#2-3-2 当协议族不是 AF_INET6 时按 IPv4 处理 转换为 sockaddr_in 指针准备提取 IPv4 地址 addr port-addrs; 指向 IPv4 的地址配置数组 遍历除通配符外的所有具体 IPv4 地址. addr[i].addr 监听地址的 IPv4 地址以 in_addr_t 整型存储。 sin-sin_addr.s_addr 本端 socket 的实际 IPv4 地址同样为整型。 直接进行整型相等比较比 memcmp 更简单高效。 匹配成功跳出循环 hc-addr_conf addr[i].conf; 赋值地址配置意义同 IPv6 分支。#3-1 情况2单地址port-naddrs 1 当端口只有一个监听地址可能是具体 IP 也可能是 * 不需要通过地址查找直接取 addrs[0].conf 即可。 这里依然根据协议族区分4 选定默认 server 配置/* the default server configuration for the address:port */hc-conf_ctxhc-addr_conf-default_server-ctx;addr_conf 是地址级别的配置 其内部有一个 default_server 指针 指向该监听地址上配置的默认虚拟主机server 块。 default_server 由 listen 指令的 default_server 参数指定 若无则为第一个 server。 ctx 是该 server 块的配置上下文 它保存了所有 HTTP 模块在 server 级别产生的配置结构 通过 ngx_http_get_module_srv_conf 能从中取出所需配置。 为什么此时就要选定默认 server 因为客户端请求尚未到达我们还不知道 Host 头会是什么无法确定最终处理请求的 server。 但初始阶段已有一些工作需要 server 级配置信息 例如读取请求头时的超时时间 client_header_timeout以及是否需要 SSL 握手。 因此先临时使用默认 server 的配置上下文后续解析请求头后 若 Host 指向另一个 server会在 ngx_http_process_request 的阶段重新进行 server 跳转。5 初始化 HTTP 日志上下文ctxngx_palloc(c-pool,sizeof(ngx_http_log_ctx_t));if(ctxNULL){ngx_http_close_connection(c);return;}ctx-connectionc;ctx-requestNULL;ctx-current_requestNULL;c-log-connectionc-number;c-log-handlerngx_http_log_error;c-log-datactx;c-log-actionwaiting for request;c-log_errorNGX_ERROR_INFO;6 设置事件回调6-1 设置默认事件回调revc-read;rev-handlerngx_http_wait_request_handler;c-write-handlerngx_http_empty_handler;rev c-read 取得读事件对象此后反复使用。 rev-handler ngx_http_wait_request_handler 将读事件回调设为等待客户端 HTTP 请求头的函数。 这是标准 HTTP/1.x 的起点该函数会读取数据解析请求行和头并在完成后进入请求处理流程。 c-write-handler ngx_http_empty_handler 写事件回调先设为一个空函数只打印调试日志 因为此时没有任何响应数据需要发送 写事件不应该被触发如果意外触发空处理安全无害。6-2 协议前置处理HTTP/2#if(NGX_HTTP_V2)if(hc-addr_conf-http2){rev-handlerngx_http_v2_init;}#endif如果编译时包含了 HTTP/2 模块 并且该监听地址配置了 http2 选项 则直接覆盖读事件回调为 ngx_http_v2_init。 ngx_http_v2_init 负责处理 HTTP/2 的连接前言preface和 SETTINGS 帧 将连接升级到 HTTP/2 模式。这意味着不再走 HTTP/1.x 的请求头读取路径。6-3 协议前置处理SSL/TLS#if(NGX_HTTP_SSL){ngx_http_ssl_srv_conf_t*sscf;sscfngx_http_get_module_srv_conf(hc-conf_ctx,ngx_http_ssl_module);if(sscf-enable||hc-addr_conf-ssl){hc-ssl1;c-log-actionSSL handshaking;rev-handlerngx_http_ssl_handshake;}}#endif获取 SSL server 配置 判断 SSL 是否启用两个条件满足其一即可 sscf-enable在 server 块里显示写了 ssl on; hc-addr_conf-ssl在 listen 指令中直接写了 ssl 参数如 listen 443 ssl;。 如果启用 SSL hc-ssl 1标记此连接为 SSL 连接后续各阶段可据此做特殊处理。 更新 action 为 SSL handshaking以便日志反映当前正在 SSL 握手。 将读事件回调替换为 ngx_http_ssl_handshake 这个函数会接管连接进行 SSL/TLS 握手。握手完成后 它会根据是否协商出 HTTP/2 等进一步设置最终的请求处理回调 这里存在一个明显的优先级 如果 HTTP/2 和 SSL 都开启由于 SSL 代码在后面rev-handler 最终会被设为 ngx_http_ssl_handshake 即先进行 SSL 握手握手成功后再由 SSL 内部逻辑触发 HTTP/2 的初始化。 这是正确的顺序因为 HTTP/2 over TLS 必须先完成 TLS 握手。6-4 协议前置处理PROXY 协议if(hc-addr_conf-proxy_protocol){hc-proxy_protocol1;c-log-actionreading PROXY protocol;}PROXY 协议用于在代理场景下传递真实客户端 IP。 如果该监听地址开启了 proxy_protocol 选项 则先在 HTTP 上下文标记 proxy_protocol 1 并更新 action。7 处理数据已就绪的情形if(rev-ready){/* the deferred accept(), iocp */if(ngx_use_accept_mutex){ngx_post_event(rev,ngx_posted_events);return;}rev-handler(rev);return;}rev-ready 如果在注册事件之前内核已经将数据放在了 socket 接收缓冲区 例如使用了 TCP_DEFER_ACCEPT 或在 Windows IOCP 下 那么此时读事件已经就绪无需再等待 epoll/kqueue 通知。 使用 accept 互斥锁时的处理 ngx_post_event(rev, ngx_posted_events) 将事件放入 ngx_posted_events 延迟队列 等互斥锁释放后再批量执行。 这是为了避免在持有锁期间执行复杂的请求处理逻辑导致其他 worker 长时间阻塞。 未使用互斥锁时 直接调用当前设置好的 handler可能是 SSL 握手、HTTP/2 初始化或等待请求立即开始处理数据。 无论哪种情况直接 return 不再执行后面的定时器和事件注册因为事件已经是就绪状态不需要额外触发。8 设置超时并注册读事件cscfngx_http_get_module_srv_conf(hc-conf_ctx,ngx_http_core_module);ngx_add_timer(rev,cscf-client_header_timeout);ngx_reusable_connection(c,1);if(ngx_handle_read_event(rev,0)!NGX_OK){ngx_http_close_connection(c);return;}}获取核心配置 cscf 是默认 server 的 ngx_http_core_srv_conf_t 其中包含 client_header_timeout默认为 60s 表示等待客户端发送完整请求头的最大时间。 添加定时器 ngx_add_timer(rev, cscf-client_header_timeout) 将读事件挂到红黑树定时器上 超时后会调用读事件的 handler ngx_reusable_connection(c, 1) 标记连接为“可重用” 将连接设置为可复用是为了在连接资源即将耗尽时将虽然还没有超时 但空闲时间最长的连接回收用来接受新的客户端连接 注册读事件 ngx_handle_read_event(rev, 0) 将读事件正式添加到事件驱动机制epoll/kqueue 等中 开始监听客户端数据。 flags0 表示普通模式如果失败则关闭连接。