前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >五分钟学NGINX-详解Nginx 如何处理 HTTP 头部

五分钟学NGINX-详解Nginx 如何处理 HTTP 头部

作者头像
五分钟学SRE
发布2024-04-16 16:49:29
2060
发布2024-04-16 16:49:29
举报
文章被收录于专栏:五分钟学SRE五分钟学SRE

Nginx 作为高性能的 HTTP 服务器和反向代理服务器,在处理 HTTP 请求时,对 HTTP 头部的处理是至关重要的一环。

接收请求事件模块

Nginx 使用了一个事件驱动的架构,这使得它能够高效地处理大量的并发连接。下面是 Nginx 处理 HTTP 请求的详细流程:

1. 建立连接

三次握手:客户端通过发送 SYN 包开始与 Nginx 建立 TCP 连接。Nginx 响应 SYN+ACK 包,客户端再次发送 ACK 包完成握手。 负载均衡:如果有多个 worker 进程在监听相同的端口,操作系统的负载均衡机制会选择一个 worker 进程来处理新的连接。 读事件:一旦连接建立,Nginx 的事件模块会监听来自客户端的数据。当有数据到达时,操作系统会通知 Nginx。

Nginx Worker 负载均衡选择

Nginx 使用事件驱动和异步 I/O 来处理请求,它的工作进程(worker processes)可以并行处理多个客户端连接。当多个工作进程监听相同的端口时,操作系统的负载均衡机制会介入:

  • 操作系统负载均衡:现代操作系统(如 Linux)通常具备内核级别的负载均衡功能。当多个进程监听同一个端口时,操作系统内核会使用特定的算法(如 round-robin)来分配新的连接请求到不同的工作进程。这种机制在多个Nginx工作进程监听同一个端口时非常有效。
  • Nginx 工作进程模型:Nginx 的配置文件中可以设置 worker_processes 指令,这决定了 Nginx 将启动多少个工作进程。每个工作进程都是独立的,并且能够处理自己的连接和请求。
读事件监听与处理

一旦 TCP 连接建立,Nginx 需要准备接收客户端发送的数据。这是通过 Nginx 的事件模块来实现的:

  1. I/O 多路复用:Nginx 使用 epoll(或其他类似的 I/O 多路复用技术)来同时监控多个网络套接字上的事件。epoll 允许 Nginx 以非阻塞的方式检测哪些套接字上有数据可读。
  2. 事件通知:当操作系统检测到某个网络套接字上有数据到达时,epoll 会通知 Nginx。Nginx 的事件模块会捕获这个事件,并将事件加入到事件队列中。
  3. 事件处理:Nginx 会从事件队列中取出事件,并调用相应的处理函数来读取数据。在 Nginx 的源码中,这个处理函数通常是 ngx_event_accept,它会处理新的连接请求。

2. 接收请求

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 来监控大量的网络套接字,以检测哪些套接字上有数据可读或可写。

  • 事件循环:Nginx 通过 epoll_wait 进入一个事件循环,在这个循环中,Nginx 会阻塞等待事件发生。当 epoll_wait 返回时,它提供了一组就绪的文件描述符(即套接字),这些套接字上的数据已经准备好读取或写入。
  • 非阻塞 I/O:由于 epoll 是非阻塞的,Nginx 可以在等待事件发生时执行其他任务,例如处理其他连接或执行定时任务。
读取请求数据

一旦 epoll_wait 检测到读事件,Nginx 将调用相应的处理函数来读取客户端发送的数据。这个过程在 Nginx 源码中是由 ngx_http_wait_request_handler 函数负责的。

  • 请求处理链ngx_http_wait_request_handler 函数是请求处理链的一部分,它负责从客户端读取请求行和请求头。
  • 缓冲区管理:读取到的数据会被存储在 Nginx 配置的缓冲区中,这个缓冲区由 client_header_buffer_sizelarge_client_header_buffers 指令控制其大小。
  • 状态机解析:Nginx 使用内部的状态机来解析请求行和请求头。状态机根据 HTTP 协议的规范逐步解析请求数据,并将其存储在 ngx_http_request_t 结构体中。
  • 请求上下文:解析过程中,Nginx 会为每个请求创建一个请求上下文,其中包含了请求的所有信息,如方法、URI、头部字段等。这个上下文会在请求的整个生命周期中被使用。

3. 分配内存资源

分配连接内存池:Nginx 会为每个新的连接分配一个连接内存池,其大小由 connection_pool_size 配置指令指定,默认为 512 字节。 设置回调方法:Nginx 会设置回调方法 ngx_http_init_connection 来处理新的连接,并将其读事件加入到 epoll 监控中。 添加超时定时器:为了防止客户端长时间不发送请求,Nginx 会添加一个超时定时器 client_header_timeout,默认为 60 秒。

