aboutsummaryrefslogtreecommitdiffstats
path: root/src
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 /src
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.
Diffstat (limited to 'src')
-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,