Browse Source

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.
Loïc Hoguin 12 years ago
parent
commit
a357c49d1b
6 changed files with 266 additions and 91 deletions
  1. 0 2
      guide/middlewares.md
  2. 25 13
      guide/routing.md
  3. 194 29
      src/cowboy_router.erl
  4. 2 2
      test/autobahn_SUITE.erl
  5. 33 33
      test/http_SUITE.erl
  6. 12 12
      test/ws_SUITE.erl

+ 0 - 2
guide/middlewares.md

@@ -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.

+ 25 - 13
guide/routing.md

@@ -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.
 
 
-```
-Path1 = {PathMatch, Handler, Module}.
-Path2 = {PathMatch, Constraints, Handler, Module}.
+``` erlang
+Path1 = {PathMatch, Handler, Opts}.
+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
-match will succeed only if they share the same value. This copies
-the Erlang pattern matching behavior.
+If a binding appears twice in the routing rules, then the match
+will succeed only if they share the same value. This copies the
+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, []}]}
 ]),
 ]),

+ 194 - 29
src/cowboy_router.erl

@@ -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">>], [
-			{[<<"users">>, '_', <<"mails">>], match_any_subdomain_users, []}
+		{[<<"eu">>, <<"ninenines">>, '_', <<"www">>], [], [
+			{[<<"users">>, '_', <<"mails">>], [], match_any_subdomain_users, []}
 		]},
 		]},
-		{[<<"ninenines">>, <<"eu">>], [
-			{[<<"users">>, id, <<"friends">>], match_extend_users_friends, []},
-			{'_', match_extend, []}
+		{[<<"eu">>, <<"ninenines">>], [], [
+			{[<<"users">>, id, <<"friends">>], [], match_extend_users_friends, []},
+			{'_', [], match_extend, []}
 		]},
 		]},
-		{[<<"ninenines">>, var], [
-			{[<<"threads">>, var], match_duplicate_vars,
+		{[var, <<"ninenines">>], [], [
+			{[<<"threads">>, var], [], match_duplicate_vars,
 				[we, {expect, two}, var, here]}
 				[we, {expect, two}, var, here]}
 		]},
 		]},
-		{[<<"erlang">>, ext], [
-			{'_', match_erlang_ext, []}
+		{[ext, <<"erlang">>], [], [
+			{'_', [], match_erlang_ext, []}
 		]},
 		]},
