diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | include/http.hrl | 3 | ||||
-rw-r--r-- | src/cowboy.app.src | 2 | ||||
-rw-r--r-- | src/cowboy_acceptor.erl | 30 | ||||
-rw-r--r-- | src/cowboy_acceptors_sup.erl | 3 | ||||
-rw-r--r-- | src/cowboy_clock.erl | 207 | ||||
-rw-r--r-- | src/cowboy_dispatcher.erl | 87 | ||||
-rw-r--r-- | src/cowboy_http_protocol.erl | 6 | ||||
-rw-r--r-- | src/cowboy_http_req.erl | 24 | ||||
-rw-r--r-- | src/cowboy_ssl_transport.erl | 2 | ||||
-rw-r--r-- | src/cowboy_sup.erl | 4 | ||||
-rw-r--r-- | src/cowboy_tcp_transport.erl | 2 |
13 files changed, 336 insertions, 46 deletions
@@ -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 @@ -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}]). |