请求匹配器
请求匹配器 可以用于通过各种标准过滤(或分类)请求。
语法
在 Caddyfile 中,紧随指令之后的 匹配器令牌 可以限制该指令的作用域。匹配器令牌可以是以下形式之一
如果指令支持匹配器,它将在其语法文档中显示为 [<matcher>]
。匹配器令牌通常是可选的,用 [ ]
表示。如果省略匹配器令牌,则它与通配符匹配器 (*
) 相同。
示例
此指令应用于 所有 HTTP 请求
reverse_proxy localhost:9000
这与上面相同(此处 *
是不必要的)
reverse_proxy * localhost:9000
但是此指令仅应用于具有以 /api/
开头的 路径 的请求
reverse_proxy /api/* localhost:9000
要匹配路径以外的任何内容,请定义一个 命名匹配器 并使用 @name
引用它
@postfoo {
method POST
path /foo/*
}
reverse_proxy @postfoo localhost:9000
通配符匹配器
通配符(或“catch-all”)匹配器 *
匹配所有请求,仅在需要匹配器令牌时才需要。例如,如果您要给指令的第一个参数恰好也是一个路径,它看起来会很像路径匹配器!因此,您可以使用通配符匹配器来消除歧义,例如
root * /home/www/mysite
否则,此匹配器并不常用。我们通常建议在语法不需要时省略它。
路径匹配器
通过 URI 路径匹配是匹配请求的最常见方式,因此匹配器可以内联,如下所示
redir /old.html /new.html
路径匹配器令牌必须以正斜杠 /
开头。
路径匹配 默认是精确匹配,而不是前缀匹配。 您必须追加 *
以进行快速前缀匹配。请注意,/foo*
将匹配 /foo
和 /foo/
以及 /foobar
;您可能实际上想要 /foo/*
来代替。
命名匹配器
所有不是路径或通配符匹配器的匹配器都必须是命名匹配器。这是一个在任何特定指令之外定义的匹配器,可以重复使用。
使用唯一名称定义匹配器为您提供了更大的灵活性,允许您将 任何可用的匹配器 组合成一个集合
@name {
...
}
或者,如果集合中只有一个匹配器,您可以将其放在同一行
@name ...
然后您可以像这样使用匹配器,方法是将其指定为指令的第一个参数
directive @name
例如,这会将 HTTP/1.1 websocket 请求代理到 localhost:6001
,并将其他请求代理到 localhost:8080
。它匹配具有名为 Connection
的标头字段包含 Upgrade
的请求,以及另一个名为 Upgrade
的字段,其值为 websocket
example.com {
@websockets {
header Connection *Upgrade*
header Upgrade websocket
}
reverse_proxy @websockets localhost:6001
reverse_proxy localhost:8080
}
如果匹配器集仅由一个匹配器组成,则单行语法也适用
@post method POST
reverse_proxy @post localhost:6001
作为特殊情况,expression
匹配器 可以在不指定其名称的情况下使用,只要一个 带引号的 参数(CEL 表达式本身)跟在匹配器名称之后即可
@not-found `{err.status_code} == 404`
与指令一样,命名匹配器定义必须位于使用它们的站点块内。
命名匹配器定义构成一个匹配器集。集合中的匹配器是 AND 关系;即,所有匹配器都必须匹配。例如,如果您的集合中同时有 header
和 path
匹配器,则两者都必须匹配。
相同类型的多个匹配器可以合并(例如,同一集合中的多个 path
匹配器),使用布尔代数(AND/OR),如下面的各自部分所述。
对于更复杂的布尔匹配逻辑,建议使用 expression
匹配器 来编写 CEL 表达式,该表达式支持 and &&
、or ||
和 括号 ( )
。
标准匹配器
完整的匹配器文档可以在每个匹配器模块的文档中找到。
请求可以通过以下方式匹配
client_ip
client_ip <ranges...>
expression client_ip('<ranges...>')
通过客户端 IP 地址。接受精确的 IP 或 CIDR 范围。支持 IPv6 区域。
当配置了 trusted_proxies
全局选项时,此匹配器最好使用,否则它的行为与 remote_ip
匹配器完全相同。只有来自受信任代理的请求才会在请求开始时解析其客户端 IP;不受信任的请求将使用直接对等方的远程 IP 地址。
作为快捷方式,private_ranges
可以用于匹配所有私有 IPv4 和 IPv6 范围。它与指定所有这些范围相同:192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.1/8 fd00::/8 ::1
每个命名匹配器可以有多个 client_ip
匹配器,它们的范围将被合并并进行 OR 运算。
示例
匹配来自私有 IPv4 地址的请求
@private-ipv4 client_ip 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.1/8
此匹配器通常与 not
匹配器配对以反转匹配。例如,中止来自公共 IPv4 和 IPv6 地址的所有连接(这是所有私有范围的逆运算)
example.com {
@denied not client_ip private_ranges
abort @denied
respond "Hello, you must be from a private network!"
}
在 CEL 表达式 中,它看起来像这样
@my-friends `client_ip('12.23.34.45', '23.34.45.56')`
expression
expression <cel...>
通过任何返回 true
或 false
的 CEL(通用表达式语言) 表达式。
Caddy 占位符 (或 Caddyfile 简写) 可以在这些 CEL 表达式中使用,因为它们在被 CEL 环境解释之前会被预处理并转换为常规 CEL 函数调用。
大多数其他请求匹配器也可以在表达式中用作函数,这比外部表达式提供了更大的布尔逻辑灵活性。有关 CEL 表达式中支持的语法,请参阅每个匹配器的文档。
为方便起见,如果定义的命名匹配器仅由 CEL 表达式组成,则可以省略匹配器名称。CEL 表达式必须带引号(建议使用反引号或 heredoc)。这看起来很简洁
@mutable `{method}.startsWith("P")`
在这种情况下,假定为 CEL 匹配器。
示例
匹配方法以 P
开头的请求,例如 PUT
或 POST
@methods expression {method}.startsWith("P")
匹配处理程序返回错误状态代码 404
的请求,将与 handle_errors
指令 结合使用
@404 expression {err.status_code} == 404
匹配路径与两个不同正则表达式之一匹配的请求;这只能使用表达式编写,因为 path_regexp
匹配器通常每个命名匹配器只能存在一次
@user expression path_regexp('^/user/(\w*)') || path_regexp('^/(\w*)')
或者相同,省略匹配器名称,并用反引号括起来,以便将其解析为单个令牌
@user `path_regexp('^/user/(\w*)') || path_regexp('^/(\w*)')`
您可以使用 heredoc 语法 来编写多行 CEL 表达式
@api <<CEL
{method} == "GET"
&& {path}.startsWith("/api/")
CEL
respond @api "Hello, API!"
file
file {
root <path>
try_files <files...>
try_policy first_exist|first_exist_fallback|smallest_size|largest_size|most_recently_modified
split_path <delims...>
}
file <files...>
expression `file({
'root': '<path>',
'try_files': ['<files...>'],
'try_policy': 'first_exist|first_exist_fallback|smallest_size|largest_size|most_recently_modified',
'split_path': ['<delims...>']
})`
expression file('<files...>')
通过文件。
-
root
定义在其中查找文件的目录。默认为当前工作目录,或者 变量root
({http.vars.root}
) (如果已设置)(可以通过root
指令 设置)。 -
try_files
检查列表中与 try_policy 匹配的文件。要匹配目录,请在路径后附加一个尾部正斜杠
/
。所有文件路径都相对于站点root,并且 glob 模式 将被展开。如果
try_policy
是first_exist
(默认值),则列表中的最后一项可以是数字,前缀为=
(例如,=404
),作为回退,它将发出带有该代码的错误;可以使用handle_errors
捕获和处理该错误。 -
try_policy
指定如何选择文件。默认为first_exist
。-
first_exist
检查文件是否存在。选择第一个存在的文件。 -
first_exist_fallback
类似于first_exist
,但假定列表中的最后一个元素始终存在,以防止磁盘访问。 -
smallest_size
选择大小最小的文件。 -
largest_size
选择大小最大的文件。 -
most_recently_modified
选择最近修改的文件。
-
-
split_path
将导致路径在列表中找到的第一个分隔符处分割,以便尝试每个文件路径。对于每个分割值,分割的左侧(包括分隔符本身)将是要尝试的文件路径。例如,使用分隔符.php
的/remote.php/dav/
将尝试文件/remote.php
。每个分隔符必须出现在 URI 路径组件的末尾才能用作分割分隔符。这是一个小众设置,主要在服务 PHP 站点时使用。
由于 try_files
与 first_exist
策略非常常见,因此有一个单行快捷方式
file <files...>
一个空的 file
匹配器(后面没有列出文件的匹配器)将查看请求的文件(与 URI 完全相同,相对于站点根目录)是否存在。这实际上与 file {path}
相同。
匹配后,将提供四个新的占位符
{file_match.relative}
文件的根相对路径。这在重写请求时通常很有用。{file_match.absolute}
匹配文件的绝对路径,包括根目录。{file_match.type}
文件类型,file
或directory
。{file_match.remainder}
分割文件路径后剩余的部分(如果配置了split_path
)
示例
匹配路径是现有文件的请求
@file file
匹配路径后跟 .html
是现有文件的请求,如果不是,则路径是现有文件的请求
@html file {
try_files {path}.html {path}
}
与上面相同,只是使用了单行快捷方式,并且在找不到文件时回退到发出 404 错误
@html-or-error file {path}.html {path} =404
更多使用 CEL 表达式 的示例。请记住,占位符在被 CEL 环境解释之前会被预处理并转换为常规 CEL 函数调用,因此此处使用串联。此外,如果与占位符串联,则由于当前解析限制,必须使用长格式
@file `file()`
@first `file({'try_files': [{path}, {path} + '/', 'index.html']})`
@smallest `file({'try_policy': 'smallest_size', 'try_files': ['a.txt', 'b.txt']})`
header
header <field> [<value> ...]
expression header({'<field>': '<value>'})
通过请求标头字段。
<field>
是要检查的 HTTP 标头字段的名称。- 如果以
!
为前缀,则该字段必须不存在才能匹配(省略 value 参数)。
- 如果以
<value>
是字段必须具有才能匹配的值。可以指定一个或多个。- 如果以
*
为前缀,则执行快速后缀匹配(出现在末尾)。 - 如果以
*
为后缀,则执行快速前缀匹配(出现在开头)。 - 如果用
*
括起来,则执行快速子字符串匹配(出现在任何位置)。 - 否则,它是快速精确匹配。
- 如果以
同一集合中不同的标头字段是 AND 关系。每个字段的多个值是 OR 关系。
请注意,标头字段可能会重复并且具有不同的值。后端应用程序必须考虑标头字段值是数组,而不是单数值,并且 Caddy 不解释此类困境中的含义。
示例
匹配 Connection
标头包含 Upgrade
的请求
@upgrade header Connection *Upgrade*
匹配 Foo
标头包含 bar
OR baz
的请求
@foo {
header Foo bar
header Foo baz
}
匹配根本没有 Foo
标头字段的请求
@not_foo header !Foo
使用 CEL 表达式,通过检查 Connection
标头是否包含 Upgrade
以及 Upgrade
标头是否等于 websocket
来匹配 WebSocket 请求(HTTP/2 为此具有 :protocol
标头)
@websockets `header({'Connection':'*Upgrade*','Upgrade':'websocket'}) || header({':protocol': 'websocket'})`
header_regexp
header_regexp [<name>] <field> <regexp>
expression header_regexp('<name>', '<field>', '<regexp>')
expression header_regexp('<field>', '<regexp>')
与 header
类似,但支持正则表达式。
使用的正则表达式语言是 RE2,包含在 Go 中。请参阅 RE2 语法参考 和 Go regexp 语法概述。
从 v2.8.0 开始,如果未提供 name
,则名称将从命名匹配器的名称中获取。例如,命名匹配器 @foo
将导致此匹配器被命名为 foo
。指定名称的主要优点是,如果同一个命名匹配器中使用了多个 regexp 匹配器(例如,header_regexp
和 path_regexp
,或多个不同的标头字段)。
可以在匹配后的指令中通过 占位符 访问捕获组
-
{re.<name>.<capture_group>}
其中<name>
是正则表达式的名称,<capture_group>
是表达式中捕获组的名称或编号。
-
为了方便起见,还填充了没有名称的
{re.<capture_group>}
。需要注意的是,如果在序列中使用多个 regexp 匹配器,则占位符值将被下一个匹配器覆盖。
捕获组 0
是完整的 regexp 匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。因此,{re.foo.1}
或 {re.1}
都将保存第一个捕获组的值。
每个标头字段仅支持一个正则表达式,因为 regexp 模式无法合并;如果您需要更多,请考虑使用 expression
匹配器。针对多个不同标头字段的匹配将是 AND 关系。
示例
匹配 Cookie 标头包含 login_
后跟十六进制字符串的请求,其捕获组可以使用 {re.login.1}
或 {re.1}
访问。
@login header_regexp login Cookie login_([a-f0-9]+)
可以通过省略名称来简化此操作,该名称将从命名匹配器推断出来
@login header_regexp Cookie login_([a-f0-9]+)
或者相同,使用 CEL 表达式
@login `header_regexp('login', 'Cookie', 'login_([a-f0-9]+)')`
host
host <hosts...>
expression host('<hosts...>')
通过请求的 Host
标头字段匹配请求。
由于大多数站点块已经在站点的地址中指示了主机,因此此匹配器更常用于使用通配符主机名的站点块(请参阅通配符证书模式),但需要特定于主机名的逻辑。
多个 host
匹配器将是 OR 关系。
示例
匹配一个子域名
@sub host sub.example.com
匹配根域名和子域名
@site host example.com www.example.com
使用 CEL 表达式 匹配多个子域名
@app `host('app1.example.com', 'app2.example.com')`
method
method <verbs...>
expression method('<verbs...>')
通过 HTTP 请求的方法(动词)。动词应为大写,如 POST
。可以匹配一个或多个方法。
多个 method
匹配器将是 OR 关系。
示例
匹配 GET
方法的请求
@get method GET
匹配 PUT
或 DELETE
方法的请求
@put-delete method PUT DELETE
使用 CEL 表达式 匹配只读方法
@read `method('GET', 'HEAD', 'OPTIONS')`
not
not <matcher>
或者,要否定将被 AND 运算的多个匹配器,请打开一个块
not {
<matchers...>
}
封闭的匹配器的结果将被否定。
示例
匹配路径不以 /css/
OR /js/
开头的请求。
@not-assets {
not path /css/* /js/*
}
匹配既没有
/api/
路径前缀,也没有POST
请求方法的请求
即,必须没有这些才能匹配
@with-neither {
not path /api/*
not method POST
}
匹配既不
/api/
路径前缀,也不POST
请求方法的请求
即,必须没有或两者都有才能匹配
@without-both {
not {
path /api/*
method POST
}
}
此匹配器没有 CEL 表达式,因为您可以改用 !
运算符进行否定。例如
@without-both `!path('/api*') && !method('POST')`
这与使用括号的此项相同
@without-both `!(path('/api*') || method('POST'))`
path
path <paths...>
expression path('<paths...>')
通过请求路径(请求 URI 的路径组件)。路径匹配是精确的,但不区分大小写。可以使用通配符 *
- 仅在末尾,用于前缀匹配 (
/prefix/*
) - 仅在开头,用于后缀匹配 (
*.suffix
) - 仅在两侧,用于子字符串匹配 (
*/contains/*
) - 仅在中间,用于全局匹配 (
/accounts/*/info
)
斜杠很重要。例如,/foo*
将匹配 /foo
、/foobar
、/foo/
和 /foo/bar
,但 /foo/*
将不匹配 /foo
或 /foobar
。
请求路径在匹配之前会被清理以解析目录遍历点。此外,除非匹配模式具有多个斜杠,否则多个斜杠将被合并。换句话说,/foo
将匹配 /foo
和 //foo
,但 //foo
将仅匹配 //foo
。
由于任何给定 URI 都有多种转义形式,因此请求路径已规范化(URL 解码、取消转义),但匹配模式中也存在转义序列的位置处的转义序列除外。例如,/foo/bar
匹配 /foo/bar
和 /foo%2Fbar
,但 /foo%2Fbar
将仅匹配 /foo%2Fbar
,因为转义序列在配置中显式给出。
特殊的通配符转义 %*
也可以代替 *
使用,以使其匹配范围保持转义状态。例如,/bands/*/*
将不匹配 /bands/AC%2FDC/T.N.T
,因为路径将在规范化空间中进行比较,在该空间中它看起来像 /bands/AC/DC/T.N.T
,这与模式不匹配;但是,/bands/%*/*
将匹配 /bands/AC%2FDC/T.N.T
,因为由 %*
表示的范围将在不解码转义序列的情况下进行比较。
多个路径将是 OR 关系。
示例
匹配多个目录及其内容
@assets path /js/* /css/* /images/*
匹配特定文件
@favicon path /favicon.ico
匹配文件扩展名
@extensions path *.js *.css
使用 CEL 表达式
@assets `path('/js/*', '/css/*', '/images/*')`
path_regexp
path_regexp [<name>] <regexp>
expression path_regexp('<name>', '<regexp>')
expression path_regexp('<regexp>')
与 path
类似,但支持正则表达式。针对 URI 解码/取消转义的路径运行。
使用的正则表达式语言是 RE2,包含在 Go 中。请参阅 RE2 语法参考 和 Go regexp 语法概述。
从 v2.8.0 开始,如果未提供 name
,则名称将从命名匹配器的名称中获取。例如,命名匹配器 @foo
将导致此匹配器被命名为 foo
。指定名称的主要优点是,如果同一个命名匹配器中使用了多个 regexp 匹配器(例如,path_regexp
和 header_regexp
)。
可以在匹配后的指令中通过 占位符 访问捕获组
-
{re.<name>.<capture_group>}
其中<name>
是正则表达式的名称,<capture_group>
是表达式中捕获组的名称或编号。
-
为了方便起见,还填充了没有名称的
{re.<capture_group>}
。需要注意的是,如果在序列中使用多个 regexp 匹配器,则占位符值将被下一个匹配器覆盖。
捕获组 0
是完整的 regexp 匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。因此,{re.foo.1}
或 {re.1}
都将保存第一个捕获组的值。
每个命名匹配器只能有一个 path_regexp
模式,因为此匹配器无法与自身合并;如果您需要更多,请考虑使用 expression
匹配器。
示例
匹配路径以 6 个字符的十六进制字符串结尾,后跟 .css
或 .js
作为文件扩展名的请求,其捕获组(括在 ( )
中的部分)可以使用 {re.static.1}
和 {re.static.2}
(或 {re.1}
和 {re.2}
)分别访问
@static path_regexp static \.([a-f0-9]{6})\.(css|js)$
可以通过省略名称来简化此操作,该名称将从命名匹配器推断出来
@static path_regexp \.([a-f0-9]{6})\.(css|js)$
或者相同,使用 CEL 表达式,也验证 file
是否在磁盘上存在
@static `path_regexp('\.([a-f0-9]{6})\.(css|js)$') && file()`
protocol
protocol http|https|grpc|http/<version>[+]
expression protocol('http|https|grpc|http/<version>[+]')
通过请求协议。可以使用广泛的协议名称,例如 http
、https
或 grpc
;或特定的或最低的 HTTP 版本,例如 http/1.1
或 http/2+
。
每个命名匹配器只能有一个 protocol
匹配器。
示例
匹配使用 HTTP/2 的请求
@http2 protocol http/2+
使用 CEL 表达式
@http2 `protocol('http/2+')`
query
query <key>=<val>...
expression query({'<key>': '<val>'})
expression query({'<key>': ['<vals...>']})
通过查询字符串参数。应为 key=value
对序列。键完全匹配(区分大小写),但也支持 *
以匹配任何值。值可以使用占位符。
每个命名匹配器可以有多个 query
匹配器,并且具有相同键的对将是 OR 关系。不同的键将是 AND 关系。因此,匹配器中的所有键都必须至少有一个匹配值。
非法查询字符串(错误的语法、未转义的分号等)将无法解析,因此将不匹配。
注意: 查询字符串参数是数组,而不是单数值。这是因为重复的键在查询字符串中是有效的,并且每个键可能具有不同的值。如果查询字符串中分配了任何一个配置的值,则此匹配器将匹配键。使用查询字符串的后端应用程序必须考虑到查询字符串值是数组并且可以具有多个值。
示例
匹配具有任何值的 q
查询参数
@search query q=*
匹配 sort
查询参数,其值为 asc
或 desc
@sorted query sort=asc sort=desc
使用 CEL 表达式 匹配 q
和 sort
@search-sort `query({'sort': ['asc', 'desc'], 'q': '*'})`
remote_ip
remote_ip <ranges...>
expression remote_ip('<ranges...>')
通过远程 IP 地址(即直接对等方的 IP 地址)。接受精确的 IP 或 CIDR 范围。支持 IPv6 区域。
作为快捷方式,private_ranges
可以用于匹配所有私有 IPv4 和 IPv6 范围。它与指定所有这些范围相同:192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.1/8 fd00::/8 ::1
如果您希望匹配从 HTTP 标头解析的客户端“真实 IP”,请改用 client_ip
匹配器。
每个命名匹配器可以有多个 remote_ip
匹配器,它们的范围将被合并并进行 OR 运算。
示例
匹配来自私有 IPv4 地址的请求
@private-ipv4 remote_ip 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.1/8
此匹配器通常与 not
匹配器配对以反转匹配。例如,中止来自公共 IPv4 和 IPv6 地址的所有连接(这是所有私有范围的逆运算)
example.com {
@denied not remote_ip private_ranges
abort @denied
respond "Hello, you must be from a private network!"
}
在 CEL 表达式 中,它看起来像这样
@my-friends `remote_ip('12.23.34.45', '23.34.45.56')`
vars
vars <variable> <values...>
通过请求上下文中的变量值或占位符值。可以指定多个值以匹配任何可能的值(OR 关系)。
<variable> 参数可以是变量名或花括号 { }
中的占位符。(占位符不会在第一个参数中展开。)
当与设置输出的 map
指令 或在请求上下文中设置某些信息的插件配对使用时,此匹配器最有用。
示例
匹配名为 magic_number
的 map
指令 的输出,其值为 3
或 5
vars {magic_number} 3 5
匹配任意占位符的值,即经过身份验证的用户的 ID,Bob
或 Alice
vars {http.auth.user.id} Bob Alice
vars_regexp
vars_regexp [<name>] <variable> <regexp>
与 vars
类似,但支持正则表达式。
使用的正则表达式语言是 RE2,包含在 Go 中。请参阅 RE2 语法参考 和 Go regexp 语法概述。
从 v2.8.0 开始,如果未提供 name
,则名称将从命名匹配器的名称中获取。例如,命名匹配器 @foo
将导致此匹配器被命名为 foo
。指定名称的主要优点是,如果同一个命名匹配器中使用了多个 regexp 匹配器(例如,vars_regexp
和 header_regexp
)。
可以在匹配后的指令中通过 占位符 访问捕获组
-
{re.<name>.<capture_group>}
其中<name>
是正则表达式的名称,<capture_group>
是表达式中捕获组的名称或编号。
-
为了方便起见,还填充了没有名称的
{re.<capture_group>}
。需要注意的是,如果在序列中使用多个 regexp 匹配器,则占位符值将被下一个匹配器覆盖。
捕获组 0
是完整的 regexp 匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。因此,{re.foo.1}
或 {re.1}
都将保存第一个捕获组的值。
每个变量名仅支持一个正则表达式,因为 regexp 模式无法合并;如果您需要更多,请考虑使用 expression
匹配器。针对多个不同变量的匹配将是 AND 关系。
示例
匹配名为 magic_number
的 map
指令 的输出,其值以 4
开头,并将该值捕获在可以使用 {re.magic.1}
或 {re.1}
访问的捕获组中
@magic vars_regexp magic {magic_number} ^(4.*)
可以通过省略名称来简化此操作,该名称将从命名匹配器推断出来
@magic vars_regexp {magic_number} ^(4.*)