Skip to content

Commit

Permalink
Add preliminary support for trailers
Browse files Browse the repository at this point in the history
The code is definitely not the best, but as long as it doesn't
break anything it should be OK for now.
  • Loading branch information
essen committed Nov 15, 2017
1 parent 2ea86ea commit cbbb4d5
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 11 deletions.
6 changes: 6 additions & 0 deletions src/gun.erl
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ await(ServerPid, StreamRef, Timeout, MRef) ->
{response, IsFin, Status, Headers};
{gun_data, ServerPid, StreamRef, IsFin, Data} ->
{data, IsFin, Data};
{gun_trailers, ServerPid, StreamRef, Trailers} ->
{trailers, Trailers};
{gun_push, ServerPid, StreamRef, NewStreamRef, Method, URI, Headers} ->
{push, NewStreamRef, Method, URI, Headers};
{gun_error, ServerPid, StreamRef, Reason} ->
Expand Down Expand Up @@ -395,6 +397,10 @@ await_body(ServerPid, StreamRef, Timeout, MRef, Acc) ->
<< Acc/binary, Data/binary >>);
{gun_data, ServerPid, StreamRef, fin, Data} ->
{ok, << Acc/binary, Data/binary >>};
%% It's OK to return trailers here because the client
%% specifically requested them.
{gun_trailers, ServerPid, StreamRef, Trailers} ->
{ok, Acc, Trailers};
{gun_error, ServerPid, StreamRef, Reason} ->
{error, Reason};
{gun_error, ServerPid, Reason} ->
Expand Down
47 changes: 38 additions & 9 deletions src/gun_http.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
-export([down/1]).
-export([ws_upgrade/7]).

-type io() :: head | {body, non_neg_integer()} | body_close | body_chunked.
-type io() :: head | {body, non_neg_integer()} | body_close | body_chunked | body_trailer.

%% @todo Make that a record.
-type websocket_info() :: {websocket, reference(), binary(), [binary()], gun:ws_opts()}. %% key, extensions, options
Expand Down Expand Up @@ -101,6 +101,7 @@ handle(Data, State=#http_state{in=head, buffer=Buffer}) ->
%% Everything sent to the socket until it closes is part of the response body.
handle(Data, State=#http_state{in=body_close}) ->
send_data_if_alive(Data, State, nofin);
%% Chunked transfer-encoding may contain both data and trailers.
handle(Data, State=#http_state{in=body_chunked, in_state=InState,
buffer=Buffer, connection=Conn}) ->
Buffer2 = << Buffer/binary, Data/binary >>,
Expand All @@ -121,20 +122,48 @@ handle(Data, State=#http_state{in=body_chunked, in_state=InState,
send_data_if_alive(Data2,
State#http_state{buffer=Rest, in_state=InState2},
nofin);
{done, _TotalLength, Rest} ->
{done, HasTrailers, Rest} ->
IsFin = case HasTrailers of
trailers -> nofin;
no_trailers -> fin
end,
%% I suppose it doesn't hurt to append an empty binary.
State1 = send_data_if_alive(<<>>, State, fin),
case Conn of
keepalive ->
State1 = send_data_if_alive(<<>>, State, IsFin),
case {HasTrailers, Conn} of
{trailers, _} ->
handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer});
{no_trailers, keepalive} ->
handle(Rest, end_stream(State1#http_state{buffer= <<>>}));
close ->
{no_trailers, close} ->
close
end;
{done, Data2, _TotalLength, Rest} ->
State1 = send_data_if_alive(Data2, State, fin),
{done, Data2, HasTrailers, Rest} ->
IsFin = case HasTrailers of
trailers -> nofin;
no_trailers -> fin
end,
State1 = send_data_if_alive(Data2, State, IsFin),
case {HasTrailers, Conn} of
{trailers, _} ->
handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer});
{no_trailers, keepalive} ->
handle(Rest, end_stream(State1#http_state{buffer= <<>>}));
{no_trailers, close} ->
close
end
end;
handle(Data, State=#http_state{in=body_trailer, buffer=Buffer, connection=Conn,
streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]}) ->
Data2 = << Buffer/binary, Data/binary >>,
case binary:match(Data2, <<"\r\n\r\n">>) of
nomatch -> State#http_state{buffer=Data2};
{_, _} ->
{Trailers, Rest} = cow_http:parse_headers(Data2),
%% @todo We probably want to pass this to gun_content_handler?
ReplyTo ! {gun_trailers, self(), stream_ref(StreamRef), Trailers},
case Conn of
keepalive ->
handle(Rest, end_stream(State1#http_state{buffer= <<>>}));
handle(Rest, end_stream(State#http_state{buffer= <<>>}));
close ->
close
end
Expand Down
10 changes: 8 additions & 2 deletions src/gun_http2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,15 @@ frame({headers, StreamID, IsFin, head_fin, HeaderBlock},
remote_fin(Stream#stream{handler_state=Handlers},
State#http2_state{decode_state=DecodeState}, IsFin)
end;
%% @todo For now we assume that it's a trailer if there's no :status.
%% A better state machine is needed to distinguish between that and errors.
false ->
stream_reset(State, StreamID, {stream_error, protocol_error,
'Malformed response; missing :status in HEADERS frame. (RFC7540 8.1.2.4)'})
%% @todo We probably want to pass this to gun_content_handler?
ReplyTo ! {gun_trailers, self(), StreamRef, Headers0},
remote_fin(Stream, State#http2_state{decode_state=DecodeState}, fin)
%% false ->
%% stream_reset(State, StreamID, {stream_error, protocol_error,
%% 'Malformed response; missing :status in HEADERS frame. (RFC7540 8.1.2.4)'})
end
catch _:_ ->
terminate(State, StreamID, {connection_error, compression_error,
Expand Down

0 comments on commit cbbb4d5

Please sign in to comment.