Browse Source

Optimize Connection header parsing

Still optimizing the critical path.

Removes cowboy_http:connection_to_atom/1.
Loïc Hoguin 12 years ago
parent
commit
cd7f37d346
2 changed files with 88 additions and 37 deletions
  1. 0 25
      src/cowboy_http.erl
  2. 88 12
      src/cowboy_req.erl

+ 0 - 25
src/cowboy_http.erl

@@ -42,7 +42,6 @@
 -export([ce_identity/1]).
 
 %% Interpretation.
--export([connection_to_atom/1]).
 -export([version_to_binary/1]).
 -export([urldecode/1]).
 -export([urldecode/2]).
@@ -773,20 +772,6 @@ ce_identity(Data) ->
 
 %% Interpretation.
 
-%% @doc Walk through a tokens list and return whether
-%% the connection is keepalive or closed.
-%%
-%% The connection token is expected to be lower-case.
--spec connection_to_atom([binary()]) -> keepalive | close.
-connection_to_atom([]) ->
-	keepalive;
-connection_to_atom([<<"keep-alive">>|_Tail]) ->
-	keepalive;
-connection_to_atom([<<"close">>|_Tail]) ->
-	close;
-connection_to_atom([_Any|Tail]) ->
-	connection_to_atom(Tail).
-
 %% @doc Convert an HTTP version tuple to its binary form.
 -spec version_to_binary(version()) -> binary().
 version_to_binary({1, 1}) -> <<"HTTP/1.1">>;
