Nginx 是事件驱动的 HTTP、流和邮件代理及服务器。应将配置、进程权限和上游超时视为生产代码:重新加载前验证变更,并保留一份已知可用的配置。
事件驱动 I/O #
Nginx 围绕非阻塞事件循环构建。主进程读取配置并启动工作进程;每个工作进程随后等待 epoll、kqueue 或平台上等效的就绪事件后端。工作进程为每个对端维护一条连接记录,并且仅在套接字就绪时推进相应请求。
因此,一个事件循环无需为每个请求分配一个线程就能处理大量连接。该模型也解释了日志记录和缓冲策略的重要性:上游、客户端与磁盘写入独立推进时,工作进程必须保持响应。
隔离配置 #
测试本地构建时,应使用明确的前缀和配置运行实例。日志和临时文件等相对路径以该前缀为基准解析。
nginx -p "$PWD" -c nginx.conf
nginx -t -p "$PWD" -c nginx.conf对于非特权本地进程,应使用非特权监听端口,并将 pid、日志以及所有请求正文或上游临时路径设置为可写位置。daemon off; 和 master_process off; 可简化前台调试,但并非生产环境设置。
include 指令以文本方式引入相对于前缀的配置或绝对路径配置。使用 nginx -t 验证完整的包含关系图,并使用 nginx -T 检查展开后的配置。重新加载时,主进程解析并验证新配置;成功后启动新工作进程,并要求旧工作进程在完成当前工作后退出。解析或资源获取失败时,旧配置会继续提供流量服务。
虚拟服务器与 CORS #
Nginx 根据监听器和 Host 标头选择 HTTP 虚拟服务器。在同一监听器内,选择顺序依次为精确的 server_name、最长的前导通配符、最长的尾随通配符,最后是第一个匹配的正则表达式。server_name .example.com 是根域名和 *.example.com 的简写;仅 *.example.com 无法匹配根域名。
选定服务器后,Nginx 根据前缀和正则表达式规则查找 location。精确 location 会立即胜出;否则 Nginx 记住最长前缀,并按顺序评估符合条件的正则表达式。^~ 修饰符会在该前缀匹配后阻止正则表达式检查。内部重写可能重新开始 location 搜索,因此代理行为与可见请求不同时,应跟踪最终 URI。
允许凭据时,CORS 必须明确指定许可来源。* 不能与 Access-Control-Allow-Credentials: true 组合使用。仅在来源匹配许可列表后反射该来源,并添加 Vary: Origin,因为共享缓存不得将带凭据的响应复用于另一个来源。
map $http_origin $cors_origin {
default "";
"~^https://(app|admin)\.example\.com$" $http_origin;
}
server {
location /api/ {
if ($cors_origin != "") {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Vary Origin always;
}
proxy_pass http://api_upstream;
}
}处理预检 OPTIONS 请求时,应使用相同的许可列表,并明确指定允许的方法和标头。不要将不可信的 Host 标头转发给使用它进行授权或生成 URL 的上游;必要时将 Host 设置为预期的上游名称。
代理的 GET 请求 #
HTTP 将 GET 定义为安全方法,但应用程序仍可能使其执行成本很高。GET 请求中的内容没有通用的既定语义,因此除非私有契约定义并保护这种行为,否则上游不应依赖 GET 正文。
Nginx 可以根据 proxy_next_upstream 在上游失败后重试代理请求,因此一次重试可能使上游应用程序多次执行同一个 GET。GET 处理程序应避免副作用,配置明确的重试策略,并对创建工作的操作使用幂等键或其他方法。
上游响应超时不能证明上游未执行任何操作。在代理和应用程序边界记录请求 ID,再用这些 ID 区分代理重试、客户端重试和重复消息。
默认情况下,代理会保留请求方法和正文,同时使用由 Nginx 控制的标头构造新的上游请求。应明确设置 authority 和转发元数据:
location /api/ {
proxy_set_header Host api.internal.example;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://api_upstream/;
}由于此处的 proxy_pass 包含 URI,与 /api/ 匹配的规范化部分会在转发前替换为 /。如果 proxy_pass 中没有 URI,Nginx 会按文档所述的重写规则转发请求 URI。修改尾随斜杠时,应检查该差异。除非上游契约也会有意处理或丢弃原始请求正文,否则不要将变更状态的请求转换为 GET,也不要为此使用 proxy_method。
源码架构 #
Nginx 启动一个读取配置并管理工作进程的主进程。工作进程使用 epoll 或 kqueue 等事件机制,在 ngx_connection_t 中维护连接,并通过处理程序阶段推进请求处理。模块声明核心、事件、HTTP、流或邮件等类型,并注册指令、配置创建及合并函数和运行时处理程序。
每个模块都公开一个全局 ngx_module_t 描述符。模块类型告诉 Nginx 哪些配置结构和初始化钩子应归为一组。核心源文件和额外模块在构建期间连接起来,另有少量模块可以构建为动态模块并在之后加载。
HTTP 请求处理由重写、访问、内容和日志等阶段组成。解析请求行和标头后,请求依次经过这些阶段,因此模块可以挂接到某个阶段,而无需负责整条请求路径。
日志内部机制 #
ngx_log_error_core() 是核心日志函数。ngx_log_error() 和 ngx_log_debug() 等上层宏对它进行封装,并遵循配置的日志级别。只有构建和运行时配置均允许时,调试日志才会被编译并启用。
error_log 指令控制错误的输出位置。访问日志与之分离,并且有意不与错误日志视为同一个通道。在调试构建中,debug 日志级别可用于跟踪请求经过配置解析、location 查找、阶段处理程序和上游处理的过程。
从固定版本的源码使用 --with-debug 构建调试二进制文件,以专用前缀运行,并检查 debug 级别的错误日志。阅读源码时,从配置解析和 cycle 初始化开始,再沿事件循环跟踪一个已接受的连接,依次经过 HTTP 请求行解析、location 配置、阶段处理程序和上游处理。
评论
在 GitHub 上阅读或评论(发表评论需要登录)