aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2017-11-15 14:39:36 +0100
committerLoïc Hoguin <[email protected]>2017-11-15 14:39:36 +0100
commitcbbb4d5523f8738b237593f9516c3a237d0cc2f4 (patch)
tree7171b2a9a2750af1409fede12e61971d479d7148
parent2ea86ea1a47d47cae7d6c8f21d49bf2913681008 (diff)
downloadgun-cbbb4d5523f8738b237593f9516c3a237d0cc2f4.tar.gz
gun-cbbb4d5523f8738b237593f9516c3a237d0cc2f4.tar.bz2
gun-cbbb4d5523f8738b237593f9516c3a237d0cc2f4.zip
Add preliminary support for trailers
The code is definitely not the best, but as long as it doesn't break anything it should be OK for now.
-rw-r--r--src/gun.erl6
-rw-r--r--src/gun_http.erl47
-rw-r--r--src/gun_http2.erl10
3 files changed, 52 insertions, 11 deletions
diff --git a/src/gun.erl b/src/gun.erl
index 311e405..e6ed183 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -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} ->
@@ -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} ->
diff --git a/src/gun_http.erl b/src/gun_http.erl
index 3837bb3..26761a8 100644
--- a/src/gun_http.erl
+++ b/src/gun_http.erl
@@ -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
@@ -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 >>,
@@ -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
diff --git a/src/gun_http2.erl b/src/gun_http2.erl
index decc206..cc57f65 100644
--- a/src/gun_http2.erl
+++ b/src/gun_http2.erl
@@ -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,