Permalink
Browse files

Optimize Connection header parsing

Still optimizing the critical path.

Removes cowboy_http:connection_to_atom/1.
  • Loading branch information...
1 parent 681a216 commit cd7f37d34645dd2f54175d60632f9684cf064c6c @essen essen committed Sep 26, 2012
Showing with 88 additions and 37 deletions.
  1. +0 −25 src/cowboy_http.erl
  2. +88 −12 src/cowboy_req.erl
View
@@ -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 = [
View
@@ -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.

0 comments on commit cd7f37d

Please sign in to comment.