请求匹配器
请求匹配器 可用于根据各种条件过滤(或分类)请求。
语法
在 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
例如,这将 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|smallest_size|largest_size|most_recently_modified
split_path <delims...>
}
file <files...>
expression `file({
'root': '<path>',
'try_files': ['<files...>'],
'try_policy': 'first_exist|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
检查文件是否存在。选择第一个存在的文件。 -
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>
是字段必须具有的值才能匹配。- 如果以
*
为前缀,则执行快速后缀匹配(出现在末尾)。 - 如果以
*
结尾,则执行快速前缀匹配(出现在开头)。 - 如果用
*
括起来,则执行快速子字符串匹配(出现在任何位置)。 - 否则,它是一个快速精确匹配。
- 如果以
同一组内的不同头字段进行AND运算。每个字段的多个值进行OR运算。
请注意,头字段可以重复并具有不同的值。后端应用程序必须考虑头字段值是数组,而不是单个值,Caddy不会解释此类难题中的含义。
示例
匹配包含Upgrade
的Connection
头部的请求
@upgrade header Connection *Upgrade*
匹配包含bar
或baz
的Foo
头部的请求
@foo {
header Foo bar
header Foo baz
}
匹配根本没有Foo
头字段的请求
@not_foo header !Foo
使用CEL表达式,通过检查包含Upgrade
的Connection
头部和等于websocket
的Upgrade
头部来匹配WebSocket请求
@websockets `header({'Connection':'*Upgrade*','Upgrade':'websocket'})`
header_regexp
header_regexp [<name>] <field> <regexp>
expression header_regexp('<name>', '<field>', '<regexp>')
expression header_regexp('<field>', '<regexp>')
与header
类似,但支持正则表达式。捕获组可以通过占位符访问,例如{re.name.capture_group}
,其中name
是正则表达式的名称(可选,但建议使用),capture_group
是表达式中捕获组的名称或编号。捕获组0
是完整的正则表达式匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。
使用的正则表达式语言是RE2,包含在Go中。请参阅RE2语法参考和Go正则表达式语法概述。
每个头字段只支持一个正则表达式。多个不同的字段将进行AND运算。
示例
匹配Cookie头包含login_
后跟十六进制字符串的请求,并使用一个捕获组,可以通过{re.login.1}
访问。
@login header_regexp login 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正则表达式语法概述。
捕获组可以通过占位符访问,例如{re.name.capture_group}
,其中name
是正则表达式的名称(可选,但建议使用),capture_group
是表达式中捕获组的名称或编号。捕获组0
是完整的正则表达式匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。
每个命名匹配器只能有一个path_regexp
模式。
示例
匹配路径以6个字符的十六进制字符串结尾,后跟.css
或.js
作为文件扩展名的请求,并使用捕获组,可以通过{re.static.1}
和{re.static.2}
分别访问每个用( )
括起来的部件。
@static path_regexp static \.([a-f0-9]{6})\.(css|js)$
或者使用CEL表达式来实现相同的功能,同时验证file
是否存在于磁盘上
@static `path_regexp('static', '\.([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运算。
非法查询字符串(语法错误,未转义的分号等)将无法解析,因此不会匹配。
注意:查询字符串参数是数组,而不是单个值。这是因为重复的键在查询字符串中是有效的,并且每个键可能具有不同的值。如果查询字符串中分配了任何一个配置值,则此匹配器将匹配该键。使用查询字符串的后端应用程序必须考虑到查询字符串值是数组,并且可以具有多个值。
示例
匹配具有任何值的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 [forwarded] <ranges...>
expression remote_ip('<ranges...>')
expression remote_ip('forwarded', '<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
⚠️ forwarded
选项已弃用,将在未来版本中删除。它的实现是不安全的。请改用client_ip
匹配器,它允许安全地匹配真实客户端IP(如果从HTTP头解析)。如果启用,则如果存在,X-Forwarded-For
请求头中的第一个IP将被优先作为参考IP,而不是直接对端的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
vars_regexp
vars_regexp [<name>] <variable> <regexp>
与vars
类似,但支持正则表达式。捕获组可以通过占位符访问,例如{re.name.capture_group}
,其中name
是正则表达式的名称(可选,但建议使用),capture_group
是表达式中捕获组的名称或编号。捕获组0
是完整的正则表达式匹配,1
是第一个捕获组,2
是第二个捕获组,依此类推。
使用的正则表达式语言是RE2,包含在Go中。请参阅RE2语法参考和Go正则表达式语法概述。
每个命名匹配器只能有一个vars_regexp
匹配器。
示例
匹配名为magic_number
的map
指令的输出,值为以4
开头的值,并将值捕获到捕获组中
vars_regexp magic {magic_number} ^(4.*)