Current section

Files

Jump to
launchdarkly_server_sdk src ldclient_event_dispatch_httpc.erl
Raw

src/ldclient_event_dispatch_httpc.erl

%%-------------------------------------------------------------------
%% @doc Event dispatcher
%% @private
%% @end
%%-------------------------------------------------------------------
-module(ldclient_event_dispatch_httpc).
-behaviour(ldclient_event_dispatch).
%% Behavior callbacks
-export([init/2, send/4]).
%% Internal type for ETag cache state
-type state() :: #{
headers => list(),
http_options => list()
}.
%% Expose non-exported methods for tests.
-ifdef(TEST).
-compile(export_all).
-endif.
%%===================================================================
%% Behavior callbacks
%%===================================================================
-spec init(Tag :: atom(), SdkKey :: string()) -> state().
init(Tag, _SdkKey) ->
Options = ldclient_config:get_value(Tag, http_options),
HttpOptions = ldclient_http_options:httpc_parse_http_options(Options),
DefaultHeaders = ldclient_headers:get_default_headers(Tag, string_pairs),
Headers = ldclient_http_options:httpc_append_custom_headers([
{"X-LaunchDarkly-Event-Schema", ldclient_config:get_event_schema()}
| DefaultHeaders
], Options),
#{
headers => Headers,
http_options => HttpOptions
}.
%% @doc Send events to LaunchDarkly event server
%%
%% @end
-spec send(State :: state(), JsonEvents :: binary(), PayloadId :: uuid:uuid(), Uri :: string()) ->
{ok, integer()} | {error, temporary, string()} | {error, permanent, string()}.
send(State, JsonEvents, PayloadId, Uri) ->
#{headers := BaseHeaders, http_options := HttpOptions} = State,
Headers = [
{"X-LaunchDarkly-Payload-ID", uuid:uuid_to_string(PayloadId)} |
BaseHeaders
],
Request = httpc:request(post, {Uri, Headers, "application/json", JsonEvents}, HttpOptions, []),
process_request(Request).
%%===================================================================
%% Internal functions
%%===================================================================
-type http_request() :: {ok, {{string(), integer(), string()}, [{string(), string()}], string() | binary()}}.
-spec process_request({error, term()} | http_request())
-> {ok, integer()} | {error, temporary, string()} | {error, permanent, string()}.
process_request({error, Reason}) ->
{error, temporary, Reason};
process_request({ok, {{_Version, StatusCode, _ReasonPhrase}, Headers, _Body}}) when StatusCode < 400 ->
{ok, get_server_time(Headers)};
process_request({ok, {{Version, StatusCode, ReasonPhrase}, _Headers, _Body}}) ->
Reason = format_response(Version, StatusCode, ReasonPhrase),
HttpErrorType = ldclient_http:is_http_error_code_recoverable(StatusCode),
{error, HttpErrorType, Reason}.
-spec format_response(Version :: string(), StatusCode :: integer(), ReasonPhrase :: string()) ->
string().
format_response(Version, StatusCode, ReasonPhrase) ->
io_lib:format("~s ~b ~s", [Version, StatusCode, ReasonPhrase]).
%% Get the server time, and if there is not time, then return 0.
-spec get_server_time(Headers :: [{Field :: [byte()], Value :: binary() | iolist()}]) -> integer().
get_server_time([{"date", Date}|_T]) when is_list(Date) ->
%% convert_request_date expects a string that is a list of characters.
%% Not a binary string. The guard can make sure it is a list, but not
%% that it is a char list. So that gets checked here.
case io_lib:char_list(Date) of
true -> case httpd_util:convert_request_date(Date) of
bad_date ->
%% This would be a date in a bad format.
0;
ParsedDate ->
ldclient_time:datetime_to_timestamp(ParsedDate)
end;
false ->
%% The date was a list, but was not a list of characters.
0
end;
get_server_time([_H|T]) ->
get_server_time(T);
get_server_time(_) -> 0.