Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Optimize Connection header parsing

Still optimizing the critical path.

Removes cowboy_http:connection_to_atom/1.
  • Loading branch information...
commit cd7f37d34645dd2f54175d60632f9684cf064c6c 1 parent 681a216
@essen essen authored
Showing with 88 additions and 37 deletions.
  1. +0 −25 src/cowboy_http.erl
  2. +88 −12 src/cowboy_req.erl
View
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 = [
View
100 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.
Please sign in to comment.
Something went wrong with that request. Please try again.