分配连接内存池

Nginx 使用内存池来管理连接相关的数据,这样可以提高内存使用的效率并减少内存分配和释放的开销。

  • connection_pool_size:这个配置指令定义了每个连接的连接内存池的大小。默认情况下,这个大小设置为 512 字节,足以存储大多数连接状态信息。
  • 连接内存池的结构:在 Nginx 源码中,ngx_connection_t 结构体代表了单个连接,它包含了连接的状态、套接字文件描述符、地址信息等。ngx_connection_t 结构体中的某些字段会使用连接内存池来存储数据。
设置回调方法

Nginx 通过设置回调方法来处理新的连接,这是事件驱动编程的一个重要部分。

  • ngx_http_init_connection:这个回调方法在新的连接建立时被调用,它负责初始化连接状态、设置读取事件的处理函数,并准备接收客户端的 HTTP 请求。
  • epoll 监控ngx_http_init_connection 会将新连接的读取事件注册到 epoll 系统中,这样当有数据可读时,epoll 能够通知 Nginx。
添加超时定时器

为了防止客户端长时间不发送请求,Nginx 会为每个连接设置一个超时定时器。

  • client_header_timeout:这个配置指令定义了客户端发送请求头的超时时间,默认为 60 秒。如果在这个时间内客户端没有发送任何数据,Nginx 会认为连接已经超时,并关闭连接。
  • 超时处理:在源码中,超时处理通常由 ngx_http_request_handler 或类似的处理函数来管理。当超时发生时,Nginx 会停止等待客户端的数据,并关闭连接。

4. 用户正式请求

读取数据:Nginx 读取客户端发送的数据,并将其存储在读缓冲区中。 分配读缓冲区:读缓冲区的大小由 client_header_buffer_size 配置指令指定,默认为 1KB。(这个值也不是越大越好,因为当用户有一个请求进来的时候,nignx 就会分配1kb 内存出来,)

在 Nginx 处理用户正式请求的过程中,读取数据和分配读缓冲区是两个基础而关键的步骤。这两个步骤确保了 Nginx 能够接收和存储客户端发送的 HTTP 请求数据。以下是结合 Nginx 底层原理与源码对这两个步骤的详细展开:

读取数据

Nginx 通过读取客户端发送的数据来开始处理 HTTP 请求。这个过程是在 I/O 事件触发时进行的,通常是在 epoll 事件循环中,当检测到读事件(即客户端发送数据)时,Nginx 会执行以下操作:

  • 读取数据到缓冲区:Nginx 使用 read 系统调用来从网络套接字读取数据。读取的数据被存储在一个特定的缓冲区中,这个缓冲区称为读缓冲区。
  • 处理分片数据:由于网络传输的特性,客户端发送的数据可能会被分片(即分成多个数据包)。Nginx 需要处理这些分片,以便正确地重组 HTTP 请求。
分配读缓冲区

读缓冲区是用来临时存储从客户端接收到的数据的内存区域。Nginx 为每个连接分配一个读缓冲区,以便存储请求头和请求体。

  • client_header_buffer_size:这个配置指令定义了读缓冲区的初始大小,默认为 1KB。这个大小适用于大多数标准的 HTTP 请求头。
  • 动态扩展:虽然默认大小为 1KB,但 Nginx 的读缓冲区是可以根据需要动态扩展的。如果接收到的请求头超过了 1KB,Nginx 会根据需要分配更多的内存。这种动态分配机制确保了 Nginx 能够有效地处理各种大小的请求,同时避免了不必要的内存浪费。
  • 内存管理:在 Nginx 源码中,内存管理是通过 ngx_pool_t 结构体来实现的,它提供了内存分配和释放的功能。ngx_palloc 函数用于从内存池中分配内存,而 ngx_pfree 用于释放内存。

上面是nginx 处理连接,下面我们来看下nginx 处理请求的过程,处理请求的过程跟处理连接是不一样的,因为系统需要进行大量的上下文分析,分析http 协议跟http的header 信息。

接收请求HTTP模块

1. 解析请求

状态机解析请求行:Nginx 使用状态机来解析客户端发送的 HTTP 请求行,这包括请求方法、URI 和 HTTP 版本。 接收 URI 和 Header:Nginx 继续读取请求的 URI 和 Header 信息。

在 Nginx 的工作流程中,解析请求是一个至关重要的步骤,它涉及到从客户端接收的原始 HTTP 请求中提取出有用的信息,如请求方法、URI 和 HTTP 版本等。这一过程是通过状态机来实现的,状态机是一种编程模式,用于按顺序处理输入数据,这里是指 HTTP 请求的不同部分。以下是结合 Nginx 底层原理与源码对解析请求过程的详细展开:

状态机解析请求行

