Cowboy 默认不执行任何操作.
要使Cowboy有用,需要将URI映射到负责处理请求的Erlang模块。这被称为路由。
URI,统一资源标志符(Uniform Resource Identifier, URI),表示的是web上每一种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个URI进行标识的。
Cowboy使用以下算法路由请求:
注意:可能会遇到这样的情况:两台主机匹配一个请求URI,但只有第二台主机上的路径匹配请求URI。在本例中,预期的结果是一个404响应,因为在路由过程中使用的唯一路径是与请求URI匹配的第一个配置主机的路径。
路由需要在Cowboy使用之前先编译。编译的结果是分派规则(dispatch rules)。
下面可能翻译得不太好 请各位结合例子就比较好理解了
路由的一般结构定义如下。
Routes = [Host1, Host2, ... HostN].
每个主机都包含该主机的匹配规则HostMatch、可选约束Constraints以及一个path组件的路由列表PathsList。
Host1 = {HostMatch, PathsList}. Host2 = {HostMatch, Constraints, PathsList}.
path组件的路由列表的定义类似于主机列表。
PathsList = [Path1, Path2, ... PathN].
最后,每个路径都包含该路径的匹配规则PathMatch和可选约束Constraints,并为我们提供了要使用的处理程序模块Handler及其初始状态InitialState。
Path1 = {PathMatch, Handler, InitialState}. Path2 = {PathMatch, Constraints, Handler, InitialState}.
继续阅读,了解更多关于匹配语法和可选约束的信息.
匹配语法用于将主机名和路径与它们各自的处理程序关联起来。
主机和路径的匹配语法是相同的,只是有一些细微差别。实际上,段分隔符是不同的,主机从最后一个段开始匹配到第一个段。所有的例子都具有主机和路径匹配规则,并解释遇到时的差异。
除去我们将在本节末尾解释的特殊值,最简单的匹配值是一个主机或一条路径。它可以以string()或binary()的形式给出。
PathMatch1 = "/". PathMatch2 = "/path/to/resource". HostMatch1 = "cowboy.example.org".
如您所见,以这种方式定义的所有路径都必须以" / "开头。请注意,就路由而言,这两条路径是相同的。
PathMatch2 = "/path/to/resource". PathMatch3 = "/path/to/resource/".
带或不带尾随点的主机等价于路由。类似地,带和不带前导点的主机也是相等的。
HostMatch1 = "cowboy.example.org". HostMatch2 = "cowboy.example.org.". HostMatch3 = ".cowboy.example.org".
可以提取主机和路径的片段,并将值存储在Req对象中以供以后使用。我们称这种值为绑定。
绑定的语法非常简单。一个以:字符开头的片段 表示 在片段结束之前后面是段值将存储在其中的绑定的名称。(可参考下面的例子)
PathMatch = "/hats/:name/prices". HostMatch = ":subdomain.example.org".
如果这两个在路由时匹配,那么您将定义两个绑定,subdomain
和name
,每个都包含定义它们的片段值。例如,URL http://test.example.org/hats/wild_cowboy_legendary/prices 将导致将值test绑定到subdomain
,并将值wild_cowboy_legendary绑定到名称name。稍后可以使用cowboy_req:binding/{2,3}来检索它们。绑定名称必须以原子(atom)的形式给出。
有一个特殊的绑定名可以用来模拟Erlang中的下划线变量。任何针对 _ 绑定的匹配都将成功,但是数据将被丢弃。这对于一次匹配多个域名特别有用。
HostMatch = "ninenines.:_".
类似地,也可以有可选的片段。括号内的内容都是可选的。
PathMatch = "/hats/[page/:number]". HostMatch = "[www.]ninenines.eu".
你也可以有堆叠的可选片段。
PathMatch = "/hats/[page/[:number]]".
虽然Cowboy不会拒绝路由中的多个括号,但如果路由未被指定,则该行为可能是未定义的。例如,这个路径需要约束来确定什么是章节,什么是页面,因为它们都是可选的:
PathMatch = "/book/[:chapter]/[:page]".
您可以使用[…]检索主机或路径的其余部分。在主机的情况下,它将匹配前面的任何内容,在路径的情况下,它将匹配前面匹配的片段之后的任何内容。它是可选段的特殊情况,因为它可以有0个、一个或多个片段。然后,你可以分别使用cowboy_req:host_info/1和cowboy_req:path_info/1找到片段。它们将表示为一个片段列表。
PathMatch = "/hats/[...]". HostMatch = "[...]ninenines.eu".
如果一个绑定在路由规则中出现两次,那么只有当它们共享相同的值时,匹配才会成功。这复制了Erlang模式匹配行为。
PathMatch = "/hats/:name/:name".
当存在一个可选的片段时,也是如此。在这种情况下,这两个值必须是相同的片段才是可用的。
PathMatch = "/hats/:name/[:name]".
如果一个绑定在主机和路径中都定义,那么它们也必须共享相同的值。
PathMatch = "/:user/[...]". HostMatch = ":user.github.com".
最后,可以使用两个特殊的匹配值。第一个是atom '_',它将匹配任何主机或路径。
PathMatch = '_'. HostMatch = '_'.
第二个是特殊的主机匹配“*”,它将匹配通配符路径,通常与OPTIONS方法一起使用。
HostMatch = "*".
匹配完成后,可以根据一组约束测试结果绑定。约束只在定义绑定时测试。它们按照您定义的顺序运行。只有他们都成功,匹配才会成功。如果匹配失败,Cowboy将尝试列表中的下一条路线。
约束使用的格式与cowboy_req中的匹配函数相同:它们作为字段列表提供,这些字段可能有一个或多个约束。当路由器接受相同的格式时,它会跳过没有约束的字段,也会忽略默认值(如果有的话)。
阅读更多关于约束constraints的内容
在Cowboy使用它们之前,必须先把路线编译好。编译步骤规范化了路由,以简化代码并加快执行速度,但最终还是会逐个查找路由。更快的编译策略可以是直接编译到Erlang代码的路由,但需要更重的依赖性。
要编译路由,只需调用适当的函数:
Dispatch = cowboy_router:compile([ %% {HostMatch, list({PathMatch, Handler, InitialState})} {'_', [{'_', my_handler, #{}}]} ]), %% Name, TransOpts, ProtoOpts cowboy:start_clear(my_http_listener, [{port, 8080}], #{env => #{dispatch => Dispatch}} ).
路由可以存储在从Erlang/OTP 21.2开始支持的persistent_term中。当有大量路由时,这可能会提高性能。
要使用这个功能,你需要编译路由,将它们存储在persistent_term中,然后通知Cowboy:
Dispatch = cowboy_router:compile([ {'_', [{'_', my_handler, #{}}]} ]), persistent_term:put(my_app_dispatch, Dispatch), cowboy:start_clear(my_http_listener, [{port, 8080}], #{env => #{dispatch => {persistent_term, my_app_dispatch}}} ).
你可以使用cowboy:set_env/3函数来更新路由使用的分派列表。这将适用于监听器接受的所有新连接:
Dispatch = cowboy_router:compile(Routes), cowboy:set_env(my_http_listener, dispatch, Dispatch).
注意,在更新之前,您需要重新编译路由。
当使用persistent_term时,不需要调用这个函数,只需将新路由放入存储中