mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
New routing
Ultimately few things change, it's mostly just a nicer syntax and slightly different expectations. The name of the value `dispatch` did not change, because the previous dispatch values will now fail if the code is not updated to using `cowboy_router:compile/1`. No constraints have been implemented in this commit.
This commit is contained in:
parent
68da864aeb
commit
a357c49d1b
6 changed files with 266 additions and 91 deletions
|
@ -61,8 +61,6 @@ environment values to perform.
|
||||||
Routing middleware
|
Routing middleware
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@todo Routing middleware value is renamed very soon.
|
|
||||||
|
|
||||||
The routing middleware requires the `dispatch` value. If routing
|
The routing middleware requires the `dispatch` value. If routing
|
||||||
succeeds, it will put the handler name and options in the `handler`
|
succeeds, it will put the handler name and options in the `handler`
|
||||||
and `handler_opts` values of the environment, respectively.
|
and `handler_opts` values of the environment, respectively.
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
Routing
|
Routing
|
||||||
=======
|
=======
|
||||||
|
|
||||||
@todo Note that this documentation is for the new routing interface
|
|
||||||
not available in master at this point.
|
|
||||||
|
|
||||||
Purpose
|
Purpose
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -49,9 +46,9 @@ Finally, each path contains matching rules for the path along with
|
||||||
optional constraints, and gives us the handler module to be used
|
optional constraints, and gives us the handler module to be used
|
||||||
along with options that will be given to it on initialization.
|
along with options that will be given to it on initialization.
|
||||||
|
|
||||||
```
|
``` erlang
|
||||||
Path1 = {PathMatch, Handler, Module}.
|
Path1 = {PathMatch, Handler, Opts}.
|
||||||
Path2 = {PathMatch, Constraints, Handler, Module}.
|
Path2 = {PathMatch, Constraints, Handler, Opts}.
|
||||||
```
|
```
|
||||||
|
|
||||||
Continue reading to learn more about the match syntax and the optional
|
Continue reading to learn more about the match syntax and the optional
|
||||||
|
@ -112,11 +109,11 @@ HostMatch = ":subdomain.example.org".
|
||||||
```
|
```
|
||||||
|
|
||||||
If these two end up matching when routing, you will end up with two
|
If these two end up matching when routing, you will end up with two
|
||||||
bindings defined, `subdomain` and `hat_name`, each containing the
|
bindings defined, `subdomain` and `name`, each containing the
|
||||||
segment value where they were defined. For example, the URL
|
segment value where they were defined. For example, the URL
|
||||||
`http://test.example.org/hats/wild_cowboy_legendary/prices` will
|
`http://test.example.org/hats/wild_cowboy_legendary/prices` will
|
||||||
result in having the value `test` bound to the name `subdomain`
|
result in having the value `test` bound to the name `subdomain`
|
||||||
and the value `wild_cowboy_legendary` bound to the name `hat_name`.
|
and the value `wild_cowboy_legendary` bound to the name `name`.
|
||||||
They can later be retrieved using `cowboy_req:binding/{2,3}`. The
|
They can later be retrieved using `cowboy_req:binding/{2,3}`. The
|
||||||
binding name must be given as an atom.
|
binding name must be given as an atom.
|
||||||
|
|
||||||
|
@ -156,9 +153,9 @@ PathMatch = "/hats/[...]".
|
||||||
HostMatch = "[...]ninenines.eu".
|
HostMatch = "[...]ninenines.eu".
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, if a binding appears twice in the routing rules, then the
|
If a binding appears twice in the routing rules, then the match
|
||||||
match will succeed only if they share the same value. This copies
|
will succeed only if they share the same value. This copies the
|
||||||
the Erlang pattern matching behavior.
|
Erlang pattern matching behavior.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
PathMatch = "/hats/:name/:name".
|
PathMatch = "/hats/:name/:name".
|
||||||
|
@ -180,6 +177,21 @@ PathMatch = "/:user/[...]".
|
||||||
HostMatch = ":user.github.com".
|
HostMatch = ":user.github.com".
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally, there are two special match values that can be used. The
|
||||||
|
first is the atom `'_'` which will match any host or path.
|
||||||
|
|
||||||
|
``` erlang
|
||||||
|
PathMatch = '_'.
|
||||||
|
HostMatch = '_'.
|
||||||
|
```
|
||||||
|
|
||||||
|
The second is the special host match `"*"` which will match the
|
||||||
|
wildcard path, generally used alongside the `OPTIONS` method.
|
||||||
|
|
||||||
|
``` erlang
|
||||||
|
HostMatch = "*".
|
||||||
|
```
|
||||||
|
|
||||||
Constraints
|
Constraints
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -212,10 +224,10 @@ The structure defined in this chapter needs to be compiled before it is
|
||||||
passed to Cowboy. This allows Cowboy to efficiently lookup the correct
|
passed to Cowboy. This allows Cowboy to efficiently lookup the correct
|
||||||
handler to run instead of having to parse the routes repeatedly.
|
handler to run instead of having to parse the routes repeatedly.
|
||||||
|
|
||||||
This can be done with a simple call to `cowboy_routing:compile/1`.
|
This can be done with a simple call to `cowboy_router:compile/1`.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Routes} = cowboy_routing:compile([
|
{ok, Routes} = cowboy_router:compile([
|
||||||
%% {HostMatch, list({PathMatch, Handler, Opts})}
|
%% {HostMatch, list({PathMatch, Handler, Opts})}
|
||||||
{'_', [{'_', my_handler, []}]}
|
{'_', [{'_', my_handler, []}]}
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -25,23 +25,143 @@
|
||||||
-module(cowboy_router).
|
-module(cowboy_router).
|
||||||
-behaviour(cowboy_middleware).
|
-behaviour(cowboy_middleware).
|
||||||
|
|
||||||
|
-export([compile/1]).
|
||||||
-export([execute/2]).
|
-export([execute/2]).
|
||||||
|
|
||||||
-type bindings() :: [{atom(), binary()}].
|
-type bindings() :: [{atom(), binary()}].
|
||||||
-type tokens() :: [binary()].
|
-type tokens() :: [binary()].
|
||||||
-type match_rule() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].
|
|
||||||
-type dispatch_path() :: [{match_rule(), module(), any()}].
|
|
||||||
-type dispatch_rule() :: {Host::match_rule(), Path::dispatch_path()}.
|
|
||||||
-type dispatch_rules() :: [dispatch_rule()].
|
|
||||||
|
|
||||||
-export_type([bindings/0]).
|
-export_type([bindings/0]).
|
||||||
-export_type([tokens/0]).
|
-export_type([tokens/0]).
|
||||||
|
|
||||||
|
-type constraints() :: [].
|
||||||
|
-export_type([constraints/0]).
|
||||||
|
|
||||||
|
-type route_match() :: binary() | string().
|
||||||
|
-type route_path() :: {Path::route_match(), Handler::module(), Opts::any()}
|
||||||
|
| {Path::route_match(), constraints(), Handler::module(), Opts::any()}.
|
||||||
|
-type route_rule() :: {Host::route_match(), Paths::[route_path()]}
|
||||||
|
| {Host::route_match(), constraints(), Paths::[route_path()]}.
|
||||||
|
-opaque routes() :: [route_rule()].
|
||||||
|
-export_type([routes/0]).
|
||||||
|
|
||||||
|
-type dispatch_match() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].
|
||||||
|
-type dispatch_path() :: {dispatch_match(), module(), any()}.
|
||||||
|
-type dispatch_rule() :: {Host::dispatch_match(), Paths::[dispatch_path()]}.
|
||||||
|
-opaque dispatch_rules() :: [dispatch_rule()].
|
||||||
-export_type([dispatch_rules/0]).
|
-export_type([dispatch_rules/0]).
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
%% @doc Compile a list of routes into the dispatch format used
|
||||||
|
%% by Cowboy's routing.
|
||||||
|
-spec compile(routes()) -> dispatch_rules().
|
||||||
|
compile(Routes) ->
|
||||||
|
compile(Routes, []).
|
||||||
|
|
||||||
|
compile([], Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
compile([{Host, Paths}|Tail], Acc) ->
|
||||||
|
compile([{Host, [], Paths}|Tail], Acc);
|
||||||
|
compile([{HostMatch, Constraints, Paths}|Tail], Acc) ->
|
||||||
|
HostRules = case HostMatch of
|
||||||
|
'_' -> '_';
|
||||||
|
_ -> compile_host(HostMatch)
|
||||||
|
end,
|
||||||
|
PathRules = compile_paths(Paths, []),
|
||||||
|
Hosts = case HostRules of
|
||||||
|
'_' -> [{'_', Constraints, PathRules}];
|
||||||
|
_ -> [{R, Constraints, PathRules} || R <- HostRules]
|
||||||
|
end,
|
||||||
|
compile(Tail, Hosts ++ Acc).
|
||||||
|
|
||||||
|
compile_host(HostMatch) when is_list(HostMatch) ->
|
||||||
|
compile_host(unicode:characters_to_binary(HostMatch));
|
||||||
|
compile_host(HostMatch) when is_binary(HostMatch) ->
|
||||||
|
compile_rules(HostMatch, $., [], [], <<>>).
|
||||||
|
|
||||||
|
compile_paths([], Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
compile_paths([{PathMatch, Handler, Opts}|Tail], Acc) ->
|
||||||
|
compile_paths([{PathMatch, [], Handler, Opts}|Tail], Acc);
|
||||||
|
compile_paths([{PathMatch, Constraints, Handler, Opts}|Tail], Acc)
|
||||||
|
when is_list(PathMatch) ->
|
||||||
|
compile_paths([{unicode:characters_to_binary(PathMatch),
|
||||||
|
Constraints, Handler, Opts}|Tail], Acc);
|
||||||
|
compile_paths([{'_', Constraints, Handler, Opts}|Tail], Acc) ->
|
||||||
|
compile_paths(Tail, [{'_', Constraints, Handler, Opts}] ++ Acc);
|
||||||
|
compile_paths([{<< $/, PathMatch/binary >>, Constraints, Handler, Opts}|Tail],
|
||||||
|
Acc) ->
|
||||||
|
PathRules = compile_rules(PathMatch, $/, [], [], <<>>),
|
||||||
|
Paths = [{lists:reverse(R), Constraints, Handler, Opts} || R <- PathRules],
|
||||||
|
compile_paths(Tail, Paths ++ Acc).
|
||||||
|
|
||||||
|
compile_rules(<<>>, _, Segments, Rules, <<>>) ->
|
||||||
|
[Segments|Rules];
|
||||||
|
compile_rules(<<>>, _, Segments, Rules, Acc) ->
|
||||||
|
[[Acc|Segments]|Rules];
|
||||||
|
compile_rules(<< S, Rest/binary >>, S, Segments, Rules, <<>>) ->
|
||||||
|
compile_rules(Rest, S, Segments, Rules, <<>>);
|
||||||
|
compile_rules(<< S, Rest/binary >>, S, Segments, Rules, Acc) ->
|
||||||
|
compile_rules(Rest, S, [Acc|Segments], Rules, <<>>);
|
||||||
|
compile_rules(<< $:, Rest/binary >>, S, Segments, Rules, <<>>) ->
|
||||||
|
{NameBin, Rest2} = compile_binding(Rest, S, <<>>),
|
||||||
|
Name = binary_to_atom(NameBin, utf8),
|
||||||
|
compile_rules(Rest2, S, Segments, Rules, Name);
|
||||||
|
compile_rules(<< $:, _/binary >>, _, _, _, _) ->
|
||||||
|
erlang:error(badarg);
|
||||||
|
compile_rules(<< $[, $., $., $., $], Rest/binary >>, S, Segments, Rules, Acc)
|
||||||
|
when Acc =:= <<>> ->
|
||||||
|
compile_rules(Rest, S, ['...'|Segments], Rules, Acc);
|
||||||
|
compile_rules(<< $[, $., $., $., $], Rest/binary >>, S, Segments, Rules, Acc) ->
|
||||||
|
compile_rules(Rest, S, ['...', Acc|Segments], Rules, Acc);
|
||||||
|
compile_rules(<< $[, S, Rest/binary >>, S, Segments, Rules, Acc) ->
|
||||||
|
compile_brackets(Rest, S, [Acc|Segments], Rules);
|
||||||
|
compile_rules(<< $[, Rest/binary >>, S, Segments, Rules, <<>>) ->
|
||||||
|
compile_brackets(Rest, S, Segments, Rules);
|
||||||
|
%% Open bracket in the middle of a segment.
|
||||||
|
compile_rules(<< $[, _/binary >>, _, _, _, _) ->
|
||||||
|
erlang:error(badarg);
|
||||||
|
%% Missing an open bracket.
|
||||||
|
compile_rules(<< $], _/binary >>, _, _, _, _) ->
|
||||||
|
erlang:error(badarg);
|
||||||
|
compile_rules(<< C, Rest/binary >>, S, Segments, Rules, Acc) ->
|
||||||
|
compile_rules(Rest, S, Segments, Rules, << Acc/binary, C >>).
|
||||||
|
|
||||||
|
%% Everything past $: until $. or $[ or $] or end of binary
|
||||||
|
%% is the binding name.
|
||||||
|
compile_binding(<<>>, _, <<>>) ->
|
||||||
|
erlang:error(badarg);
|
||||||
|
compile_binding(Rest = <<>>, _, Acc) ->
|
||||||
|
{Acc, Rest};
|
||||||
|
compile_binding(Rest = << C, _/binary >>, S, Acc)
|
||||||
|
when C =:= S; C =:= $[; C =:= $] ->
|
||||||
|
{Acc, Rest};
|
||||||
|
compile_binding(<< C, Rest/binary >>, S, Acc) ->
|
||||||
|
compile_binding(Rest, S, << Acc/binary, C >>).
|
||||||
|
|
||||||
|
compile_brackets(Rest, S, Segments, Rules) ->
|
||||||
|
{Bracket, Rest2} = compile_brackets_split(Rest, <<>>, 0),
|
||||||
|
Rules1 = compile_rules(Rest2, S, Segments, [], <<>>),
|
||||||
|
Rules2 = compile_rules(<< Bracket/binary, Rest2/binary >>,
|
||||||
|
S, Segments, [], <<>>),
|
||||||
|
Rules ++ Rules2 ++ Rules1.
|
||||||
|
|
||||||
|
%% Missing a close bracket.
|
||||||
|
compile_brackets_split(<<>>, _, _) ->
|
||||||
|
erlang:error(badarg);
|
||||||
|
%% Make sure we don't confuse the closing bracket we're looking for.
|
||||||
|
compile_brackets_split(<< C, Rest/binary >>, Acc, N) when C =:= $[ ->
|
||||||
|
compile_brackets_split(Rest, << Acc/binary, C >>, N + 1);
|
||||||
|
compile_brackets_split(<< C, Rest/binary >>, Acc, N) when C =:= $], N > 0 ->
|
||||||
|
compile_brackets_split(Rest, << Acc/binary, C >>, N - 1);
|
||||||
|
%% That's the right one.
|
||||||
|
compile_brackets_split(<< $], Rest/binary >>, Acc, 0) ->
|
||||||
|
{Acc, Rest};
|
||||||
|
compile_brackets_split(<< C, Rest/binary >>, Acc, N) ->
|
||||||
|
compile_brackets_split(Rest, << Acc/binary, C >>, N).
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
-spec execute(Req, Env)
|
-spec execute(Req, Env)
|
||||||
-> {ok, Req, Env} | {error, 400 | 404, Req}
|
-> {ok, Req, Env} | {error, 400 | 404, Req}
|
||||||
|
@ -99,11 +219,12 @@ execute(Req, Env) ->
|
||||||
| {error, badrequest, path}.
|
| {error, badrequest, path}.
|
||||||
match([], _, _) ->
|
match([], _, _) ->
|
||||||
{error, notfound, host};
|
{error, notfound, host};
|
||||||
match([{'_', PathMatchs}|_Tail], _, Path) ->
|
%% If the host is '_' then there can be no constraints.
|
||||||
|
match([{'_', [], PathMatchs}|_Tail], _, Path) ->
|
||||||
match_path(PathMatchs, undefined, Path, []);
|
match_path(PathMatchs, undefined, Path, []);
|
||||||
match([{HostMatch, PathMatchs}|Tail], Tokens, Path)
|
match([{HostMatch, _Constraints, PathMatchs}|Tail], Tokens, Path)
|
||||||
when is_list(Tokens) ->
|
when is_list(Tokens) ->
|
||||||
case list_match(Tokens, lists:reverse(HostMatch), []) of
|
case list_match(Tokens, HostMatch, []) of
|
||||||
false ->
|
false ->
|
||||||
match(Tail, Tokens, Path);
|
match(Tail, Tokens, Path);
|
||||||
{true, Bindings, undefined} ->
|
{true, Bindings, undefined} ->
|
||||||
|
@ -115,7 +236,7 @@ match([{HostMatch, PathMatchs}|Tail], Tokens, Path)
|
||||||
match(Dispatch, Host, Path) ->
|
match(Dispatch, Host, Path) ->
|
||||||
match(Dispatch, split_host(Host), Path).
|
match(Dispatch, split_host(Host), Path).
|
||||||
|
|
||||||
-spec match_path(dispatch_path(),
|
-spec match_path([dispatch_path()],
|
||||||
HostInfo::undefined | tokens(), binary() | tokens(), bindings())
|
HostInfo::undefined | tokens(), binary() | tokens(), bindings())
|
||||||
-> {ok, module(), any(), bindings(),
|
-> {ok, module(), any(), bindings(),
|
||||||
HostInfo::undefined | tokens(),
|
HostInfo::undefined | tokens(),
|
||||||
|
@ -123,11 +244,12 @@ match(Dispatch, Host, Path) ->
|
||||||
| {error, notfound, path} | {error, badrequest, path}.
|
| {error, notfound, path} | {error, badrequest, path}.
|
||||||
match_path([], _, _, _) ->
|
match_path([], _, _, _) ->
|
||||||
{error, notfound, path};
|
{error, notfound, path};
|
||||||
match_path([{'_', Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
|
%% If the path is '_' then there can be no constraints.
|
||||||
|
match_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
|
||||||
{ok, Handler, Opts, Bindings, HostInfo, undefined};
|
{ok, Handler, Opts, Bindings, HostInfo, undefined};
|
||||||
match_path([{<<"*">>, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
|
match_path([{<<"*">>, _Constraints, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
|
||||||
{ok, Handler, Opts, Bindings, HostInfo, undefined};
|
{ok, Handler, Opts, Bindings, HostInfo, undefined};
|
||||||
match_path([{PathMatch, Handler, Opts}|Tail], HostInfo, Tokens,
|
match_path([{PathMatch, _Constraints, Handler, Opts}|Tail], HostInfo, Tokens,
|
||||||
Bindings) when is_list(Tokens) ->
|
Bindings) when is_list(Tokens) ->
|
||||||
case list_match(Tokens, PathMatch, []) of
|
case list_match(Tokens, PathMatch, []) of
|
||||||
false ->
|
false ->
|
||||||
|
@ -184,7 +306,7 @@ split_path(Path, Acc) ->
|
||||||
badrequest
|
badrequest
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec list_match(tokens(), match_rule(), bindings())
|
-spec list_match(tokens(), dispatch_match(), bindings())
|
||||||
-> {true, bindings(), undefined | tokens()} | false.
|
-> {true, bindings(), undefined | tokens()} | false.
|
||||||
%% Atom '...' matches any trailing path, stop right now.
|
%% Atom '...' matches any trailing path, stop right now.
|
||||||
list_match(List, ['...'], Binds) ->
|
list_match(List, ['...'], Binds) ->
|
||||||
|
@ -209,6 +331,49 @@ list_match(_List, _Match, _Binds) ->
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
|
|
||||||
|
compile_test_() ->
|
||||||
|
%% {Routes, Result}
|
||||||
|
Tests = [
|
||||||
|
%% Match any host and path.
|
||||||
|
{[{'_', [{'_', h, o}]}],
|
||||||
|
[{'_', [], [{'_', [], h, o}]}]},
|
||||||
|
{[{"cowboy.example.org",
|
||||||
|
[{"/", ha, oa}, {"/path/to/resource", hb, ob}]}],
|
||||||
|
[{[<<"org">>, <<"example">>, <<"cowboy">>], [], [
|
||||||
|
{[], [], ha, oa},
|
||||||
|
{[<<"path">>, <<"to">>, <<"resource">>], [], hb, ob}]}]},
|
||||||
|
{[{'_', [{"/path/to/resource/", h, o}]}],
|
||||||
|
[{'_', [], [{[<<"path">>, <<"to">>, <<"resource">>], [], h, o}]}]},
|
||||||
|
{[{"cowboy.example.org.", [{'_', h, o}]}],
|
||||||
|
[{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
|
||||||
|
{[{".cowboy.example.org", [{'_', h, o}]}],
|
||||||
|
[{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
|
||||||
|
{[{":subdomain.example.org", [{"/hats/:name/prices", h, o}]}],
|
||||||
|
[{[<<"org">>, <<"example">>, subdomain], [], [
|
||||||
|
{[<<"hats">>, name, <<"prices">>], [], h, o}]}]},
|
||||||
|
{[{"ninenines.:_", [{"/hats/:_", h, o}]}],
|
||||||
|
[{['_', <<"ninenines">>], [], [{[<<"hats">>, '_'], [], h, o}]}]},
|
||||||
|
{[{"[www.]ninenines.eu",
|
||||||
|
[{"/horses", h, o}, {"/hats/[page/:number]", h, o}]}], [
|
||||||
|
{[<<"eu">>, <<"ninenines">>], [], [
|
||||||
|
{[<<"horses">>], [], h, o},
|
||||||
|
{[<<"hats">>], [], h, o},
|
||||||
|
{[<<"hats">>, <<"page">>, number], [], h, o}]},
|
||||||
|
{[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
|
||||||
|
{[<<"horses">>], [], h, o},
|
||||||
|
{[<<"hats">>], [], h, o},
|
||||||
|
{[<<"hats">>, <<"page">>, number], [], h, o}]}]},
|
||||||
|
{[{'_', [{"/hats/[page/[:number]]", h, o}]}], [{'_', [], [
|
||||||
|
{[<<"hats">>], [], h, o},
|
||||||
|
{[<<"hats">>, <<"page">>], [], h, o},
|
||||||
|
{[<<"hats">>, <<"page">>, number], [], h, o}]}]},
|
||||||
|
{[{"[...]ninenines.eu", [{"/hats/[...]", h, o}]}],
|
||||||
|
[{[<<"eu">>, <<"ninenines">>, '...'], [], [
|
||||||
|
{[<<"hats">>, '...'], [], h, o}]}]}
|
||||||
|
],
|
||||||
|
[{lists:flatten(io_lib:format("~p", [Rt])),
|
||||||
|
fun() -> Rs = compile(Rt) end} || {Rt, Rs} <- Tests].
|
||||||
|
|
||||||
split_host_test_() ->
|
split_host_test_() ->
|
||||||
%% {Host, Result}
|
%% {Host, Result}
|
||||||
Tests = [
|
Tests = [
|
||||||
|
@ -239,23 +404,23 @@ split_path_test_() ->
|
||||||
|
|
||||||
match_test_() ->
|
match_test_() ->
|
||||||
Dispatch = [
|
Dispatch = [
|
||||||
{[<<"www">>, '_', <<"ninenines">>, <<"eu">>], [
|
{[<<"eu">>, <<"ninenines">>, '_', <<"www">>], [], [
|
||||||
{[<<"users">>, '_', <<"mails">>], match_any_subdomain_users, []}
|
{[<<"users">>, '_', <<"mails">>], [], match_any_subdomain_users, []}
|
||||||
]},
|
]},
|
||||||
{[<<"ninenines">>, <<"eu">>], [
|
{[<<"eu">>, <<"ninenines">>], [], [
|
||||||
{[<<"users">>, id, <<"friends">>], match_extend_users_friends, []},
|
{[<<"users">>, id, <<"friends">>], [], match_extend_users_friends, []},
|
||||||
{'_', match_extend, []}
|
{'_', [], match_extend, []}
|
||||||
]},
|
]},
|
||||||
{[<<"ninenines">>, var], [
|
{[var, <<"ninenines">>], [], [
|
||||||
{[<<"threads">>, var], match_duplicate_vars,
|
{[<<"threads">>, var], [], match_duplicate_vars,
|
||||||
[we, {expect, two}, var, here]}
|
[we, {expect, two}, var, here]}
|
||||||
]},
|
]},
|
||||||
{[<<"erlang">>, ext], [
|
{[ext, <<"erlang">>], [], [
|
||||||
{'_', match_erlang_ext, []}
|
{'_', [], match_erlang_ext, []}
|
||||||
]},
|
]},
|
||||||
{'_', [
|
{'_', [], [
|
||||||
{[<<"users">>, id, <<"friends">>], match_users_friends, []},
|
{[<<"users">>, id, <<"friends">>], [], match_users_friends, []},
|
||||||
{'_', match_any, []}
|
{'_', [], match_any, []}
|
||||||
]}
|
]}
|
||||||
],
|
],
|
||||||
%% {Host, Path, Result}
|
%% {Host, Path, Result}
|
||||||
|
@ -288,11 +453,11 @@ match_test_() ->
|
||||||
|
|
||||||
match_info_test_() ->
|
match_info_test_() ->
|
||||||
Dispatch = [
|
Dispatch = [
|
||||||
{[<<"www">>, <<"ninenines">>, <<"eu">>], [
|
{[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
|
||||||
{[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], match_path, []}
|
{[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], [], match_path, []}
|
||||||
]},
|
]},
|
||||||
{['...', <<"ninenines">>, <<"eu">>], [
|
{[<<"eu">>, <<"ninenines">>, '...'], [], [
|
||||||
{'_', match_any, []}
|
{'_', [], match_any, []}
|
||||||
]}
|
]}
|
||||||
],
|
],
|
||||||
Tests = [
|
Tests = [
|
||||||
|
|
|
@ -75,8 +75,8 @@ end_per_group(Listener, _Config) ->
|
||||||
%% Dispatch configuration.
|
%% Dispatch configuration.
|
||||||
|
|
||||||
init_dispatch() ->
|
init_dispatch() ->
|
||||||
[{[<<"localhost">>], [
|
cowboy_router:compile([{"localhost", [
|
||||||
{[<<"echo">>], websocket_echo_handler, []}]}].
|
{"/echo", websocket_echo_handler, []}]}]).
|
||||||
|
|
||||||
%% autobahn cases
|
%% autobahn cases
|
||||||
|
|
||||||
|
|
|
@ -283,58 +283,58 @@ end_per_group(Name, _) ->
|
||||||
%% Dispatch configuration.
|
%% Dispatch configuration.
|
||||||
|
|
||||||
init_dispatch(Config) ->
|
init_dispatch(Config) ->
|
||||||
[
|
cowboy_router:compile([
|
||||||
{[<<"localhost">>], [
|
{"localhost", [
|
||||||
{[<<"chunked_response">>], chunked_handler, []},
|
{"/chunked_response", chunked_handler, []},
|
||||||
{[<<"init_shutdown">>], http_handler_init_shutdown, []},
|
{"/init_shutdown", http_handler_init_shutdown, []},
|
||||||
{[<<"long_polling">>], http_handler_long_polling, []},
|
{"/long_polling", http_handler_long_polling, []},
|
||||||
{[<<"headers">>, <<"dupe">>], http_handler,
|
{"/headers/dupe", http_handler,
|
||||||
[{headers, [{<<"connection">>, <<"close">>}]}]},
|
[{headers, [{<<"connection">>, <<"close">>}]}]},
|
||||||
{[<<"set_resp">>, <<"header">>], http_handler_set_resp,
|
{"/set_resp/header", http_handler_set_resp,
|
||||||
[{headers, [{<<"vary">>, <<"Accept">>}]}]},
|
[{headers, [{<<"vary">>, <<"Accept">>}]}]},
|
||||||
{[<<"set_resp">>, <<"overwrite">>], http_handler_set_resp,
|
{"/set_resp/overwrite", http_handler_set_resp,
|
||||||
[{headers, [{<<"server">>, <<"DesireDrive/1.0">>}]}]},
|
[{headers, [{<<"server">>, <<"DesireDrive/1.0">>}]}]},
|
||||||
{[<<"set_resp">>, <<"body">>], http_handler_set_resp,
|
{"/set_resp/body", http_handler_set_resp,
|
||||||
[{body, <<"A flameless dance does not equal a cycle">>}]},
|
[{body, <<"A flameless dance does not equal a cycle">>}]},
|
||||||
{[<<"stream_body">>, <<"set_resp">>], http_handler_stream_body,
|
{"/stream_body/set_resp", http_handler_stream_body,
|
||||||
[{reply, set_resp}, {body, <<"stream_body_set_resp">>}]},
|
[{reply, set_resp}, {body, <<"stream_body_set_resp">>}]},
|
||||||
{[<<"stream_body">>, <<"set_resp_close">>],
|
{"/stream_body/set_resp_close",
|
||||||
http_handler_stream_body, [
|
http_handler_stream_body, [
|
||||||
{reply, set_resp_close},
|
{reply, set_resp_close},
|
||||||
{body, <<"stream_body_set_resp_close">>}]},
|
{body, <<"stream_body_set_resp_close">>}]},
|
||||||
{[<<"static">>, '...'], cowboy_static,
|
{"/static/[...]", cowboy_static,
|
||||||
[{directory, ?config(static_dir, Config)},
|
[{directory, ?config(static_dir, Config)},
|
||||||
{mimetypes, [{<<".css">>, [<<"text/css">>]}]}]},
|
{mimetypes, [{<<".css">>, [<<"text/css">>]}]}]},
|
||||||
{[<<"static_mimetypes_function">>, '...'], cowboy_static,
|
{"/static_mimetypes_function/[...]", cowboy_static,
|
||||||
[{directory, ?config(static_dir, Config)},
|
[{directory, ?config(static_dir, Config)},
|
||||||
{mimetypes, {fun(Path, data) when is_binary(Path) ->
|
{mimetypes, {fun(Path, data) when is_binary(Path) ->
|
||||||
[<<"text/html">>] end, data}}]},
|
[<<"text/html">>] end, data}}]},
|
||||||
{[<<"handler_errors">>], http_handler_errors, []},
|
{"/handler_errors", http_handler_errors, []},
|
||||||
{[<<"static_attribute_etag">>, '...'], cowboy_static,
|
{"/static_attribute_etag/[...]", cowboy_static,
|
||||||
[{directory, ?config(static_dir, Config)},
|
[{directory, ?config(static_dir, Config)},
|
||||||
{etag, {attributes, [filepath, filesize, inode, mtime]}}]},
|
{etag, {attributes, [filepath, filesize, inode, mtime]}}]},
|
||||||
{[<<"static_function_etag">>, '...'], cowboy_static,
|
{"/static_function_etag/[...]", cowboy_static,
|
||||||
[{directory, ?config(static_dir, Config)},
|
[{directory, ?config(static_dir, Config)},
|
||||||
{etag, {fun static_function_etag/2, etag_data}}]},
|
{etag, {fun static_function_etag/2, etag_data}}]},
|
||||||
{[<<"static_specify_file">>, '...'], cowboy_static,
|
{"/static_specify_file/[...]", cowboy_static,
|
||||||
[{directory, ?config(static_dir, Config)},
|
[{directory, ?config(static_dir, Config)},
|
||||||
{mimetypes, [{<<".css">>, [<<"text/css">>]}]},
|
{mimetypes, [{<<".css">>, [<<"text/css">>]}]},
|
||||||
{file, <<"test_file.css">>}]},
|
{file, <<"test_file.css">>}]},
|
||||||
{[<<"multipart">>], http_handler_multipart, []},
|
{"/multipart", http_handler_multipart, []},
|
||||||
{[<<"echo">>, <<"body">>], http_handler_echo_body, []},
|
{"/echo/body", http_handler_echo_body, []},
|
||||||
{[<<"bad_accept">>], rest_simple_resource, []},
|
{"/bad_accept", rest_simple_resource, []},
|
||||||
{[<<"simple">>], rest_simple_resource, []},
|
{"/simple", rest_simple_resource, []},
|
||||||
{[<<"forbidden_post">>], rest_forbidden_resource, [true]},
|
{"/forbidden_post", rest_forbidden_resource, [true]},
|
||||||
{[<<"simple_post">>], rest_forbidden_resource, [false]},
|
{"/simple_post", rest_forbidden_resource, [false]},
|
||||||
{[<<"missing_get_callbacks">>], rest_missing_callbacks, []},
|
{"/missing_get_callbacks", rest_missing_callbacks, []},
|
||||||
{[<<"missing_put_callbacks">>], rest_missing_callbacks, []},
|
{"/missing_put_callbacks", rest_missing_callbacks, []},
|
||||||
{[<<"nodelete">>], rest_nodelete_resource, []},
|
{"/nodelete", rest_nodelete_resource, []},
|
||||||
{[<<"resetags">>], rest_resource_etags, []},
|
{"/resetags", rest_resource_etags, []},
|
||||||
{[<<"rest_expires">>], rest_expires, []},
|
{"/rest_expires", rest_expires, []},
|
||||||
{[<<"loop_timeout">>], http_handler_loop_timeout, []},
|
{"/loop_timeout", http_handler_loop_timeout, []},
|
||||||
{[], http_handler, []}
|
{"/", http_handler, []}
|
||||||
]}
|
]}
|
||||||
].
|
]).
|
||||||
|
|
||||||
init_static_dir(Config) ->
|
init_static_dir(Config) ->
|
||||||
Dir = filename:join(?config(priv_dir, Config), "static"),
|
Dir = filename:join(?config(priv_dir, Config), "static"),
|
||||||
|
@ -576,8 +576,8 @@ http10_hostless(Config) ->
|
||||||
ranch:start_listener(Name, 5,
|
ranch:start_listener(Name, 5,
|
||||||
?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}],
|
?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}],
|
||||||
cowboy_protocol, [
|
cowboy_protocol, [
|
||||||
{env, [{dispatch, [{'_', [
|
{env, [{dispatch, cowboy_router:compile([
|
||||||
{[<<"http1.0">>, <<"hostless">>], http_handler, []}]}]}]},
|
{'_', [{"/http1.0/hostless", http_handler, []}]}])}]},
|
||||||
{max_keepalive, 50},
|
{max_keepalive, 50},
|
||||||
{timeout, 500}]
|
{timeout, 500}]
|
||||||
),
|
),
|
||||||
|
|
|
@ -88,35 +88,35 @@ end_per_group(Listener, _Config) ->
|
||||||
%% Dispatch configuration.
|
%% Dispatch configuration.
|
||||||
|
|
||||||
init_dispatch() ->
|
init_dispatch() ->
|
||||||
[
|
cowboy_router:compile([
|
||||||
{[<<"localhost">>], [
|
{"localhost", [
|
||||||
{[<<"websocket">>], websocket_handler, []},
|
{"/websocket", websocket_handler, []},
|
||||||
{[<<"ws_echo_handler">>], websocket_echo_handler, []},
|
{"/ws_echo_handler", websocket_echo_handler, []},
|
||||||
{[<<"ws_init_shutdown">>], websocket_handler_init_shutdown, []},
|
{"/ws_init_shutdown", websocket_handler_init_shutdown, []},
|
||||||
{[<<"ws_send_many">>], ws_send_many_handler, [
|
{"/ws_send_many", ws_send_many_handler, [
|
||||||
{sequence, [
|
{sequence, [
|
||||||
{text, <<"one">>},
|
{text, <<"one">>},
|
||||||
{text, <<"two">>},
|
{text, <<"two">>},
|
||||||
{text, <<"seven!">>}]}
|
{text, <<"seven!">>}]}
|
||||||
]},
|
]},
|
||||||
{[<<"ws_send_close">>], ws_send_many_handler, [
|
{"/ws_send_close", ws_send_many_handler, [
|
||||||
{sequence, [
|
{sequence, [
|
||||||
{text, <<"send">>},
|
{text, <<"send">>},
|
||||||
close,
|
close,
|
||||||
{text, <<"won't be received">>}]}
|
{text, <<"won't be received">>}]}
|
||||||
]},
|
]},
|
||||||
{[<<"ws_send_close_payload">>], ws_send_many_handler, [
|
{"/ws_send_close_payload", ws_send_many_handler, [
|
||||||
{sequence, [
|
{sequence, [
|
||||||
{text, <<"send">>},
|
{text, <<"send">>},
|
||||||
{close, 1001, <<"some text!">>},
|
{close, 1001, <<"some text!">>},
|
||||||
{text, <<"won't be received">>}]}
|
{text, <<"won't be received">>}]}
|
||||||
]},
|
]},
|
||||||
{[<<"ws_timeout_hibernate">>], ws_timeout_hibernate_handler, []},
|
{"/ws_timeout_hibernate", ws_timeout_hibernate_handler, []},
|
||||||
{[<<"ws_timeout_cancel">>], ws_timeout_cancel_handler, []},
|
{"/ws_timeout_cancel", ws_timeout_cancel_handler, []},
|
||||||
{[<<"ws_upgrade_with_opts">>], ws_upgrade_with_opts_handler,
|
{"/ws_upgrade_with_opts", ws_upgrade_with_opts_handler,
|
||||||
<<"failure">>}
|
<<"failure">>}
|
||||||
]}
|
]}
|
||||||
].
|
]).
|
||||||
|
|
||||||
%% ws and wss.
|
%% ws and wss.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue