文档
一个 项目

请求匹配器

请求匹配器 可用于根据各种条件过滤(或分类)请求。

语法

在 Caddyfile 中,紧随指令后的匹配器标记可以限制该指令的作用域。匹配器标记可以采用以下形式之一

  1. * 匹配所有请求(通配符;默认)。
  2. /path 以正斜杠开头,匹配请求路径。
  3. @name 指定一个命名匹配器

如果指令支持匹配器,它将在其语法文档中显示为 [<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 连接的;即所有匹配器都必须匹配。例如,如果您在集合中同时具有 headerpath 匹配器,则两者都必须匹配。

同一类型的多个匹配器可以合并(例如,同一集合中的多个 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...>

通过任何返回 truefalseCEL(通用表达式语言) 表达式。

Caddy 占位符(或 Caddyfile 简写)可以在这些 CEL 表达式中使用,因为它们在被 CEL 环境解释之前会被预处理并转换为常规 CEL 函数调用。

大多数其他请求匹配器也可以在表达式中用作函数,这比表达式外部提供了更大的布尔逻辑灵活性。有关 CEL 表达式中支持的语法,请参阅每个匹配器的文档。

为了方便起见,如果定义的命名匹配器仅包含 CEL 表达式,则可以省略匹配器名称。CEL 表达式必须用 引号(推荐使用反引号或 heredoc)括起来。这读起来很不错

@mutable `{method}.startsWith("P")`

在这种情况下,CEL 匹配器是假设的。

示例

匹配方法以 P 开头的请求,例如 PUTPOST

@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_policyfirst_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} 文件类型,filedirectory
  • {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 <field> [<value>]

expression header({'<field>': '<value>'})

通过请求头字段。

  • <field> 是要检查的 HTTP 头字段的名称。
    • 如果以 ! 为前缀,则字段必须不存在才能匹配(省略值参数)。
  • <value> 是字段必须具有的值才能匹配。
    • 如果以 * 为前缀,则执行快速后缀匹配(出现在末尾)。
    • 如果以*结尾,则执行快速前缀匹配(出现在开头)。
    • 如果用*括起来,则执行快速子字符串匹配(出现在任何位置)。
    • 否则,它是一个快速精确匹配。

同一组内的不同头字段进行AND运算。每个字段的多个值进行OR运算。

请注意,头字段可以重复并具有不同的值。后端应用程序必须考虑头字段值是数组,而不是单个值,Caddy不会解释此类难题中的含义。

示例

匹配包含UpgradeConnection头部的请求

@upgrade header Connection *Upgrade*

匹配包含barbazFoo头部的请求

@foo {
	header Foo bar
	header Foo baz
}

匹配根本没有Foo头字段的请求

@not_foo header !Foo

使用CEL表达式,通过检查包含UpgradeConnection头部和等于websocketUpgrade头部来匹配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

匹配使用PUTDELETE方法的请求

@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>[+]')

根据请求协议。可以使用广泛的协议名称,例如httphttpsgrpc;或者可以使用特定的或最低的HTTP版本,例如http/1.1http/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=*

匹配值为ascdescsort查询参数

@sorted query sort=asc sort=desc

使用CEL表达式匹配qsort

@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_numbermap指令的输出,值为35

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_numbermap指令的输出,值为以4开头的值,并将值捕获到捕获组中

vars_regexp magic {magic_number} ^(4.*)