mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Optimize Connection header parsing
Still optimizing the critical path. Removes cowboy_http:connection_to_atom/1.
This commit is contained in:
parent
681a216a24
commit
cd7f37d346
2 changed files with 88 additions and 37 deletions
|
@ -42,7 +42,6 @@
|
||||||
-export([ce_identity/1]).
|
-export([ce_identity/1]).
|
||||||
|
|
||||||
%% Interpretation.
|
%% Interpretation.
|
||||||
-export([connection_to_atom/1]).
|
|
||||||
-export([version_to_binary/1]).
|
-export([version_to_binary/1]).
|
||||||
-export([urldecode/1]).
|
-export([urldecode/1]).
|
||||||
-export([urldecode/2]).
|
-export([urldecode/2]).
|
||||||
|
@ -773,20 +772,6 @@ ce_identity(Data) ->
|
||||||
|
|
||||||
%% Interpretation.
|
%% 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.
|
%% @doc Convert an HTTP version tuple to its binary form.
|
||||||
-spec version_to_binary(version()) -> binary().
|
-spec version_to_binary(version()) -> binary().
|
||||||
version_to_binary({1, 1}) -> <<"HTTP/1.1">>;
|
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].
|
[{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_() ->
|
content_type_test_() ->
|
||||||
%% {ContentType, Result}
|
%% {ContentType, Result}
|
||||||
Tests = [
|
Tests = [
|
||||||
|
|
|
@ -170,6 +170,9 @@
|
||||||
%%
|
%%
|
||||||
%% This function takes care of setting the owner's pid to self().
|
%% This function takes care of setting the owner's pid to self().
|
||||||
%% @private
|
%% @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(),
|
-spec new(inet:socket(), module(), binary(), binary(), binary(), binary(),
|
||||||
cowboy_http:version(), cowboy_http:headers(), binary(),
|
cowboy_http:version(), cowboy_http:headers(), binary(),
|
||||||
inet:port_number() | undefined, binary(), boolean(),
|
inet:port_number() | undefined, binary(), boolean(),
|
||||||
|
@ -187,16 +190,16 @@ new(Socket, Transport, Method, Path, Query, Fragment,
|
||||||
false ->
|
false ->
|
||||||
Req#http_req{connection=close};
|
Req#http_req{connection=close};
|
||||||
true ->
|
true ->
|
||||||
case lists:keymember(<<"connection">>, 1, Headers) of
|
case lists:keyfind(<<"connection">>, 1, Headers) of
|
||||||
false when Version =:= {1, 1} ->
|
false when Version =:= {1, 1} ->
|
||||||
Req; %% keepalive
|
Req; %% keepalive
|
||||||
false ->
|
false ->
|
||||||
Req#http_req{connection=close};
|
Req#http_req{connection=close};
|
||||||
true ->
|
{_, ConnectionHeader} ->
|
||||||
{ok, Tokens, Req2} = parse_header(<<"connection">>, Req),
|
Tokens = parse_connection_before(ConnectionHeader, []),
|
||||||
%% @todo Might want to bring this function into cowboy_req.
|
Connection = connection_to_atom(Tokens),
|
||||||
Connection = cowboy_http:connection_to_atom(Tokens),
|
Req#http_req{connection=Connection,
|
||||||
Req2#http_req{connection=Connection}
|
p_headers=[{<<"connection">>, Tokens}]}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -432,11 +435,6 @@ parse_header(Name, Req, Default) when Name =:= <<"accept-language">> ->
|
||||||
fun (Value) ->
|
fun (Value) ->
|
||||||
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
|
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
|
||||||
end);
|
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) when Name =:= <<"content-length">> ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default,
|
||||||
fun (Value) ->
|
fun (Value) ->
|
||||||
|
@ -1099,7 +1097,7 @@ response_connection([{Name, Value}|Tail], Connection) ->
|
||||||
-spec response_connection_parse(binary()) -> keepalive | close.
|
-spec response_connection_parse(binary()) -> keepalive | close.
|
||||||
response_connection_parse(ReplyConn) ->
|
response_connection_parse(ReplyConn) ->
|
||||||
Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2),
|
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(),
|
-spec response_merge_headers(cowboy_http:headers(), cowboy_http: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) ->
|
atom_to_connection(close) ->
|
||||||
<<"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().
|
-spec status(cowboy_http:status()) -> binary().
|
||||||
status(100) -> <<"100 Continue">>;
|
status(100) -> <<"100 Continue">>;
|
||||||
status(101) -> <<"101 Switching Protocols">>;
|
status(101) -> <<"101 Switching Protocols">>;
|
||||||
|
@ -1225,4 +1291,14 @@ url_test() ->
|
||||||
pid=self()}),
|
pid=self()}),
|
||||||
ok.
|
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.
|
-endif.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue