请求匹配器
请求匹配器可以用于根据各种标准过滤(或分类)请求。
语法
在 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
匹配器,则两者都必须匹配。
同一类型的多个匹配器可以使用布尔代数(AND/OR)合并(例如,同一集合中的多个 path
匹配器),如下面各自的部分所述。
对于更复杂的布尔匹配逻辑,建议使用 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...>
通过任何 CEL (通用表达式语言) 表达式,该表达式返回 true
或 false
。
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
将导致路径在列表中找到的第一个分隔符处拆分,该分隔符在要尝试的每个文件路径中找到。对于每个拆分值,拆分的左侧部分(包括分隔符本身)将是要尝试的文件路径。例如,/remote.php/dav/
使用分隔符.php
将尝试文件/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
或 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 正则表达式语法概述 。
从 v2.8.0 开始,如果 未 提供 name
,则名称将从命名匹配器的名称中获取。例如,命名匹配器 @foo
将导致此匹配器被命名为 foo
。指定名称的主要优点是,如果同一命名匹配器中使用多个正则表达式匹配器(例如 header_regexp
和 path_regexp
,或多个不同的标头字段)。
匹配后,可以通过指令中的 占位符 访问捕获组
-
{re.<name>.<capture_group>}
其中<name>
是正则表达式的名称,<capture_group>
是表达式中捕获组的名称或编号。
-
为了方便起见,还填充了没有名称的
{re.<capture_group>}
。需要注意的是,如果按顺序使用多个正则表达式匹配器,则占位符值将被下一个匹配器覆盖。
捕获组 0
是完整的正则表达式匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。因此,{re.foo.1}
或 {re.1}
都将保存第一个捕获组的值。
每个标头字段仅支持一个正则表达式,因为正则表达式模式无法合并;如果需要更多,请考虑使用 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/
或 /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 正则表达式语法概述 。
从 v2.8.0 开始,如果 未 提供 name
,则名称将从命名匹配器的名称中获取。例如,命名匹配器 @foo
将导致此匹配器被命名为 foo
。指定名称的主要优点是,如果同一命名匹配器中使用多个正则表达式匹配器(例如 path_regexp
和 header_regexp
)。
匹配后,可以通过指令中的 占位符 访问捕获组
-
{re.<name>.<capture_group>}
其中<name>
是正则表达式的名称,<capture_group>
是表达式中捕获组的名称或编号。
-
为了方便起见,还填充了没有名称的
{re.<capture_group>}
。需要注意的是,如果按顺序使用多个正则表达式匹配器,则占位符值将被下一个匹配器覆盖。
捕获组 0
是完整的正则表达式匹配,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=*
匹配值为 asc
或 desc
的 sort
查询参数
@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
如果您希望匹配客户端的“真实 IP”,如从 HTTP 标头解析的那样,请改用 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 正则表达式语法概述 。
从 v2.8.0 开始,如果 未 提供 name
,则名称将从命名匹配器的名称中获取。例如,命名匹配器 @foo
将导致此匹配器被命名为 foo
。指定名称的主要优点是,如果同一命名匹配器中使用多个正则表达式匹配器(例如 vars_regexp
和 header_regexp
)。
匹配后,可以通过指令中的 占位符 访问捕获组
-
{re.<name>.<capture_group>}
其中<name>
是正则表达式的名称,<capture_group>
是表达式中捕获组的名称或编号。
-
为了方便起见,还填充了没有名称的
{re.<capture_group>}
。需要注意的是,如果按顺序使用多个正则表达式匹配器,则占位符值将被下一个匹配器覆盖。
捕获组 0
是完整的正则表达式匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。因此,{re.foo.1}
或 {re.1}
都将保存第一个捕获组的值。
每个变量名仅支持一个正则表达式,因为正则表达式模式无法合并;如果需要更多,请考虑使用 expression
匹配器 。针对多个不同变量的匹配将是 AND 关系。
示例
匹配名为 magic_number
的 map
指令 的输出,其值以 4
开头,将该值捕获在可以使用 {re.magic.1}
或 {re.1}
访问的捕获组中
@magic vars_regexp magic {magic_number} ^(4.*)
可以通过省略名称来简化此操作,该名称将从命名匹配器中推断出来
@magic vars_regexp {magic_number} ^(4.*)