在上一个篇章我们理解了nginx 处理HTTP 头部的原理跟流程,我们继续细化的看下 nginx 处理HTTP请求的过程。
Nginx,作为当今最流行的开源Web服务器之一,以其高性能、高稳定性和丰富的功能而闻名。在处理HTTP请求的过程中,Nginx采用了模块化的设计,将整个请求处理流程划分为若干个阶段,每个阶段都可以由特定的模块来处理。这种设计不仅使得Nginx具有极高的灵活性和可扩展性,而且也方便了开发者对Nginx进行定制和优化。我们将深入探讨Nginx处理HTTP请求的11个阶段,揭示其背后的工作原理。
在Nginx中,一个HTTP请求从接收到响应的整个生命周期被划分为以下11个阶段:
我们来详细的看下nginx 处理的 11 个阶段
这一步是在请求读取之后执行的。在这个阶段,Nginx 可能会执行一些在读取请求头和请求体之后需要进行的操作,例如清理请求体数据,以便后续模块可以使用。里面有一个realip模块:
realip
模块用于记录和设置客户端的真实 IP 地址。在代理服务器中,客户端的真实 IP 可能被隐藏在 X-Forwarded-For 头或者通过代理协议传递。realip
模块可以帮助确定真实的客户端 IP 地址,这对于日志记录、安全控制等非常有用。
在HTTP协议中,为了穿越多个代理层并最终确定用户的真实IP地址,通常会使用两个特定的HTTP头部字段:X-Forwarded-For
和X-Real-IP
。这两个字段在处理通过Nginx等反向代理服务器传输的请求时非常重要。
X-Forwarded-For
头部字段用于传递客户端请求经过的所有代理服务器的IP地址。这个头部通常包含一个或多个IP地址,它们按照请求经过代理的顺序排列。如果请求直接发送到服务器,则此头部可能不存在或只包含客户端的IP地址。在存在多个代理的情况下,X-Forwarded-For
头部将包含一个由逗号分隔的IP地址列表,其中列表中的最后一个IP地址是客户端的真实IP地址。X-Real-IP
头部字段旨在记录客户端的真实IP地址,它只包含一个IP地址。这个头部字段由第一个代理服务器设置,并且在请求穿越后续代理时不会被更改,因此它代表了客户端的原始IP地址。 拿到真实用户IP后,Nginx可以通过基于变量的方式来使用这些信息。例如,binary_remote_addr
和remote_addr
这样的变量可以被设置为客户端的真实IP地址。这些变量可以在Nginx的配置文件中使用,以实现各种基于IP地址的功能,如:
limit_conn
模块允许Nginx限制来自单个IP地址的连接数。这是通过使用remote_addr
变量来实现的,它可以识别每个连接的客户端IP地址。通过这种方式,Nginx可以防止单个客户端建立过多的连接,从而保护服务器免受拒绝服务攻击或过度负载。limit_req
模块可以限制来自单个IP地址的请求速率。这同样依赖于remote_addr
变量来追踪每个请求的来源。通过限制每个客户端的请求频率,Nginx有助于防止恶意用户或爬虫对服务器造成过大压力。realip模块是Nginx的一个关键组件,它允许Nginx识别和使用客户端的真实IP地址。这对于反向代理配置、日志记录、访问控制和安全策略等场景至关重要。
realip
模块在Nginx的默认编译版本中是不包含的。--with-http_realip_module
参数,可以启用realip
模块。X-Real-IP
和X-Forwarded-For
头部字段才会被Nginx接受和处理。X-Real-IP
还是X-Forwarded-For
头部来确定客户端的真实IP地址。X-Real-IP
头部的值,即客户端的真实IP地址。如果该头部不存在,则变量为空。realip
模块启用并正确配置后,它会被设置为客户端的真实IP地址。X-Forwarded-For
头部的值,这是一个IP地址列表,记录了客户端以及所有中间代理服务器的IP。realip
模块是否已经递归地处理了X-Forwarded-For
头部字段。realip_remote_addr
这个变量用于存储由 realip
模块解析出的真实客户端IP地址。当 realip
模块启用并且配置正确时,它会覆盖 $remote_addr
变量,确保Nginx使用的是客户端的真实IP地址。realip_remote_port
变量包含客户端的端口号。如果 X-Real-IP
头部字段中包含了端口号的信息,那么 realip_remote_port
变量就会使用这个端口号。这有助于Nginx在处理请求时,能够获取到完整的客户端连接信息,包括IP地址和端口号。realip
模块可以确保日志中记录的是客户端的真实IP地址,这对于分析和审计非常重要。realip
模块有助于防止欺诈和滥用行为,确保服务器的安全。 值得注意的是,limit_conn
和limit_req
模块必须在preaccess
阶段生效,这是因为在postread
阶段之后,请求体可能已经被读取,某些信息可能不再可用。在preaccess
阶段,Nginx已经获取了请求头中的X-Forwarded-For
和X-Real-IP
信息,并将其存储在相应的变量中,这时可以基于这些变量来实施限制。这样,Nginx能够确保连接限制和请求速率限制是在接收到足够信息的情况下进行的,从而有效地管理客户端请求。
在 Nginx 的处理流程中,rewrite 阶段扮演着至关重要的角色。该阶段主要负责对请求的 URI 进行重写操作,从而实现复杂的路由逻辑。rewrite 阶段分为两个子阶段:server_rewrite 和 rewrite。这两个子阶段都依赖于 rewrite 模块来执行 URI 的重写操作。
在 rewrite 模块中,return 指令起着决定性的作用。当 Nginx 执行到 return 指令时,它会立即停止进一步的处理,并根据指令的内容返回相应的响应。return 指令的语法非常灵活,可以返回状态码、文本或 URL,具体取决于实际的应用场景。
让我们深入了解一下 return 指令的工作原理。return 指令的基本语法如下:
return code [text];:根据指定的状态码和可选的文本返回响应。return code URL;:根据指定的状态码和 URL 进行重定向。return URL;:直接返回指定的 URL。
状态码的种类繁多,涵盖了 Nginx 自定义状态码以及 HTTP 标准状态码。
在实际应用中,return 指令经常与 error_page 指令一起使用。error_page 指令用于定义当特定错误代码发生时应该如何处理。通过配置 error_page,可以为用户提供友好的错误页面,而不是简单地显示一个生硬的错误代码。
可以找一些具体的例子来加深对 error_page 指令的理解:
error_page 404 /404.html;
:当请求返回 404 错误时,Nginx 会返回 /404.html 页面。error_page 500 502 503 504 /50x.html;
:当请求返回 500、502、503 或 504 错误时,Nginx 都会返回 /50x.html 页面。现在,我们来探讨一个实际的问题:当 server 块下包含 error_page 指令,而 location 块下也有 return 指令时,Nginx 会优先执行哪个指令?通过实际测试,我们发现 Nginx 会先执行 location 块下的 return 指令。这是因为 location 块具有更高的优先级,它会覆盖 server 块下的相关设置。
除了 return 指令外,rewrite 指令也是 rewrite 模块中的重要组成部分。rewrite 指令用于根据正则表达式匹配请求的 URI,并将其替换为新的 URI。这使得我们能够实现复杂的 URL 重写规则,从而满足不同业务场景的需求。
rewrite 指令的语法如下:
rewrite regex replacement [flag];:根据正则表达式 regex 匹配请求的 URI,并将其替换为 replacement 指定的新 URI。可选的 flag 参数用于指定后续行为。
rewrite 指令的功能非常强大,它不仅可以实现简单的 URI 替换,还可以利用正则表达式和变量进行复杂的匹配和提取操作。此外,通过设置不同的 flag 参数,我们可以控制重写后的 URI 如何被进一步处理。
让我们通过一个实际的例子来演示 how rewrite 指令工作。假设我们有以下目录结构:
html/first/:包含 1.txt 文件html/second/:包含 2.txt 文件html/third/:包含 3.txt 文件
配置文件如下所示:
server { listen 80; server_name rewrite.ziyang.com;
root html/; location /first { rewrite /first(.*) /second$1 last; return 200 'first!\n'; }
location /second { rewrite /second(.*) /third$1; return 200 'second!\n'; }
location /third { return 200 'third!\n'; }}
当请求到达 /first/3.txt 时,由于 location /first 中的 rewrite 规则,URI 会被重写为 /second/3.txt。然后,Nginx 会继续在 location /second 中查找匹配的规则。由于 /second/3.txt 与 location /second 的匹配模式相匹配,因此 Nginx 会再次执行 rewrite 规则,将 URI 重写为 /third/3.txt。最后,Nginx 在 location /third 中找到匹配的规则,并返回 'third!\n' 作为响应。
通过以上示例,我们可以看到 rewrite 指令如何根据正则表达式匹配和替换 URI,从而实现复杂的路由逻辑。同时,我们也了解了 return 指令的工作原理以及它与 error_page 指令的关系。这些知识对于编写高效、可维护的 Nginx 配置文件至关重要。
在 Nginx 中,
find_config
阶段发生在 URI 重写之后,其目的是在服务器的配置中找到与请求 URI 匹配的location
块。这一阶段是 Nginx 处理请求过程中的路由环节,确保请求被正确地引导到相应的处理程序。
location
指令是 Nginx 配置中用于定义请求处理上下文的关键部分。它允许开发者根据不同的 URI 路径、正则表达式或命名位置来设置特定的配置。
以下是 location 指令的基本语法:
location [ = | ~ | ~* | ^~ ] uri { # 配置指令}
=
:精确匹配,仅当 URI 完全匹配时才使用此 location
块。^~
:前缀匹配,如果匹配成功,则不再搜索正则表达式 location
块。~
:大小写敏感的正则表达式匹配。~*
:大小写不敏感的正则表达式匹配。此外,还可以使用命名位置:
location @name { # 配置指令}
merge_slashes 指令
merge_slashes
指令控制 Nginx 是否合并 URI 中的重复斜杠 /
。默认情况下,merge_slashes
被设置为 on
,这意味着 Nginx 会自动将 URI
中的多个连续斜杠合并为一个。这个行为对于大多数应用来说是透明的,但在处理如 base64 编码的 URI 时,可能需要关闭此功能。
merge_slashes on | off;
Nginx 的 location
匹配遵循以下规则:
^~
前缀的 location
块将进行前缀匹配,如果匹配成功,则 Nginx 将不会考虑任何正则表达式 location
块。=
前缀的 location
块将进行精确匹配,仅当请求的 URI 完全等同于 location
块中定义的 URI 时才会匹配。~
或 ~*
前缀的 location
块将根据正则表达式进行匹配。~
是大小写敏感的,而 ~*
是大小写不敏感的。@
前缀的 location
块定义了一个命名位置,可以通过 error_page
或 try_files
等指令进行内部跳转。location
时,Nginx 仅考虑 URI 的路径部分,忽略查询字符串。location
块匹配同一个 URI,Nginx 将使用第一个找到的匹配项。这是又一次的重写阶段,但这次是在找到匹配的 location 之后。这允许 location 级别的重写规则来修改请求的 URI。
在所有的重写规则应用之后,Nginx 执行这个阶段的处理。这可以用于执行一些在 URI 重写之后需要进行的操作,例如权限检查或者额外的日志记录。
这个阶段包括
limit_conn
和limit_req
指令,用于限制每个客户端的连接数和请求速率。这是一个访问控制的阶段,可以在允许请求访问服务器资源之前进行一些限制。
在 Nginx 的请求处理流程中,preaccess
阶段是一个关键的控制点,它在实际请求处理之前执行,主要用于处理如限制并发连接数和访问频率等任务。以下是对 Nginx 中 limit_conn
和 limit_req
模块的详细描述,以及相关指令的语法和应用场景。
limit_conn
模块(ngx_http_limit_conn_module
)用于限制每个客户端的并发连接数。以下是该模块的关键特性和指令语法:
NGX_HTTP_PREACCESS_PHASE
,即在请求进入 preaccess
阶段时生效。--without-http_limit_conn_module
禁用。key
的设计,通常基于真实 IP 地址进行限制。key
:limit_conn_zone key zone=name:size;
key
:用于限制的变量,通常是客户端的真实 IP 地址。zone
:共享内存区域的名称和大小。imit_conn zone number;
limit_conn_log_level info | notice | warn | error;
limit_conn_status code;
limit_req
模块(ngx_http_limit_req_module
)用于限制客户端的访问频率,以防止滥用或过载。以下是该模块的关键特性和指令语法:
NGX_HTTP_PREACCESS_PHASE
。--without-http_limit_req_module
禁用。漏桶算法是一种恒定的速率限制算法,它定义了一个固定容量的桶,请求像水一样流入桶中,并以恒定速率流出。如果桶满了,额外的请求就会被拒绝。指令语法
key
和限制速率:limit_req_zone key zone=name:size rate=rate;
key
:用于限制的变量,通常是客户端的真实 IP 地址。zone
:共享内存区域的名称和大小。rate
:请求的处理速率,单位为 r/s(每秒处理的请求数)或 r/m(每分钟处理的请求数)。limit_req zone=name [burst=number] [nodelay];
zone
:指定共享内存区域的名称。burst
:桶的初始容量,默认为 0。nodelay
:如果设置,即使请求在桶中也会被立即拒绝。limit_req_log_level info | notice | warn | error;
limit_req_status code;
code
:当请求频率超过限制时返回给客户端的错误码,默认为 503。 通过合理配置 limit_conn
和 limit_req
模块,Nginx 能够有效地控制客户端的并发连接数和访问频率,从而提高服务器的稳定性和安全性。
ACCESS
在这个阶段,Nginx 执行 auth_basic
和 access
指令,用于基于用户凭证和 IP 地址等条件来控制访问。auth_request
指令允许子请求来验证用户是否有权限访问资源。
在 Nginx 的请求处理流程中,access
阶段负责执行访问控制和用户认证。以下是对 Nginx 中 access
模块、auth_basic
模块和 auth_request
模块的描述,以及相关指令的语法和应用场景。
access 模块
access
模块(ngx_http_access_module
)提供了基于 IP 地址的访问控制。以下是该模块的关键特性和指令语法:
NGX_HTTP_ACCESS_PHASE
,即在请求进入 access
阶段时生效。--without-http_access_module
禁用。指令语法
allow address | CIDR | unix: | all;deny address | CIDR | unix: | all;
address
:指定的 IP 地址。CIDR
:使用无类别域间路由(CIDR)表示法的 IP 地址范围。unix:
:用于 Unix 套接字的访问控制。all
:匹配所有地址。allow
和 deny
指令是顺序执行的。一旦匹配成功,后续指令将不再执行。auth_basic 模块
auth_basic
模块用于实现基于 HTTP Basic Authentication 的用户认证。以下是该模块的关键特性和指令语法:
NGX_HTTP_ACCESS_PHASE
。--without-http_auth_basic_module
禁用。指令语法
auth_basic string | off;
string
:认证领域,如 "test auth_basic"
。auth_basic_user_file file;
file
:包含用户名和密码的文件,通常使用 htpasswd
工具生成。命令创建密码文件:
htpasswd -c file -b user pass
auth_request 模块
auth_request
模块允许 Nginx 将认证请求转发给上游服务。以下是该模块的关键特性和指令语法:
NGX_HTTP_ACCESS_PHASE
。--with-http_auth_request_module
启用。指令语法
auth_request uri | off;
uri
:上游服务的 URI,用于处理认证请求。auth_request_set $variable value;
satisfy 指令
satisfy
指令控制 access
阶段模块的满足条件:
all
,则必须所有 access
阶段的模块都满足条件才会放行请求。any
,则任意一个模块满足条件即可放行请求。satisfy all | any; 常见问题
return
指令与 access
阶段的关系:return
指令属于 rewrite
阶段,在 access
阶段之前执行,因此如果 return
指令返回了响应,access
阶段将不会生效。access
模块的顺序影响:auth_basic
与密码输入:satisfy
设置为 any
,且 auth_basic
模块满足条件,那么即使输入了正确的密码,请求也会被放行,因为后续的 deny all
将不会被执行。allow all
与密码输入的机会:allow all
,由于 allow
指令属于 access
模块,它会在 auth_basic
模块之前执行,因此用户将没有机会输入密码,请求将被立即放行。 通过这些模块和指令,Nginx 提供了灵活的访问控制和用户认证机制,以满足不同的安全需求。
POST ACCESS
在请求通过访问控制之后,Nginx 执行这个阶段的处理。这可以用于执行一些在访问控制之后需要进行的操作,例如记录请求到后端的时间等。
PRECONTENT
在生成内容之前,Nginx 执行这个阶段的处理。try_files
指令通常在这个位置使用,用于尝试按顺序查找文件,如果找不到,则返回 404 或者执行其他操作。
在 Nginx 的请求处理流程中,precontent
阶段是内容生成之前的最后一个阶段。在这个阶段,Nginx 可以执行一些操作,如尝试提供静态文件或重定向到其他 URI。以下是对 Nginx 中 try_files
指令和 mirror
模块的描述,以及相关指令的语法和应用场景。
try_files 指令
try_files
指令允许 Nginx 尝试按顺序查找并提供多个文件。如果请求的文件存在,则 Nginx 会直接返回该文件的内容;如果所有列出的文件都不存在,则 Nginx 可以返回特定的错误码或重定向到一个 URI。
指令语法
try_files file ... uri;try_files file ... =code;
file
:Nginx 将尝试按顺序提供这些文件。uri
:如果所有文件都不存在,Nginx 将重定向到这个 URI。code
:如果所有文件都不存在,Nginx 将返回这个 HTTP 状态码。上下文
server
:可以在服务器块中使用。location
:可以在 location 块中使用。默认值
try_files
指令不被设置。模块
ngx_http_try_files_module
:提供 try_files
指令的模块。mirror 模块
mirror
模块允许 Nginx 在处理请求时生成子请求,并将请求镜像到其他服务。这个模块对于负载测试或将流量复制到多个环境非常有用。
指令语法
mirror uri | off;mirror_request_body on | off;
uri
:指定子请求的目标 URI。off
:关闭镜像功能。mirror_request_body
:控制是否包括请求体在子请求中。默认值
mirror
默认关闭。mirror_request_body
默认开启。模块
ngx_http_mirror_module
:提供镜像功能的模块。编译选项
--without-http_mirror_module
移除模块。应用场景
try_files
可以简化静态文件的提供过程。例如,如果请求 /images/image.png
并且该文件存在,则直接提供;如果不存在,则可能重定向到 /404.html
。try_files
可以创建软链接,将请求重定向到另一个 URI。mirror
模块可以复制请求到多个服务,进行负载测试。mirror
模块还可以用于将请求实时复制到开发、测试和生产环境,以便于调试和监控。 通过合理配置 try_files
指令和 mirror
模块,Nginx 能够灵活地处理请求,提供静态文件,以及进行有效的负载测试和流量复制
CONTENT
这是内容生成阶段。Nginx 根据请求和配置生成响应内容。index
指令用于定义目录索引,autoindex
用于自动生成目录索引,concat
用于合并多个文件作为响应发送。
Nginx 的 content
阶段是处理 HTTP 请求的最终阶段,其中涉及到的模块负责生成或提供请求的内容。以下是对 Nginx 中 static
模块、index
模块、autoindex
模块以及 concat
模块的描述,以及相关指令的语法和应用场景。
static 模块
static
模块在 Nginx 中用于提供静态文件内容,它通过 root
或 alias
指令将 URL 映射为文件路径。
alias 指令
alias path;
location
root 指令
root path;
root html;
http
, server
, location
, if in location
应用场景
root
通常用于映射网站根目录。alias
用于为特定的 location 创建快捷路径。index 模块
index
模块用于指定当请求以 /
结尾的目录时,Nginx 应返回的 index 文件。
指令语法
index file ...;
index index.html;
http
, server
, location
应用场景
http://example.com/
这样的 URL 时,Nginx 会尝试提供 index.html
或其他指定的文件。autoindex 模块
autoindex
模块可以自动生成目录索引,以 HTML 格式展示目录内容。
指令语法
autoindex on | off;
autoindex off;
http
, server
, location
其他相关指令
autoindex_exact_size
autoindex_format
autoindex_localtime
应用场景
autoindex
模块。concat 模块
concat
模块由阿里巴巴开发,用于合并多个小文件请求,提升 HTTP 请求性能。
指令语法
concat on | off;
default concat off;
http
, server
, location
其他相关指令
concat_types
:指定可以合并的文件 MIME 类型。concat_unique
:控制是否允许重复文件的合并。concat_max_files
:设置合并文件的最大数量。concat_delimiter
:定义合并文件时的分隔符。concat_ignore_file_error
:控制是否忽略文件错误。应用场景
concat
模块可以减少 HTTP 请求次数,提高网站性能。 通过这些模块和指令,Nginx 能够高效地处理静态资源的提供、目录索引的生成以及小文件合并,从而优化网站的性能和用户体验。
LOG
最后,在 access_log
阶段,Nginx 记录请求的日志。日志可以记录请求的处理结果、性能数据等,对于分析服务器性能和调试问题非常重要。
在 Nginx 的请求处理流程中,log
阶段是最后一个阶段,负责将 HTTP 请求的相关信息记录到日志文件中。以下是对 Nginx 中 log
模块的功能、访问日志格式、日志文件路径配置、日志缓存、日志压缩以及对包含变量的日志文件名的优化的描述。
log 模块
log
模块(ngx_http_log_module
)是 Nginx 中用于记录日志的核心模块,无法被禁用。它负责生成和维护访问日志,这对于分析流量、监控请求和排错非常重要。
访问日志格式
Nginx 允许自定义访问日志的格式,使用 log_format
指令定义。
指令语法
log_format name [escape=default|json|none] string ...;
name
:自定义的日志格式名称。escape
:设置变量值的转义方式。string
:日志的具体格式,可以包含各种变量,如 $request
、$status
等。默认日志格式
Nginx 默认使用 combined
格式,包含以下信息:
log_format combined '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"';
配置日志文件路径
access_log
指令用于指定日志文件的存储路径和格式。
指令语法
access_log path [format=name [buffer=size] [gzip[=level]] [flush=time] [if=condition]];access_log off;
path
:日志文件的路径,可以包含变量。format
:指定日志格式的名称。buffer
:设置日志写入的缓冲区大小。gzip
:启用日志文件的压缩,并可选地设置压缩级别。flush
:设置日志刷新的频率。if
:通过条件判断来控制是否记录日志。日志缓存 日志缓存功能可以减少磁盘 I/O 操作,通过批量写入日志来提高性能。 写入磁盘的条件
flush
指定的时间。日志压缩 日志压缩功能可以在写入磁盘前压缩日志内容,以节省存储空间。 功能特点
对日志文件名包含变量的优化
open_log_file_cache
指令用于优化包含变量的日志文件名的处理。
指令语法
open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];open_log_file_cache off;
max
:缓存内的最大文件句柄数。inactive
:文件句柄在这段时间内未被访问则会被关闭。min_uses
:在 inactive
时间内,文件句柄被访问的次数超过 min_uses
才会保持在内存中。valid
:缓存的日志文件在这段时间后会进行检查,确认文件是否仍然存在。off
:关闭日志文件句柄的缓存功能。 通过合理配置 log
模块,Nginx 能够高效地记录和维护访问日志,同时提供了日志格式自定义、日志文件路径配置、日志缓存和压缩等高级功能,以满足不同的日志记录需求。
通过上述11个阶段的解析,我们可以看到Nginx在处理HTTP请求时的严谨和细致。每个阶段都承担着特定的任务,确保了请求能够正确、高效地被处理。Nginx的模块化设计和清晰的处理流程,不仅提高了服务器的性能,也极大地方便了SRE进行功能扩展和问题排查。理解这些阶段对于深入学习Nginx原理、优化配置和解决实际问题具有重要意义。
具体可看微信公号:五分钟学SRE
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。