mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Merge remote-tracking branch 'nox/path-info'
This commit is contained in:
commit
98b9b1124c
5 changed files with 93 additions and 29 deletions
10
README.md
10
README.md
|
@ -124,6 +124,16 @@ you accept anything in that position. For example if you have both
|
||||||
"dev-extend.eu" and "dev-extend.fr" domains, you can use the match spec
|
"dev-extend.eu" and "dev-extend.fr" domains, you can use the match spec
|
||||||
`[<<"dev-extend">>, '_']` to match any top level extension.
|
`[<<"dev-extend">>, '_']` to match any top level extension.
|
||||||
|
|
||||||
|
Finally, you can also match multiple leading segments of the domain name and
|
||||||
|
multiple trailing segments of the request path using the atom `'...'` (the atom
|
||||||
|
ellipsis) respectively as the first host token or the last path token. For
|
||||||
|
example, host rule `['...', <<"dev-extend">>, <<"eu">>]` can match both
|
||||||
|
"cowboy.bugs.dev-extend.eu" and "dev-extend.eu" and path rule
|
||||||
|
`[<<"projects">>, '...']` can math both "/projects" and
|
||||||
|
"/projects/cowboy/issues/42". The host leading segments and the path trailing
|
||||||
|
segments can later be retrieved through `cowboy_http_req:host_info/1` and
|
||||||
|
`cowboy_http_req:path_info/1`.
|
||||||
|
|
||||||
Any other atom used as a token will bind the value to this atom when
|
Any other atom used as a token will bind the value to this atom when
|
||||||
matching. To follow on our hostnames example, `[<<"dev-extend">>, ext]`
|
matching. To follow on our hostnames example, `[<<"dev-extend">>, ext]`
|
||||||
would bind the values `<<"eu">>` and `<<"fr">>` to the ext atom, that you
|
would bind the values `<<"eu">>` and `<<"fr">>` to the ext atom, that you
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
|
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
|
||||||
|
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
|
||||||
%%
|
%%
|
||||||
%% Permission to use, copy, modify, and/or distribute this software for any
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||||
%% purpose with or without fee is hereby granted, provided that the above
|
%% purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -49,9 +50,11 @@
|
||||||
version = {1, 1} :: http_version(),
|
version = {1, 1} :: http_version(),
|
||||||
peer = undefined :: undefined | {Address::ip_address(), Port::ip_port()},
|
peer = undefined :: undefined | {Address::ip_address(), Port::ip_port()},
|
||||||
host = undefined :: undefined | cowboy_dispatcher:path_tokens(),
|
host = undefined :: undefined | cowboy_dispatcher:path_tokens(),
|
||||||
|
host_info = undefined :: undefined | cowboy_dispatcher:path_tokens(),
|
||||||
raw_host = undefined :: undefined | binary(),
|
raw_host = undefined :: undefined | binary(),
|
||||||
port = undefined :: undefined | ip_port(),
|
port = undefined :: undefined | ip_port(),
|
||||||
path = undefined :: undefined | '*' | cowboy_dispatcher:path_tokens(),
|
path = undefined :: undefined | '*' | cowboy_dispatcher:path_tokens(),
|
||||||
|
path_info = undefined :: undefined | cowboy_dispatcher:path_tokens(),
|
||||||
raw_path = undefined :: undefined | binary(),
|
raw_path = undefined :: undefined | binary(),
|
||||||
qs_vals = undefined :: undefined
|
qs_vals = undefined :: undefined
|
||||||
| list({Name::binary(), Value::binary() | true}),
|
| list({Name::binary(), Value::binary() | true}),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
|
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
|
||||||
|
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
|
||||||
%%
|
%%
|
||||||
%% Permission to use, copy, modify, and/or distribute this software for any
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||||
%% purpose with or without fee is hereby granted, provided that the above
|
%% purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -61,53 +62,59 @@ do_split_path(RawPath, Separator) ->
|
||||||
|
|
||||||
-spec match(Host::path_tokens(), Path::path_tokens(),
|
-spec match(Host::path_tokens(), Path::path_tokens(),
|
||||||
Dispatch::dispatch_rules())
|
Dispatch::dispatch_rules())
|
||||||
-> {ok, Handler::module(), Opts::term(), Binds::bindings()}
|
-> {ok, Handler::module(), Opts::term(), Binds::bindings(),
|
||||||
|
HostInfo::undefined | path_tokens(),
|
||||||
|
PathInfo::undefined | path_tokens()}
|
||||||
| {error, notfound, host} | {error, notfound, path}.
|
| {error, notfound, host} | {error, notfound, path}.
|
||||||
match(_Host, _Path, []) ->
|
match(_Host, _Path, []) ->
|
||||||
{error, notfound, host};
|
{error, notfound, host};
|
||||||
match(_Host, Path, [{'_', PathMatchs}|_Tail]) ->
|
match(_Host, Path, [{'_', PathMatchs}|_Tail]) ->
|
||||||
match_path(Path, PathMatchs, []);
|
match_path(Path, PathMatchs, [], undefined);
|
||||||
match(Host, Path, [{HostMatch, PathMatchs}|Tail]) ->
|
match(Host, Path, [{HostMatch, PathMatchs}|Tail]) ->
|
||||||
case try_match(host, Host, HostMatch) of
|
case try_match(host, Host, HostMatch) of
|
||||||
false ->
|
false ->
|
||||||
match(Host, Path, Tail);
|
match(Host, Path, Tail);
|
||||||
{true, HostBinds} ->
|
{true, HostBinds, undefined} ->
|
||||||
match_path(Path, PathMatchs, HostBinds)
|
match_path(Path, PathMatchs, HostBinds, undefined);
|
||||||
|
{true, HostBinds, HostInfo} ->
|
||||||
|
match_path(Path, PathMatchs, HostBinds, lists:reverse(HostInfo))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec match_path(Path::path_tokens(), list({Path::match_rule(),
|
-spec match_path(Path::path_tokens(), list({Path::match_rule(),
|
||||||
Handler::module(), Opts::term()}), HostBinds::bindings())
|
Handler::module(), Opts::term()}), HostBinds::bindings(),
|
||||||
-> {ok, Handler::module(), Opts::term(), Binds::bindings()}
|
HostInfo::undefined | path_tokens())
|
||||||
|
-> {ok, Handler::module(), Opts::term(), Binds::bindings(),
|
||||||
|
HostInfo::undefined | path_tokens(),
|
||||||
|
PathInfo::undefined | path_tokens()}
|
||||||
| {error, notfound, path}.
|
| {error, notfound, path}.
|
||||||
match_path(_Path, [], _HostBinds) ->
|
match_path(_Path, [], _HostBinds, _HostInfo) ->
|
||||||
{error, notfound, path};
|
{error, notfound, path};
|
||||||
match_path(_Path, [{'_', Handler, Opts}|_Tail], HostBinds) ->
|
match_path(_Path, [{'_', Handler, Opts}|_Tail], HostBinds, HostInfo) ->
|
||||||
{ok, Handler, Opts, HostBinds};
|
{ok, Handler, Opts, HostBinds, HostInfo, undefined};
|
||||||
match_path('*', [{'*', Handler, Opts}|_Tail], HostBinds) ->
|
match_path('*', [{'*', Handler, Opts}|_Tail], HostBinds, HostInfo) ->
|
||||||
{ok, Handler, Opts, HostBinds};
|
{ok, Handler, Opts, HostBinds, HostInfo, undefined};
|
||||||
match_path(Path, [{PathMatch, Handler, Opts}|Tail], HostBinds) ->
|
match_path(Path, [{PathMatch, Handler, Opts}|Tail], HostBinds, HostInfo) ->
|
||||||
case try_match(path, Path, PathMatch) of
|
case try_match(path, Path, PathMatch) of
|
||||||
false ->
|
false ->
|
||||||
match_path(Path, Tail, HostBinds);
|
match_path(Path, Tail, HostBinds, HostInfo);
|
||||||
{true, PathBinds} ->
|
{true, PathBinds, PathInfo} ->
|
||||||
{ok, Handler, Opts, HostBinds ++ PathBinds}
|
{ok, Handler, Opts, HostBinds ++ PathBinds, HostInfo, PathInfo}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Internal.
|
%% Internal.
|
||||||
|
|
||||||
-spec try_match(Type::host | path, List::path_tokens(), Match::match_rule())
|
-spec try_match(Type::host | path, List::path_tokens(), Match::match_rule())
|
||||||
-> {true, Binds::bindings()} | false.
|
-> {true, Binds::bindings(), ListInfo::undefined | path_tokens()} | false.
|
||||||
try_match(_Type, _List, '_') ->
|
|
||||||
{true, []};
|
|
||||||
try_match(_Type, List, Match) when length(List) =/= length(Match) ->
|
|
||||||
false;
|
|
||||||
try_match(host, List, Match) ->
|
try_match(host, List, Match) ->
|
||||||
list_match(lists:reverse(List), lists:reverse(Match), []);
|
list_match(lists:reverse(List), lists:reverse(Match), []);
|
||||||
try_match(path, List, Match) ->
|
try_match(path, List, Match) ->
|
||||||
list_match(List, Match, []).
|
list_match(List, Match, []).
|
||||||
|
|
||||||
-spec list_match(List::path_tokens(), Match::match_rule(), Binds::bindings())
|
-spec list_match(List::path_tokens(), Match::match_rule(), Binds::bindings())
|
||||||
-> {true, Binds::bindings()} | false.
|
-> {true, Binds::bindings(), ListInfo::undefined | path_tokens()} | false.
|
||||||
|
%% Atom '...' matches any trailing path, stop right now.
|
||||||
|
list_match(List, ['...'], Binds) ->
|
||||||
|
{true, Binds, List};
|
||||||
%% Atom '_' matches anything, continue.
|
%% Atom '_' matches anything, continue.
|
||||||
list_match([_E|Tail], ['_'|TailMatch], Binds) ->
|
list_match([_E|Tail], ['_'|TailMatch], Binds) ->
|
||||||
list_match(Tail, TailMatch, Binds);
|
list_match(Tail, TailMatch, Binds);
|
||||||
|
@ -117,12 +124,12 @@ list_match([E|Tail], [E|TailMatch], Binds) ->
|
||||||
%% Bind E to the variable name V and continue.
|
%% Bind E to the variable name V and continue.
|
||||||
list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->
|
list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->
|
||||||
list_match(Tail, TailMatch, [{V, E}|Binds]);
|
list_match(Tail, TailMatch, [{V, E}|Binds]);
|
||||||
%% Values don't match, stop.
|
|
||||||
list_match([_E|_Tail], [_F|_TailMatch], _Binds) ->
|
|
||||||
false;
|
|
||||||
%% Match complete.
|
%% Match complete.
|
||||||
list_match([], [], Binds) ->
|
list_match([], [], Binds) ->
|
||||||
{true, Binds}.
|
{true, Binds, undefined};
|
||||||
|
%% Values don't match, stop.
|
||||||
|
list_match(_List, _Match, _Binds) ->
|
||||||
|
false.
|
||||||
|
|
||||||
%% Tests.
|
%% Tests.
|
||||||
|
|
||||||
|
@ -229,6 +236,36 @@ match_test_() ->
|
||||||
{ok, match_duplicate_vars, [we, {expect, two}, var, here],
|
{ok, match_duplicate_vars, [we, {expect, two}, var, here],
|
||||||
[{var, <<"fr">>}, {var, <<"987">>}]}}
|
[{var, <<"fr">>}, {var, <<"987">>}]}}
|
||||||
],
|
],
|
||||||
|
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
|
||||||
|
{ok, Handler, Opts, Binds, undefined, undefined} = match(H, P, Dispatch)
|
||||||
|
end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].
|
||||||
|
|
||||||
|
match_info_test_() ->
|
||||||
|
Dispatch = [
|
||||||
|
{[<<"www">>, <<"dev-extend">>, <<"eu">>], [
|
||||||
|
{[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], match_path, []}
|
||||||
|
]},
|
||||||
|
{['...', <<"dev-extend">>, <<"eu">>], [
|
||||||
|
{'_', match_any, []}
|
||||||
|
]}
|
||||||
|
],
|
||||||
|
Tests = [
|
||||||
|
{[<<"dev-extend">>, <<"eu">>], [],
|
||||||
|
{ok, match_any, [], [], [], undefined}},
|
||||||
|
{[<<"bugs">>, <<"dev-extend">>, <<"eu">>], [],
|
||||||
|
{ok, match_any, [], [], [<<"bugs">>], undefined}},
|
||||||
|
{[<<"cowboy">>, <<"bugs">>, <<"dev-extend">>, <<"eu">>], [],
|
||||||
|
{ok, match_any, [], [], [<<"cowboy">>, <<"bugs">>], undefined}},
|
||||||
|
{[<<"www">>, <<"dev-extend">>, <<"eu">>],
|
||||||
|
[<<"pathinfo">>, <<"is">>, <<"next">>],
|
||||||
|
{ok, match_path, [], [], undefined, []}},
|
||||||
|
{[<<"www">>, <<"dev-extend">>, <<"eu">>],
|
||||||
|
[<<"pathinfo">>, <<"is">>, <<"next">>, <<"path_info">>],
|
||||||
|
{ok, match_path, [], [], undefined, [<<"path_info">>]}},
|
||||||
|
{[<<"www">>, <<"dev-extend">>, <<"eu">>],
|
||||||
|
[<<"pathinfo">>, <<"is">>, <<"next">>, <<"foo">>, <<"bar">>],
|
||||||
|
{ok, match_path, [], [], undefined, [<<"foo">>, <<"bar">>]}}
|
||||||
|
],
|
||||||
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
|
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
|
||||||
R = match(H, P, Dispatch)
|
R = match(H, P, Dispatch)
|
||||||
end} || {H, P, R} <- Tests].
|
end} || {H, P, R} <- Tests].
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
|
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
|
||||||
|
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
|
||||||
%%
|
%%
|
||||||
%% Permission to use, copy, modify, and/or distribute this software for any
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||||
%% purpose with or without fee is hereby granted, provided that the above
|
%% purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -158,8 +159,9 @@ dispatch(Req=#http_req{host=Host, path=Path},
|
||||||
%% @todo We probably want to filter the Host and Path here to allow
|
%% @todo We probably want to filter the Host and Path here to allow
|
||||||
%% things like url rewriting.
|
%% things like url rewriting.
|
||||||
case cowboy_dispatcher:match(Host, Path, Dispatch) of
|
case cowboy_dispatcher:match(Host, Path, Dispatch) of
|
||||||
{ok, Handler, Opts, Binds} ->
|
{ok, Handler, Opts, Binds, HostInfo, PathInfo} ->
|
||||||
parse_header(Req#http_req{bindings=Binds},
|
parse_header(Req#http_req{host_info=HostInfo, path_info=PathInfo,
|
||||||
|
bindings=Binds},
|
||||||
State#state{handler={Handler, Opts}});
|
State#state{handler={Handler, Opts}});
|
||||||
{error, notfound, host} ->
|
{error, notfound, host} ->
|
||||||
error_terminate(400, State);
|
error_terminate(400, State);
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
method/1, version/1, peer/1,
|
method/1, version/1, peer/1,
|
||||||
host/1, raw_host/1, port/1,
|
host/1, host_info/1, raw_host/1, port/1,
|
||||||
path/1, raw_path/1,
|
path/1, path_info/1, raw_path/1,
|
||||||
qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
|
qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
|
||||||
binding/2, binding/3, bindings/1,
|
binding/2, binding/3, bindings/1,
|
||||||
header/2, header/3, headers/1
|
header/2, header/3, headers/1
|
||||||
|
@ -59,6 +59,12 @@ peer(Req) ->
|
||||||
host(Req) ->
|
host(Req) ->
|
||||||
{Req#http_req.host, Req}.
|
{Req#http_req.host, Req}.
|
||||||
|
|
||||||
|
-spec host_info(Req::#http_req{})
|
||||||
|
-> {HostInfo::cowboy_dispatcher:path_tokens() | undefined,
|
||||||
|
Req::#http_req{}}.
|
||||||
|
host_info(Req) ->
|
||||||
|
{Req#http_req.host_info, Req}.
|
||||||
|
|
||||||
-spec raw_host(Req::#http_req{}) -> {RawHost::binary(), Req::#http_req{}}.
|
-spec raw_host(Req::#http_req{}) -> {RawHost::binary(), Req::#http_req{}}.
|
||||||
raw_host(Req) ->
|
raw_host(Req) ->
|
||||||
{Req#http_req.raw_host, Req}.
|
{Req#http_req.raw_host, Req}.
|
||||||
|
@ -72,6 +78,12 @@ port(Req) ->
|
||||||
path(Req) ->
|
path(Req) ->
|
||||||
{Req#http_req.path, Req}.
|
{Req#http_req.path, Req}.
|
||||||
|
|
||||||
|
-spec path_info(Req::#http_req{})
|
||||||
|
-> {PathInfo::cowboy_dispatcher:path_tokens() | undefined,
|
||||||
|
Req::#http_req{}}.
|
||||||
|
path_info(Req) ->
|
||||||
|
{Req#http_req.path_info, Req}.
|
||||||
|
|
||||||
-spec raw_path(Req::#http_req{}) -> {RawPath::binary(), Req::#http_req{}}.
|
-spec raw_path(Req::#http_req{}) -> {RawPath::binary(), Req::#http_req{}}.
|
||||||
raw_path(Req) ->
|
raw_path(Req) ->
|
||||||
{Req#http_req.raw_path, Req}.
|
{Req#http_req.raw_path, Req}.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue