Current section

Files

Jump to
eetcd_fork src eetcd_stream.erl
Raw

src/eetcd_stream.erl

%% @private
-module(eetcd_stream).
%% API
-export([unary/7, bidi_streaming/4, server_streaming/6]).
-export([data/6, await/4]).
-include("eetcd.hrl").
-export_type([conn/0]).
-type conn() :: {GunPid :: pid(), Headers :: gun:req_headers()}.
-spec bidi_streaming(Client, Path, PbModule, Opts) -> Result when
Result :: {ok, GunPid, Http2Ref, PbModule} | {error, eetcd:eetcd_error()},
Client :: eetcd:client(),
Path :: iodata(),
PbModule :: module(),
Opts :: eetcd:request_opts(),
GunPid :: pid(),
Http2Ref :: eetcd:stream_ref().
bidi_streaming(EtcdName, Path, PbModule, _Opts) when is_atom(EtcdName) ->
case eetcd_conn:round_robin_select(EtcdName) of
{ok, GunPid, Headers} ->
bidi_streaming({EtcdName, {GunPid, Headers}}, Path, PbModule, _Opts);
Err -> Err
end;
bidi_streaming({EtcdName, {GunPid, Headers}}, Path, PbModule, _Opts) when is_atom(EtcdName) ->
Ref = gun:headers(GunPid, <<"POST">>, Path, Headers),
{ok, GunPid, Ref, PbModule}.
-spec server_streaming(Client, EtcdMsg, EtcdMsgName, Http2Path, PbModule, Opts) -> Result when
Result :: {ok, GunPid, Http2Ref, PbModule} | {error, eetcd:eetcd_error()},
Client :: eetcd:client(),
EtcdMsg :: map(),
EtcdMsgName :: atom(),
Http2Path :: iodata(),
PbModule :: module(),
Opts :: eetcd:request_opts(),
GunPid :: pid(),
Http2Ref :: eetcd:stream_ref().
server_streaming(EtcdName, Msg, MsgName, Path, PbModule, Opts) ->
case bidi_streaming(EtcdName, Path, PbModule, Opts) of
{ok, Pid, Ref, _} ->
data(Pid, Ref, Msg, MsgName, nofin, PbModule),
{ok, Pid, Ref, PbModule};
Err -> Err
end.
-spec data(GunPid, Http2Ref, EtcdMsg, EtcdMsgName, IsFin, PbModule) -> Http2Ref when
GunPid :: pid(),
Http2Ref :: eetcd:stream_ref(),
EtcdMsg :: map(),
EtcdMsgName :: atom(),
IsFin :: fin | nofin,
PbModule :: module().
data(Pid, Ref, Msg, MsgName, IsFin, PbModule) ->
EncodeBody = eetcd_grpc:encode(identity, Msg, MsgName, PbModule),
gun:data(Pid, Ref, IsFin, EncodeBody),
Ref.
-spec unary(EtcdName, EtcdRequest, EtcdRequestName, Http2Path, EtcdResponseType, PbModule, Opts) -> EtcdResponse when
EtcdName :: etcd_name() | {etcd_name(), conn()},
EtcdRequest :: map(),
EtcdRequestName :: atom(),
Http2Path :: iodata(),
EtcdResponseType :: atom(),
PbModule :: module(),
Opts :: eetcd:request_opts(),
EtcdResponse :: {ok, ResponseMsg :: map()} | {error, eetcd_error()}.
unary(EtcdName, Request, RequestName, Path, ResponseType, PbModule, Opts) when is_atom(EtcdName) ->
case eetcd_conn:round_robin_select(EtcdName) of
{ok, GunPid, Headers} ->
unary(EtcdName, GunPid, Request, RequestName, Path, ResponseType, Headers, PbModule,
Opts);
Err -> Err
end;
unary({EtcdName, {GunPid, Headers}}, Request, RequestName, Path, ResponseType, PbModule, Opts) ->
unary(EtcdName, GunPid, Request, RequestName, Path, ResponseType, Headers, PbModule, Opts).
-spec unary(EtcdName, GunPid, EtcdRequest, EtcdRequestName, Http2Path, EtcdResponseType, Http2Headers,
PbModule, Opts) -> EtcdResponse when
EtcdName :: etcd_name(),
GunPid :: pid(),
EtcdRequest :: map(),
EtcdRequestName :: atom(),
Http2Path :: iodata(),
EtcdResponseType :: atom(),
Http2Headers :: gun:req_headers(),
PbModule :: module(),
Opts :: eetcd:request_opts(),
EtcdResponse :: {ok, ResponseMsg :: map()} | {error, eetcd_error()}.
unary(EtcdName, GunPid, Request, RequestName, Path, ResponseType, Headers, PbModule, Opts) when is_pid(GunPid) ->
Timeout = proplists:get_value(reply_timeout, Opts, 9000),
EncodeBody = eetcd_grpc:encode(identity, Request, RequestName, PbModule),
MRef = erlang:monitor(process, GunPid),
StreamRef = gun:request(GunPid, <<"POST">>, Path, Headers, EncodeBody),
Res =
case await(GunPid, StreamRef, Timeout, MRef) of
{response, nofin, 200, _Headers} ->
await_body(GunPid, StreamRef, Timeout, MRef, ResponseType, PbModule);
{response, fin, 200, RespHeaders} ->
case eetcd_grpc:grpc_status(RespHeaders) of
#{'grpc-status' := ?GRPC_STATUS_UNAUTHENTICATED,
'grpc-message' := <<"etcdserver: invalid auth token">>} ->
NewHeaders = eetcd_conn:refresh_token(EtcdName, Headers),
StreamRef1 = gun:request(GunPid, <<"POST">>, Path, NewHeaders, EncodeBody),
case await(GunPid, StreamRef1, Timeout, MRef) of
{response, nofin, 200, _Headers} ->
await_body(GunPid, StreamRef1, Timeout, MRef, ResponseType, PbModule);
{response, fin, 200, RespHeaders1} ->
{error, {grpc_error, eetcd_grpc:grpc_status(RespHeaders1)}}
end;
Error3 -> {error, {grpc_error, Error3}}
end;
{error, _} = Error2 -> Error2
end,
erlang:demonitor(MRef, [flush]),
Res.
%% `gun:await/2,3,4`, `gun:await_body/2,3,4` and `gun:await_up/1,2,3` don't distinguish the error types until v2.0.0.
%% They can be a timeout, a connection error, a stream error or a down error (when the Gun process exited while waiting).
%% so we copy some code from gun v2.0.0 to replace `gun:await/4`
%% TODO remove this when upgrade gun to v2.0.0
-spec await(ServerPid, StreamRef, Timeout, MonitorRef) ->
{response, IsFin, StatusCode, Headers} |
{data, IsFin, Data} | {error, eetcd_error()}
when
ServerPid :: pid(),
StreamRef :: gun:stream_ref(),
Timeout :: timeout(),
MonitorRef :: reference(),
IsFin :: fin | nofin,
StatusCode :: non_neg_integer(),
Headers :: [{binary(), binary()}],
Data :: binary().
await(ServerPid, StreamRef, Timeout, MRef) ->
case gun:await(ServerPid, StreamRef, Timeout, MRef) of
{response, _, _, _}=Resp ->
Resp;
{data, _, _}=Resp ->
Resp;
{error, _} = Resp ->
transfer_error(Resp);
Other ->
?LOG_INFO("eetcd_await_resp_other ~p", [Other]),
await(ServerPid, StreamRef, Timeout, MRef)
end.
await_body(ServerPid, StreamRef, Timeout, MRef, ResponseType, PbModule) ->
case transfer_error(gun:await_body(ServerPid, StreamRef, Timeout, MRef)) of
{ok, ResBody, _Trailers} ->
{ok, Resp, <<>>} = eetcd_grpc:decode(identity, ResBody, ResponseType, PbModule),
{ok, Resp};
{error, _} = Error -> Error
end.
transfer_error({error, {stream_error, Reason}}) ->
{error, {gun_stream_error, Reason}};
transfer_error({error, {connection_error, Reason}}) ->
{error, {gun_conn_error, Reason}};
transfer_error({error, {down, Reason}}) ->
{error, {gun_down, Reason}};
transfer_error(Other) ->
Other.