连接、会话、请求、日志#
本指南跟踪流量在 Angie 中的流转:连接处理方法,HTTP 请求或 TCP/UDP 会话经过的各个阶段,如何选择服务器(HTTP 还包括 location),以及请求如何被代理和记录。
相关模块
连接处理机制#
Angie 支持多种连接处理方法。特定方法的可用性取决于所使用的平台。在支持多种方法的平台上,Angie 通常会自动选择最高效的方法。但是,如有必要,可以使用 use 指令显式选择连接处理方法。
以下是可用的连接处理方法:
方法 | 描述 |
|---|---|
| 标准方法。在没有更高效方法的平台上会自动构建支持模块。可以使用 |
| 标准方法。在没有更高效方法的平台上会自动构建支持模块。可以使用 |
| 高效方法,可用于 FreeBSD 4.1+、OpenBSD 2.9+、NetBSD 2.0 和 macOS。 |
| 高效方法,可用于 Linux 2.6+。 |
| 高效方法,可用于 Solaris 7 11/99+、HP/UX 11.22+ (eventport)、IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+。 |
|
|
HTTP 请求处理#
HTTP 请求会经历一系列阶段,在每个阶段执行特定类型的处理。
| 初始阶段。在此阶段调用 RealIP 模块。 |
| 处理 Rewrite 模块中定义在 |
| 特殊阶段,根据请求 URI 选择 location。 |
| 类似于 |
| 特殊阶段,如果在 |
| 在此阶段,标准 Angie 模块(如 Limit Req)注册它们的处理程序。 |
| 验证客户端是否有权发出请求的阶段,通常通过调用标准 Angie 模块(如 Auth Basic)来实现。 |
| 特殊阶段,处理 satisfy any 指令。 |
| |
| 通常生成响应的阶段。多个标准 Angie 模块在此阶段注册它们的处理程序,包括 Index。proxy_pass、fastcgi_pass、uwsgi_pass、scgi_pass 和 grpc_pass 指令也在此处理。 处理程序按顺序调用,直到其中一个产生输出。 |
| 最后阶段,执行请求日志记录。目前只有 Log 模块在此阶段注册其处理程序以进行访问日志记录。 |
TCP/UDP 会话处理#
来自客户端的 TCP/UDP 会话会经历一系列阶段,在每个阶段执行特定类型的处理:
| 接受客户端连接后的初始阶段。在此阶段调用 RealIP 模块。 |
| 检查访问权限的预备阶段。在此阶段调用 Set 模块。 |
| 在实际数据处理之前限制客户端访问的阶段。在此阶段调用 Access 模块。 |
| 发生 TLS/SSL 终止的阶段。在此阶段调用 SSL 模块。 |
| 将初始数据字节读入 预读缓冲区 的阶段,以允许模块(如 SSL Preread)在处理之前分析数据。 |
| 实际处理数据的强制阶段,通常涉及 Return 模块向客户端发送响应。proxy_pass 指令也在此处理。 |
| 记录客户端会话处理结果的最后阶段。在此阶段调用 Log 模块。 |
为会话选择服务器#
Angie 为会话选择 server 块的方式,与它为 HTTP 连接选择服务器块完全相同:都依据接受连接的地址和端口进行匹配;匹配规则以及每个对的默认服务器,参见 按 IP 地址和端口选择服务器。同一端口的 TCP 对和 UDP 对相互独立,各自都有自己的默认服务器:使用 listen 9000 udp; 的服务器不会收到端口 9000 的 TCP 连接。
在匹配到的对内,会话的服务器名称仅来自 SNI:可以由 SSL 模块终止 TLS 并从握手中读取该名称,也可以由 SSL Preread 模块在不终止连接的情况下读取该名称。然后 Angie 将该名称与监听该对的各服务器的 server_name 条目进行比对;没有 SNI 名称的会话,或名称与其中任何一个都不匹配的会话,均由该对的默认服务器处理。
处理请求#
虚拟服务器选择#
最初,连接是在默认服务器的上下文中创建的。然后可以在请求处理的以下阶段确定服务器名称,每个阶段都参与服务器配置的选择:
在 SSL 握手期间,根据 SNI 提前确定。
处理请求行之后。
处理
Host头字段之后。
如果在处理请求行或 Host 头字段之后未确定服务器名称,Angie 将使用空名称作为服务器名称。
在这些阶段的每一个阶段,都可能应用不同的服务器配置。因此,某些指令应谨慎指定:
对于 ssl_protocols 指令,协议列表由 OpenSSL 库在根据通过 SNI 请求的名称应用服务器配置之前设置。因此,协议应仅为默认服务器指定。
client_header_buffer_size 和 merge_slashes 指令在读取请求行之前应用。因此,这些指令使用默认服务器配置或由 SNI 选择的服务器配置。
对于 ignore_invalid_headers、large_client_header_buffers 和 underscores_in_headers 指令,它们参与处理请求头字段,服务器配置还取决于是否根据请求行或
Host头字段进行了更新。错误响应使用当前正在处理请求的服务器中的 error_page 指令处理。
基于名称的虚拟服务器#
Angie 首先确定哪个服务器应该处理请求。考虑一个简单的配置,其中所有三个虚拟服务器都监听端口 80:
server {
listen 80;
server_name example.org www.example.org;
# ...
}
server {
listen 80;
server_name example.net www.example.net;
# ...
}
server {
listen 80;
server_name example.com www.example.com;
# ...
}
在此配置中,Angie 仅依据 Host 头字段确定应由哪个服务器处理请求。如果该头字段的值与任何服务器名称都不匹配,Angie 会将请求路由到 *:80 对的默认服务器。在上面的配置中,默认服务器是第一个服务器——这是 Angie 的标准默认行为。也可以使用 listen 指令中的 default_server 参数显式指定哪个服务器应作为默认服务器:
server {
listen 80 default_server;
server_name example.net www.example.net;
# ...
}
备注
请注意,默认服务器是 address:port 对的属性,而不是服务器名称的属性。
国际化名称#
国际化域名(IDN)应在 server_name 指令中使用 ASCII(Punycode)表示形式指定:
server {
listen 80;
server_name xn--e1afmkfd.xn--80akhbyknj4f; # пример.испытание
# ...
}
阻止未定义服务器名称的请求#
如果不应允许没有 Host 头字段的请求,可以定义一个简单丢弃此类请求的服务器:
server {
listen 80;
server_name "";
return 444;
}
在此配置中,服务器名称设置为空字符串,它匹配没有 Host 头字段的请求。然后返回一个特殊的非标准代码 444,该代码会关闭连接。
按 IP 地址和端口选择服务器#
Angie 分两步为连接选择 server 块:先按接受该连接的地址和端口,再按名称。
配置中的 listen 指令定义了 Angie 接受连接所使用的 address:port 对。端口不同、地址族不同或传输方式不同的对,彼此之间永远不会争夺同一个连接:quic 监听属于 UDP,因此即使某个端口上还有 TCP 监听,它也拥有自己的对。连接到达时,Angie 会将接受该连接的地址与该端口的各个对进行匹配:明确指定的地址优先匹配,*:port 是最后的选择。因此,监听 *:port 的服务器拥有自己的对,并不能替代该端口上监听明确地址的那些服务器。匹配到的对决定了后续选择的其余部分:Angie 会按名称测试哪些服务器、其中哪一个是默认服务器,以及该连接适用 ssl、http2、quic 和 proxy_protocol 中的哪些参数。UNIX 域套接字路径在同样的意义上也是一个对:它有自己的默认服务器和一组服务器名称。
然后 Angie 将 Host 头字段与那些 listen 指令指定了该匹配对的服务器的 server_name 条目进行比对;监听同一端口其他地址(包括 *:port)的服务器永远不会被考虑在内。如果没有名称匹配,请求将由该对的默认服务器处理:即配置中第一个为该对提供服务的服务器,除非另有服务器通过 default_server 参数将其指定为该对的默认服务器。不带主机名的请求会与空服务器名称匹配,具体见 阻止未定义服务器名称的请求;在 HTTP/2 和 HTTP/3 中,此类请求会在选定服务器之前就被拒绝。如果两个服务器都声称是同一个对的默认服务器,Angie 会拒绝该配置,并报告 a duplicate default server for address:port。
对于 TLS 连接,Angie 会在该对的默认服务器的配置中开始握手,因此默认情况下提供的是该服务器的 ssl_certificate,除非 SNI 名称匹配同一个对上的另一个服务器;有关如何在一个对上同时提供多个名称的服务,参见 基于名称的HTTPS服务器。
在下面的配置中,四个服务器监听端口 80——其中两个在 198.51.100.1 上,一个在 198.51.100.2 上,一个在 *:80 上:
server {
listen 198.51.100.1:80;
server_name example.org www.example.org;
# ...
}
server {
listen 198.51.100.1:80;
server_name example.net www.example.net;
# ...
}
server {
listen 198.51.100.2:80;
server_name example.com www.example.com;
# ...
}
server {
listen 80;
server_name www.example.test;
# ...
}
在 198.51.100.1:80 上接受的连接匹配该对,因此 Angie 只针对前两个服务器测试 Host 头字段。因此,对 www.example.test 的请求由其中第一个服务器处理,即 198.51.100.1:80 的默认服务器,尽管第四个服务器也带有该名称;www.example.com 同理,它属于 198.51.100.2:80 这个对。第四个服务器只处理那些在端口 80 上、且没有被其他任何 listen 指令指定的地址上接受的连接。
每个对都有自己的默认服务器,default_server 会将其指定为同一条 listen 指令所命名的那个对的默认服务器:
server {
listen 198.51.100.1:80;
server_name example.org www.example.org;
# ...
}
server {
listen 198.51.100.1:80 default_server;
server_name example.net www.example.net;
# ...
}
server {
listen 198.51.100.2:80 default_server;
server_name example.com www.example.com;
# ...
}
如果再给另一个服务器添加 listen 80 default_server;,只会设置 *:80 的默认服务器;198.51.100.1:80 的默认服务器仍然是那里带有 default_server 的服务器。
选择位置#
考虑一个简单的PHP网站配置:
server {
listen 80;
server_name example.org www.example.org;
root /data/www;
location / {
index index.html index.php;
}
location ~* \.(gif|jpg|png)$ {
expires 30d;
}
location ~ \.php$ {
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Angie 首先搜索由字面字符串给出的最具体的前缀 location,而不考虑它们的列出顺序。在上面的配置中,唯一的前缀 location 是 location /,它匹配任何请求,并将作为最后的选择。然后 Angie 按照配置文件中出现的顺序检查由正则表达式定义的 location。第一个匹配的表达式会停止搜索,Angie 将使用该 location。如果没有正则表达式匹配请求,Angie 将使用之前找到的最具体的前缀 location。
备注
所有类型的 location 仅测试请求行的 URI 部分,不包括参数。这是因为查询字符串中的参数可以以各种方式指定,例如:
/index.php?user=john&page=1/index.php?page=1&user=john
此外,查询字符串可能包含任意数量的参数:
/index.php?page=1&something+else&user=john
现在让我们看看在上述配置中如何处理请求:
请求
/logo.gif首先由前缀location /匹配,然后由正则表达式.(gif|jpg|png)$匹配。因此,它由后者 location 处理。使用指令root /data/www,请求被映射到文件/data/www/logo.gif,并将文件发送给客户端。请求
/index.php也首先由前缀location /匹配,然后由正则表达式.(php)$匹配。因此,它由后者 location 处理,请求被传递到监听localhost:9000的 FastCGI 服务器。fastcgi_param 指令将 FastCGI 参数SCRIPT_FILENAME设置为/data/www/index.php,FastCGI 服务器执行该文件。变量 $document_root 设置为root指令的值,变量 $fastcgi_script_name 设置为请求 URI,即/index.php。请求
/about.html仅由前缀location /匹配,因此在此 location 中处理。使用指令root /data/www,请求被映射到文件/data/www/about.html,并将文件发送给客户端。
处理请求 / 更为复杂。它仅由前缀 location / 匹配,因此由此 location 处理。然后 index 指令根据其参数和 root /data/www 指令测试索引文件是否存在。如果文件 /data/www/index.html 不存在但文件 /data/www/index.php 存在,该指令执行到 /index.php 的内部重定向,Angie 再次搜索 location,就像请求是由客户端发送的一样。如前所述,重定向的请求最终将由 FastCGI 服务器处理。
上述示例通过 root 指令将请求映射到文件,但完整的映射涉及多个指令的协同作用。一旦匹配到 location,Angie 将请求规范化后的 $uri 映射到文件系统路径:root 指令将完整 URI 追加到其值,而 alias 指令则替换已匹配的 location 前缀。try_files 指令随后相对于该路径逐一探测候选文件;若均不存在,其最后一个参数将触发内部重定向,Angie 再次根据新的 $uri 搜索 location,与上文索引重定向的机制相同。
对于相同的请求 /images/top.gif,以下两个 location 均将其解析到同一文件 /data/w3/images/top.gif:
location /images/ {
root /data/w3; # 追加:/data/w3 + /images/top.gif
}
location /images/ {
alias /data/w3/images/; # 替换:/images/ -> /data/w3/images/,再加 top.gif
}
使用 alias 时,匹配的前缀按其长度被移除,因此 location 末尾斜杠与 alias 值末尾斜杠必须保持一致。若将上面 alias 路径末尾的斜杠去掉,结果路径将变为 /data/w3/imagestop.gif。
代理和负载均衡#
Angie 的一个常见用途是将其设置为代理服务器。在此角色中,Angie 接收请求,将它们转发到被代理的服务器,从这些服务器检索响应,并将响应发送回客户端。
一个简单的代理服务器:
server {
location / {
proxy_pass http://backend:8080;
}
proxy_pass 指令指示 Angie 将客户端请求传递给后端 Angie 可用于将请求路由到运行使用各种框架和编程语言(如 PHP)构建的应用程序的 FastCGI 服务器。 与 FastCGI 服务器配合使用的最基本的 Angie 配置涉及使用 fastcgi_pass 指令代替 proxy_pass 指令,以及使用 fastcgi_param 指令设置传递给 FastCGI 服务器的参数。假设 FastCGI 服务器可在 此配置设置了一个服务器,该服务器将所有请求(静态图像请求除外)通过 FastCGI 协议路由到在 要将连接从 HTTP/1.1 升级到 WebSocket,需要使用 HTTP/1.1 中提供的 协议切换机制。 然而,这里有一个微妙之处:由于 Angie 实现了一种特殊的操作模式,如果被代理的服务器返回代码为 101(切换协议)的响应,并且客户端通过请求中的 如前所述,逐跳头(包括 一个更复杂的示例演示了如何根据客户端请求头中是否存在 默认情况下,如果任一方向上 60 秒内都没有数据通过连接,连接将被关闭。可以使用 proxy_read_timeout 指令增加此超时时间。或者,可以将被代理的服务器配置为定期发送 WebSocket ping 帧以重置超时并检查连接是否仍然活动。 Angie 可以作为负载均衡器,将请求分配到一组上游服务器,并按某种负载均衡方法选择服务器。有关如何选择方法、每种方法中服务器权重的含义,以及服务器故障的处理方式,请参阅 负载均衡 页面。backend:8080 (被代理的服务器)。还有许多其他 指令 可用于进一步配置代理连接。FastCGI 代理#
localhost:9000 访问。在 PHP 中,SCRIPT_FILENAME 参数用于确定脚本名称,QUERY_STRING 参数用于传递请求参数。生成的配置如下:server {
location / {
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
}
location ~ \.(gif|jpg|png)$ {
root /data/images;
}
}
localhost:9000 上运行的被代理服务器。WebSocket 代理#
Upgrade 头是一个 逐跳头,它不会从客户端传递到被代理的服务器。在正向代理中,客户端可以使用 CONNECT 方法来规避这个问题。但这种方法不适用于反向代理,因为客户端不知道任何代理服务器的存在,需要在代理服务器上进行特殊处理。Upgrade 头请求协议切换,则允许在客户端和被代理服务器之间建立隧道。Upgrade 和 Connection)不会从客户端传递到被代理的服务器。因此,为了让被代理的服务器知道客户端切换到 WebSocket 协议的意图,必须显式传递这些头:location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Upgrade 字段,来决定发送到被代理服务器的请求中 Connection 头字段的值:http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
...
location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}
负载均衡#
日志记录#
备注
除了此处列出的选项外, 您还可以启用 调试日志。
Syslog#
error_log 和 access_log 指令支持记录到 syslog。以下参数用于配置到 syslog 的日志记录:
| 指定 |
| 设置 |
| 定义 access_log 的 |
| 设置 |
| 禁止在 |
syslog 配置示例:
error_log syslog:server=192.168.1.1 debug;
access_log syslog:server=unix:/var/log/angie.sock,nohostname;
access_log syslog:server=[2001:db8::1]:12345,facility=local7,tag=angie,severity=info combined;