Nginx 使用内部的状态机来逐行解析客户端发送的 HTTP 请求。状态机的主要任务是识别请求行中的各个组成部分,并将其存储在相应的数据结构中。

  • 请求行的结构:HTTP 请求行通常包含三个部分:请求方法(如 GET、POST)、请求的资源路径(URI)和 HTTP 版本(如 HTTP/1.1)。
  • 状态机的工作方式:状态机根据当前读取的字符和预定义的规则(如空格分隔方法和 URI)来确定请求的各个部分。状态机在 Nginx 源码中通常由一系列函数和跳转表组成,这些函数会根据解析的进度调用彼此。
  • 错误处理:如果在解析过程中遇到不符合 HTTP 协议规范的数据,状态机会触发错误处理机制,这可能导致请求被拒绝或产生 400 错误响应。
接收 URI 和 Header

在请求行被成功解析之后,Nginx 会继续读取请求的 URI 和 Header 信息。

  • 读取数据:Nginx 会从客户端读取更多的数据,直到遇到请求头的结束标志(即两个连续的换行符)。
  • 解析 Header:每个 HTTP 头部由一个字段名、一个冒号和一个字段值组成。Nginx 会逐个解析这些头部字段,并将它们存储在请求的上下文中,以便后续的处理阶段可以使用。
  • 内存管理:在解析过程中,如果遇到大的请求头或 URI,Nginx 会动态地分配更多的内存来存储这些数据。这是通过 large_client_header_buffers 指令来控制的,它定义了可以分配的最大内存块的数量和大小。

2. 分配内存资源

分配请求内存池:为了存储请求数据,Nginx 会分配一个请求内存池,其大小由 request_pool_size 指令指定,默认为 4KB。 分配大内存:如果请求行或 Header 超过了基础的内存池大小,Nginx 会根据 large_client_header_buffers 指令分配更大的内存块,该指令默认设置为 4 个 8KB 的内存块。

分配请求内存池

当一个 HTTP 请求到达 Nginx 时,Nginx 需要一块内存区域来存储请求的各个部分,包括请求行(包含方法、URI 和 HTTP 版本)、请求头(包含各种头部字段)以及可能的请求体(例如 POST 请求中的数据)。为了高效地管理这些数据,Nginx 使用了一个称为“内存池”的机制。

  • request_pool_size:这个指令定义了每个请求的内存池的大小,默认为 4KB。这个内存池足够存储大多数请求的头部和部分请求体数据。
分配大内存

在某些情况下,请求的头部或请求行可能会非常大,超出了默认的 4KB 内存池的限制。例如,如果客户端发送了一个包含大量头部字段的请求,或者 URI 非常长,那么就需要更多的内存来存储这些数据。

  • large_client_header_buffers:这个指令允许 Nginx 分配更大的内存块来存储请求头部。默认情况下,它设置为 4 个 8KB 的内存块。这意味着 Nginx 可以处理最大 32KB 的请求头部。
  • large_client_header_buffers 的工作机制:当状态机解析请求头时,如果发现当前的内存池不足以存储更多的数据,Nginx 会动态地分配一个 8KB 的大内存块,然后把1KB里面的内容拷贝到这个8KB里面。这些大内存块是按需分配的,只有当实际需要时才会分配更多的内存。
状态机解析请求行

Nginx 使用内部的状态机来解析客户端发送的 HTTP 请求行和请求头。状态机是一种编程模型,它根据输入数据(在这种情况下是 HTTP 请求的各个部分)和当前的状态来决定下一步的操作。

  • 解析请求行:状态机首先解析请求行,这包括识别 HTTP 方法(如 GET、POST 等)、URI 和 HTTP 版本。这一步骤需要从接收到的数据中提取这些关键信息,并为后续的处理做准备。
  • 解析请求头:请求行之后,状态机开始解析请求头。它会逐行读取头部数据,并根据头部字段的名称和值执行相应的操作。例如,如果遇到 Content-Length 字段,状态机需要记录下请求体的大小,以便后续能够正确地读取请求体。
  • 动态内存分配:在解析过程中,如果状态机发现需要更多的内存来存储请求头或请求行,它会触发内存分配机制,如上文所述的 large_client_header_buffers

通过这种机制,Nginx 能够灵活地处理各种大小的 HTTP 请求,同时保持内存使用的高效性。状态机的解析过程是 Nginx 请求处理的核心部分,它确保了请求数据的正确解析和后续处理的顺利进行。

3.标识 URI、状态机解析 Header 和分配大内存

在 Nginx 处理 HTTP 请求的过程中,标识 URI、状态机解析 Header 和分配大内存是关键步骤,这些步骤确保了 Nginx 能够正确解析客户端请求并为后续处理做好准备。以下是结合 Nginx 底层原理与源码对这些步骤的详细展开:

