请求匹配器
请求匹配器可用于按各种条件过滤(或分类)请求。
语法
在 Caddyfile 中,紧跟在指令后面的匹配器 token可以限制该指令的作用范围。匹配器 token 可以是以下形式之一:
如果某个指令支持匹配器,在其语法文档中会以 [<matcher>]
形式出现。匹配器 token 通常是可选的,用 [ ]
表示。如果省略匹配器 token,则等同于通配符匹配器(*
)。
示例
此指令适用于所有 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
通配符匹配器
通配符(或"捕获全部")匹配器 *
匹配所有请求,仅在需要匹配器 token 时才需要。例如,如果你想给指令的第一个参数也是路径,它看起来就像路径匹配器!所以你可以用通配符匹配器来消除歧义,例如:
root * /home/www/mysite
否则,这个匹配器不常用。一般建议如果语法不要求就省略它。
路径匹配器
按 URI 路径匹配是最常见的方式,因此匹配器可以内联,如下:
redir /old.html /new.html
路径匹配器 token 必须以斜杠 /
开头。
路径匹配 默认是精确匹配,不是前缀匹配。你必须加 *
才是快速前缀匹配。注意 /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 表达式,支持 &&
、||
和括号。
标准匹配器
完整的匹配器文档可见各自的 matcher 模块文档。
请求可以通过以下方式匹配:
client_ip
client_ip <ranges...>
expression client_ip('<ranges...>')
按客户端 IP 地址匹配。支持精确 IP 或 CIDR 范围。支持 IPv6 zone。
建议在配置了 trusted_proxies
全局选项时使用此匹配器,否则它与 remote_ip
匹配器行为一致。只有来自受信任代理的请求才会在请求开始时解析 client IP;不受信任的请求会用直接对等方的 remote 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 (Common Expression Language) 表达式匹配,返回 true
或 false
。
Caddy 占位符(或 Caddyfile 简写)可在这些 CEL 表达式中使用,它们会被预处理并转换为常规 CEL 函数调用,然后由 CEL 环境解释。
大多数其他请求匹配器也可作为函数在表达式中使用,这样布尔逻辑更灵活。详见各匹配器在 CEL 表达式中的支持语法。
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...>')
By files.
-
root
defines the directory in which to look for files. Default is the current working directory, or theroot
variable ({http.vars.root}
) if set (can be set via theroot
directive). -
try_files
checks files in its list that match the try_policy.To match directories, append a trailing forward slash
/
to the path. All file paths are relative to the site root, and glob patterns will be expanded.If the
try_policy
isfirst_exist
(the default), then the last item in the list may be a number prefixed by=
(e.g.=404
), which as a fallback, will emit an error with that code; the error can be caught and handled withhandle_errors
. -
try_policy
specifies how to choose a file. Default isfirst_exist
.-
first_exist
checks for file existence. The first file that exists is selected. -
first_exist_fallback
is similar tofirst_exist
, but assumes that the last element in the list always exists to prevent a disk access. -
smallest_size
chooses the file with the smallest size. -
largest_size
chooses the file with the largest size. -
most_recently_modified
chooses the file that was most recently modified.
-
-
split_path
will cause the path to be split at the first delimiter in the list that is found in each filepath to try. For each split value, the left-hand side of the split including the delimiter itself will be the filepath that is tried. For example,/remote.php/dav/
using a delimiter of.php
would try the file/remote.php
. Each delimiter must appear at the end of a URI path component in order to be used as a split delimiter. This is a niche setting and is mostly used when serving PHP sites.
Because try_files
with a policy of first_exist
is so common, there is a one-line shortcut for that:
file <files...>
An empty file
matcher (one with no files listed after it) will see if the requested file--verbatim from the URI, relative to the site root--exists. This is effectively the same as file {path}
.
Upon matching, four new placeholders will be made available:
{file_match.relative}
The root-relative path of the file. This is often useful when rewriting requests.{file_match.absolute}
The absolute path of the matched file, including the root.{file_match.type}
The type of file,file
ordirectory
.{file_match.remainder}
The portion remaining after splitting the file path (ifsplit_path
is configured)
Examples:
Match requests where the path is a file that exists:
@file file
Match requests where the path followed by .html
is a file that exists, or if not, where the path is a file that exists:
@html file {
try_files {path}.html {path}
}
Same as above, except using the one-line shortcut, and falling back to emitting a 404 error if a file is not found:
@html-or-error file {path}.html {path} =404
Some more examples using CEL expressions. Keep in mind that placeholders are preprocessed and converted to regular CEL function calls before being interpreted by the CEL environment, so concatenation is used here. Additionally, the long-form must be used if concatenating with placeholders due to current parsing limitations:
@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>'})
By request header fields.
<field>
is the name of the HTTP header field to check.- If prefixed with
!
, the field must not exist to match (omit value arg).
- If prefixed with
<value>
is the value the field must have to match. One or more may be specified.- If prefixed with
*
, it performs a fast suffix match (appears at the end). - If suffixed with
*
, it performs a fast prefix match (appears at the start). - If enclosed by
*
, it performs a fast substring match (appears anywhere). - Otherwise, it is a fast exact match.
- If prefixed with
Different header fields within the same set are AND-ed. Multiple values per field are OR'ed.
Note that header fields may be repeated and have different values. Backend applications MUST consider that header field values are arrays, not singular values, and Caddy does not interpret meaning in such quandaries.
Example:
Match requests with the Connection
header containing Upgrade
:
@upgrade header Connection *Upgrade*
Match requests with the Foo
header containing bar
OR baz
:
@foo {
header Foo bar
header Foo baz
}
Match requests that do not have the Foo
header field at all:
@not_foo header !Foo
Using an CEL expression, match WebSocket requests by checking for the Connection
header containing Upgrade
and the Upgrade
header equalling websocket
(HTTP/2 has the :protocol
header for this):
@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>')
Like header
, but supports regular expressions.
The regular expression language used is RE2, included in Go. See the RE2 syntax reference and the Go regexp syntax overview.
As of v2.8.0, if name
is not provided, the name will be taken from the named matcher's name. For example a named matcher @foo
will cause this matcher to be named foo
. The main advantage of specifying a name is if more than one regexp matcher (e.g. header_regexp
and path_regexp
, or multiple different header fields) is used in the same named matcher.
Capture groups can be accessed via placeholder in directives after matching:
-
{re.<name>.<capture_group>}
where:<name>
is the name of the regular expression,<capture_group>
is either the name or number of the capture group in the expression.
-
{re.<capture_group>}
without a name, is also populated for convenience. The caveat is that if multiple regexp matchers are used in sequence, then the placeholder values will be overwritten by the next matcher.
Capture group 0
is the full regexp match, 1
is the first capture group, 2
is the second capture group, and so on. So {re.foo.1}
or {re.1}
will both hold the value of the first capture group.
Only one regular expression is supported per header field, since regexp patterns cannot be merged; if you need more, consider using an expression
matcher. Matches against multiple different header fields will be AND'ed.
Example:
Match requests where the Cookie header contains login_
followed by a hex string, with a capture group that can be accessed with {re.login.1}
or {re.1}
.
@login header_regexp login Cookie login_([a-f0-9]+)
This can be simplified by omitting the name, which will be inferred from the named matcher:
@login header_regexp Cookie login_([a-f0-9]+)
Or the same, using a CEL expression:
@login `header_regexp('login', 'Cookie', 'login_([a-f0-9]+)')`
host
host <hosts...>
expression host('<hosts...>')
Matches request by the Host
header field of the request.
Since most site blocks already indicate hosts in the address of the site, this matcher is more commonly used in site blocks that use a wildcard hostname (see the wildcard certificates pattern), but where hostname-specific logic is required.
Multiple host
matchers will be OR'ed together.
Example:
Matching one subdomain:
@sub host sub.example.com
Matching the apex domain and a subdomain:
@site host example.com www.example.com
Multiple subdomains using a CEL expression:
@app `host('app1.example.com', 'app2.example.com')`
method
method <verbs...>
expression method('<verbs...>')
By the method (verb) of the HTTP request. Verbs should be uppercase, like POST
. Can match one or many methods.
Multiple method
matchers will be OR'ed together.
Examples:
Match requests with the GET
method:
@get method GET
Match requests with the PUT
or DELETE
methods:
@put-delete method PUT DELETE
Match read-only methods using a CEL expression:
@read `method('GET', 'HEAD', 'OPTIONS')`
not
not <matcher>
or, to negate multiple matchers which get AND'ed, open a block:
not {
<matchers...>
}
The results of the enclosed matchers will be negated.
Examples:
Match requests with paths that do NOT begin with /css/
OR /js/
.
@not-assets {
not path /css/* /js/*
}
Match requests WITH NEITHER:
- an
/api/
path prefix, NOR - the
POST
request method
i.e. must have none of these to match:
@with-neither {
not path /api/*
not method POST
}
Match requests WITHOUT BOTH:
- an
/api/
path prefix, AND - the
POST
request method
i.e. must have neither or either of these to match:
@without-both {
not {
path /api/*
method POST
}
}
There's no CEL expression for this matcher, because you may use the !
operator for negation instead. For example:
@without-both `!path('/api*') && !method('POST')`
Which is the same as this, using parentheses:
@without-both `!(path('/api*') || method('POST'))`
path
path <paths...>
expression path('<paths...>')
By request path (the path component of the request URI). Path matches are exact but case-insensitive. Wildcards *
may be used:
- At the end only, for a prefix match (
/prefix/*
) - At the beginning only, for a suffix match (
*.suffix
) - On both sides only, for a substring match (
*/contains/*
) - In the middle only, for a globular match (
/accounts/*/info
)
Slashes are significant. For example, /foo*
will match /foo
, /foobar
, /foo/
, and /foo/bar
, but /foo/*
will not match /foo
or /foobar
.
Request paths are cleaned to resolve directory traversal dots before matching. Additionally, multiple slashes are merged unless the match pattern has multiple slashes. In other words, /foo
will match /foo
and //foo
, but //foo
will only match //foo
.
Because there are multiple escaped forms of any given URI, the request path is normalized (URL-decoded, unescaped) except for those escape sequences at positions where escape sequences are also present in the match pattern. For example, /foo/bar
matches both /foo/bar
and /foo%2Fbar
, but /foo%2Fbar
will match only /foo%2Fbar
, because the escape sequence is explicitly given in the configuration.
The special wildcard escape %*
can also be used instead of *
to leave its matching span escaped. For example, /bands/*/*
will not match /bands/AC%2FDC/T.N.T
because the path will be compared in normalized space where it looks like /bands/AC/DC/T.N.T
, which does not match the pattern; however, /bands/%*/*
will match /bands/AC%2FDC/T.N.T
because the span represented by %*
will be compared without decoding escape sequences.
Multiple paths will be OR'ed together.
Examples:
Match multiple directories and their contents:
@assets path /js/* /css/* /images/*
Match a specific file:
@favicon path /favicon.ico
Match file extensions:
@extensions path *.js *.css
With a CEL expression:
@assets `path('/js/*', '/css/*', '/images/*')`
path_regexp
path_regexp [<name>] <regexp>
expression path_regexp('<name>', '<regexp>')
expression path_regexp('<regexp>')
Like path
, but supports regular expressions. Runs against the URI-decoded/unescaped path.
The regular expression language used is RE2, included in Go. See the RE2 syntax reference and the Go regexp syntax overview.
As of v2.8.0, if name
is not provided, the name will be taken from the named matcher's name. For example a named matcher @foo
will cause this matcher to be named foo
. The main advantage of specifying a name is if more than one regexp matcher (e.g. path_regexp
and header_regexp
) is used in the same named matcher.
Capture groups can be accessed via placeholder in directives after matching:
-
{re.<name>.<capture_group>}
where:<name>
is the name of the regular expression,<capture_group>
is either the name or number of the capture group in the expression.
-
{re.<capture_group>}
without a name, is also populated for convenience. The caveat is that if multiple regexp matchers are used in sequence, then the placeholder values will be overwritten by the next matcher.
Capture group 0
is the full regexp match, 1
is the first capture group, 2
is the second capture group, and so on. So {re.foo.1}
or {re.1}
will both hold the value of the first capture group.
There can only be one path_regexp
pattern per named matcher, since this matcher cannot be merged with itself; if you need more, consider using an expression
matcher.
Example:
Match requests where the path ends a 6 character hex string followed by .css
or .js
as the file extension, with capture groups (parts enclosed in ( )
), that can be accessed with {re.static.1}
and {re.static.2}
(or {re.1}
and {re.2}
), respectively:
@static path_regexp static \.([a-f0-9]{6})\.(css|js)$
This can be simplified by omitting the name, which will be inferred from the named matcher:
@static path_regexp \.([a-f0-9]{6})\.(css|js)$
Or the same, using a CEL expression, also validating that the file
exists on disk:
@static `path_regexp('\.([a-f0-9]{6})\.(css|js)$') && file()`
protocol
protocol http|https|grpc|http/<version>[+]
expression protocol('http|https|grpc|http/<version>[+]')
By request protocol. A broad protocol name such as http
, https
, or grpc
can be used; or specific or minimum HTTP versions such as http/1.1
or http/2+
.
There can only be one protocol
matcher per named matcher.
Example:
Match requests using HTTP/2:
@http2 protocol http/2+
With a CEL expression:
@http2 `protocol('http/2+')`
query
query <key>=<val>...
expression query({'<key>': '<val>'})
expression query({'<key>': ['<vals...>']})
By query string parameters. Should be a sequence of key=value
pairs. Keys are matched exactly (case-sensitively) but also support *
to match any value. Values can use placeholders.
There can be multiple query
matchers per named matcher, and pairs with the same keys will be OR'ed together. Different keys will be AND'ed together. So, all keys in the matcher must have at least one matching value.
Illegal query strings (bad syntax, unescaped semicolons, etc.) will fail to parse and thus will not match.
NOTE: Query string parameters are arrays, not singular values. This is because repeated keys are valid in query strings, and each one may have a different value. This matcher will match for a key if any one of its configured values is assigned in the query string. Backend applications using query strings MUST take into consideration that query string values are arrays and can have multiple values.
Example:
Match a q
query parameter with any value:
@search query q=*
Match a sort
query parameter with the value asc
or desc
:
@sorted query sort=asc sort=desc
Matching both q
and sort
, with a CEL expression:
@search-sort `query({'sort': ['asc', 'desc'], 'q': '*'})`
remote_ip
remote_ip <ranges...>
expression remote_ip('<ranges...>')
By remote IP address (i.e. the IP address of the immediate peer). Accepts exact IPs or CIDR ranges. IPv6 zones are supported.
As a shortcut, private_ranges
can be used to match all private IPv4 and IPv6 ranges. It's the same as specifying all of these ranges: 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.1/8 fd00::/8 ::1
if you wish to match the "real IP" of the client, as parsed from HTTP headers, use the client_ip
matcher instead.
There can be multiple remote_ip
matchers per named matcher, and their ranges will be merged and OR'ed together.
Example:
Match requests from private IPv4 addresses:
@private-ipv4 remote_ip 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.1/8
This matcher is commonly paired with the not
matcher to invert the match. For example, to abort all connections from public IPv4 and IPv6 addresses (which is the inverse of all private ranges):
example.com {
@denied not remote_ip private_ranges
abort @denied
respond "Hello, you must be from a private network!"
}
In a CEL expression, it would look like this:
@my-friends `remote_ip('12.23.34.45', '23.34.45.56')`
vars
vars <variable> <values...>
By the value of a variable in the request context, or the value of a placeholder. Multiple values may be specified to match any of those possible values (OR'ed).
The { }
. (Placeholders are not expanded in the first parameter.)
This matcher is most useful when paired with the map
directive which sets outputs, or with plugins which set some information in the request context.
Example:
Match an output of the map
directive named magic_number
for the values 3
or 5
:
vars {magic_number} 3 5
Match an arbitrary placeholder's value, i.e. the authenticated user's ID, either Bob
or Alice
:
vars {http.auth.user.id} Bob Alice
vars_regexp
vars_regexp [<name>] <variable> <regexp>
Like vars
, but supports regular expressions.
The regular expression language used is RE2, included in Go. See the RE2 syntax reference and the Go regexp syntax overview.
As of v2.8.0, if name
is not provided, the name will be taken from the named matcher's name. For example a named matcher @foo
will cause this matcher to be named foo
. The main advantage of specifying a name is if more than one regexp matcher (e.g. vars_regexp
and header_regexp
) is used in the same named matcher.
Capture groups can be accessed via placeholder in directives after matching:
-
{re.<name>.<capture_group>}
where:<name>
is the name of the regular expression,<capture_group>
is either the name or number of the capture group in the expression.
-
{re.<capture_group>}
without a name, is also populated for convenience. The caveat is that if multiple regexp matchers are used in sequence, then the placeholder values will be overwritten by the next matcher.
Capture group 0
is the full regexp match, 1
is the first capture group, 2
is the second capture group, and so on. So {re.foo.1}
or {re.1}
will both hold the value of the first capture group.
Only one regular expression is supported per variable name, since regexp patterns cannot be merged; if you need more, consider using an expression
matcher. Matches against multiple different variables will be AND'ed.
Example:
Match an output of the map
directive named magic_number
for a value starting with 4
, capturing the value in a capture group that can be accessed with {re.magic.1}
or {re.1}
:
@magic vars_regexp magic {magic_number} ^(4.*)
This can be simplified by omitting the name, which will be inferred from the named matcher:
@magic vars_regexp {magic_number} ^(4.*)