aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md10
-rw-r--r--include/http.hrl3
-rw-r--r--src/cowboy.app.src2
-rw-r--r--src/cowboy_acceptor.erl30
-rw-r--r--src/cowboy_acceptors_sup.erl3
-rw-r--r--src/cowboy_clock.erl207
-rw-r--r--src/cowboy_dispatcher.erl87
-rw-r--r--src/cowboy_http_protocol.erl6
-rw-r--r--src/cowboy_http_req.erl24
-rw-r--r--src/cowboy_ssl_transport.erl2
-rw-r--r--src/cowboy_sup.erl4
-rw-r--r--src/cowboy_tcp_transport.erl2
13 files changed, 336 insertions, 46 deletions
diff --git a/Makefile b/Makefile
index 220ab9d..7678009 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ clean:
rm -f test/*.beam
rm -f erl_crash.dump
-tests: app eunit ct
+tests: clean app eunit ct
eunit:
@$(REBAR) eunit
diff --git a/README.md b/README.md
index 5c7df2d..133af3b 100644
--- a/README.md
+++ b/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">>, '_']` 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
matching. To follow on our hostnames example, `[<<"dev-extend">>, ext]`
would bind the values `<<"eu">>` and `<<"fr">>` to the ext atom, that you
diff --git a/include/http.hrl b/include/http.hrl
index 20c63cb..7af6a7e 100644
--- a/include/http.hrl
+++ b/include/http.hrl
@@ -1,4 +1,5 @@
%% Copyright (c) 2011, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011, Anthony Ramine <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -49,9 +50,11 @@
version = {1, 1} :: http_version(),
peer = undefined :: undefined | {Address::ip_address(), Port::ip_port()},
host = undefined :: undefined | cowboy_dispatcher:path_tokens(),
+ host_info = undefined :: undefined | cowboy_dispatcher:path_tokens(),
raw_host = undefined :: undefined | binary(),
port = undefined :: undefined | ip_port(),
path = undefined :: undefined | '*' | cowboy_dispatcher:path_tokens(),
+ path_info = undefined :: undefined | cowboy_dispatcher:path_tokens(),
raw_path = undefined :: undefined | binary(),
qs_vals = undefined :: undefined
| list({Name::binary(), Value::binary() | true}),
diff --git a/src/cowboy.app.src b/src/cowboy.app.src
index 1cfc361..6f4a67f 100644
--- a/src/cowboy.app.src
+++ b/src/cowboy.app.src
@@ -16,7 +16,7 @@
{description, "Small, fast, modular HTTP server."},
{vsn, "0.1.0"},
{modules, []},
- {registered, [cowboy_sup]},
+ {registered, [cowboy_clock, cowboy_sup]},
{applications, [
kernel,
stdlib
diff --git a/src/cowboy_acceptor.erl b/src/cowboy_acceptor.erl
index f27f446..9bcc733 100644
--- a/src/cowboy_acceptor.erl
+++ b/src/cowboy_acceptor.erl
@@ -13,28 +13,31 @@
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_acceptor).
--export([start_link/5]). %% API.
--export([acceptor/5]). %% Internal.
+-export([start_link/6]). %% API.
+-export([acceptor/6]). %% Internal.
%% API.
-spec start_link(LSocket::inet:socket(), Transport::module(),
- Protocol::module(), Opts::term(), ReqsSup::pid()) -> {ok, Pid::pid()}.
-start_link(LSocket, Transport, Protocol, Opts, ReqsSup) ->
+ Protocol::module(), Opts::term(),
+ MaxConns::non_neg_integer(), ReqsSup::pid()) -> {ok, Pid::pid()}.
+start_link(LSocket, Transport, Protocol, Opts, MaxConns, ReqsSup) ->
Pid = spawn_link(?MODULE, acceptor,
- [LSocket, Transport, Protocol, Opts, ReqsSup]),
+ [LSocket, Transport, Protocol, Opts, MaxConns, ReqsSup]),
{ok, Pid}.
%% Internal.
-spec acceptor(LSocket::inet:socket(), Transport::module(),
- Protocol::module(), Opts::term(), ReqsSup::pid()) -> no_return().
-acceptor(LSocket, Transport, Protocol, Opts, ReqsSup) ->
+ Protocol::module(), Opts::term(),
+ MaxConns::non_neg_integer(), ReqsSup::pid()) -> no_return().
+acceptor(LSocket, Transport, Protocol, Opts, MaxConns, ReqsSup) ->
case Transport:accept(LSocket, 2000) of
{ok, CSocket} ->
{ok, Pid} = supervisor:start_child(ReqsSup,
[CSocket, Transport, Protocol, Opts]),
- Transport:controlling_process(CSocket, Pid);
+ Transport:controlling_process(CSocket, Pid),
+ limit_reqs(MaxConns, ReqsSup);
{error, timeout} ->
ignore;
{error, _Reason} ->
@@ -42,4 +45,13 @@ acceptor(LSocket, Transport, Protocol, Opts, ReqsSup) ->
%% we may want to try and listen again on the port?
ignore
end,
- ?MODULE:acceptor(LSocket, Transport, Protocol, Opts, ReqsSup).
+ ?MODULE:acceptor(LSocket, Transport, Protocol, Opts, MaxConns, ReqsSup).
+
+-spec limit_reqs(MaxConns::non_neg_integer(), ReqsSup::pid()) -> ok.
+limit_reqs(MaxConns, ReqsSup) ->
+ Counts = supervisor:count_children(ReqsSup),
+ Active = lists:keyfind(active, 1, Counts),
+ case Active < MaxConns of
+ true -> ok;
+ false -> timer:sleep(1)
+ end.
diff --git a/src/cowboy_acceptors_sup.erl b/src/cowboy_acceptors_sup.erl
index 3fbffb1..f5b2de3 100644
--- a/src/cowboy_acceptors_sup.erl
+++ b/src/cowboy_acceptors_sup.erl
@@ -32,8 +32,9 @@ start_link(NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ReqsPid) ->
-spec init(list(term())) -> term(). %% @todo These specs should be improved.
init([NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts, ReqsPid]) ->
{ok, LSocket} = Transport:listen(TransOpts),
+ MaxConns = proplists:get_value(max_connections, TransOpts, 1024),
Procs = [{{acceptor, self(), N}, {cowboy_acceptor, start_link, [
- LSocket, Transport, Protocol, ProtoOpts, ReqsPid
+ LSocket, Transport, Protocol, ProtoOpts, MaxConns, ReqsPid
]}, permanent, brutal_kill, worker, dynamic}
|| N <- lists:seq(1, NbAcceptors)],
{ok, {{one_for_one, 10, 10}, Procs}}.
diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl
new file mode 100644
index 0000000..6d8d8f9
--- /dev/null
+++ b/src/cowboy_clock.erl
@@ -0,0 +1,207 @@
+%% Copyright (c) 2011, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_clock).
+-behaviour(gen_server).
+
+-export([start_link/0, stop/0, rfc1123/0]). %% API.
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]). %% gen_server.
+
+%% @todo Use calendar types whenever they get exported.
+-type year() :: non_neg_integer().
+-type month() :: 1..12.
+-type day() :: 1..31.
+-type hour() :: 0..23.
+-type minute() :: 0..59.
+-type second() :: 0..59.
+-type daynum() :: 1..7.
+
+-type date() :: {year(), month(), day()}.
+-type time() :: {hour(), minute(), second()}.
+
+-type datetime() :: {date(), time()}.
+
+-record(state, {
+ universaltime = undefined :: undefined | datetime(),
+ rfc1123 = <<>> :: binary(),
+ tref = undefined :: undefined | timer:tref()
+}).
+
+-define(SERVER, ?MODULE).
+-define(TABLE, ?MODULE).
+
+-include_lib("eunit/include/eunit.hrl").
+
+%% API.
+
+-spec start_link() -> {ok, Pid::pid()}.
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+-spec stop() -> stopped.
+stop() ->
+ gen_server:call(?SERVER, stop).
+
+-spec rfc1123() -> binary().
+rfc1123() ->
+ ets:lookup_element(?TABLE, rfc1123, 2).
+
+%% gen_server.
+
+init([]) ->
+ ?TABLE = ets:new(?TABLE, [set, protected,
+ named_table, {read_concurrency, true}]),
+ T = erlang:universaltime(),
+ B = update_rfc1123(undefined, T, <<>>),
+ {ok, TRef} = timer:send_interval(1000, update),
+ ets:insert(?TABLE, {rfc1123, B}),
+ {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
+
+handle_call(stop, _From, State=#state{tref=TRef}) ->
+ {ok, cancel} = timer:cancel(TRef),
+ {stop, normal, stopped, State};
+
+handle_call(_Request, _From, State) ->
+ {reply, ignored, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) ->
+ T = erlang:universaltime(),
+ B2 = update_rfc1123(Prev, T, B1),
+ ets:insert(?TABLE, {rfc1123, B2}),
+ {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% Internal.
+
+-spec update_rfc1123(Prev::undefined | datetime(), Now::datetime(),
+ Bin::binary()) -> binary().
+update_rfc1123(Now, Now, Bin) ->
+ Bin;
+update_rfc1123({Date, {H, M, _}}, {Date, {H, M, S}},
+ << Keep:23/binary, _/bits >>) ->
+ << Keep/binary, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123({Date, {H, _, _}}, {Date, {H, M, S}},
+ << Keep:20/binary, _/bits >>) ->
+ << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123({Date, _}, {Date, {H, M, S}}, << Keep:17/binary, _/bits >>) ->
+ << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123({{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}},
+ << _:7/binary, Keep:10/binary, _/bits >>) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary,
+ (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123({{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}},
+ << _:11/binary, Keep:6/binary, _/bits >>) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
+ (month(Mo))/binary, Keep/binary,
+ (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(_, {Date = {Y, Mo, D}, {H, M, S}}, _) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
+ (month(Mo))/binary, " ", (list_to_binary(integer_to_list(Y)))/binary,
+ " ", (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>.
+
+%% Following suggestion by MononcQc on #erlounge.
+-spec pad_int(0..59) -> binary().
+pad_int(X) when X < 10 ->
+ << $0, ($0 + X) >>;
+pad_int(X) ->
+ list_to_binary(integer_to_list(X)).
+
+-spec weekday(daynum()) -> binary().
+weekday(1) -> <<"Mon">>;
+weekday(2) -> <<"Tue">>;
+weekday(3) -> <<"Wed">>;
+weekday(4) -> <<"Thu">>;
+weekday(5) -> <<"Fri">>;
+weekday(6) -> <<"Sat">>;
+weekday(7) -> <<"Sun">>.
+
+-spec month(month()) -> binary().
+month( 1) -> <<"Jan">>;
+month( 2) -> <<"Feb">>;
+month( 3) -> <<"Mar">>;
+month( 4) -> <<"Apr">>;
+month( 5) -> <<"May">>;
+month( 6) -> <<"Jun">>;
+month( 7) -> <<"Jul">>;
+month( 8) -> <<"Aug">>;
+month( 9) -> <<"Sep">>;
+month(10) -> <<"Oct">>;
+month(11) -> <<"Nov">>;
+month(12) -> <<"Dec">>.
+
+%% Tests.
+
+-ifdef(TEST).
+
+update_rfc1123_test_() ->
+ Tests = [
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined,
+ {{2011, 5, 14}, {14, 25, 33}}, <<>>},
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
+ {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
+ {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
+ {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
+ {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}},
+ {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>},
+ {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}},
+ {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>},
+ {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}},
+ {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>},
+ {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
+ {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>},
+ {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
+ {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>}
+ ],
+ [{R, fun() -> R = update_rfc1123(P, N, B) end} || {R, P, N, B} <- Tests].
+
+pad_int_test_() ->
+ Tests = [
+ { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>},
+ { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>},
+ { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>},
+ {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>},
+ {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>},
+ {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>},
+ {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>},
+ {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>},
+ {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>},
+ {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>},
+ {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>},
+ {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>},
+ {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>},
+ {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>},
+ {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>}
+ ],
+ [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].
+
+-endif.
diff --git a/src/cowboy_dispatcher.erl b/src/cowboy_dispatcher.erl
index f540cd5..67a7bf6 100644
--- a/src/cowboy_dispatcher.erl
+++ b/src/cowboy_dispatcher.erl
@@ -1,4 +1,5 @@
%% Copyright (c) 2011, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011, Anthony Ramine <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% 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(),
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}.
match(_Host, _Path, []) ->
{error, notfound, host};
match(_Host, Path, [{'_', PathMatchs}|_Tail]) ->
- match_path(Path, PathMatchs, []);
+ match_path(Path, PathMatchs, [], undefined);
match(Host, Path, [{HostMatch, PathMatchs}|Tail]) ->
case try_match(host, Host, HostMatch) of
false ->
match(Host, Path, Tail);
- {true, HostBinds} ->
- match_path(Path, PathMatchs, HostBinds)
+ {true, HostBinds, undefined} ->
+ match_path(Path, PathMatchs, HostBinds, undefined);
+ {true, HostBinds, HostInfo} ->
+ match_path(Path, PathMatchs, HostBinds, lists:reverse(HostInfo))
end.
-spec match_path(Path::path_tokens(), list({Path::match_rule(),
- Handler::module(), Opts::term()}), HostBinds::bindings())
- -> {ok, Handler::module(), Opts::term(), Binds::bindings()}
+ Handler::module(), Opts::term()}), HostBinds::bindings(),
+ HostInfo::undefined | path_tokens())
+ -> {ok, Handler::module(), Opts::term(), Binds::bindings(),
+ HostInfo::undefined | path_tokens(),
+ PathInfo::undefined | path_tokens()}
| {error, notfound, path}.
-match_path(_Path, [], _HostBinds) ->
+match_path(_Path, [], _HostBinds, _HostInfo) ->
{error, notfound, path};
-match_path(_Path, [{'_', Handler, Opts}|_Tail], HostBinds) ->
- {ok, Handler, Opts, HostBinds};
-match_path('*', [{'*', Handler, Opts}|_Tail], HostBinds) ->
- {ok, Handler, Opts, HostBinds};
-match_path(Path, [{PathMatch, Handler, Opts}|Tail], HostBinds) ->
+match_path(_Path, [{'_', Handler, Opts}|_Tail], HostBinds, HostInfo) ->
+ {ok, Handler, Opts, HostBinds, HostInfo, undefined};
+match_path('*', [{'*', Handler, Opts}|_Tail], HostBinds, HostInfo) ->
+ {ok, Handler, Opts, HostBinds, HostInfo, undefined};
+match_path(Path, [{PathMatch, Handler, Opts}|Tail], HostBinds, HostInfo) ->
case try_match(path, Path, PathMatch) of
false ->
- match_path(Path, Tail, HostBinds);
- {true, PathBinds} ->
- {ok, Handler, Opts, HostBinds ++ PathBinds}
+ match_path(Path, Tail, HostBinds, HostInfo);
+ {true, PathBinds, PathInfo} ->
+ {ok, Handler, Opts, HostBinds ++ PathBinds, HostInfo, PathInfo}
end.
%% Internal.
-spec try_match(Type::host | path, List::path_tokens(), Match::match_rule())
- -> {true, Binds::bindings()} | false.
-try_match(_Type, _List, '_') ->
- {true, []};
-try_match(_Type, List, Match) when length(List) =/= length(Match) ->
- false;
+ -> {true, Binds::bindings(), ListInfo::undefined | path_tokens()} | false.
try_match(host, List, Match) ->
list_match(lists:reverse(List), lists:reverse(Match), []);
try_match(path, List, Match) ->
list_match(List, Match, []).
-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.
list_match([_E|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.
list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->
list_match(Tail, TailMatch, [{V, E}|Binds]);
-%% Values don't match, stop.
-list_match([_E|_Tail], [_F|_TailMatch], _Binds) ->
- false;
%% Match complete.
list_match([], [], Binds) ->
- {true, Binds}.
+ {true, Binds, undefined};
+%% Values don't match, stop.
+list_match(_List, _Match, _Binds) ->
+ false.
%% Tests.
@@ -230,6 +237,36 @@ match_test_() ->
[{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() ->
R = match(H, P, Dispatch)
end} || {H, P, R} <- Tests].
diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl
index 302df26..ed4d963 100644
--- a/src/cowboy_http_protocol.erl
+++ b/src/cowboy_http_protocol.erl
@@ -1,4 +1,5 @@
%% Copyright (c) 2011, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011, Anthony Ramine <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% 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
%% things like url rewriting.
case cowboy_dispatcher:match(Host, Path, Dispatch) of
- {ok, Handler, Opts, Binds} ->
- parse_header(Req#http_req{bindings=Binds},
+ {ok, Handler, Opts, Binds, HostInfo, PathInfo} ->
+ parse_header(Req#http_req{host_info=HostInfo, path_info=PathInfo,
+ bindings=Binds},
State#state{handler={Handler, Opts}});
{error, notfound, host} ->
error_terminate(400, State);
diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl
index 60f0b55..44286e2 100644
--- a/src/cowboy_http_req.erl
+++ b/src/cowboy_http_req.erl
@@ -17,8 +17,8 @@
-export([
method/1, version/1, peer/1,
- host/1, raw_host/1, port/1,
- path/1, raw_path/1,
+ host/1, host_info/1, raw_host/1, port/1,
+ path/1, path_info/1, raw_path/1,
qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
binding/2, binding/3, bindings/1,
header/2, header/3, headers/1
@@ -59,6 +59,12 @@ peer(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{}}.
raw_host(Req) ->
{Req#http_req.raw_host, Req}.
@@ -72,6 +78,12 @@ port(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{}}.
raw_path(Req) ->
{Req#http_req.raw_path, Req}.
@@ -187,7 +199,9 @@ reply(Code, Headers, Body, Req=#http_req{socket=Socket,
Head = response_head(Code, Headers, [
{<<"Connection">>, atom_to_connection(Connection)},
{<<"Content-Length">>,
- list_to_binary(integer_to_list(iolist_size(Body)))}
+ list_to_binary(integer_to_list(iolist_size(Body)))},
+ {<<"Date">>, cowboy_clock:rfc1123()},
+ {<<"Server">>, <<"Cowboy">>}
]),
Transport:send(Socket, [Head, Body]),
{ok, Req#http_req{resp_state=done}}.
@@ -198,7 +212,9 @@ chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport,
resp_state=waiting}) ->
Head = response_head(Code, Headers, [
{<<"Connection">>, <<"close">>},
- {<<"Transfer-Encoding">>, <<"chunked">>}
+ {<<"Transfer-Encoding">>, <<"chunked">>},
+ {<<"Date">>, cowboy_clock:rfc1123()},
+ {<<"Server">>, <<"Cowboy">>}
]),
Transport:send(Socket, Head),
{ok, Req#http_req{resp_state=chunks}}.
diff --git a/src/cowboy_ssl_transport.erl b/src/cowboy_ssl_transport.erl
index 41577fc..cbe1ac1 100644
--- a/src/cowboy_ssl_transport.erl
+++ b/src/cowboy_ssl_transport.erl
@@ -31,7 +31,7 @@ messages() -> {ssl, ssl_closed, ssl_error}.
-> {ok, LSocket::ssl:sslsocket()} | {error, Reason::atom()}.
listen(Opts) ->
{port, Port} = lists:keyfind(port, 1, Opts),
- Backlog = proplists:get_value(backlog, Opts, 128),
+ Backlog = proplists:get_value(backlog, Opts, 1024),
{certfile, CertFile} = lists:keyfind(certfile, 1, Opts),
{keyfile, KeyFile} = lists:keyfind(keyfile, 1, Opts),
{password, Password} = lists:keyfind(password, 1, Opts),
diff --git a/src/cowboy_sup.erl b/src/cowboy_sup.erl
index e12b3aa..278df54 100644
--- a/src/cowboy_sup.erl
+++ b/src/cowboy_sup.erl
@@ -30,4 +30,6 @@ start_link() ->
-spec init([]) -> term(). %% @todo These specs should be improved.
init([]) ->
- {ok, {{one_for_one, 10, 10}, []}}.
+ Procs = [{cowboy_clock, {cowboy_clock, start_link, []},
+ permanent, 5000, worker, dynamic}],
+ {ok, {{one_for_one, 10, 10}, Procs}}.
diff --git a/src/cowboy_tcp_transport.erl b/src/cowboy_tcp_transport.erl
index 49003bd..fdd3ca6 100644
--- a/src/cowboy_tcp_transport.erl
+++ b/src/cowboy_tcp_transport.erl
@@ -30,7 +30,7 @@ messages() -> {tcp, tcp_closed, tcp_error}.
-> {ok, LSocket::inet:socket()} | {error, Reason::atom()}.
listen(Opts) ->
{port, Port} = lists:keyfind(port, 1, Opts),
- Backlog = proplists:get_value(backlog, Opts, 128),
+ Backlog = proplists:get_value(backlog, Opts, 1024),
gen_tcp:listen(Port, [binary, {active, false},
{backlog, Backlog}, {packet, raw}, {reuseaddr, true}]).