Nginx 作为高性能的 HTTP 服务器和反向代理服务器,在处理 HTTP 请求时,对 HTTP 头部的处理是至关重要的一环。
Nginx 使用了一个事件驱动的架构,这使得它能够高效地处理大量的并发连接。下面是 Nginx 处理 HTTP 请求的详细流程:
三次握手:客户端通过发送 SYN 包开始与 Nginx 建立 TCP 连接。Nginx 响应 SYN+ACK 包,客户端再次发送 ACK 包完成握手。 负载均衡:如果有多个 worker 进程在监听相同的端口,操作系统的负载均衡机制会选择一个 worker 进程来处理新的连接。 读事件:一旦连接建立,Nginx 的事件模块会监听来自客户端的数据。当有数据到达时,操作系统会通知 Nginx。
Nginx 使用事件驱动和异步 I/O 来处理请求,它的工作进程(worker processes)可以并行处理多个客户端连接。当多个工作进程监听相同的端口时,操作系统的负载均衡机制会介入:
worker_processes
指令,这决定了 Nginx 将启动多少个工作进程。每个工作进程都是独立的,并且能够处理自己的连接和请求。一旦 TCP 连接建立,Nginx 需要准备接收客户端发送的数据。这是通过 Nginx 的事件模块来实现的:
epoll
(或其他类似的 I/O 多路复用技术)来同时监控多个网络套接字上的事件。epoll
允许 Nginx 以非阻塞的方式检测哪些套接字上有数据可读。epoll
会通知 Nginx。Nginx 的事件模块会捕获这个事件,并将事件加入到事件队列中。ngx_event_accept
,它会处理新的连接请求。epoll_wait:Nginx 使用
epoll_wait
系统调用来等待 I/O 事件的发生,如客户端发送的数据到达。 读取请求:当epoll_wait
检测到读事件时,Nginx 会调用ngx_http_wait_request_handler
来读取客户端发送的 HTTP 请求数据。
epoll_wait 系统调用
epoll_wait
是 Linux 系统中用于等待 I/O 事件的系统调用,它是 epoll
I/O 多路复用机制的一部分。Nginx 使用 epoll
来监控大量的网络套接字,以检测哪些套接字上有数据可读或可写。
epoll_wait
进入一个事件循环,在这个循环中,Nginx 会阻塞等待事件发生。当 epoll_wait
返回时,它提供了一组就绪的文件描述符(即套接字),这些套接字上的数据已经准备好读取或写入。epoll
是非阻塞的,Nginx 可以在等待事件发生时执行其他任务,例如处理其他连接或执行定时任务。 一旦 epoll_wait
检测到读事件,Nginx 将调用相应的处理函数来读取客户端发送的数据。这个过程在 Nginx 源码中是由 ngx_http_wait_request_handler
函数负责的。
ngx_http_wait_request_handler
函数是请求处理链的一部分,它负责从客户端读取请求行和请求头。client_header_buffer_size
和 large_client_header_buffers
指令控制其大小。ngx_http_request_t
结构体中。分配连接内存池:Nginx 会为每个新的连接分配一个连接内存池,其大小由
connection_pool_size
配置指令指定,默认为 512 字节。 设置回调方法:Nginx 会设置回调方法ngx_http_init_connection
来处理新的连接,并将其读事件加入到epoll
监控中。 添加超时定时器:为了防止客户端长时间不发送请求,Nginx 会添加一个超时定时器client_header_timeout
,默认为 60 秒。
Nginx 使用内存池来管理连接相关的数据,这样可以提高内存使用的效率并减少内存分配和释放的开销。
ngx_connection_t
结构体代表了单个连接,它包含了连接的状态、套接字文件描述符、地址信息等。ngx_connection_t
结构体中的某些字段会使用连接内存池来存储数据。Nginx 通过设置回调方法来处理新的连接,这是事件驱动编程的一个重要部分。
ngx_http_init_connection
会将新连接的读取事件注册到 epoll
系统中,这样当有数据可读时,epoll
能够通知 Nginx。为了防止客户端长时间不发送请求,Nginx 会为每个连接设置一个超时定时器。
ngx_http_request_handler
或类似的处理函数来管理。当超时发生时,Nginx 会停止等待客户端的数据,并关闭连接。读取数据:Nginx 读取客户端发送的数据,并将其存储在读缓冲区中。 分配读缓冲区:读缓冲区的大小由
client_header_buffer_size
配置指令指定,默认为 1KB。(这个值也不是越大越好,因为当用户有一个请求进来的时候,nignx 就会分配1kb 内存出来,)
在 Nginx 处理用户正式请求的过程中,读取数据和分配读缓冲区是两个基础而关键的步骤。这两个步骤确保了 Nginx 能够接收和存储客户端发送的 HTTP 请求数据。以下是结合 Nginx 底层原理与源码对这两个步骤的详细展开:
Nginx 通过读取客户端发送的数据来开始处理 HTTP 请求。这个过程是在 I/O 事件触发时进行的,通常是在 epoll
事件循环中,当检测到读事件(即客户端发送数据)时,Nginx 会执行以下操作:
read
系统调用来从网络套接字读取数据。读取的数据被存储在一个特定的缓冲区中,这个缓冲区称为读缓冲区。读缓冲区是用来临时存储从客户端接收到的数据的内存区域。Nginx 为每个连接分配一个读缓冲区,以便存储请求头和请求体。
ngx_pool_t
结构体来实现的,它提供了内存分配和释放的功能。ngx_palloc
函数用于从内存池中分配内存,而 ngx_pfree
用于释放内存。上面是nginx 处理连接,下面我们来看下nginx 处理请求的过程,处理请求的过程跟处理连接是不一样的,因为系统需要进行大量的上下文分析,分析http 协议跟http的header 信息。
状态机解析请求行:Nginx 使用状态机来解析客户端发送的 HTTP 请求行,这包括请求方法、URI 和 HTTP 版本。 接收 URI 和 Header:Nginx 继续读取请求的 URI 和 Header 信息。
在 Nginx 的工作流程中,解析请求是一个至关重要的步骤,它涉及到从客户端接收的原始 HTTP 请求中提取出有用的信息,如请求方法、URI 和 HTTP 版本等。这一过程是通过状态机来实现的,状态机是一种编程模式,用于按顺序处理输入数据,这里是指 HTTP 请求的不同部分。以下是结合 Nginx 底层原理与源码对解析请求过程的详细展开:
Nginx 使用内部的状态机来逐行解析客户端发送的 HTTP 请求。状态机的主要任务是识别请求行中的各个组成部分,并将其存储在相应的数据结构中。
在请求行被成功解析之后,Nginx 会继续读取请求的 URI 和 Header 信息。
large_client_header_buffers
指令来控制的,它定义了可以分配的最大内存块的数量和大小。分配请求内存池:为了存储请求数据,Nginx 会分配一个请求内存池,其大小由
request_pool_size
指令指定,默认为 4KB。 分配大内存:如果请求行或 Header 超过了基础的内存池大小,Nginx 会根据large_client_header_buffers
指令分配更大的内存块,该指令默认设置为 4 个 8KB 的内存块。
当一个 HTTP 请求到达 Nginx 时,Nginx 需要一块内存区域来存储请求的各个部分,包括请求行(包含方法、URI 和 HTTP 版本)、请求头(包含各种头部字段)以及可能的请求体(例如 POST 请求中的数据)。为了高效地管理这些数据,Nginx 使用了一个称为“内存池”的机制。
在某些情况下,请求的头部或请求行可能会非常大,超出了默认的 4KB 内存池的限制。例如,如果客户端发送了一个包含大量头部字段的请求,或者 URI 非常长,那么就需要更多的内存来存储这些数据。
Nginx 使用内部的状态机来解析客户端发送的 HTTP 请求行和请求头。状态机是一种编程模型,它根据输入数据(在这种情况下是 HTTP 请求的各个部分)和当前的状态来决定下一步的操作。
Content-Length
字段,状态机需要记录下请求体的大小,以便后续能够正确地读取请求体。large_client_header_buffers
。通过这种机制,Nginx 能够灵活地处理各种大小的 HTTP 请求,同时保持内存使用的高效性。状态机的解析过程是 Nginx 请求处理的核心部分,它确保了请求数据的正确解析和后续处理的顺利进行。
在 Nginx 处理 HTTP 请求的过程中,标识 URI、状态机解析 Header 和分配大内存是关键步骤,这些步骤确保了 Nginx 能够正确解析客户端请求并为后续处理做好准备。以下是结合 Nginx 底层原理与源码对这些步骤的详细展开:
location
块,这决定了请求将如何被处理,例如转发到代理服务器或直接提供静态文件。Host
、User-Agent
、Content-Type
等。ngx_http_request_t
结构体中,以便在后续的请求处理阶段中使用。request_pool_size
指令指定,默认为 4KB。large_client_header_buffers
指令动态分配额外的内存。这个指令定义了可以分配的最大内存块的数量和大小,通常设置为 4 个 8KB 的内存块。Content-Length
字段,Nginx 会知道请求体的大小,并准备读取相应的数据。Sec-WebSocket-Key
字段以支持 WebSocket 连接。
4. 处理请求
移除超时定时器:在请求行和 Header 被成功解析之后,Nginx 会移除之前设置的
client_header_timeout
超时定时器,该定时器默认设置为 60 秒,用于检测客户端是否在超时时间内发送完整的请求头。 开始 11 个阶段的 HTTP 请求处理:Nginx 将请求处理分为 11 个阶段,每个阶段可以包含多个模块的处理函数。这些阶段包括重写、权限检查、内容生成、日志记录等。
处理请求是 Nginx 接收到客户端 HTTP 请求后的核心环节,涉及到多个阶段的执行和多个模块的参与。以下是结合 Nginx 底层原理与源码对处理请求过程的详细展开:
在 Nginx 配置中,client_header_timeout
指令用于设置读取客户端请求头的超时时间。如果在指定时间内,Nginx 未能接收到完整的请求头,那么连接将被关闭以避免资源占用。
Nginx 将每个请求的处理分为 11 个阶段,每个阶段负责处理特定的任务。这种模块化的设计使得 Nginx 能够灵活地配置和扩展其功能。
NGX_HTTP_SERVER_REWRITE_PHASE
):在这个阶段,Nginx 可以重写请求的 URI 和请求方法。NGX_HTTP_FIND_CONFIG_PHASE
):Nginx 根据请求的 URI 查找最合适的服务器配置。NGX_HTTP_REWRITE_PHASE
):在这个阶段,Nginx 可以根据 location 块进一步重写 URI。NGX_HTTP_ACCESS_PHASE
):Nginx 检查客户端是否有权限访问请求的资源。NGX_HTTP_CONTENT_PHASE
):在这个阶段,Nginx 调用内容生成模块来生成响应内容。NGX_HTTP_LOG_PHASE
):Nginx 记录请求的日志信息。上面的所有步骤都是nginx 的框架执行的,后面的11 个阶段的 HTTP 请求处理 是nginx http 模块的执行流程
NGX_HTTP_SERVER_REWRITE_PHASE:服务器重写阶段,用于修改请求的 URI。
NGX_HTTP_FIND_CONFIG_PHASE:查找配置阶段,用于确定请求应该由哪个 server 块处理。
NGX_HTTP_REWRITE_PHASE:重写阶段,用于修改请求的 URI 和头部。
NGX_HTTP_POST_REWRITE_PHASE:重写后阶段,用于处理重写后的结果。
NGX_HTTP_PREACCESS_PHASE:访问前阶段,用于进行权限检查。
NGX_HTTP_ACCESS_PHASE:访问阶段,用于执行访问控制。
NGX_HTTP_POST_ACCESS_PHASE:访问后阶段,用于执行访问控制后的清理工作。
NGX_HTTP_TRY_FILES_PHASE:尝试文件阶段,用于尝试查找请求的文件。
NGX_HTTP_CONTENT_PHASE:内容生成阶段,用于生成响应的内容。
NGX_HTTP_LOG_PHASE:日志记录阶段,用于记录请求的日志。
NGX_HTTP_CLOSE_REQUEST_PHASE:关闭请求阶段,用于清理请求资源。
Nginx 处理 HTTP 头部的过程是高效且灵活的,它通过精细的内存管理和状态机解析,确保了在各种情况下都能快速准确地处理客户端请求。我们在下一篇详细的学习Nginx 处理 HTTP 请求的 11 个阶段的过程与原理。