-		{'_', [
-			{[<<"users">>, id, <<"friends">>], match_users_friends, []},
-			{'_', match_any, []}
+		{'_', [], [
+			{[<<"users">>, id, <<"friends">>], [], match_users_friends, []},
+			{'_', [], 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">>], [
-			{[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], match_path, []}
+		{[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
+			{[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], [], match_path, []}
 		]},
 		]},
-		{['...', <<"ninenines">>, <<"eu">>], [
-			{'_', match_any, []}
+		{[<<"eu">>, <<"ninenines">>, '...'], [], [
+			{'_', [], match_any, []}
 		]}
 		]}
 	],
 	],
 	Tests = [
 	Tests = [

+ 2 - 2
test/autobahn_SUITE.erl

@@ -75,8 +75,8 @@ end_per_group(Listener, _Config) ->
 %% Dispatch configuration.
 %% Dispatch configuration.
 
 
 init_dispatch() ->
 init_dispatch() ->
-	[{[<<"localhost">>], [
-		{[<<"echo">>], websocket_echo_handler, []}]}].
+	cowboy_router:compile([{"localhost", [
+		{"/echo", websocket_echo_handler, []}]}]).
 
 
 %% autobahn cases
 %% autobahn cases
 
 

+ 33 - 33
test/http_SUITE.erl

@@ -283,58 +283,58 @@ end_per_group(Name, _) ->
 %% Dispatch configuration.
 %% Dispatch configuration.
 
 
 init_dispatch(Config) ->
 init_dispatch(Config) ->
-	[
-		{[<<"localhost">>], [
-			{[<<"chunked_response">>], chunked_handler, []},
-			{[<<"init_shutdown">>], http_handler_init_shutdown, []},
-			{[<<"long_polling">>], http_handler_long_polling, []},
-			{[<<"headers">>, <<"dupe">>], http_handler,
+	cowboy_router:compile([
+		{"localhost", [
+			{"/chunked_response", chunked_handler, []},
+			{"/init_shutdown", http_handler_init_shutdown, []},
+			{"/long_polling", http_handler_long_polling, []},
+			{"/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, []},
-			{[<<"static_attribute_etag">>, '...'], cowboy_static,
+			{"/handler_errors", http_handler_errors, []},
+			{"/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, []},
-			{[<<"echo">>, <<"body">>], http_handler_echo_body, []},
-			{[<<"bad_accept">>], rest_simple_resource, []},
-			{[<<"simple">>], rest_simple_resource, []},
-			{[<<"forbidden_post">>], rest_forbidden_resource, [true]},
-			{[<<"simple_post">>], rest_forbidden_resource, [false]},
-			{[<<"missing_get_callbacks">>], rest_missing_callbacks, []},
-			{[<<"missing_put_callbacks">>], rest_missing_callbacks, []},
-			{[<<"nodelete">>], rest_nodelete_resource, []},
-			{[<<"resetags">>], rest_resource_etags, []},
-			{[<<"rest_expires">>], rest_expires, []},
-			{[<<"loop_timeout">>], http_handler_loop_timeout, []},
-			{[], http_handler, []}
+			{"/multipart", http_handler_multipart, []},
+			{"/echo/body", http_handler_echo_body, []},
+			{"/bad_accept", rest_simple_resource, []},
+			{"/simple", rest_simple_resource, []},
+			{"/forbidden_post", rest_forbidden_resource, [true]},
+			{"/simple_post", rest_forbidden_resource, [false]},
+			{"/missing_get_callbacks", rest_missing_callbacks, []},
+			{"/missing_put_callbacks", rest_missing_callbacks, []},
+			{"/nodelete", rest_nodelete_resource, []},
+			{"/resetags", rest_resource_etags, []},
+			{"/rest_expires", rest_expires, []},
+			{"/loop_timeout", http_handler_loop_timeout, []},
+			{"/", 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, [{'_', [
-				{[<<"http1.0">>, <<"hostless">>], http_handler, []}]}]}]},
+			{env, [{dispatch, cowboy_router:compile([
+				{'_', [{"/http1.0/hostless", http_handler, []}]}])}]},
 			{max_keepalive, 50},
 			{max_keepalive, 50},
 			{timeout, 500}]
 			{timeout, 500}]
 	),
 	),

+ 12 - 12
test/ws_SUITE.erl

@@ -88,35 +88,35 @@ end_per_group(Listener, _Config) ->
 %% Dispatch configuration.
 %% Dispatch configuration.
 
 
 init_dispatch() ->
 init_dispatch() ->
-	[
-		{[<<"localhost">>], [
-			{[<<"websocket">>], websocket_handler, []},
-			{[<<"ws_echo_handler">>], websocket_echo_handler, []},
-			{[<<"ws_init_shutdown">>], websocket_handler_init_shutdown, []},
-			{[<<"ws_send_many">>], ws_send_many_handler, [
+	cowboy_router:compile([
+		{"localhost", [
+			{"/websocket", websocket_handler, []},
+			{"/ws_echo_handler", websocket_echo_handler, []},
+			{"/ws_init_shutdown", websocket_handler_init_shutdown, []},
+			{"/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_cancel">>], ws_timeout_cancel_handler, []},
-			{[<<"ws_upgrade_with_opts">>], ws_upgrade_with_opts_handler,
+			{"/ws_timeout_hibernate", ws_timeout_hibernate_handler, []},
+			{"/ws_timeout_cancel", ws_timeout_cancel_handler, []},
+			{"/ws_upgrade_with_opts", ws_upgrade_with_opts_handler,
 				<<"failure">>}
 				<<"failure">>}
 		]}
 		]}
-	].
+	]).
 
 
 %% ws and wss.
 %% ws and wss.