标识 URI

  1. 解析请求行:Nginx 首先使用状态机解析请求行,这包括识别请求方法(如 GET 或 POST)、URI 和 HTTP 版本。
  2. URI 处理:解析出的 URI 会被进一步处理,Nginx 会根据配置的路由规则和重写规则来确定最终的请求目标。
  3. 位置匹配:Nginx 会查找与请求的 URI 匹配的 location 块,这决定了请求将如何被处理,例如转发到代理服务器或直接提供静态文件。

状态机解析 Header

  1. 读取请求头:在请求行被解析之后,Nginx 继续读取请求头。请求头包含了客户端传递的元数据,如 HostUser-AgentContent-Type 等。
  2. 状态机:Nginx 使用一个内部状态机来逐行解析请求头。状态机根据 HTTP 协议规范和请求头的格式来逐个处理头部字段。
  3. 存储头部信息:解析出的头部信息被存储在 ngx_http_request_t 结构体中,以便在后续的请求处理阶段中使用。

分配大内存

  1. 默认内存池:每个请求都会分配一个默认大小的内存池,由 request_pool_size 指令指定,默认为 4KB。
  2. 大内存需求:如果请求头的大小超过了默认内存池的容量,Nginx 需要分配额外的内存来存储这些数据。
  3. 动态内存分配:Nginx 根据 large_client_header_buffers 指令动态分配额外的内存。这个指令定义了可以分配的最大内存块的数量和大小,通常设置为 4 个 8KB 的内存块。

标识 Header

  1. 处理头部字段:一旦请求头被读取和解析,Nginx 会根据头部字段的内容执行特定的操作。例如,如果存在 Content-Length 字段,Nginx 会知道请求体的大小,并准备读取相应的数据。
  2. 变量赋值:Nginx 会将请求头中的某些值赋给内部变量,这些变量可以在配置文件中引用,用于重写规则、日志记录等。
  3. 模块处理:不同的 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 使用定时器来实现超时机制。当请求头和请求行被成功解析后,Nginx 会检查是否有设置超时定时器,并将其移除,因为此时请求已经被认为是有效的。
  • 事件通知:如果超时发生,Nginx 会收到一个事件通知,这将触发一个内部函数来关闭连接并释放资源。
11 个阶段的 HTTP 请求处理

Nginx 将每个请求的处理分为 11 个阶段,每个阶段负责处理特定的任务。这种模块化的设计使得 Nginx 能够灵活地配置和扩展其功能。

  1. 服务器重写阶段 (NGX_HTTP_SERVER_REWRITE_PHASE):在这个阶段,Nginx 可以重写请求的 URI 和请求方法。
  2. 查找配置阶段 (NGX_HTTP_FIND_CONFIG_PHASE):Nginx 根据请求的 URI 查找最合适的服务器配置。
  3. 重写阶段 (NGX_HTTP_REWRITE_PHASE):在这个阶段,Nginx 可以根据 location 块进一步重写 URI。
  4. 权限检查阶段 (NGX_HTTP_ACCESS_PHASE):Nginx 检查客户端是否有权限访问请求的资源。
  5. 内容生成阶段 (NGX_HTTP_CONTENT_PHASE):在这个阶段,Nginx 调用内容生成模块来生成响应内容。
  6. 日志记录阶段 (NGX_HTTP_LOG_PHASE):Nginx 记录请求的日志信息。
  7. 其他阶段:还包括尝试文件阶段、文件查找阶段、错误处理阶段等。

上面的所有步骤都是nginx 的框架执行的,后面的11 个阶段的 HTTP 请求处理 是nginx http 模块的执行流程

5. 详细处理流程

代码语言:javascript
复制
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:关闭请求阶段,用于清理请求资源。

6. 结束处理

  • 发送响应:Nginx 根据处理结果构建 HTTP 响应,并将其发送给客户端。
  • 清理资源:请求结束后,Nginx 释放分配的内存资源,并关闭连接或保持连接以待后续请求。

Nginx 处理 HTTP 头部的过程是高效且灵活的,它通过精细的内存管理和状态机解析,确保了在各种情况下都能快速准确地处理客户端请求。我们在下一篇详细的学习Nginx 处理 HTTP 请求的 11 个阶段的过程与原理。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 五分钟学SRE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 接收请求事件模块
    • 1. 建立连接
      • 2. 接收请求
        • 3. 分配内存资源
          • 4. 用户正式请求
          • 接收请求HTTP模块
            • 1. 解析请求
              • 2. 分配内存资源
                • 3.标识 URI、状态机解析 Header 和分配大内存
                  • 标识 URI
                  • 状态机解析 Header
                  • 分配大内存
                  • 标识 Header
                  • 5. 详细处理流程
                  • 6. 结束处理
              相关产品与服务
              负载均衡
              负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档