From 29e71cf4daec684c13047952a95ec0dc9540aad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 5 May 2011 14:03:39 +0200 Subject: Switch the HTTP protocol to use binary packets instead of lists. The server now does a single recv (or more, but only if needed) which is then sent to erlang:decode_packet/3 multiple times. Since most requests are smaller than the default MTU on many platforms, we benefit from this greatly. In the case of requests with a body, the server usually read at least part of the body on the first recv. This is bufferized properly and used when later retrieving the body. In the case of pipelined requests, we can end up reading many requests in a single recv, which are then handled properly using only the buffer containing the received data. --- src/cowboy_dispatcher.erl | 165 ++++++++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 73 deletions(-) (limited to 'src/cowboy_dispatcher.erl') diff --git a/src/cowboy_dispatcher.erl b/src/cowboy_dispatcher.erl index 4769da0..f540cd5 100644 --- a/src/cowboy_dispatcher.erl +++ b/src/cowboy_dispatcher.erl @@ -15,9 +15,9 @@ -module(cowboy_dispatcher). -export([split_host/1, split_path/1, match/3]). %% API. --type bindings() :: list({Key::atom(), Value::string()}). --type path_tokens() :: list(nonempty_string()). --type match_rule() :: '_' | '*' | list(string() | '_' | atom()). +-type bindings() :: list({Key::atom(), Value::binary()}). +-type path_tokens() :: list(binary()). +-type match_rule() :: '_' | '*' | list(binary() | '_' | atom()). -type dispatch_rule() :: {Host::match_rule(), list({Path::match_rule(), Handler::module(), Opts::term()})}. -type dispatch_rules() :: list(dispatch_rule()). @@ -29,25 +29,34 @@ %% API. --spec split_host(Host::string()) - -> {Tokens::path_tokens(), Host::string(), Port::undefined | ip_port()}. +-spec split_host(Host::binary()) + -> {Tokens::path_tokens(), RawHost::binary(), Port::undefined | ip_port()}. +split_host(<<>>) -> + {[], <<>>, undefined}; split_host(Host) -> - case string:chr(Host, $:) of - 0 -> {string:tokens(Host, "."), Host, undefined}; - N -> - {Host2, [$:|Port]} = lists:split(N - 1, Host), - {string:tokens(Host2, "."), Host2, list_to_integer(Port)} + case binary:split(Host, <<":">>) of + [Host] -> + {binary:split(Host, <<".">>, [global, trim]), Host, undefined}; + [Host2, Port] -> + {binary:split(Host2, <<".">>, [global, trim]), Host2, + list_to_integer(binary_to_list(Port))} end. --spec split_path(Path::string()) - -> {Tokens::path_tokens(), Path::string(), Qs::string()}. +-spec split_path(Path::binary()) + -> {Tokens::path_tokens(), RawPath::binary(), Qs::binary()}. split_path(Path) -> - case string:chr(Path, $?) of - 0 -> - {string:tokens(Path, "/"), Path, []}; - N -> - {Path2, [$?|Qs]} = lists:split(N - 1, Path), - {string:tokens(Path2, "/"), Path2, Qs} + case binary:split(Path, <<"?">>) of + [Path] -> {do_split_path(Path, <<"/">>), Path, <<>>}; + [<<>>, Qs] -> {[], <<>>, Qs}; + [Path2, Qs] -> {do_split_path(Path2, <<"/">>), Path2, Qs} + end. + +-spec do_split_path(RawPath::binary(), Separator::binary()) + -> Tokens::path_tokens(). +do_split_path(RawPath, Separator) -> + case binary:split(RawPath, Separator, [global, trim]) of + [<<>>|Path] -> Path; + Path -> Path end. -spec match(Host::path_tokens(), Path::path_tokens(), @@ -122,33 +131,40 @@ list_match([], [], Binds) -> split_host_test_() -> %% {Host, Result} Tests = [ - {"", {[], "", undefined}}, - {".........", {[], ".........", undefined}}, - {"*", {["*"], "*", undefined}}, - {"cowboy.dev-extend.eu", {["cowboy", "dev-extend", "eu"], - "cowboy.dev-extend.eu", undefined}}, - {"dev-extend..eu", - {["dev-extend", "eu"], "dev-extend..eu", undefined}}, - {"dev-extend.eu", {["dev-extend", "eu"], "dev-extend.eu", undefined}}, - {"dev-extend.eu:8080", {["dev-extend", "eu"], "dev-extend.eu", 8080}}, - {"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", - {["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", - "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"], - "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", undefined}} + {<<"">>, {[], <<"">>, undefined}}, + {<<".........">>, {[], <<".........">>, undefined}}, + {<<"*">>, {[<<"*">>], <<"*">>, undefined}}, + {<<"cowboy.dev-extend.eu">>, + {[<<"cowboy">>, <<"dev-extend">>, <<"eu">>], + <<"cowboy.dev-extend.eu">>, undefined}}, + {<<"dev-extend..eu">>, + {[<<"dev-extend">>, <<>>, <<"eu">>], + <<"dev-extend..eu">>, undefined}}, + {<<"dev-extend.eu">>, + {[<<"dev-extend">>, <<"eu">>], <<"dev-extend.eu">>, undefined}}, + {<<"dev-extend.eu:8080">>, + {[<<"dev-extend">>, <<"eu">>], <<"dev-extend.eu">>, 8080}}, + {<<"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z">>, + {[<<"a">>, <<"b">>, <<"c">>, <<"d">>, <<"e">>, <<"f">>, <<"g">>, + <<"h">>, <<"i">>, <<"j">>, <<"k">>, <<"l">>, <<"m">>, <<"n">>, + <<"o">>, <<"p">>, <<"q">>, <<"r">>, <<"s">>, <<"t">>, <<"u">>, + <<"v">>, <<"w">>, <<"x">>, <<"y">>, <<"z">>], + <<"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z">>, + undefined}} ], [{H, fun() -> R = split_host(H) end} || {H, R} <- Tests]. split_host_fail_test_() -> Tests = [ - "dev-extend.eu:owns", - "dev-extend.eu: owns", - "dev-extend.eu:42fun", - "dev-extend.eu: 42fun", - "dev-extend.eu:42 fun", - "dev-extend.eu:fun 42", - "dev-extend.eu: 42", - ":owns", - ":42 fun" + <<"dev-extend.eu:owns">>, + <<"dev-extend.eu: owns">>, + <<"dev-extend.eu:42fun">>, + <<"dev-extend.eu: 42fun">>, + <<"dev-extend.eu:42 fun">>, + <<"dev-extend.eu:fun 42">>, + <<"dev-extend.eu: 42">>, + <<":owns">>, + <<":42 fun">> ], [{H, fun() -> case catch split_host(H) of {'EXIT', _Reason} -> ok @@ -157,58 +173,61 @@ split_host_fail_test_() -> split_path_test_() -> %% {Path, Result, QueryString} Tests = [ - {"?", [], "", ""}, - {"???", [], "", "??"}, - {"/", [], "/", ""}, - {"/users", ["users"], "/users", ""}, - {"/users?", ["users"], "/users", ""}, - {"/users?a", ["users"], "/users", "a"}, - {"/users/42/friends?a=b&c=d&e=notsure?whatever", - ["users", "42", "friends"], - "/users/42/friends", "a=b&c=d&e=notsure?whatever"} + {<<"?">>, [], <<"">>, <<"">>}, + {<<"???">>, [], <<"">>, <<"??">>}, + {<<"/">>, [], <<"/">>, <<"">>}, + {<<"/users">>, [<<"users">>], <<"/users">>, <<"">>}, + {<<"/users?">>, [<<"users">>], <<"/users">>, <<"">>}, + {<<"/users?a">>, [<<"users">>], <<"/users">>, <<"a">>}, + {<<"/users/42/friends?a=b&c=d&e=notsure?whatever">>, + [<<"users">>, <<"42">>, <<"friends">>], + <<"/users/42/friends">>, <<"a=b&c=d&e=notsure?whatever">>} ], - [{P, fun() -> {R, RawP, Qs} = split_path(P) end} || {P, R, RawP, Qs} <- Tests]. + [{P, fun() -> {R, RawP, Qs} = split_path(P) end} + || {P, R, RawP, Qs} <- Tests]. match_test_() -> Dispatch = [ - {["www", '_', "dev-extend", "eu"], [ - {["users", '_', "mails"], match_any_subdomain_users, []} + {[<<"www">>, '_', <<"dev-extend">>, <<"eu">>], [ + {[<<"users">>, '_', <<"mails">>], match_any_subdomain_users, []} ]}, - {["dev-extend", "eu"], [ - {["users", id, "friends"], match_extend_users_friends, []}, + {[<<"dev-extend">>, <<"eu">>], [ + {[<<"users">>, id, <<"friends">>], match_extend_users_friends, []}, {'_', match_extend, []} ]}, - {["dev-extend", var], [ - {["threads", var], match_duplicate_vars, + {[<<"dev-extend">>, var], [ + {[<<"threads">>, var], match_duplicate_vars, [we, {expect, two}, var, here]} ]}, - {["erlang", ext], [ + {[<<"erlang">>, ext], [ {'_', match_erlang_ext, []} ]}, {'_', [ - {["users", id, "friends"], match_users_friends, []}, + {[<<"users">>, id, <<"friends">>], match_users_friends, []}, {'_', match_any, []} ]} ], %% {Host, Path, Result} Tests = [ - {["any"], [], {ok, match_any, [], []}}, - {["www", "any", "dev-extend", "eu"], ["users", "42", "mails"], + {[<<"any">>], [], {ok, match_any, [], []}}, + {[<<"www">>, <<"any">>, <<"dev-extend">>, <<"eu">>], + [<<"users">>, <<"42">>, <<"mails">>], {ok, match_any_subdomain_users, [], []}}, - {["www", "dev-extend", "eu"], ["users", "42", "mails"], - {ok, match_any, [], []}}, - {["www", "dev-extend", "eu"], [], {ok, match_any, [], []}}, - {["www", "any", "dev-extend", "eu"], ["not_users", "42", "mails"], - {error, notfound, path}}, - {["dev-extend", "eu"], [], {ok, match_extend, [], []}}, - {["dev-extend", "eu"], ["users", "42", "friends"], - {ok, match_extend_users_friends, [], [{id, "42"}]}}, - {["erlang", "fr"], '_', {ok, match_erlang_ext, [], [{ext, "fr"}]}}, - {["any"], ["users", "444", "friends"], - {ok, match_users_friends, [], [{id, "444"}]}}, - {["dev-extend", "fr"], ["threads", "987"], + {[<<"www">>, <<"dev-extend">>, <<"eu">>], + [<<"users">>, <<"42">>, <<"mails">>], {ok, match_any, [], []}}, + {[<<"www">>, <<"dev-extend">>, <<"eu">>], [], {ok, match_any, [], []}}, + {[<<"www">>, <<"any">>, <<"dev-extend">>, <<"eu">>], + [<<"not_users">>, <<"42">>, <<"mails">>], {error, notfound, path}}, + {[<<"dev-extend">>, <<"eu">>], [], {ok, match_extend, [], []}}, + {[<<"dev-extend">>, <<"eu">>], [<<"users">>, <<"42">>, <<"friends">>], + {ok, match_extend_users_friends, [], [{id, <<"42">>}]}}, + {[<<"erlang">>, <<"fr">>], '_', + {ok, match_erlang_ext, [], [{ext, <<"fr">>}]}}, + {[<<"any">>], [<<"users">>, <<"444">>, <<"friends">>], + {ok, match_users_friends, [], [{id, <<"444">>}]}}, + {[<<"dev-extend">>, <<"fr">>], [<<"threads">>, <<"987">>], {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() -> R = match(H, P, Dispatch) -- cgit v1.2.3