@@ -1030,16 +1015,6 @@ asctime_date_test_() ->
 	],
 	[{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests].
 
-connection_to_atom_test_() ->
-	%% {Tokens, Result}
-	Tests = [
-		{[<<"close">>], close},
-		{[<<"keep-alive">>], keepalive},
-		{[<<"keep-alive">>, <<"upgrade">>], keepalive}
-	],
-	[{lists:flatten(io_lib:format("~p", [T])),
-		fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
-
 content_type_test_() ->
 	%% {ContentType, Result}
 	Tests = [

+ 88 - 12
src/cowboy_req.erl

@@ -170,6 +170,9 @@
 %%
 %% This function takes care of setting the owner's pid to self().
 %% @private
+%%
+%% Since we always need to parse the Connection header, we do it
+%% in an optimized way and add the parsed value to p_headers' cache.
 -spec new(inet:socket(), module(), binary(), binary(), binary(), binary(),
 	cowboy_http:version(), cowboy_http:headers(), binary(),
 	inet:port_number() | undefined, binary(), boolean(),
@@ -187,16 +190,16 @@ new(Socket, Transport, Method, Path, Query, Fragment,
 		false ->
 			Req#http_req{connection=close};
 		true ->
-			case lists:keymember(<<"connection">>, 1, Headers) of
+			case lists:keyfind(<<"connection">>, 1, Headers) of
 				false when Version =:= {1, 1} ->
 					Req; %% keepalive
 				false ->
 					Req#http_req{connection=close};
-				true ->
-					{ok, Tokens, Req2} = parse_header(<<"connection">>, Req),
-					%% @todo Might want to bring this function into cowboy_req.
-					Connection = cowboy_http:connection_to_atom(Tokens),
-					Req2#http_req{connection=Connection}
+				{_, ConnectionHeader} ->
+					Tokens = parse_connection_before(ConnectionHeader, []),
+					Connection = connection_to_atom(Tokens),
+					Req#http_req{connection=Connection,
+						p_headers=[{<<"connection">>, Tokens}]}
 			end
 	end.
 
@@ -432,11 +435,6 @@ parse_header(Name, Req, Default) when Name =:= <<"accept-language">> ->
 		fun (Value) ->
 			cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
 		end);
-parse_header(Name, Req, Default) when Name =:= <<"connection">> ->
-	parse_header(Name, Req, Default,
-		fun (Value) ->
-			cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
-		end);
 parse_header(Name, Req, Default) when Name =:= <<"content-length">> ->
 	parse_header(Name, Req, Default,
 		fun (Value) ->
@@ -1099,7 +1097,7 @@ response_connection([{Name, Value}|Tail], Connection) ->
 -spec response_connection_parse(binary()) -> keepalive | close.
 response_connection_parse(ReplyConn) ->
 	Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2),
-	cowboy_http:connection_to_atom(Tokens).
+	connection_to_atom(Tokens).
 
 -spec response_merge_headers(cowboy_http:headers(), cowboy_http:headers(),
 	cowboy_http:headers()) -> cowboy_http:headers().
@@ -1127,6 +1125,74 @@ atom_to_connection(keepalive) ->
 atom_to_connection(close) ->
 	<<"close">>.
 
+%% Optimized parsing functions for the Connection header.
+parse_connection_before(<<>>, Acc) ->
+	lists:reverse(Acc);
+parse_connection_before(<< C, Rest/bits >>, Acc)
+		when C =:= $,; C =:= $\s; C =:= $\t ->
+	parse_connection_before(Rest, Acc);
+parse_connection_before(Buffer, Acc) ->
+	parse_connection(Buffer, Acc, <<>>).
+
+%% An evil block of code appeared!
+parse_connection(<<>>, Acc, <<>>) ->
+	lists:reverse(Acc);
+parse_connection(<<>>, Acc, Token) ->
+	lists:reverse([Token|Acc]);
+parse_connection(<< C, Rest/bits >>, Acc, Token)
+		when C =:= $,; C =:= $\s; C =:= $\t ->
+	parse_connection_after(Rest, [Token|Acc]);
+parse_connection(<< C, Rest/bits >>, Acc, Token) ->
+	case C of
+		$A -> parse_connection(Rest, Acc, << Token/binary, $a >>);
+		$B -> parse_connection(Rest, Acc, << Token/binary, $b >>);
+		$C -> parse_connection(Rest, Acc, << Token/binary, $c >>);
+		$D -> parse_connection(Rest, Acc, << Token/binary, $d >>);
+		$E -> parse_connection(Rest, Acc, << Token/binary, $e >>);
+		$F -> parse_connection(Rest, Acc, << Token/binary, $f >>);
+		$G -> parse_connection(Rest, Acc, << Token/binary, $g >>);
+		$H -> parse_connection(Rest, Acc, << Token/binary, $h >>);
+		$I -> parse_connection(Rest, Acc, << Token/binary, $i >>);
+		$J -> parse_connection(Rest, Acc, << Token/binary, $j >>);
+		$K -> parse_connection(Rest, Acc, << Token/binary, $k >>);
+		$L -> parse_connection(Rest, Acc, << Token/binary, $l >>);
+		$M -> parse_connection(Rest, Acc, << Token/binary, $m >>);
+		$N -> parse_connection(Rest, Acc, << Token/binary, $n >>);
+		$O -> parse_connection(Rest, Acc, << Token/binary, $o >>);
+		$P -> parse_connection(Rest, Acc, << Token/binary, $p >>);
+		$Q -> parse_connection(Rest, Acc, << Token/binary, $q >>);
+		$R -> parse_connection(Rest, Acc, << Token/binary, $r >>);
+		$S -> parse_connection(Rest, Acc, << Token/binary, $s >>);
+		$T -> parse_connection(Rest, Acc, << Token/binary, $t >>);
+		$U -> parse_connection(Rest, Acc, << Token/binary, $u >>);
+		$V -> parse_connection(Rest, Acc, << Token/binary, $v >>);
+		$W -> parse_connection(Rest, Acc, << Token/binary, $w >>);
+		$X -> parse_connection(Rest, Acc, << Token/binary, $x >>);
+		$Y -> parse_connection(Rest, Acc, << Token/binary, $y >>);
+		$Z -> parse_connection(Rest, Acc, << Token/binary, $z >>);
+		C -> parse_connection(Rest, Acc, << Token/binary, C >>)
+	end.
+
+parse_connection_after(<<>>, Acc) ->
+	lists:reverse(Acc);
+parse_connection_after(<< $,, Rest/bits >>, Acc) ->
+	parse_connection_before(Rest, Acc);
+parse_connection_after(<< C, Rest/bits >>, Acc)
+		when C =:= $\s; C =:= $\t ->
+	parse_connection_after(Rest, Acc).
+
+%% @doc Walk through a tokens list and return whether
+%% the connection is keepalive or closed.
+%%
+%% We don't match on <<"keep-alive">> since it is the default value.
+-spec connection_to_atom([binary()]) -> keepalive | close.
+connection_to_atom([]) ->
+	keepalive;
+connection_to_atom([<<"close">>|_]) ->
+	close;
+connection_to_atom([_|Tail]) ->
+	connection_to_atom(Tail).
+
 -spec status(cowboy_http:status()) -> binary().
 status(100) -> <<"100 Continue">>;
 status(101) -> <<"101 Switching Protocols">>;
@@ -1225,4 +1291,14 @@ url_test() ->
 			pid=self()}),
 	ok.
 
+connection_to_atom_test_() ->
+	%% {Tokens, Result}
+	Tests = [
+		{[<<"close">>], close},
+		{[<<"keep-alive">>], keepalive},
+		{[<<"keep-alive">>, <<"upgrade">>], keepalive}
+	],
+	[{lists:flatten(io_lib:format("~p", [T])),
+		fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
+
 -endif.