From 30a971a039c2725726080ce6d50ce90e1108cb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20S=C3=B6derqvist?= Date: Wed, 21 Oct 2020 16:58:22 +0200 Subject: Fail fast while closing if reconnect is off If a request/headers/connect/ws_upgrade is created when a connection is in state 'closing', such as after receiving an HTTP/2 GOAWAY frame or an HTTP/1.1 "Connection: close" header, an error message is sent back to the caller immediately, if reconnect is off (that is if the option retry is set to 0). This allows an application to retry the request on another connection without waiting for all streams on the current connection to complete. --- src/gun.erl | 21 +++++++++++++++++++++ src/gun_http.erl | 22 ++++++++++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/gun.erl b/src/gun.erl index ac643e0..ddb5007 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -1332,6 +1332,27 @@ closing(state_timeout, closing_timeout, State=#state{status=Status}) -> _ -> normal end, disconnect(State, Reason); +%% When reconnect is disabled, fail HTTP/Websocket operations immediately. +closing(cast, {headers, ReplyTo, StreamRef, _Method, _Path, _Headers, _InitialFlow}, + State=#state{opts=#{retry := 0}}) -> + ReplyTo ! {gun_error, self(), StreamRef, closing}, + {keep_state, State}; +closing(cast, {request, ReplyTo, StreamRef, _Method, _Path, _Headers, _Body, _InitialFlow}, + State=#state{opts=#{retry := 0}}) -> + ReplyTo ! {gun_error, self(), StreamRef, closing}, + {keep_state, State}; +closing(cast, {connect, ReplyTo, StreamRef, _Destination, _Headers, _InitialFlow}, + State=#state{opts=#{retry := 0}}) -> + ReplyTo ! {gun_error, self(), StreamRef, closing}, + {keep_state, State}; +closing(cast, {ws_upgrade, ReplyTo, StreamRef, _Path, _Headers}, + State=#state{opts=#{retry := 0}}) -> + ReplyTo ! {gun_error, self(), StreamRef, closing}, + {keep_state, State}; +closing(cast, {ws_upgrade, ReplyTo, StreamRef, _Path, _Headers, _WsOpts}, + State=#state{opts=#{retry := 0}}) -> + ReplyTo ! {gun_error, self(), StreamRef, closing}, + {keep_state, State}; closing(Type, Event, State) -> handle_common_connected(Type, Event, ?FUNCTION_NAME, State). diff --git a/src/gun_http.erl b/src/gun_http.erl index 8cbeada..9c52f2d 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -418,7 +418,7 @@ handle_response(Rest, State=#http_state{version=ClientVersion, opts=Opts, connec Status, Headers, Handlers0), EvHandlerState1} end end, - EvHandlerState = case IsFin of + EvHandlerState3 = case IsFin of nofin -> EvHandlerState2; fin -> @@ -436,17 +436,31 @@ handle_response(Rest, State=#http_state{version=ClientVersion, opts=Opts, connec %% We always reset in_state even if not chunked. if IsFin =:= fin, Conn2 =:= close -> - {close, CookieStore, EvHandlerState}; + {close, CookieStore, EvHandlerState3}; IsFin =:= fin -> handle(Rest, end_stream(State#http_state{in=In, in_state={0, 0}, connection=Conn2, streams=[Stream#stream{handler_state=Handlers}|Tail]}), - CookieStore, EvHandler, EvHandlerState); + CookieStore, EvHandler, EvHandlerState3); + Conn2 =:= close -> + close_streams(State, Tail, closing), + {CommandOrCommands, CookieStore1, EvHandlerState4} = + handle(Rest, State#http_state{in=In, + in_state={0, 0}, connection=Conn2, + streams=[Stream#stream{handler_state=Handlers}]}, + CookieStore, EvHandler, EvHandlerState3), + Commands = if + is_list(CommandOrCommands) -> + CommandOrCommands ++ [closing(State)]; + true -> + [CommandOrCommands, closing(State)] + end, + {Commands, CookieStore1, EvHandlerState4}; true -> handle(Rest, State#http_state{in=In, in_state={0, 0}, connection=Conn2, streams=[Stream#stream{handler_state=Handlers}|Tail]}, - CookieStore, EvHandler, EvHandlerState) + CookieStore, EvHandler, EvHandlerState3) end. %% The state must be first in order to retrieve it when the stream ended. -- cgit v1.2.3