mirror of
https://github.com/valitydev/woody_erlang.git
synced 2024-11-06 02:15:19 +00:00
Use new performant thrift codec infrastructure (#145)
* Implement woody_client_thrift_v2 over new thrift codec facility, make it default * Move tracing facilities into woody_trace_h * Implement woody_server_thrift_v2 over thrift codec facility, make it default * Bump to rbkmoney/genlib@54920e76 * Enforce tuple-based args representation * Enable cross tests between impls * Switch to master rbkmoney/thrift_erlang@4eda678c
This commit is contained in:
parent
42dbb91b2b
commit
d106ef66bd
@ -48,7 +48,7 @@ Erlang реализация [Библиотеки RPC вызовов для об
|
||||
```erlang
|
||||
7> Url = <<"localhost:8022/v1/thrift_money_service">>.
|
||||
8> Function = give_me_money. %% thrift метод
|
||||
9> Args = [100, <<"rub">>].
|
||||
9> Args = {100, <<"rub">>}.
|
||||
10> Request = {Service, Function, Args}.
|
||||
11> ClientEventHandler = {my_event_handler, MyCustomOptions}.
|
||||
12> Context1 = woody_context:new(<<"myUniqRequestID1">>).
|
||||
@ -119,7 +119,7 @@ Erlang реализация [Библиотеки RPC вызовов для об
|
||||
|
||||
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) ->
|
||||
{ok, woody:result()} | no_return().
|
||||
handle_function(give_me_money, Sum = [Amount, Currency], Context, _MyOpts) ->
|
||||
handle_function(give_me_money, Sum = {Amount, Currency}, Context, _MyOpts) ->
|
||||
|
||||
%% RpcId можно получить из Context, полученного handle_function,
|
||||
%% для использования при логировании.
|
||||
|
@ -8,12 +8,17 @@
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace},
|
||||
{elvis_style, macro_module_names},
|
||||
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
|
||||
{elvis_style, nesting_level, #{level => 3}},
|
||||
{elvis_style, god_modules, #{limit => 25, ignore => [woody_tests_SUITE]}},
|
||||
{elvis_style, no_if_expression},
|
||||
{elvis_style, invalid_dynamic_call, #{ignore => [woody_client_thrift, woody_event_formatter]}},
|
||||
{elvis_style, invalid_dynamic_call, #{
|
||||
ignore => [
|
||||
woody_client_thrift,
|
||||
woody_event_formatter,
|
||||
woody_util
|
||||
]
|
||||
}},
|
||||
{elvis_style, used_ignored_variable},
|
||||
{elvis_style, no_behavior_info},
|
||||
{elvis_style, module_naming_convention, #{regex => "^[a-z]([a-z0-9]*_?)*(_SUITE)?$"}},
|
||||
|
@ -14,7 +14,7 @@
|
||||
1},
|
||||
{<<"genlib">>,
|
||||
{git,"https://github.com/rbkmoney/genlib.git",
|
||||
{ref,"a6b9f52d61372cdbea6c5967053662dd3309e0d7"}},
|
||||
{ref,"54920e768a71f121304a5eda547ee60295398f3c"}},
|
||||
0},
|
||||
{<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},0},
|
||||
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.15.2">>},0},
|
||||
@ -34,7 +34,7 @@
|
||||
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.5">>},1},
|
||||
{<<"thrift">>,
|
||||
{git,"https://github.com/rbkmoney/thrift_erlang.git",
|
||||
{ref,"aa233e29a8d8682ae9e088eedde772f0ee45d105"}},
|
||||
{ref,"4eda678c985d2894251b91ae43aacf7941846cc9"}},
|
||||
0},
|
||||
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.4.1">>},2}]}.
|
||||
[
|
||||
|
@ -34,7 +34,7 @@
|
||||
-type service_name() :: atom().
|
||||
-type service() :: {module(), service_name()}.
|
||||
-type func() :: atom().
|
||||
-type args() :: list().
|
||||
-type args() :: tuple().
|
||||
-type request() :: {service(), func(), args()}.
|
||||
-type result() :: _.
|
||||
-type th_handler() :: {service(), handler(options())}.
|
||||
|
@ -224,6 +224,6 @@ add_thrift_meta({Service = {_, ServiceName}, Function, Args}, Meta) ->
|
||||
service => ServiceName,
|
||||
service_schema => Service,
|
||||
function => Function,
|
||||
type => woody_client_thrift:get_rpc_type(Service, Function),
|
||||
type => woody_util:get_rpc_type(Service, Function),
|
||||
args => Args
|
||||
}.
|
||||
|
@ -10,16 +10,17 @@
|
||||
-export([call /2]).
|
||||
-export([call /3]).
|
||||
|
||||
-type transport_options() :: woody_client_thrift_http_transport:transport_options().
|
||||
|
||||
%% Types
|
||||
-type options() :: #{
|
||||
url := woody:url(),
|
||||
event_handler := woody:ev_handlers(),
|
||||
transport_opts => transport_options(), %% See hackney:request/5 for available options.
|
||||
resolver_opts => woody_resolver:options(),
|
||||
protocol => thrift,
|
||||
transport => http
|
||||
transport => http,
|
||||
%% Set to override protocol handler module selection, useful for test purposes, rarely
|
||||
%% if ever needed otherwise.
|
||||
protocol_handler_override => module(),
|
||||
%% Implementation-specific options
|
||||
_ => _
|
||||
}.
|
||||
-export_type([options/0]).
|
||||
|
||||
|
@ -9,9 +9,16 @@
|
||||
-export([call /3]).
|
||||
-export([child_spec/1]).
|
||||
|
||||
-export([get_rpc_type/2]).
|
||||
|
||||
%% Types
|
||||
-type options() :: #{
|
||||
url := woody:url(),
|
||||
event_handler := woody:ev_handlers(),
|
||||
transport_opts => woody_client_thrift_http_transport:transport_options(),
|
||||
resolver_opts => woody_resolver:options(),
|
||||
protocol => thrift,
|
||||
transport => http
|
||||
}.
|
||||
|
||||
-type thrift_client() :: term().
|
||||
|
||||
-define(WOODY_OPTS, [protocol, transport, event_handler]).
|
||||
@ -19,12 +26,12 @@
|
||||
%%
|
||||
%% API
|
||||
%%
|
||||
-spec child_spec(woody_client:options()) ->
|
||||
-spec child_spec(options()) ->
|
||||
supervisor:child_spec().
|
||||
child_spec(Options) ->
|
||||
woody_client_thrift_http_transport:child_spec(get_transport_opts(Options)).
|
||||
|
||||
-spec call(woody:request(), woody_client:options(), woody_state:st()) ->
|
||||
-spec call(woody:request(), options(), woody_state:st()) ->
|
||||
woody_client:result().
|
||||
call({Service = {_, ServiceName}, Function, Args}, Opts, WoodyState) ->
|
||||
WoodyContext = woody_state:get_context(WoodyState),
|
||||
@ -33,7 +40,7 @@ call({Service = {_, ServiceName}, Function, Args}, Opts, WoodyState) ->
|
||||
service => ServiceName,
|
||||
service_schema => Service,
|
||||
function => Function,
|
||||
type => get_rpc_type(Service, Function),
|
||||
type => woody_util:get_rpc_type(Service, Function),
|
||||
args => Args,
|
||||
deadline => woody_context:get_deadline(WoodyContext),
|
||||
metadata => woody_context:get_meta(WoodyContext)
|
||||
@ -43,19 +50,10 @@ call({Service = {_, ServiceName}, Function, Args}, Opts, WoodyState) ->
|
||||
_ = log_event(?EV_CALL_SERVICE, WoodyState1, #{}),
|
||||
do_call(make_thrift_client(Service, Opts, WoodyState1), Function, Args, WoodyState1).
|
||||
|
||||
-spec get_rpc_type(woody:service(), woody:func()) ->
|
||||
woody:rpc_type().
|
||||
get_rpc_type(ThriftService = {Module, Service}, Function) ->
|
||||
try woody_util:get_rpc_reply_type(Module:function_info(Service, Function, reply_type))
|
||||
catch
|
||||
error:Reason when Reason =:= undef orelse Reason =:= badarg ->
|
||||
error(badarg, [ThriftService, Function])
|
||||
end.
|
||||
|
||||
%%
|
||||
%% Internal functions
|
||||
%%
|
||||
-spec make_thrift_client(woody:service(), woody_client:options(), woody_state:st()) ->
|
||||
-spec make_thrift_client(woody:service(), options(), woody_state:st()) ->
|
||||
thrift_client().
|
||||
make_thrift_client(Service, Opts = #{url := Url}, WoodyState) ->
|
||||
{ok, Protocol} = thrift_binary_protocol:new(
|
||||
@ -70,12 +68,12 @@ make_thrift_client(Service, Opts = #{url := Url}, WoodyState) ->
|
||||
{ok, Client} = thrift_client:new(Protocol, Service),
|
||||
Client.
|
||||
|
||||
-spec get_transport_opts(woody_client:options()) ->
|
||||
-spec get_transport_opts(options()) ->
|
||||
woody_client_thrift_http_transport:transport_options().
|
||||
get_transport_opts(Opts) ->
|
||||
maps:get(transport_opts, Opts, #{}).
|
||||
|
||||
-spec get_resolver_opts(woody_client:options()) ->
|
||||
-spec get_resolver_opts(options()) ->
|
||||
woody_resolver:options().
|
||||
get_resolver_opts(Opts) ->
|
||||
maps:get(resolver_opts, Opts, #{}).
|
||||
@ -83,7 +81,7 @@ get_resolver_opts(Opts) ->
|
||||
-spec do_call(thrift_client(), woody:func(), woody:args(), woody_state:st()) ->
|
||||
woody_client:result().
|
||||
do_call(Client, Function, Args, WoodyState) ->
|
||||
{ClientNext, Result} = try thrift_client:call(Client, Function, Args)
|
||||
{ClientNext, Result} = try thrift_client:call(Client, Function, tuple_to_list(Args))
|
||||
catch
|
||||
throw:{Client1, {exception, #'TApplicationException'{}}} ->
|
||||
{Client1, {error, {system, get_server_violation_error()}}};
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
|
||||
%% Types
|
||||
|
||||
%% See hackney:request/5 for available options.
|
||||
-type transport_options() :: map().
|
||||
-export_type([transport_options/0]).
|
||||
|
||||
|
392
src/woody_client_thrift_v2.erl
Normal file
392
src/woody_client_thrift_v2.erl
Normal file
@ -0,0 +1,392 @@
|
||||
-module(woody_client_thrift_v2).
|
||||
|
||||
-behaviour(woody_client_behaviour).
|
||||
|
||||
-include_lib("thrift/include/thrift_constants.hrl").
|
||||
-include("woody_defs.hrl").
|
||||
|
||||
%% woody_client_behaviour callback
|
||||
-export([call /3]).
|
||||
-export([child_spec/1]).
|
||||
|
||||
%% Types
|
||||
-type options() :: #{
|
||||
url := woody:url(),
|
||||
event_handler := woody:ev_handlers(),
|
||||
transport_opts => transport_options(),
|
||||
resolver_opts => woody_resolver:options(),
|
||||
protocol => thrift,
|
||||
transport => http
|
||||
}.
|
||||
|
||||
%% See hackney:request/5 for available options.
|
||||
-type transport_options() :: map().
|
||||
|
||||
-define(DEFAULT_CONNECT_AND_SEND_TIMEOUT, 1000). %% millisec
|
||||
-define(DEFAULT_TRANSPORT_OPTIONS, #{
|
||||
connect_options => [
|
||||
% Turn TCP_NODELAY on.
|
||||
% We expect that Nagle's algorithm would not be very helpful for typical
|
||||
% Woody RPC workloads, negatively impacting perceived latency. So it's
|
||||
% better to turn it off.
|
||||
{nodelay, true}
|
||||
]
|
||||
}).
|
||||
|
||||
%%
|
||||
%% API
|
||||
%%
|
||||
-spec child_spec(options()) ->
|
||||
supervisor:child_spec().
|
||||
child_spec(Options) ->
|
||||
TransportOpts = get_transport_opts(Options),
|
||||
Name = maps:get(pool, TransportOpts, undefined),
|
||||
hackney_pool:child_spec(Name, maps:to_list(TransportOpts)).
|
||||
|
||||
-spec call(woody:request(), options(), woody_state:st()) ->
|
||||
woody_client:result().
|
||||
call({Service = {_, ServiceName}, Function, Args}, Opts, WoodyState) ->
|
||||
WoodyContext = woody_state:get_context(WoodyState),
|
||||
WoodyState1 = woody_state:add_ev_meta(
|
||||
#{
|
||||
service => ServiceName,
|
||||
service_schema => Service,
|
||||
function => Function,
|
||||
type => woody_util:get_rpc_type(Service, Function),
|
||||
args => Args,
|
||||
deadline => woody_context:get_deadline(WoodyContext),
|
||||
metadata => woody_context:get_meta(WoodyContext)
|
||||
},
|
||||
WoodyState
|
||||
),
|
||||
_ = log_event(?EV_CALL_SERVICE, WoodyState1, #{}),
|
||||
do_call(Service, Function, Args, Opts, WoodyState1).
|
||||
|
||||
%%
|
||||
%% Internal functions
|
||||
%%
|
||||
|
||||
-include_lib("hackney/include/hackney_lib.hrl").
|
||||
|
||||
-type http_headers() :: [{binary(), binary()}].
|
||||
-type header_parse_value() :: none | woody:http_header_val().
|
||||
|
||||
-define(CODEC, thrift_strict_binary_codec).
|
||||
-define(ERROR_RESP_BODY , <<"parse http response body error">> ).
|
||||
-define(ERROR_RESP_HEADER , <<"parse http response headers error">>).
|
||||
-define(BAD_RESP_HEADER , <<"reason unknown due to bad ", ?HEADER_PREFIX/binary, "-error- headers">>).
|
||||
|
||||
-define(SERVER_VIOLATION_ERROR,
|
||||
{external, result_unexpected, <<
|
||||
"server violated thrift protocol: "
|
||||
"sent TApplicationException (unknown exception) with http code 200"
|
||||
>>}
|
||||
).
|
||||
|
||||
-spec get_transport_opts(options()) ->
|
||||
woody_client_thrift_http_transport:transport_options().
|
||||
get_transport_opts(Opts) ->
|
||||
maps:get(transport_opts, Opts, #{}).
|
||||
|
||||
-spec get_resolver_opts(options()) ->
|
||||
woody_resolver:options().
|
||||
get_resolver_opts(Opts) ->
|
||||
maps:get(resolver_opts, Opts, #{}).
|
||||
|
||||
-spec do_call(woody:service(), woody:func(), woody:args(), options(), woody_state:st()) ->
|
||||
woody_client:result().
|
||||
do_call(Service, Function, Args, Opts, WoodyState) ->
|
||||
Buffer = ?CODEC:new(),
|
||||
Result = case thrift_client_codec:write_function_call(Buffer, ?CODEC, Service, Function, Args, 0) of
|
||||
{ok, Buffer1} ->
|
||||
case send_call(Buffer1, Opts, WoodyState) of
|
||||
{ok, Response} ->
|
||||
handle_result(Service, Function, Response);
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end,
|
||||
log_result(Result, WoodyState),
|
||||
map_result(Result).
|
||||
|
||||
-spec handle_result(woody:service(), woody:func(), binary()) ->
|
||||
_Result.
|
||||
handle_result(Service, Function, Response) ->
|
||||
Buffer = ?CODEC:new(Response),
|
||||
case thrift_client_codec:read_function_result(Buffer, ?CODEC, Service, Function, 0) of
|
||||
{ok, Result, Leftovers} ->
|
||||
Bytes = ?CODEC:close(Leftovers),
|
||||
case Result of
|
||||
ok when Bytes == <<>> ->
|
||||
{ok, ok};
|
||||
{reply, Reply} when Bytes == <<>> ->
|
||||
{ok, Reply};
|
||||
{exception, #'TApplicationException'{}} when Bytes == <<>> ->
|
||||
{error, {system, ?SERVER_VIOLATION_ERROR}};
|
||||
{exception, Exception} when Bytes == <<>> ->
|
||||
{error, {business, Exception}};
|
||||
_ ->
|
||||
{error, {excess_response_body, Bytes, Result}}
|
||||
end;
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec send_call(?CODEC:buffer(), options(), woody_state:st()) ->
|
||||
{ok, binary()} | {error, {system, _}}.
|
||||
send_call(Buffer, Opts = #{url := Url}, WoodyState) ->
|
||||
Context = woody_state:get_context(WoodyState),
|
||||
TransportOpts = get_transport_opts(Opts),
|
||||
ResolverOpts = get_resolver_opts(Opts),
|
||||
case is_deadline_reached(Context) of
|
||||
true ->
|
||||
_ = log_event(?EV_INTERNAL_ERROR, WoodyState, #{status => error, reason => <<"Deadline reached">>}),
|
||||
{error, {system, {internal, resource_unavailable, <<"deadline reached">>}}};
|
||||
false ->
|
||||
_ = log_event(?EV_CLIENT_SEND, WoodyState, #{url => Url}),
|
||||
% MSPF-416: We resolve url host to an ip here to prevent
|
||||
% reusing keep-alive connections to dead hosts
|
||||
case woody_resolver:resolve_url(Url, WoodyState, ResolverOpts) of
|
||||
{ok, {OldUrl, NewUrl}} ->
|
||||
Headers = add_host_header(OldUrl, make_woody_headers(Context)),
|
||||
TransportOpts1 = set_defaults(TransportOpts),
|
||||
TransportOpts2 = set_timeouts(TransportOpts1, Context),
|
||||
Result = hackney:request(post, NewUrl, Headers, Buffer, maps:to_list(TransportOpts2)),
|
||||
handle_response(Result, WoodyState);
|
||||
{error, Reason} ->
|
||||
Error = {error, {resolve_failed, Reason}},
|
||||
handle_response(Error, WoodyState)
|
||||
end
|
||||
end.
|
||||
|
||||
is_deadline_reached(Context) ->
|
||||
woody_deadline:is_reached(woody_context:get_deadline(Context)).
|
||||
|
||||
set_defaults(Options) ->
|
||||
maps:merge(?DEFAULT_TRANSPORT_OPTIONS, Options).
|
||||
|
||||
set_timeouts(Options, Context) ->
|
||||
case woody_context:get_deadline(Context) of
|
||||
undefined ->
|
||||
Options;
|
||||
Deadline ->
|
||||
Timeout = woody_deadline:to_unixtime_ms(Deadline) - woody_deadline:unow(),
|
||||
ConnectTimeout = SendTimeout = calc_timeouts(Timeout),
|
||||
%% It is intentional, that application can override the timeout values
|
||||
%% calculated from the deadline (first option value in the list takes
|
||||
%% the precedence).
|
||||
maps:merge(#{
|
||||
connect_timeout => ConnectTimeout,
|
||||
send_timeout => SendTimeout,
|
||||
recv_timeout => Timeout
|
||||
}, Options)
|
||||
end.
|
||||
|
||||
calc_timeouts(Timeout) ->
|
||||
%% It is assumed that connect and send timeouts each
|
||||
%% should take no more than 20% of the total request time
|
||||
%% and in any case no more, than DEFAULT_CONNECT_AND_SEND_TIMEOUT together.
|
||||
case max(0, Timeout) div 5 of
|
||||
T when (T*2) > ?DEFAULT_CONNECT_AND_SEND_TIMEOUT ->
|
||||
?DEFAULT_CONNECT_AND_SEND_TIMEOUT;
|
||||
T ->
|
||||
T
|
||||
end.
|
||||
|
||||
-spec make_woody_headers(woody_context:ctx()) ->
|
||||
http_headers().
|
||||
make_woody_headers(Context) ->
|
||||
add_optional_headers(Context, [
|
||||
{<<"content-type">> , ?CONTENT_TYPE_THRIFT},
|
||||
{<<"accept">> , ?CONTENT_TYPE_THRIFT},
|
||||
{?HEADER_RPC_ROOT_ID , woody_context:get_rpc_id(trace_id , Context)},
|
||||
{?HEADER_RPC_ID , woody_context:get_rpc_id(span_id , Context)},
|
||||
{?HEADER_RPC_PARENT_ID , woody_context:get_rpc_id(parent_id, Context)}
|
||||
]).
|
||||
|
||||
-spec add_optional_headers(woody_context:ctx(), http_headers()) ->
|
||||
http_headers().
|
||||
add_optional_headers(Context, Headers) ->
|
||||
add_deadline_header(Context, add_metadata_headers(Context, Headers)).
|
||||
|
||||
-spec add_metadata_headers(woody_context:ctx(), http_headers()) ->
|
||||
http_headers().
|
||||
add_metadata_headers(Context, Headers) ->
|
||||
maps:fold(fun add_metadata_header/3, Headers, woody_context:get_meta(Context)).
|
||||
|
||||
-spec add_metadata_header(woody:http_header_name(), woody:http_header_val(), http_headers()) ->
|
||||
http_headers() | no_return().
|
||||
add_metadata_header(H, V, Headers) when is_binary(H) and is_binary(V) ->
|
||||
[{<<?HEADER_META_PREFIX/binary, H/binary>>, V} | Headers].
|
||||
|
||||
add_deadline_header(Context, Headers) ->
|
||||
do_add_deadline_header(woody_context:get_deadline(Context), Headers).
|
||||
|
||||
do_add_deadline_header(undefined, Headers) ->
|
||||
Headers;
|
||||
do_add_deadline_header(Deadline, Headers) ->
|
||||
[{?HEADER_DEADLINE, woody_deadline:to_binary(Deadline)} | Headers].
|
||||
|
||||
add_host_header(#hackney_url{netloc = Netloc}, Headers) ->
|
||||
[{<<"Host">>, Netloc} | Headers].
|
||||
|
||||
-spec handle_response(_, woody_state:st()) ->
|
||||
{ok, woody:http_body()} | {error, {system, woody_error:system_error()}}.
|
||||
handle_response({ok, 200, Headers, Ref}, WoodyState) ->
|
||||
Meta = case check_error_reason(Headers, 200, WoodyState) of
|
||||
<<>> -> #{};
|
||||
Reason -> #{reason => Reason}
|
||||
end,
|
||||
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, Meta#{status => ok, code => 200}),
|
||||
get_body(hackney:body(Ref), WoodyState);
|
||||
handle_response({ok, Code, Headers, Ref}, WoodyState) ->
|
||||
{Class, Details} = check_error_headers(Code, Headers, WoodyState),
|
||||
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, code => Code, reason => Details}),
|
||||
%% Free the connection
|
||||
case hackney:skip_body(Ref) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
_ = log_event(?EV_INTERNAL_ERROR, WoodyState, #{status => error, reason => woody_util:to_binary(Reason)})
|
||||
end,
|
||||
{error, {system, {external, Class, Details}}};
|
||||
handle_response({error, {closed, _}}, WoodyState) ->
|
||||
Reason = <<"partial response">>,
|
||||
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, reason => Reason}),
|
||||
{error, {system, {external, result_unknown, Reason}}};
|
||||
handle_response({error, Reason}, WoodyState) when
|
||||
Reason =:= timeout ;
|
||||
Reason =:= econnaborted ;
|
||||
Reason =:= enetreset ;
|
||||
Reason =:= econnreset ;
|
||||
Reason =:= eshutdown ;
|
||||
Reason =:= etimedout ;
|
||||
Reason =:= closed
|
||||
->
|
||||
BinReason = woody_util:to_binary(Reason),
|
||||
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, reason => BinReason}),
|
||||
{error, {system, {external, result_unknown, BinReason}}};
|
||||
handle_response({error, Reason}, WoodyState) when
|
||||
Reason =:= econnrefused ;
|
||||
Reason =:= connect_timeout ;
|
||||
Reason =:= checkout_timeout;
|
||||
Reason =:= enetdown ;
|
||||
Reason =:= enetunreach ;
|
||||
Reason =:= ehostunreach ;
|
||||
Reason =:= eacces ;
|
||||
element(1, Reason) =:= resolve_failed
|
||||
->
|
||||
BinReason = woody_error:format_details(Reason),
|
||||
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, reason => BinReason}),
|
||||
{error, {system, {internal, resource_unavailable, BinReason}}};
|
||||
handle_response(Error = {error, {system, _}}, _) ->
|
||||
Error;
|
||||
handle_response({error, Reason}, WoodyState) ->
|
||||
Details = woody_error:format_details(Reason),
|
||||
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, reason => Details}),
|
||||
{error, {system, {internal, result_unexpected, Details}}}.
|
||||
|
||||
-spec check_error_reason(http_headers(), woody:http_code(), woody_state:st()) ->
|
||||
woody_error:details().
|
||||
check_error_reason(Headers, Code, WoodyState) ->
|
||||
do_check_error_reason(get_header_value(?HEADER_E_REASON, Headers), Code, WoodyState).
|
||||
|
||||
-spec do_check_error_reason(header_parse_value(), woody:http_code(), woody_state:st()) ->
|
||||
woody_error:details().
|
||||
do_check_error_reason(none, 200, _WoodyState) ->
|
||||
<<>>;
|
||||
do_check_error_reason(none, Code, WoodyState) ->
|
||||
_ = log_event(?EV_TRACE, WoodyState, #{event => woody_util:to_binary([?HEADER_E_REASON, " header missing"])}),
|
||||
woody_util:to_binary(["got response with http code ", Code, " and without ", ?HEADER_E_REASON, " header"]);
|
||||
do_check_error_reason(Reason, _, _) ->
|
||||
Reason.
|
||||
|
||||
-spec check_error_headers(woody:http_code(), http_headers(), woody_state:st()) ->
|
||||
{woody_error:class(), woody_error:details()}.
|
||||
check_error_headers(502, Headers, WoodyState) ->
|
||||
check_502_error_class(get_error_class_header_value(Headers), Headers, WoodyState);
|
||||
check_error_headers(Code, Headers, WoodyState) ->
|
||||
{get_error_class(Code), check_error_reason(Headers, Code, WoodyState)}.
|
||||
|
||||
-spec get_error_class(woody:http_code()) ->
|
||||
woody_error:class().
|
||||
get_error_class(503) ->
|
||||
resource_unavailable;
|
||||
get_error_class(504) ->
|
||||
result_unknown;
|
||||
get_error_class(_) ->
|
||||
result_unexpected.
|
||||
|
||||
-spec check_502_error_class(header_parse_value(), http_headers(), woody_state:st()) ->
|
||||
{woody_error:class(), woody_error:details()}.
|
||||
check_502_error_class(none, Headers, WoodyState) ->
|
||||
_ = log_event(
|
||||
?EV_TRACE,
|
||||
WoodyState,
|
||||
#{event => <<?HEADER_E_CLASS/binary, " header missing">>}
|
||||
),
|
||||
{result_unexpected, check_error_reason(Headers, 502, WoodyState)};
|
||||
check_502_error_class(<<"result unexpected">>, Headers, WoodyState) ->
|
||||
{result_unexpected, check_error_reason(Headers, 502, WoodyState)};
|
||||
check_502_error_class(<<"resource unavailable">>, Headers, WoodyState) ->
|
||||
{resource_unavailable, check_error_reason(Headers, 502, WoodyState)};
|
||||
check_502_error_class(<<"result unknown">>, Headers, WoodyState) ->
|
||||
{result_unknown, check_error_reason(Headers, 502, WoodyState)};
|
||||
check_502_error_class(Bad, _, WoodyState) ->
|
||||
_ = log_internal_error(
|
||||
?ERROR_RESP_HEADER,
|
||||
["unknown ", ?HEADER_E_CLASS, " header value: ", Bad],
|
||||
WoodyState
|
||||
),
|
||||
{result_unexpected, ?BAD_RESP_HEADER}.
|
||||
|
||||
-spec get_error_class_header_value(http_headers()) ->
|
||||
header_parse_value().
|
||||
get_error_class_header_value(Headers) ->
|
||||
case get_header_value(?HEADER_E_CLASS, Headers) of
|
||||
None when None =:= none orelse None =:= multiple ->
|
||||
None;
|
||||
Value ->
|
||||
genlib_string:to_lower(Value)
|
||||
end.
|
||||
|
||||
-spec get_header_value(woody:http_header_name(), http_headers()) ->
|
||||
header_parse_value().
|
||||
get_header_value(Name, Headers) ->
|
||||
case lists:dropwhile(fun ({K, _}) -> Name /= genlib_string:to_lower(K) end, Headers) of
|
||||
[{_, Value} | _] -> Value;
|
||||
[] -> none
|
||||
end.
|
||||
|
||||
-spec get_body({ok, woody:http_body()} | {error, atom()}, woody_state:st()) ->
|
||||
{ok, woody:http_body()} | {error, {system, woody_error:system_error()}}.
|
||||
get_body(B = {ok, _}, _) ->
|
||||
B;
|
||||
get_body({error, Reason}, WoodyState) ->
|
||||
_ = log_internal_error(?ERROR_RESP_BODY, Reason, WoodyState),
|
||||
{error, {system, {internal, result_unknown, ?ERROR_RESP_BODY}}}.
|
||||
|
||||
log_result({ok, Result}, WoodyState) ->
|
||||
log_event(?EV_SERVICE_RESULT, WoodyState, #{status => ok, result => Result});
|
||||
log_result({error, {business, ThriftExcept}}, WoodyState) ->
|
||||
log_event(?EV_SERVICE_RESULT, WoodyState, #{status => ok, class => business, result => ThriftExcept});
|
||||
log_result({error, Result}, WoodyState) ->
|
||||
log_event(?EV_SERVICE_RESULT, WoodyState, #{status => error, class => system, result => Result}).
|
||||
|
||||
-spec map_result(woody_client:result() | {error, _ThriftError}) ->
|
||||
woody_client:result().
|
||||
map_result(Res = {ok, _}) ->
|
||||
Res;
|
||||
map_result(Res = {error, {Type, _}}) when Type =:= business orelse Type =:= system ->
|
||||
Res;
|
||||
map_result({error, ThriftError}) ->
|
||||
BinError = woody_error:format_details(ThriftError),
|
||||
{error, {system, {internal, result_unexpected, <<"client thrift error: ", BinError/binary>>}}}.
|
||||
|
||||
log_internal_error(Error, Reason, WoodyState) ->
|
||||
log_event(?EV_INTERNAL_ERROR, WoodyState, #{error => Error, reason => woody_util:to_binary(Reason)}).
|
||||
|
||||
log_event(Event, WoodyState, ExtraMeta) ->
|
||||
woody_event_handler:handle_event(Event, WoodyState, ExtraMeta).
|
@ -11,6 +11,7 @@
|
||||
-export([from_binary/1]).
|
||||
-export([to_unixtime_ms/1]).
|
||||
-export([from_unixtime_ms/1]).
|
||||
-export([unow/0]).
|
||||
|
||||
%% Types
|
||||
-type millisec() :: 0..1000.
|
||||
|
@ -35,7 +35,7 @@ format_call(Module, Service, Function, Arguments, Opts) ->
|
||||
ML = maps:get(max_length, Opts1),
|
||||
MD = maps:get(max_depth, Opts1),
|
||||
MPSL = maps:get(max_printable_string_length, Opts1),
|
||||
Result1 = format_call_(ArgTypes, Arguments, Result0, MD, dec(ML), MPSL, false),
|
||||
Result1 = format_arguments(ArgTypes, Arguments, 1, Result0, MD, dec(ML), MPSL, false),
|
||||
<<Result1/binary, ")">>
|
||||
catch error:badarg ->
|
||||
Result1 = format_verbatim(Arguments, Result0, Opts1),
|
||||
@ -43,18 +43,20 @@ format_call(Module, Service, Function, Arguments, Opts) ->
|
||||
end,
|
||||
{"~ts", [Result]}.
|
||||
|
||||
format_call_([], [], Result, _MD, _ML, _MPSL, _AD) ->
|
||||
format_arguments([], _, _, Result, _MD, _ML, _MPSL, _AD) ->
|
||||
Result;
|
||||
format_call_(_, ArgumentList, Result0, _MD, ML, _MPSL, AD) when byte_size(Result0) > ML ->
|
||||
HasMoreArguments = AD and (ArgumentList =/= []),
|
||||
format_arguments(Types, _, _, Result0, _MD, ML, _MPSL, AD) when byte_size(Result0) > ML ->
|
||||
HasMoreArguments = AD and (Types =/= []),
|
||||
Result1 = maybe_add_delimiter(HasMoreArguments, Result0),
|
||||
Result2 = maybe_add_more_marker(HasMoreArguments, Result1),
|
||||
Result2;
|
||||
format_call_([Type | RestType], [Argument | RestArgs], Result0, MD, ML, MPSL, AD) ->
|
||||
format_arguments([Type | RestType], Arguments, I, Result0, MD, ML, MPSL, AD) ->
|
||||
Argument = element(I, Arguments),
|
||||
{Result1, AD1} = format_argument(Type, Argument, Result0, MD, ML, MPSL, AD),
|
||||
format_call_(
|
||||
format_arguments(
|
||||
RestType,
|
||||
RestArgs,
|
||||
Arguments,
|
||||
I + 1,
|
||||
Result1,
|
||||
MD,
|
||||
ML,
|
||||
@ -433,7 +435,7 @@ stop_format(MaybeAddMoreMarker, Result0) ->
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-spec test() -> _.
|
||||
|
||||
-define(ARGS, [undefined, <<"1CR1Xziml7o">>,
|
||||
-define(ARGS, {undefined, <<"1CR1Xziml7o">>,
|
||||
[{contract_modification,
|
||||
{payproc_ContractModificationUnit, <<"1CR1Y2ZcrA0">>,
|
||||
{creation,
|
||||
@ -470,9 +472,9 @@ stop_format(MaybeAddMoreMarker, Result0) ->
|
||||
{payproc_ShopModificationUnit, <<"1CR1Y2ZcrA2">>,
|
||||
{shop_account_creation,
|
||||
{payproc_ShopAccountParams, {domain_CurrencyRef, <<"RUB">>}}}}}]
|
||||
]).
|
||||
}).
|
||||
|
||||
-define(ARGS2, [{mg_stateproc_CallArgs,
|
||||
-define(ARGS2, {{mg_stateproc_CallArgs,
|
||||
{bin,
|
||||
<<131, 104, 4, 100, 0, 11, 116, 104, 114, 105, 102, 116, 95, 99, 97, 108, 108,
|
||||
100, 0, 16, 112, 97, 114, 116, 121, 95, 109, 97, 110, 97, 103, 101, 109, 101,
|
||||
@ -558,7 +560,7 @@ stop_format(MaybeAddMoreMarker, Result0) ->
|
||||
101, 120, 116, 0, 0, 0, 0, 100, 0, 14, 115, 110, 97, 112,
|
||||
115, 104, 111, 116, 95, 105, 110, 100, 101, 120, 106>>},
|
||||
{str, <<"ct">>} =>
|
||||
{str, <<"application/x-erlang-binary">>}}}}}]
|
||||
{str, <<"application/x-erlang-binary">>}}}}}}
|
||||
).
|
||||
|
||||
format_msg({Fmt, Params}) ->
|
||||
@ -945,9 +947,9 @@ depth_and_length_test_() -> [
|
||||
verbatim_test_() -> [
|
||||
?_assertEqual(
|
||||
"PartyManagement:CallMissingFunction("
|
||||
"[undefined,<<\"1CR1Xziml7o\">>,[{contract_modification,{payproc_ContractModificationUnit,"
|
||||
"{undefined,<<\"1CR1Xziml7o\">>,[{contract_modification,{payproc_ContractModificationUnit,"
|
||||
"<<\"1CR1\"...>>,{...}}},{contract_modification,{payproc_ContractModificationUnit,<<...>>,"
|
||||
"...}},{shop_modification,{payproc_ShopModificationUnit,...}}|...]]"
|
||||
"...}},{shop_modification,{payproc_ShopModificationUnit,...}}|...]}"
|
||||
")",
|
||||
format_msg(
|
||||
format_call(
|
||||
|
@ -441,8 +441,8 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[undefined, <<"1CQdDqPROyW">>,
|
||||
{payproc_PartyParams, {domain_PartyContactInfo, <<"hg_ct_helper">>}}],
|
||||
{undefined, <<"1CQdDqPROyW">>,
|
||||
{payproc_PartyParams, {domain_PartyContactInfo, <<"hg_ct_helper">>}}},
|
||||
deadline => undefined, execution_start_time => 1565596875497,
|
||||
function => 'Create',
|
||||
metadata =>
|
||||
@ -468,9 +468,9 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[{payproc_UserInfo, <<"1CQdDqPROyW">>,
|
||||
{{payproc_UserInfo, <<"1CQdDqPROyW">>,
|
||||
{external_user, {payproc_ExternalUser}}}, undefined,
|
||||
{payproc_PartyParams, {domain_PartyContactInfo, <<"hg_ct_helper">>}}],
|
||||
{payproc_PartyParams, {domain_PartyContactInfo, <<"hg_ct_helper">>}}},
|
||||
deadline => undefined, execution_start_time => 1565596875497,
|
||||
function => 'Create',
|
||||
metadata =>
|
||||
@ -493,7 +493,7 @@ format_service_request_test_() -> [
|
||||
format_msg_limited(
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args => [undefined, <<"1CQdDqPROyW">>],
|
||||
#{args => {undefined, <<"1CQdDqPROyW">>},
|
||||
deadline => undefined, execution_start_time => 1565596875696,
|
||||
function => 'Get',
|
||||
metadata =>
|
||||
@ -516,7 +516,7 @@ format_service_request_test_() -> [
|
||||
format_msg_limited(
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args => [undefined, <<"~s">>],
|
||||
#{args => {undefined, <<"~s">>},
|
||||
deadline => undefined, execution_start_time => 1565596875696,
|
||||
function => 'Get',
|
||||
metadata =>
|
||||
@ -541,9 +541,9 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[{payproc_CustomerParams, <<"1CQdDqPROyW">>, <<"1CQdDwgt3R3">>,
|
||||
{{payproc_CustomerParams, <<"1CQdDqPROyW">>, <<"1CQdDwgt3R3">>,
|
||||
{domain_ContactInfo, undefined, <<"invalid_shop">>},
|
||||
{nl, {json_Null}}}],
|
||||
{nl, {json_Null}}}},
|
||||
deadline => undefined,
|
||||
execution_start_time => 1565596876258,
|
||||
function => 'Create',
|
||||
@ -569,9 +569,9 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[{payproc_UserInfo, <<"1CQdDqPROyW">>,
|
||||
{{payproc_UserInfo, <<"1CQdDqPROyW">>,
|
||||
{external_user, {payproc_ExternalUser}}},
|
||||
<<"1CQdDqPROyW">>],
|
||||
<<"1CQdDqPROyW">>},
|
||||
deadline => {{{2019, 8, 12}, {8, 1, 46}}, 263},
|
||||
execution_start_time => 1565596876266, function => 'GetRevision',
|
||||
metadata =>
|
||||
@ -596,10 +596,10 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[{payproc_UserInfo, <<"1CQdDqPROyW">>,
|
||||
{{payproc_UserInfo, <<"1CQdDqPROyW">>,
|
||||
{external_user, {payproc_ExternalUser}}},
|
||||
<<"1CQdDqPROyW">>,
|
||||
{revision, 1}],
|
||||
{revision, 1}},
|
||||
deadline => {{{2019, 8, 12}, {8, 1, 46}}, 263},
|
||||
execution_start_time => 1565596876292,
|
||||
function => 'Checkout',
|
||||
@ -623,7 +623,7 @@ format_service_request_test_() -> [
|
||||
format_msg_limited(
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args => [undefined, <<"1CQdDqPROyW">>, <<>>],
|
||||
#{args => {undefined, <<"1CQdDqPROyW">>, <<>>},
|
||||
deadline => undefined,
|
||||
execution_start_time => 1565596876383,
|
||||
function => 'Block',
|
||||
@ -648,7 +648,7 @@ format_service_request_test_() -> [
|
||||
format_msg_limited(
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args => [undefined, <<"1CQdDqPROyW">>, <<>>],
|
||||
#{args => {undefined, <<"1CQdDqPROyW">>, <<>>},
|
||||
deadline => undefined,
|
||||
execution_start_time => 1565596876458,
|
||||
function => 'Unblock',
|
||||
@ -676,7 +676,7 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[{mg_stateproc_SignalArgs,
|
||||
{{mg_stateproc_SignalArgs,
|
||||
{init,
|
||||
{mg_stateproc_InitSignal,
|
||||
{bin,
|
||||
@ -686,7 +686,7 @@ format_service_request_test_() -> [
|
||||
{mg_stateproc_HistoryRange, undefined, undefined, forward},
|
||||
{mg_stateproc_Content, undefined, {bin, <<>>}},
|
||||
undefined,
|
||||
{bin, <<>>}}}],
|
||||
{bin, <<>>}}}},
|
||||
deadline => {{{2019, 8, 12}, {12, 46, 36}}, 433},
|
||||
execution_start_time => 1565613966542,
|
||||
function => 'ProcessSignal',
|
||||
@ -723,7 +723,7 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[undefined, <<"1CR1Xziml7o">>,
|
||||
{undefined, <<"1CR1Xziml7o">>,
|
||||
[{contract_modification,
|
||||
{payproc_ContractModificationUnit, <<"1CR1Y2ZcrA0">>,
|
||||
{creation,
|
||||
@ -759,7 +759,7 @@ format_service_request_test_() -> [
|
||||
{shop_modification,
|
||||
{payproc_ShopModificationUnit, <<"1CR1Y2ZcrA2">>,
|
||||
{shop_account_creation,
|
||||
{payproc_ShopAccountParams, {domain_CurrencyRef, <<"RUB">>}}}}}]],
|
||||
{payproc_ShopAccountParams, {domain_CurrencyRef, <<"RUB">>}}}}}]},
|
||||
deadline => undefined,
|
||||
execution_start_time => 1565617299263,
|
||||
function => 'CreateClaim',
|
||||
@ -791,7 +791,7 @@ format_service_request_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[{mg_stateproc_CallArgs,
|
||||
{{mg_stateproc_CallArgs,
|
||||
{bin,
|
||||
<<131, 104, 4, 100, 0, 11, 116, 104, 114, 105, 102, 116, 95, 99, 97, 108, 108,
|
||||
100, 0, 16, 112, 97, 114, 116, 121, 95, 109, 97, 110, 97, 103, 101, 109, 101,
|
||||
@ -877,7 +877,7 @@ format_service_request_test_() -> [
|
||||
101, 120, 116, 0, 0, 0, 0, 100, 0, 14, 115, 110, 97, 112,
|
||||
115, 104, 111, 116, 95, 105, 110, 100, 101, 120, 106>>},
|
||||
{str, <<"ct">>} =>
|
||||
{str, <<"application/x-erlang-binary">>}}}}}],
|
||||
{str, <<"application/x-erlang-binary">>}}}}}},
|
||||
deadline => {{{2019, 8, 13}, {7, 52, 41}}, 105},
|
||||
execution_start_time => 1565682731109,
|
||||
function => 'ProcessCall',
|
||||
@ -918,7 +918,7 @@ format_service_request_with_limit_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[undefined, <<"1CR1Xziml7o">>,
|
||||
{undefined, <<"1CR1Xziml7o">>,
|
||||
[{contract_modification,
|
||||
{payproc_ContractModificationUnit, <<"1CR1Y2ZcrA0">>,
|
||||
{creation,
|
||||
@ -954,7 +954,7 @@ format_service_request_with_limit_test_() -> [
|
||||
{shop_modification,
|
||||
{payproc_ShopModificationUnit, <<"1CR1Y2ZcrA2">>,
|
||||
{shop_account_creation,
|
||||
{payproc_ShopAccountParams, {domain_CurrencyRef, <<"RUB">>}}}}}]],
|
||||
{payproc_ShopAccountParams, {domain_CurrencyRef, <<"RUB">>}}}}}]},
|
||||
deadline => undefined,
|
||||
execution_start_time => 1565617299263,
|
||||
function => 'CreateClaim',
|
||||
@ -1077,10 +1077,10 @@ result_test_() -> [
|
||||
format_event(
|
||||
?EV_SERVICE_HANDLER_RESULT,
|
||||
#{args =>
|
||||
[{payproc_UserInfo, <<"1CSWG2vduGe">>,
|
||||
{{payproc_UserInfo, <<"1CSWG2vduGe">>,
|
||||
{external_user, {payproc_ExternalUser}}},
|
||||
<<"1CSWG2vduGe">>,
|
||||
{revision, 6}],
|
||||
{revision, 6}},
|
||||
deadline => {{{2019, 8, 13}, {11, 19, 33}}, 42},
|
||||
execution_start_time => 1565695143068,
|
||||
function => 'Checkout',
|
||||
@ -1153,7 +1153,7 @@ result_test_() -> [
|
||||
format_event(
|
||||
?EV_SERVICE_HANDLER_RESULT,
|
||||
#{args =>
|
||||
[{mg_stateproc_SignalArgs,
|
||||
{{mg_stateproc_SignalArgs,
|
||||
{init,
|
||||
{mg_stateproc_InitSignal,
|
||||
{bin,
|
||||
@ -1168,7 +1168,7 @@ result_test_() -> [
|
||||
{mg_stateproc_HistoryRange, undefined, undefined, forward},
|
||||
{mg_stateproc_Content, undefined, {bin, <<>>}},
|
||||
undefined,
|
||||
{bin, <<>>}}}],
|
||||
{bin, <<>>}}}},
|
||||
deadline => {{{2019, 8, 13}, {11, 19, 33}}, 606},
|
||||
execution_start_time => 1565695143707, function => 'ProcessSignal',
|
||||
metadata =>
|
||||
@ -1216,7 +1216,7 @@ result_test_() -> [
|
||||
format_event(
|
||||
?EV_CALL_SERVICE,
|
||||
#{args =>
|
||||
[{mg_stateproc_SignalArgs,
|
||||
{{mg_stateproc_SignalArgs,
|
||||
{init,
|
||||
{mg_stateproc_InitSignal,
|
||||
{bin,
|
||||
@ -1231,7 +1231,7 @@ result_test_() -> [
|
||||
{mg_stateproc_HistoryRange, undefined, undefined, forward},
|
||||
{mg_stateproc_Content, undefined, {bin, <<>>}},
|
||||
undefined,
|
||||
{bin, <<>>}}}],
|
||||
{bin, <<>>}}}},
|
||||
deadline => {{{2019, 8, 13}, {11, 19, 33}}, 606},
|
||||
execution_start_time => 1565695143707, function => 'ProcessSignal',
|
||||
metadata =>
|
||||
@ -1309,7 +1309,7 @@ exception_test_() -> [
|
||||
format_msg_limited(
|
||||
format_event(
|
||||
?EV_SERVICE_RESULT,
|
||||
#{args => [<<"1Cfo5OJzx6O">>],
|
||||
#{args => {<<"1Cfo5OJzx6O">>},
|
||||
deadline => undefined,
|
||||
execution_start_time => 1566386841317,
|
||||
class => business,
|
||||
@ -1332,7 +1332,7 @@ exception_test_() -> [
|
||||
format_msg_limited(
|
||||
format_event(
|
||||
?EV_SERVICE_RESULT,
|
||||
#{args => [
|
||||
#{args => {
|
||||
undefined,
|
||||
<<"1Cfo9igJRS4">>,
|
||||
{payproc_InvoicePaymentParams, {payment_resource, {payproc_PaymentResourcePayerParams,
|
||||
@ -1340,7 +1340,7 @@ exception_test_() -> [
|
||||
visa, <<"424242">>, <<"4242">>, undefined, undefined, undefined, undefined, undefined}},
|
||||
<<"SESSION42">>, {domain_ClientInfo, undefined, undefined}},
|
||||
{domain_ContactInfo, undefined, undefined}}},
|
||||
{instant, {payproc_InvoicePaymentParamsFlowInstant}}, true, undefined, undefined, undefined}],
|
||||
{instant, {payproc_InvoicePaymentParamsFlowInstant}}, true, undefined, undefined, undefined}},
|
||||
deadline => undefined, execution_start_time => 1566386899959,
|
||||
class => business,
|
||||
function => 'StartPayment',
|
||||
@ -1361,7 +1361,7 @@ exception_test_() -> [
|
||||
format_msg_limited(
|
||||
format_event(
|
||||
?EV_SERVICE_RESULT,
|
||||
#{args=>[
|
||||
#{args=>{
|
||||
undefined,
|
||||
<<"1FToOuf532G">>,
|
||||
{payproc_InvoicePaymentParams,
|
||||
@ -1369,7 +1369,7 @@ exception_test_() -> [
|
||||
{domain_RecurrentParentPayment, <<"1FToOLnG2Ou">>, <<"1">>},
|
||||
{domain_ContactInfo, undefined, undefined}}},
|
||||
{instant, {payproc_InvoicePaymentParamsFlowInstant}},
|
||||
true, undefined, undefined, undefined, undefined}],
|
||||
true, undefined, undefined, undefined, undefined}},
|
||||
deadline => undefined,
|
||||
execution_start_time => 1575444908463,
|
||||
class => business,
|
||||
|
@ -7,7 +7,16 @@
|
||||
-export([child_spec/2]).
|
||||
|
||||
%% Types
|
||||
-type options() :: woody_server_thrift_http_handler:options().
|
||||
-type options() :: #{
|
||||
event_handler := woody:ev_handlers(),
|
||||
protocol => thrift,
|
||||
transport => http,
|
||||
%% Set to override protocol handler module selection, useful for test purposes, rarely
|
||||
%% if ever needed otherwise.
|
||||
protocol_handler_override => module(),
|
||||
%% Implementation-specific options
|
||||
_ => _
|
||||
}.
|
||||
-export_type([options/0]).
|
||||
|
||||
%% Behaviour definition
|
||||
|
@ -3,6 +3,9 @@
|
||||
%% API
|
||||
-export([init_handler/3, invoke_handler/1]).
|
||||
|
||||
%% Behaviour dispatch
|
||||
-export([handle_function/4]).
|
||||
|
||||
-include_lib("thrift/include/thrift_constants.hrl").
|
||||
-include_lib("thrift/include/thrift_protocol.hrl").
|
||||
-include("woody_defs.hrl").
|
||||
@ -72,6 +75,13 @@ invoke_handler(State) ->
|
||||
{_, {ok, Reply}} = thrift_protocol:close_transport(Proto),
|
||||
handle_result(Result, Reply, MsgType).
|
||||
|
||||
-spec handle_function(woody:handler(woody:options()), woody:func(), woody:args(), woody_state:st()) ->
|
||||
{ok, woody:result()} | no_return().
|
||||
handle_function(Handler, Function, Args, WoodyState) ->
|
||||
_ = woody_event_handler:handle_event(?EV_INVOKE_SERVICE_HANDLER, WoodyState, #{}),
|
||||
{Module, Opts} = woody_util:get_mod_opts(Handler),
|
||||
Module:handle_function(Function, Args, woody_state:get_context(WoodyState), Opts).
|
||||
|
||||
%%
|
||||
%% Internal functions
|
||||
%%
|
||||
@ -151,8 +161,7 @@ add_ev_meta(WoodyState, Service = {_, ServiceName}, Function, ReplyType) ->
|
||||
decode_request(State = #{th_proto := Proto, th_param_type := ParamsType, woody_state := WoodyState}) ->
|
||||
case thrift_protocol:read(Proto, ParamsType) of
|
||||
{Proto1, {ok, Args}} ->
|
||||
Args1 = tuple_to_list(Args),
|
||||
State#{th_proto => Proto1, args => Args1, woody_state := add_ev_meta(WoodyState, Args1)};
|
||||
State#{th_proto => Proto1, args => Args, woody_state := add_ev_meta(WoodyState, Args)};
|
||||
{_, {error, Error}} ->
|
||||
throw_decode_error(Error)
|
||||
end.
|
||||
@ -212,9 +221,7 @@ call_handler(#{
|
||||
function := Function,
|
||||
args := Args})
|
||||
->
|
||||
_ = woody_event_handler:handle_event(?EV_INVOKE_SERVICE_HANDLER, WoodyState, #{}),
|
||||
{Module, Opts} = woody_util:get_mod_opts(Handler),
|
||||
Module:handle_function(Function, Args, woody_state:get_context(WoodyState), Opts).
|
||||
handle_function(Handler, Function, Args, WoodyState).
|
||||
|
||||
-spec handle_success({ok, woody:result()}, state()) ->
|
||||
{ok | {error, {system, woody_error:system_error()}}, state()}.
|
||||
|
@ -17,10 +17,6 @@
|
||||
-export([init/2]).
|
||||
-export([terminate/3]).
|
||||
|
||||
%% API for woody_stream_handler
|
||||
-export([trace_req/4]).
|
||||
-export([trace_resp/6]).
|
||||
|
||||
%% Types
|
||||
-type handler_limits() :: #{
|
||||
max_heap_size => integer() %% process words, see erlang:process_flag(max_heap_size, MaxHeapSize) for details.
|
||||
@ -71,6 +67,7 @@
|
||||
-type route_opts() :: #{
|
||||
handlers := list(woody:http_handler(woody:th_handler())),
|
||||
event_handler := woody:ev_handlers(),
|
||||
read_body_opts => read_body_opts(),
|
||||
protocol => thrift,
|
||||
transport => http,
|
||||
handler_limits => handler_limits()
|
||||
@ -82,13 +79,12 @@
|
||||
|
||||
-export_type([protocol_opts/0]).
|
||||
|
||||
-type server_opts() :: #{regexp_meta => re_mp()}.
|
||||
|
||||
-type state() :: #{
|
||||
th_handler := woody:th_handler(),
|
||||
th_handler => woody:th_handler(),
|
||||
ev_handler := woody:ev_handlers(),
|
||||
server_opts := server_opts(),
|
||||
read_body_opts := read_body_opts(),
|
||||
handler_limits := handler_limits(),
|
||||
regexp_meta := re_mp(),
|
||||
url => woody:url(),
|
||||
woody_state => woody_state:st()
|
||||
}.
|
||||
@ -151,9 +147,9 @@ get_cowboy_config(Opts = #{event_handler := EvHandler}) ->
|
||||
Dispatch = get_dispatch(Opts),
|
||||
ProtocolOpts = maps:get(protocol_opts, Opts, #{}),
|
||||
CowboyOpts = maps:put(stream_handlers, [woody_monitor_h, woody_trace_h, cowboy_stream_h], ProtocolOpts),
|
||||
ReadBodyOpts = maps:get(read_body_opts, Opts, #{}),
|
||||
Env = woody_trace_h:env(maps:with([event_handler], Opts)),
|
||||
maps:merge(#{
|
||||
env =>#{dispatch => Dispatch, event_handler => EvHandler, read_body_opts => ReadBodyOpts},
|
||||
env => Env#{dispatch => Dispatch},
|
||||
max_header_name_length => 64
|
||||
}, CowboyOpts).
|
||||
|
||||
@ -175,35 +171,32 @@ get_dispatch(Opts) ->
|
||||
-spec get_all_routes(options())->
|
||||
[route(_)].
|
||||
get_all_routes(Opts) ->
|
||||
RouteOpts = maps:with([handlers, event_handler, read_body_opts, handler_limits, protocol, transport], Opts),
|
||||
AdditionalRoutes = maps:get(additional_routes, Opts, []),
|
||||
AdditionalRoutes ++ get_routes(maps:with([handlers, event_handler, handler_limits, protocol, transport], Opts)).
|
||||
AdditionalRoutes ++ get_routes(RouteOpts).
|
||||
|
||||
-spec get_routes(route_opts())->
|
||||
-spec get_routes(route_opts()) ->
|
||||
[route(state())].
|
||||
get_routes(Opts = #{handlers := Handlers, event_handler := EvHandler}) ->
|
||||
Limits = maps:get(handler_limits, Opts, #{}),
|
||||
get_routes(config(), Limits, EvHandler, Handlers, []).
|
||||
get_routes(Opts = #{handlers := Handlers}) ->
|
||||
State0 = init_state(Opts),
|
||||
[get_route(State0, Handler) || Handler <- Handlers].
|
||||
|
||||
-spec get_routes(server_opts(), handler_limits(), woody:ev_handlers(), Handlers, Routes) ->
|
||||
Routes when Handlers :: list(woody:http_handler(woody:th_handler())), Routes :: [route(state())].
|
||||
get_routes(_, _, _, [], Routes) ->
|
||||
Routes;
|
||||
get_routes(ServerOpts, Limits, EvHandler, [{PathMatch, {Service, Handler}} | T], Routes) ->
|
||||
get_routes(ServerOpts, Limits, EvHandler, T, [
|
||||
{PathMatch, ?MODULE, #{
|
||||
th_handler => {Service, Handler},
|
||||
ev_handler => EvHandler,
|
||||
server_opts => ServerOpts,
|
||||
handler_limits => Limits
|
||||
}} | Routes
|
||||
]);
|
||||
get_routes(_, _, _, [Handler | _], _) ->
|
||||
-spec get_route(state(), woody:http_handler(woody:th_handler())) ->
|
||||
route(state()).
|
||||
get_route(State0, {PathMatch, {Service, Handler}}) ->
|
||||
{PathMatch, ?MODULE, State0#{th_handler => {Service, Handler}}};
|
||||
get_route(_, Handler) ->
|
||||
error({bad_handler_spec, Handler}).
|
||||
|
||||
-spec config() ->
|
||||
server_opts().
|
||||
config() ->
|
||||
#{regexp_meta => compile_filter_meta()}.
|
||||
-spec init_state(route_opts()) ->
|
||||
state().
|
||||
init_state(Opts = #{}) ->
|
||||
#{
|
||||
ev_handler => maps:get(event_handler, Opts),
|
||||
read_body_opts => maps:get(read_body_opts, Opts, #{}),
|
||||
handler_limits => maps:get(handler_limits, Opts, #{}),
|
||||
regexp_meta => compile_filter_meta()
|
||||
}.
|
||||
|
||||
-spec compile_filter_meta() ->
|
||||
re_mp().
|
||||
@ -211,46 +204,6 @@ compile_filter_meta() ->
|
||||
{ok, Re} = re:compile(?HEADER_META_RE, [unicode, caseless]),
|
||||
Re.
|
||||
|
||||
-spec trace_req(true, cowboy_req:req(), woody:ev_handlers(), server_opts()) ->
|
||||
cowboy_req:req().
|
||||
trace_req(true, Req, EvHandler, ServerOpts) ->
|
||||
Url = unicode:characters_to_binary(cowboy_req:uri(Req)),
|
||||
Headers = cowboy_req:headers(Req),
|
||||
Meta = #{
|
||||
role => server,
|
||||
event => <<"http request received">>,
|
||||
url => Url,
|
||||
headers => Headers
|
||||
},
|
||||
Meta1 = case get_body(Req, ServerOpts) of
|
||||
{ok, Body, _} ->
|
||||
Meta#{body => Body, body_status => ok}
|
||||
end,
|
||||
_ = woody_event_handler:handle_event(EvHandler, ?EV_TRACE, undefined, Meta1),
|
||||
Req;
|
||||
trace_req(_, Req, _, _) ->
|
||||
Req.
|
||||
|
||||
-spec trace_resp(
|
||||
true,
|
||||
cowboy_req:req(),
|
||||
woody:http_code(),
|
||||
woody:http_headers(),
|
||||
woody:http_body(),
|
||||
woody:ev_handlers()
|
||||
) ->
|
||||
cowboy_req:req().
|
||||
trace_resp(true, Req, Code, Headers, Body, EvHandler) ->
|
||||
_ = woody_event_handler:handle_event(EvHandler, ?EV_TRACE, undefined, #{
|
||||
role => server,
|
||||
event => <<"http response send">>,
|
||||
code => Code,
|
||||
headers => Headers,
|
||||
body => Body}),
|
||||
Req;
|
||||
trace_resp(_, Req, _, _, _, _) ->
|
||||
Req.
|
||||
|
||||
%%
|
||||
%% cowboy_http_handler callbacks
|
||||
%%
|
||||
@ -286,10 +239,10 @@ set_handler_limits(Limits) ->
|
||||
handle(Req, State = #{
|
||||
url := Url,
|
||||
woody_state := WoodyState,
|
||||
server_opts := ServerOpts,
|
||||
read_body_opts := ReadBodyOpts,
|
||||
th_handler := ThriftHandler
|
||||
}) ->
|
||||
Req2 = case get_body(Req, ServerOpts) of
|
||||
Req2 = case get_body(Req, ReadBodyOpts) of
|
||||
{ok, Body, Req1} when byte_size(Body) > 0 ->
|
||||
_ = woody_event_handler:handle_event(?EV_SERVER_RECEIVE, WoodyState, #{url => Url, status => ok}),
|
||||
handle_request(Body, ThriftHandler, WoodyState, Req1);
|
||||
@ -433,13 +386,13 @@ check_deadline(Deadline, Req, State = #{url := Url, woody_state := WoodyState})
|
||||
|
||||
-spec check_metadata_headers(woody:http_headers(), cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_metadata_headers(Headers, Req, State = #{woody_state := WoodyState, server_opts := ServerOpts}) ->
|
||||
WoodyState1 = set_metadata(find_metadata(Headers, ServerOpts), WoodyState),
|
||||
check_metadata_headers(Headers, Req, State = #{woody_state := WoodyState, regexp_meta := ReMeta}) ->
|
||||
WoodyState1 = set_metadata(find_metadata(Headers, ReMeta), WoodyState),
|
||||
{ok, Req, update_woody_state(State, WoodyState1, Req)}.
|
||||
|
||||
-spec find_metadata(woody:http_headers(), server_opts()) ->
|
||||
-spec find_metadata(woody:http_headers(), re_mp()) ->
|
||||
woody_context:meta().
|
||||
find_metadata(Headers, #{regexp_meta := Re}) ->
|
||||
find_metadata(Headers, Re) ->
|
||||
RpcId = ?HEADER_RPC_ID,
|
||||
RootId = ?HEADER_RPC_ROOT_ID,
|
||||
ParentId = ?HEADER_RPC_PARENT_ID,
|
||||
@ -493,12 +446,10 @@ reply_client_error(Code, Reason, Req, #{url := Url, woody_state := WoodyState})
|
||||
reply(Code, set_error_headers(<<"Result Unexpected">>, Reason, Req), WoodyState).
|
||||
|
||||
%% handle functions
|
||||
-spec get_body(cowboy_req:req(), server_opts()) ->
|
||||
-spec get_body(cowboy_req:req(), read_body_opts()) ->
|
||||
{ok, woody:http_body(), cowboy_req:req()}.
|
||||
get_body(Req, #{read_body_opts := ReadBodyOpts}) ->
|
||||
do_get_body(<<>>, Req, ReadBodyOpts);
|
||||
get_body(Req, _) ->
|
||||
do_get_body(<<>>, Req, #{}).
|
||||
get_body(Req, ReadBodyOpts) ->
|
||||
do_get_body(<<>>, Req, ReadBodyOpts).
|
||||
|
||||
do_get_body(Body, Req, Opts) ->
|
||||
case cowboy_req:read_body(Req, Opts) of
|
||||
|
713
src/woody_server_thrift_v2.erl
Normal file
713
src/woody_server_thrift_v2.erl
Normal file
@ -0,0 +1,713 @@
|
||||
-module(woody_server_thrift_v2).
|
||||
|
||||
-behaviour(woody_server).
|
||||
|
||||
-dialyzer(no_undefined_callbacks).
|
||||
|
||||
-include("woody_defs.hrl").
|
||||
|
||||
%% woody_server callback
|
||||
-export([child_spec/2]).
|
||||
|
||||
%% API
|
||||
-export([get_routes/1]).
|
||||
|
||||
%% cowboy_handler callbacks
|
||||
-behaviour(cowboy_handler).
|
||||
-export([init/2]).
|
||||
-export([terminate/3]).
|
||||
|
||||
%% Types
|
||||
-type handler_limits() :: #{
|
||||
max_heap_size => integer() %% process words, see erlang:process_flag(max_heap_size, MaxHeapSize) for details.
|
||||
}.
|
||||
-export_type([handler_limits/0]).
|
||||
|
||||
-type transport_opts() :: #{
|
||||
connection_type => worker | supervisor,
|
||||
handshake_timeout => timeout(),
|
||||
max_connections => ranch:max_conns(),
|
||||
logger => module(),
|
||||
num_acceptors => pos_integer(),
|
||||
shutdown => timeout() | brutal_kill,
|
||||
socket => any(),
|
||||
socket_opts => any(),
|
||||
transport => module() % ranch_tcp | ranch_ssl
|
||||
}.
|
||||
|
||||
-export_type([transport_opts/0]).
|
||||
|
||||
-type route(T) :: {woody:path(), module(), T}.
|
||||
-export_type([route/1]).
|
||||
|
||||
-type read_body_opts() :: cowboy_req:read_body_opts().
|
||||
|
||||
%% ToDo: restructure options() to split server options and route options and
|
||||
%% get rid of separate route_opts() when backward compatibility isn't an issue.
|
||||
|
||||
-type options() :: #{
|
||||
handlers := list(woody:http_handler(woody:th_handler())),
|
||||
event_handler := woody:ev_handlers(),
|
||||
ip := inet:ip_address(),
|
||||
port := inet:port_number(),
|
||||
protocol => thrift,
|
||||
transport => http,
|
||||
transport_opts => transport_opts(),
|
||||
read_body_opts => read_body_opts(),
|
||||
protocol_opts => protocol_opts(),
|
||||
handler_limits => handler_limits(),
|
||||
additional_routes => [route(_)],
|
||||
%% shutdown_timeout: time to drain current connections when shutdown signal is recieved
|
||||
%% NOTE: when changing this value make sure to take into account the request_timeout and
|
||||
%% max_keepalive settings of protocol_opts() to achieve the desired effect
|
||||
shutdown_timeout => timeout()
|
||||
}.
|
||||
-export_type([options/0]).
|
||||
|
||||
-type route_opts() :: #{
|
||||
handlers := list(woody:http_handler(woody:th_handler())),
|
||||
event_handler := woody:ev_handlers(),
|
||||
protocol => thrift,
|
||||
transport => http,
|
||||
read_body_opts => read_body_opts(),
|
||||
handler_limits => handler_limits()
|
||||
}.
|
||||
-export_type([route_opts/0]).
|
||||
-define(ROUTE_OPT_NAMES, [
|
||||
handlers,
|
||||
event_handler,
|
||||
protocol,
|
||||
transport,
|
||||
read_body_opts,
|
||||
handler_limits
|
||||
]).
|
||||
|
||||
-type re_mp() :: tuple().
|
||||
-type protocol_opts() :: cowboy_http:opts().
|
||||
|
||||
-export_type([protocol_opts/0]).
|
||||
|
||||
-type state() :: #{
|
||||
th_handler => woody:th_handler(),
|
||||
ev_handler := woody:ev_handlers(),
|
||||
read_body_opts := read_body_opts(),
|
||||
handler_limits := handler_limits(),
|
||||
regexp_meta := re_mp(),
|
||||
url => woody:url(),
|
||||
woody_state => woody_state:st()
|
||||
}.
|
||||
|
||||
-type cowboy_init_result() ::
|
||||
{ok, cowboy_req:req(), state() | undefined}
|
||||
| {module(), cowboy_req:req(), state() | undefined, any()}.
|
||||
|
||||
-type check_result() ::
|
||||
{ok, cowboy_req:req(), state() | undefined}
|
||||
| {stop, cowboy_req:req(), undefined}.
|
||||
|
||||
-define(DEFAULT_ACCEPTORS_POOLSIZE, 100).
|
||||
-define(DEFAULT_SHUTDOWN_TIMEOUT, 0).
|
||||
|
||||
%% nginx should be configured to take care of various limits.
|
||||
|
||||
-define(DUMMY_REQ_ID, <<"undefined">>).
|
||||
|
||||
%%
|
||||
%% woody_server callback
|
||||
%%
|
||||
-spec child_spec(atom(), options()) ->
|
||||
supervisor:child_spec().
|
||||
child_spec(Id, Opts) ->
|
||||
{Transport, TransportOpts} = get_socket_transport(Opts),
|
||||
CowboyOpts = get_cowboy_config(Opts),
|
||||
RanchRef = {?MODULE, Id},
|
||||
DrainSpec = make_drain_childspec(RanchRef, Opts),
|
||||
RanchSpec = ranch:child_spec(RanchRef, Transport, TransportOpts, cowboy_clear, CowboyOpts),
|
||||
make_server_childspec(Id, [RanchSpec, DrainSpec]).
|
||||
|
||||
make_drain_childspec(Ref, Opts) ->
|
||||
ShutdownTimeout = maps:get(shutdown_timeout, Opts, ?DEFAULT_SHUTDOWN_TIMEOUT),
|
||||
DrainOpts = #{shutdown => ShutdownTimeout, ranch_ref => Ref},
|
||||
woody_server_http_drainer:child_spec(DrainOpts).
|
||||
|
||||
make_server_childspec(Id, Children) ->
|
||||
Flags = #{strategy => one_for_all},
|
||||
#{
|
||||
id => Id,
|
||||
start => {genlib_adhoc_supervisor, start_link, [Flags, Children]},
|
||||
type => supervisor
|
||||
}.
|
||||
|
||||
get_socket_transport(Opts = #{ip := Ip, port := Port}) ->
|
||||
Defaults = #{num_acceptors => ?DEFAULT_ACCEPTORS_POOLSIZE},
|
||||
TransportOpts = maps:merge(Defaults, maps:get(transport_opts, Opts, #{})),
|
||||
Transport = maps:get(transport, TransportOpts, ranch_tcp),
|
||||
SocketOpts = [{ip, Ip}, {port, Port} | maps:get(socket_opts, TransportOpts, [])],
|
||||
{Transport, set_ranch_option(socket_opts, SocketOpts, TransportOpts)}.
|
||||
|
||||
set_ranch_option(Key, Value, Opts) ->
|
||||
Opts#{Key => Value}.
|
||||
|
||||
-spec get_cowboy_config(options()) ->
|
||||
protocol_opts().
|
||||
get_cowboy_config(Opts = #{event_handler := EvHandler}) ->
|
||||
ok = validate_event_handler(EvHandler),
|
||||
Dispatch = get_dispatch(Opts),
|
||||
ProtocolOpts = maps:get(protocol_opts, Opts, #{}),
|
||||
CowboyOpts = maps:put(stream_handlers, [woody_monitor_h, woody_trace_h, cowboy_stream_h], ProtocolOpts),
|
||||
Env = woody_trace_h:env(maps:with([event_handler], Opts)),
|
||||
maps:merge(#{
|
||||
env => Env#{dispatch => Dispatch},
|
||||
max_header_name_length => 64
|
||||
}, CowboyOpts).
|
||||
|
||||
validate_event_handler(Handlers) when is_list(Handlers) ->
|
||||
true = lists:all(
|
||||
fun(Handler) ->
|
||||
is_tuple(woody_util:get_mod_opts(Handler))
|
||||
end, Handlers),
|
||||
ok;
|
||||
validate_event_handler(Handler) ->
|
||||
validate_event_handler([Handler]).
|
||||
|
||||
|
||||
-spec get_dispatch(options())->
|
||||
cowboy_router:dispatch_rules().
|
||||
get_dispatch(Opts) ->
|
||||
cowboy_router:compile([{'_', get_all_routes(Opts)}]).
|
||||
|
||||
-spec get_all_routes(options())->
|
||||
[route(_)].
|
||||
get_all_routes(Opts) ->
|
||||
AdditionalRoutes = maps:get(additional_routes, Opts, []),
|
||||
AdditionalRoutes ++ get_routes(maps:with(?ROUTE_OPT_NAMES, Opts)).
|
||||
|
||||
-spec get_routes(route_opts()) ->
|
||||
[route(state())].
|
||||
get_routes(Opts = #{handlers := Handlers}) ->
|
||||
State0 = init_state(Opts),
|
||||
[get_route(State0, Handler) || Handler <- Handlers].
|
||||
|
||||
-spec get_route(state(), woody:http_handler(woody:th_handler())) ->
|
||||
route(state()).
|
||||
get_route(State0, {PathMatch, {Service, Handler}}) ->
|
||||
{PathMatch, ?MODULE, State0#{th_handler => {Service, Handler}}};
|
||||
get_route(_, Handler) ->
|
||||
error({bad_handler_spec, Handler}).
|
||||
|
||||
-spec init_state(route_opts()) ->
|
||||
state().
|
||||
init_state(Opts = #{}) ->
|
||||
#{
|
||||
ev_handler => maps:get(event_handler, Opts),
|
||||
read_body_opts => maps:get(read_body_opts, Opts, #{}),
|
||||
handler_limits => maps:get(handler_limits, Opts, #{}),
|
||||
regexp_meta => compile_filter_meta()
|
||||
}.
|
||||
|
||||
-spec compile_filter_meta() ->
|
||||
re_mp().
|
||||
compile_filter_meta() ->
|
||||
{ok, Re} = re:compile(?HEADER_META_RE, [unicode, caseless]),
|
||||
Re.
|
||||
|
||||
%%
|
||||
%% cowboy_http_handler callbacks
|
||||
%%
|
||||
-spec init(cowboy_req:req(), state()) ->
|
||||
cowboy_init_result().
|
||||
init(Req, Opts = #{ev_handler := EvHandler, handler_limits := Limits}) ->
|
||||
ok = set_handler_limits(Limits),
|
||||
Url = unicode:characters_to_binary(cowboy_req:uri(Req)),
|
||||
WoodyState = create_dummy_state(EvHandler),
|
||||
Opts1 = update_woody_state(Opts#{url => Url}, WoodyState, Req),
|
||||
case check_request(Req, Opts1) of
|
||||
{ok, Req1, State} -> handle(Req1, State);
|
||||
{stop, Req1, State} -> {ok, Req1, State}
|
||||
end.
|
||||
|
||||
-spec set_handler_limits(handler_limits()) ->
|
||||
ok.
|
||||
set_handler_limits(Limits) ->
|
||||
case maps:get(max_heap_size, Limits, undefined) of
|
||||
undefined ->
|
||||
ok;
|
||||
MaxHeapSize ->
|
||||
_ = erlang:process_flag(max_heap_size, #{
|
||||
size => MaxHeapSize,
|
||||
kill => true,
|
||||
error_logger => true
|
||||
}),
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec handle(cowboy_req:req(), state()) ->
|
||||
{ok, cowboy_req:req(), _}.
|
||||
handle(Req, State = #{
|
||||
url := Url,
|
||||
woody_state := WoodyState,
|
||||
read_body_opts := ReadBodyOpts,
|
||||
th_handler := ThriftHandler
|
||||
}) ->
|
||||
Req2 = case get_body(Req, ReadBodyOpts) of
|
||||
{ok, Body, Req1} when byte_size(Body) > 0 ->
|
||||
_ = handle_event(?EV_SERVER_RECEIVE, WoodyState, #{url => Url, status => ok}),
|
||||
handle_request(Body, ThriftHandler, WoodyState, Req1);
|
||||
{ok, <<>>, Req1} ->
|
||||
reply_client_error(400, <<"body empty">>, Req1, State)
|
||||
end,
|
||||
{ok, Req2, undefined}.
|
||||
|
||||
create_dummy_state(EvHandler) ->
|
||||
DummyRpcID = #{
|
||||
span_id => ?DUMMY_REQ_ID,
|
||||
trace_id => ?DUMMY_REQ_ID,
|
||||
parent_id => ?DUMMY_REQ_ID
|
||||
},
|
||||
woody_state:new(server, woody_context:new(DummyRpcID), EvHandler).
|
||||
|
||||
-spec terminate(_Reason, _Req, state() | _) ->
|
||||
ok.
|
||||
terminate(normal, _Req, _Status) ->
|
||||
ok;
|
||||
terminate(Reason, _Req, #{ev_handler := EvHandler} = Opts) ->
|
||||
WoodyState = maps:get(woody_state, Opts, create_dummy_state(EvHandler)),
|
||||
_ = woody_event_handler:handle_event(?EV_INTERNAL_ERROR, WoodyState, #{
|
||||
error => <<"http handler terminated abnormally">>,
|
||||
reason => woody_error:format_details(Reason),
|
||||
class => undefined,
|
||||
final => true
|
||||
}),
|
||||
ok.
|
||||
|
||||
|
||||
%% init functions
|
||||
|
||||
-define(CODEC, thrift_strict_binary_codec).
|
||||
|
||||
%% First perform basic http checks: method, content type, etc,
|
||||
%% then check woody related headers: IDs, deadline, meta.
|
||||
|
||||
-spec check_request(cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_request(Req, State) ->
|
||||
check_method(cowboy_req:method(Req), Req, State).
|
||||
|
||||
-spec check_method(woody:http_header_val(), cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_method(<<"POST">>, Req, State) ->
|
||||
check_content_type(cowboy_req:header(<<"content-type">>, Req), Req, State);
|
||||
|
||||
check_method(Method, Req, State) ->
|
||||
Req1 = cowboy_req:set_resp_header(<<"allow">>, <<"POST">>, Req),
|
||||
Reason = woody_util:to_binary(["wrong method: ", Method]),
|
||||
reply_bad_header(405, Reason, Req1, State).
|
||||
|
||||
-spec check_content_type(woody:http_header_val() | undefined, cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_content_type(?CONTENT_TYPE_THRIFT, Req, State) ->
|
||||
Header = cowboy_req:header(<<"accept">>, Req),
|
||||
check_accept(Header, Req, State);
|
||||
check_content_type(BadCType, Req, State) ->
|
||||
reply_bad_header(415, woody_util:to_binary(["wrong content type: ", BadCType]), Req, State).
|
||||
|
||||
-spec check_accept(woody:http_header_val() | undefined, cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_accept(Accept, Req, State) when
|
||||
Accept =:= ?CONTENT_TYPE_THRIFT ;
|
||||
Accept =:= undefined
|
||||
->
|
||||
check_woody_headers(Req, State);
|
||||
check_accept(BadAccept, Req1, State) ->
|
||||
reply_bad_header(406, woody_util:to_binary(["wrong client accept: ", BadAccept]), Req1, State).
|
||||
|
||||
-spec check_woody_headers(cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_woody_headers(Req, State = #{woody_state := WoodyState0}) ->
|
||||
case get_rpc_id(Req) of
|
||||
{ok, RpcId, Req1} ->
|
||||
WoodyState1 = set_cert(Req1, set_rpc_id(RpcId, WoodyState0)),
|
||||
check_deadline_header(
|
||||
cowboy_req:header(?HEADER_DEADLINE, Req1),
|
||||
Req1,
|
||||
update_woody_state(State, WoodyState1, Req1)
|
||||
);
|
||||
{error, BadRpcId, Req1} ->
|
||||
WoodyState1 = set_rpc_id(BadRpcId, WoodyState0),
|
||||
reply_bad_header(400, woody_util:to_binary(["bad ", ?HEADER_PREFIX, " id header"]),
|
||||
Req1, update_woody_state(State, WoodyState1, Req1)
|
||||
)
|
||||
end.
|
||||
|
||||
-spec get_rpc_id(cowboy_req:req()) ->
|
||||
{ok | error, woody:rpc_id(), cowboy_req:req()}.
|
||||
get_rpc_id(Req) ->
|
||||
check_ids(maps:fold(
|
||||
fun get_rpc_id/3,
|
||||
#{req => Req},
|
||||
#{
|
||||
span_id => ?HEADER_RPC_ID,
|
||||
trace_id => ?HEADER_RPC_ROOT_ID,
|
||||
parent_id => ?HEADER_RPC_PARENT_ID
|
||||
}
|
||||
)).
|
||||
|
||||
get_rpc_id(Id, Header, Acc = #{req := Req}) ->
|
||||
case cowboy_req:header(Header, Req) of
|
||||
undefined ->
|
||||
Acc#{Id => ?DUMMY_REQ_ID, req => Req, status => error};
|
||||
IdVal ->
|
||||
Acc#{Id => IdVal, req => Req}
|
||||
end.
|
||||
|
||||
check_ids(Map = #{status := error, req := Req}) ->
|
||||
{error, maps:without([req, status], Map), Req};
|
||||
check_ids(Map = #{req := Req}) ->
|
||||
{ok, maps:without([req], Map), Req}.
|
||||
|
||||
-spec check_deadline_header(Header, Req, state()) -> cowboy_init_result() when
|
||||
Header :: woody:http_header_val() | undefined, Req :: cowboy_req:req().
|
||||
check_deadline_header(undefined, Req, State) ->
|
||||
check_metadata_headers(cowboy_req:headers(Req), Req, State);
|
||||
check_deadline_header(DeadlineBin, Req, State) ->
|
||||
try woody_deadline:from_binary(DeadlineBin) of
|
||||
Deadline -> check_deadline(Deadline, Req, State)
|
||||
catch
|
||||
error:{bad_deadline, Error} ->
|
||||
ErrorDescription = woody_util:to_binary(["bad ", ?HEADER_DEADLINE, " header: ", Error]),
|
||||
reply_bad_header(400, ErrorDescription, Req, State)
|
||||
end.
|
||||
|
||||
-spec check_deadline(woody:deadline(), cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_deadline(Deadline, Req, State = #{url := Url, woody_state := WoodyState}) ->
|
||||
case woody_deadline:is_reached(Deadline) of
|
||||
true ->
|
||||
_ = handle_event(
|
||||
?EV_SERVER_RECEIVE,
|
||||
WoodyState,
|
||||
#{url => Url, status => error, reason => <<"Deadline reached">>}
|
||||
),
|
||||
Req1 = handle_error({system, {internal, resource_unavailable, <<"deadline reached">>}}, Req, WoodyState),
|
||||
{stop, Req1, undefined};
|
||||
false ->
|
||||
WoodyState1 = set_deadline(Deadline, WoodyState),
|
||||
Headers = cowboy_req:headers(Req),
|
||||
check_metadata_headers(Headers, Req, update_woody_state(State, WoodyState1, Req))
|
||||
end.
|
||||
|
||||
-spec check_metadata_headers(woody:http_headers(), cowboy_req:req(), state()) ->
|
||||
check_result().
|
||||
check_metadata_headers(Headers, Req, State = #{woody_state := WoodyState, regexp_meta := ReMeta}) ->
|
||||
WoodyState1 = set_metadata(find_metadata(Headers, ReMeta), WoodyState),
|
||||
{ok, Req, update_woody_state(State, WoodyState1, Req)}.
|
||||
|
||||
-spec find_metadata(woody:http_headers(), re_mp()) ->
|
||||
woody_context:meta().
|
||||
find_metadata(Headers, Re) ->
|
||||
RpcId = ?HEADER_RPC_ID,
|
||||
RootId = ?HEADER_RPC_ROOT_ID,
|
||||
ParentId = ?HEADER_RPC_PARENT_ID,
|
||||
maps:fold(
|
||||
fun(H, V, Acc) when
|
||||
H =/= RpcId andalso
|
||||
H =/= RootId andalso
|
||||
H =/= ParentId
|
||||
->
|
||||
case re:replace(H, Re, "", [{return, binary}, anchored]) of
|
||||
H -> Acc;
|
||||
MetaHeader -> Acc#{MetaHeader => V}
|
||||
end;
|
||||
(_, _, Acc) -> Acc
|
||||
end,
|
||||
#{}, Headers).
|
||||
|
||||
-spec set_rpc_id(woody:rpc_id(), woody_state:st()) ->
|
||||
woody_state:st().
|
||||
set_rpc_id(RpcId, WoodyState) ->
|
||||
woody_state:update_context(woody_context:new(RpcId), WoodyState).
|
||||
|
||||
-spec set_cert(cowboy_req:req(), woody_state:st()) ->
|
||||
woody_state:st().
|
||||
set_cert(Req, WoodyState) ->
|
||||
Cert = woody_cert:from_req(Req),
|
||||
Context = woody_state:get_context(WoodyState),
|
||||
woody_state:update_context(woody_context:set_cert(Cert, Context), WoodyState).
|
||||
|
||||
-spec set_deadline(woody:deadline(), woody_state:st()) ->
|
||||
woody_state:st().
|
||||
set_deadline(Deadline, WoodyState) ->
|
||||
woody_state:add_context_deadline(Deadline, WoodyState).
|
||||
|
||||
-spec set_metadata(woody_context:meta(), woody_state:st()) ->
|
||||
woody_state:st().
|
||||
set_metadata(Meta, WoodyState) ->
|
||||
woody_state:add_context_meta(Meta, WoodyState).
|
||||
|
||||
-spec reply_bad_header(woody:http_code(), woody:http_header_val(), cowboy_req:req(), state()) ->
|
||||
{stop, cowboy_req:req(), undefined}.
|
||||
reply_bad_header(Code, Reason, Req, State) when is_integer(Code) ->
|
||||
Req1 = reply_client_error(Code, Reason, Req, State),
|
||||
{stop, Req1, undefined}.
|
||||
|
||||
-spec reply_client_error(woody:http_code(), woody:http_header_val(), cowboy_req:req(), state()) ->
|
||||
cowboy_req:req().
|
||||
reply_client_error(Code, Reason, Req, #{url := Url, woody_state := WoodyState}) ->
|
||||
_ = handle_event(
|
||||
?EV_SERVER_RECEIVE,
|
||||
WoodyState,
|
||||
#{url => Url, status => error, reason => Reason}
|
||||
),
|
||||
reply(Code, set_error_headers(<<"Result Unexpected">>, Reason, Req), WoodyState).
|
||||
|
||||
-spec get_body(cowboy_req:req(), read_body_opts()) ->
|
||||
{ok, woody:http_body(), cowboy_req:req()}.
|
||||
get_body(Req, Opts) ->
|
||||
do_get_body(<<>>, Req, Opts).
|
||||
|
||||
do_get_body(Body, Req, Opts) ->
|
||||
case cowboy_req:read_body(Req, Opts) of
|
||||
{ok, Body1, Req1} ->
|
||||
{ok, <<Body/binary, Body1/binary>>, Req1};
|
||||
{more, Body1, Req1} ->
|
||||
do_get_body(<<Body/binary, Body1/binary>>, Req1, Opts)
|
||||
end.
|
||||
|
||||
-spec handle_request(woody:http_body(), woody:th_handler(), woody_state:st(), cowboy_req:req()) ->
|
||||
cowboy_req:req().
|
||||
handle_request(Body, ThriftHandler = {Service, _}, WoodyState, Req) ->
|
||||
ok = woody_monitor_h:set_event(?EV_SERVICE_HANDLER_RESULT, Req),
|
||||
Buffer = ?CODEC:new(Body),
|
||||
case thrift_processor_codec:read_function_call(Buffer, ?CODEC, Service) of
|
||||
{ok, SeqId, Invocation, Leftovers} ->
|
||||
case ?CODEC:close(Leftovers) of
|
||||
<<>> ->
|
||||
handle_invocation(SeqId, Invocation, ThriftHandler, Req, WoodyState);
|
||||
Bytes ->
|
||||
handle_decode_error({excess_response_body, Bytes, Invocation}, Req, WoodyState)
|
||||
end;
|
||||
{error, Reason} ->
|
||||
handle_decode_error(Reason, Req, WoodyState)
|
||||
end.
|
||||
|
||||
-spec handle_decode_error(_Reason, cowboy_req:req(), woody_state:st()) ->
|
||||
cowboy_req:req().
|
||||
handle_decode_error(Reason, Req, WoodyState) ->
|
||||
_ = handle_event(
|
||||
?EV_INTERNAL_ERROR,
|
||||
WoodyState,
|
||||
#{
|
||||
error => <<"thrift protocol read failed">>,
|
||||
reason => woody_error:format_details(Reason)
|
||||
}
|
||||
),
|
||||
handle_error(client_error(Reason), Req, WoodyState).
|
||||
|
||||
-spec client_error(_Reason) ->
|
||||
{client, woody_error:details()}.
|
||||
client_error({bad_binary_protocol_version, Version}) ->
|
||||
BinVersion = genlib:to_binary(Version),
|
||||
{client, <<"thrift: bad binary protocol version: ", BinVersion/binary>>};
|
||||
client_error(no_binary_protocol_version) ->
|
||||
{client, <<"thrift: no binary protocol version">>};
|
||||
client_error({bad_function_name, FName}) ->
|
||||
case binary:match(FName, <<":">>) of
|
||||
nomatch ->
|
||||
{client, woody_util:to_binary([<<"thrift: unknown function: ">>, FName])};
|
||||
_ ->
|
||||
{client, <<"thrift: multiplexing (not supported)">>}
|
||||
end;
|
||||
client_error(Reason) ->
|
||||
{client, woody_util:to_binary([<<"thrift decode error: ">>, woody_error:format_details(Reason)])}.
|
||||
|
||||
-spec handle_invocation(integer(), Invocation, woody:th_handler(), cowboy_req:req(), woody_state:st()) ->
|
||||
cowboy_req:req() when Invocation :: {call | oneway, woody:func(), woody:args()}.
|
||||
handle_invocation(SeqId, {ReplyType, Function, Args}, {Service, Handler}, Req, WoodyState) ->
|
||||
WoodyState1 = add_ev_meta(WoodyState, Service, ReplyType, Function, Args),
|
||||
case ReplyType of
|
||||
call ->
|
||||
Result = handle_call(Handler, Service, Function, Args, WoodyState1),
|
||||
handle_result(Result, Service, Function, SeqId, Req, WoodyState1);
|
||||
oneway ->
|
||||
Req1 = reply(200, Req, WoodyState1),
|
||||
_Result = handle_call(Handler, Service, Function, Args, WoodyState1),
|
||||
Req1
|
||||
end.
|
||||
|
||||
-type call_result() ::
|
||||
ok |
|
||||
{reply, woody:result()} |
|
||||
{exception, _TypeName, _Exception} |
|
||||
{error, {system, woody_error:system_error()}}.
|
||||
|
||||
-spec handle_call(woody:handler(_), woody:service(), woody:func(), woody:args(), woody_state:st()) ->
|
||||
call_result().
|
||||
handle_call(Handler, Service, Function, Args, WoodyState) ->
|
||||
try
|
||||
Result = call_handler(Handler, Function, Args, WoodyState),
|
||||
_ = handle_event(
|
||||
?EV_SERVICE_HANDLER_RESULT,
|
||||
WoodyState,
|
||||
#{status => ok, result => Result}
|
||||
),
|
||||
case Result of
|
||||
{ok, ok} -> ok;
|
||||
{ok, Reply} -> {reply, Reply}
|
||||
end
|
||||
catch
|
||||
throw:Exception:Stack ->
|
||||
process_handler_throw(Exception, Stack, Service, Function, WoodyState);
|
||||
Class:Reason:Stack ->
|
||||
process_handler_error(Class, Reason, Stack, WoodyState)
|
||||
end.
|
||||
|
||||
-spec call_handler(woody:handler(_), woody:func(), woody:args(), woody_state:st()) ->
|
||||
{ok, woody:result()} | no_return().
|
||||
call_handler(Handler, Function, Args, WoodyState) ->
|
||||
woody_server_thrift_handler:handle_function(Handler, Function, Args, WoodyState).
|
||||
|
||||
-spec process_handler_throw(_Exception, woody_error:stack(), woody:service(), woody:func(), woody_state:st()) ->
|
||||
{exception, _TypeName, _Exception} |
|
||||
{error, {system, woody_error:system_error()}}.
|
||||
process_handler_throw(Exception, Stack, Service, Function, WoodyState) ->
|
||||
case thrift_processor_codec:match_exception(Service, Function, Exception) of
|
||||
{ok, TypeName} ->
|
||||
_ = handle_event(
|
||||
?EV_SERVICE_HANDLER_RESULT,
|
||||
WoodyState,
|
||||
#{status => error, class => business, result => Exception}
|
||||
),
|
||||
{exception, TypeName, Exception};
|
||||
{error, _} ->
|
||||
process_handler_error(throw, Exception, Stack, WoodyState)
|
||||
end.
|
||||
|
||||
-spec process_handler_error(_Class :: atom(), _Reason, woody_error:stack(), woody_state:st()) ->
|
||||
{error, {system, woody_error:system_error()}}.
|
||||
process_handler_error(error, {woody_error, Error = {_, _, _}}, _Stack, WoodyState) ->
|
||||
_ = handle_event(
|
||||
?EV_SERVICE_HANDLER_RESULT,
|
||||
WoodyState,
|
||||
#{status => error, class => system, result => Error}
|
||||
),
|
||||
{error, {system, Error}};
|
||||
process_handler_error(Class, Reason, Stack, WoodyState) ->
|
||||
_ = handle_event(
|
||||
?EV_SERVICE_HANDLER_RESULT,
|
||||
WoodyState,
|
||||
#{status => error, class => system, result => Reason, except_class => Class, stack => Stack}
|
||||
),
|
||||
Error = {internal, result_unexpected, format_unexpected_error(Class, Reason, Stack)},
|
||||
{error, {system, Error}}.
|
||||
|
||||
-spec handle_result(call_result(), woody:service(), woody:func(), integer(), Req, woody_state:st()) ->
|
||||
Req when Req :: cowboy_req:req().
|
||||
handle_result(Res, Service, Function, SeqId, Req, WoodyState) when
|
||||
Res == ok; element(1, Res) == reply
|
||||
->
|
||||
Buffer = ?CODEC:new(),
|
||||
case thrift_processor_codec:write_function_result(Buffer, ?CODEC, Service, Function, Res, SeqId) of
|
||||
{ok, Buffer1} ->
|
||||
Response = ?CODEC:close(Buffer1),
|
||||
reply(200, cowboy_req:set_resp_body(Response, Req), WoodyState);
|
||||
{error, Reason} ->
|
||||
handle_encode_error(Reason, Req, WoodyState)
|
||||
end;
|
||||
handle_result(Res = {exception, TypeName, Exception}, Service, Function, SeqId, Req, WoodyState) ->
|
||||
Buffer = ?CODEC:new(),
|
||||
case thrift_processor_codec:write_function_result(Buffer, ?CODEC, Service, Function, Res, SeqId) of
|
||||
{ok, Buffer1} ->
|
||||
ExceptionName = get_exception_name(TypeName, Exception),
|
||||
Response = ?CODEC:close(Buffer1),
|
||||
handle_error({business, {ExceptionName, Response}}, Req, WoodyState);
|
||||
{error, Reason} ->
|
||||
handle_encode_error(Reason, Req, WoodyState)
|
||||
end;
|
||||
handle_result({error, Error}, _Service, _Function, _SeqId, Req, WoodyState) ->
|
||||
handle_error(Error, Req, WoodyState).
|
||||
|
||||
get_exception_name({{struct, exception, {_Mod, Name}}, _}, _) ->
|
||||
genlib:to_binary(Name);
|
||||
get_exception_name(_TypeName, Exception) ->
|
||||
genlib:to_binary(element(1, Exception)).
|
||||
|
||||
-spec handle_encode_error(_Reason, cowboy_req:req(), woody_state:st()) ->
|
||||
cowboy_req:req().
|
||||
handle_encode_error(Reason, Req, WoodyState) ->
|
||||
_ = handle_event(
|
||||
?EV_INTERNAL_ERROR,
|
||||
WoodyState,
|
||||
#{
|
||||
error => <<"thrift protocol write failed">>,
|
||||
reason => woody_error:format_details(Reason)
|
||||
}
|
||||
),
|
||||
Error = {internal, result_unexpected, format_unexpected_error(error, Reason, [])},
|
||||
handle_error({system, Error}, Req, WoodyState).
|
||||
|
||||
add_ev_meta(WoodyState, Service = {_, ServiceName}, ReplyType, Function, Args) ->
|
||||
woody_state:add_ev_meta(#{
|
||||
service => ServiceName,
|
||||
service_schema => Service,
|
||||
function => Function,
|
||||
args => Args,
|
||||
type => get_rpc_reply_type(ReplyType),
|
||||
deadline => woody_context:get_deadline(woody_state:get_context(WoodyState))
|
||||
}, WoodyState).
|
||||
|
||||
get_rpc_reply_type(oneway) -> cast;
|
||||
get_rpc_reply_type(call) -> call.
|
||||
|
||||
format_unexpected_error(Class, Reason, Stack) ->
|
||||
woody_util:to_binary(
|
||||
[Class, ":", woody_error:format_details(Reason), " ", genlib_format:format_stacktrace(Stack)]
|
||||
).
|
||||
|
||||
-spec handle_error(Error, cowboy_req:req(), woody_state:st()) -> cowboy_req:req() when
|
||||
Error :: woody_error:error() | woody_server_thrift_handler:client_error().
|
||||
handle_error({business, {ExceptName, Except}}, Req, WoodyState) ->
|
||||
reply(200, set_error_headers(<<"Business Error">>, ExceptName, cowboy_req:set_resp_body(Except, Req)), WoodyState);
|
||||
handle_error({client, Error}, Req, WoodyState) ->
|
||||
reply(400, set_error_headers(<<"Result Unexpected">>, Error, Req), WoodyState);
|
||||
handle_error({system, {internal, result_unexpected, Details}}, Req, WoodyState) ->
|
||||
reply(500, set_error_headers(<<"Result Unexpected">>, Details, Req), WoodyState);
|
||||
handle_error({system, {internal, resource_unavailable, Details}}, Req, WoodyState) ->
|
||||
reply(503, set_error_headers(<<"Resource Unavailable">>, Details, Req), WoodyState);
|
||||
handle_error({system, {internal, result_unknown, Details}}, Req, WoodyState) ->
|
||||
reply(504, set_error_headers(<<"Result Unknown">>, Details, Req), WoodyState);
|
||||
handle_error({system, {external, result_unexpected, Details}}, Req, WoodyState) ->
|
||||
reply(502, set_error_headers(<<"Result Unexpected">>, Details, Req), WoodyState);
|
||||
handle_error({system, {external, resource_unavailable, Details}}, Req, WoodyState) ->
|
||||
reply(502, set_error_headers(<<"Resource Unavailable">>, Details, Req), WoodyState);
|
||||
handle_error({system, {external, result_unknown, Details}}, Req, WoodyState) ->
|
||||
reply(502, set_error_headers(<<"Result Unknown">>, Details, Req), WoodyState).
|
||||
|
||||
-spec set_error_headers(woody:http_header_val(), woody:http_header_val(), cowboy_req:req()) ->
|
||||
cowboy_req:req().
|
||||
set_error_headers(Class, Reason, Req) ->
|
||||
Headers = #{
|
||||
?HEADER_E_CLASS => Class,
|
||||
?HEADER_E_REASON => Reason
|
||||
},
|
||||
cowboy_req:set_resp_headers(Headers, Req).
|
||||
|
||||
-spec reply(woody:http_code(), cowboy_req:req(), woody_state:st()) ->
|
||||
cowboy_req:req().
|
||||
reply(200, Req, WoodyState) ->
|
||||
do_reply(200, cowboy_req:set_resp_header(<<"content-type">>, ?CONTENT_TYPE_THRIFT, Req), WoodyState);
|
||||
reply(Code, Req, WoodyState) ->
|
||||
do_reply(Code, Req, WoodyState).
|
||||
|
||||
do_reply(Code, Req, WoodyState) ->
|
||||
_ = handle_event(?EV_SERVER_SEND, WoodyState, #{code => Code, status => reply_status(Code)}),
|
||||
cowboy_req:reply(Code, Req).
|
||||
|
||||
reply_status(200) -> ok;
|
||||
reply_status(_) -> error.
|
||||
|
||||
handle_event(Event, WoodyState, ExtraMeta) ->
|
||||
woody_event_handler:handle_event(Event, WoodyState, ExtraMeta).
|
||||
|
||||
update_woody_state(State, WoodyState, Req) ->
|
||||
ok = woody_monitor_h:put_woody_state(WoodyState, Req),
|
||||
State#{woody_state => WoodyState}.
|
@ -1,8 +1,12 @@
|
||||
-module(woody_trace_h).
|
||||
-behaviour(cowboy_stream).
|
||||
|
||||
-include("woody_defs.hrl").
|
||||
|
||||
-dialyzer(no_undefined_callbacks).
|
||||
|
||||
-export([env/1]).
|
||||
|
||||
-behaviour(cowboy_stream).
|
||||
-export([init/3]).
|
||||
-export([data/4]).
|
||||
-export([info/3]).
|
||||
@ -11,34 +15,82 @@
|
||||
|
||||
-type state() :: #{
|
||||
req := cowboy_req:req(),
|
||||
ev_handler := woody:ev_handler(),
|
||||
opts := woody:ev_handler(),
|
||||
next := any()
|
||||
}.
|
||||
|
||||
-define(TRACER, trace_http_server).
|
||||
|
||||
-type options() :: #{
|
||||
event_handler := woody:ev_handlers()
|
||||
}.
|
||||
|
||||
-spec env(options()) ->
|
||||
cowboy_middleware:env().
|
||||
env(Opts = #{}) ->
|
||||
EvHandler = maps:get(event_handler, Opts),
|
||||
#{?MODULE => EvHandler}.
|
||||
|
||||
extract_trace_options(#{env := Env}) ->
|
||||
maps:get(?MODULE, Env).
|
||||
|
||||
%% private functions
|
||||
|
||||
trace_request(Req, EvHandler, ReadBodyOpts) ->
|
||||
woody_server_thrift_http_handler:trace_req(genlib_app:env(woody, ?TRACER), Req, EvHandler, ReadBodyOpts).
|
||||
trace_request(Req, EvHandler) ->
|
||||
trace_req(genlib_app:env(woody, ?TRACER), Req, EvHandler).
|
||||
|
||||
trace_response(Req, {response, Code, Headers, Body}, EvHandler) ->
|
||||
woody_server_thrift_http_handler:trace_resp(genlib_app:env(woody, ?TRACER), Req, Code, Headers, Body, EvHandler).
|
||||
trace_resp(genlib_app:env(woody, ?TRACER), Req, Code, Headers, Body, EvHandler).
|
||||
|
||||
% We can't go without Env AND event handler, so we want to fail, reading opts are optional tho
|
||||
extract_trace_options(Opts) ->
|
||||
Env = maps:get(env, Opts),
|
||||
{maps:get(event_handler, Env), maps:get(read_body_opts, Env, #{})}.
|
||||
-spec trace_req(true, cowboy_req:req(), woody:ev_handlers()) ->
|
||||
cowboy_req:req().
|
||||
trace_req(true, Req, EvHandler) ->
|
||||
Url = unicode:characters_to_binary(cowboy_req:uri(Req)),
|
||||
Headers = cowboy_req:headers(Req),
|
||||
% TODO
|
||||
% No body here since with Cowboy 2 we can consume it only once.
|
||||
% Ideally we would need to embed tracing directly into handler itself.
|
||||
Meta = #{
|
||||
role => server,
|
||||
event => <<"http request received">>,
|
||||
url => Url,
|
||||
headers => Headers
|
||||
},
|
||||
_ = woody_event_handler:handle_event(EvHandler, ?EV_TRACE, undefined, Meta),
|
||||
Req;
|
||||
trace_req(_, Req, _) ->
|
||||
Req.
|
||||
|
||||
-spec trace_resp(
|
||||
true,
|
||||
cowboy_req:req(),
|
||||
woody:http_code(),
|
||||
woody:http_headers(),
|
||||
woody:http_body(),
|
||||
woody:ev_handlers()
|
||||
) ->
|
||||
cowboy_req:req().
|
||||
trace_resp(true, Req, Code, Headers, Body, EvHandler) ->
|
||||
_ = woody_event_handler:handle_event(EvHandler, ?EV_TRACE, undefined, #{
|
||||
role => server,
|
||||
event => <<"http response send">>,
|
||||
code => Code,
|
||||
headers => Headers,
|
||||
body => Body
|
||||
}),
|
||||
Req;
|
||||
trace_resp(_, Req, _, _, _, _) ->
|
||||
Req.
|
||||
|
||||
%% callbacks
|
||||
|
||||
-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
|
||||
-> {cowboy_stream:commands(), state()}.
|
||||
init(StreamID, Req, Opts) ->
|
||||
{EvHandler, ReadBodyOpts} = extract_trace_options(Opts),
|
||||
_ = trace_request(Req, EvHandler, ReadBodyOpts),
|
||||
TraceOpts = extract_trace_options(Opts),
|
||||
_ = trace_request(Req, TraceOpts),
|
||||
{Commands0, Next} = cowboy_stream:init(StreamID, Req, Opts),
|
||||
{Commands0, #{next => Next, req => Req, ev_handler => EvHandler}}.
|
||||
{Commands0, #{next => Next, req => Req, opts => TraceOpts}}.
|
||||
|
||||
-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
|
||||
-> {cowboy_stream:commands(), State} when State::state().
|
||||
@ -48,8 +100,8 @@ data(StreamID, IsFin, Data, #{next := Next0} = State) ->
|
||||
|
||||
-spec info(cowboy_stream:streamid(), any(), State)
|
||||
-> {cowboy_stream:commands(), State} when State::state().
|
||||
info(StreamID, {response, _, _, _} = Info, #{next := Next0, req := Req, ev_handler := EvHandler} = State) ->
|
||||
_ = trace_response(Req, Info, EvHandler),
|
||||
info(StreamID, {response, _, _, _} = Info, #{next := Next0, req := Req, opts := TraceOpts} = State) ->
|
||||
_ = trace_response(Req, Info, TraceOpts),
|
||||
{Commands0, Next} = cowboy_stream:info(StreamID, Info, Next0),
|
||||
{Commands0, State#{next => Next}};
|
||||
info(StreamID, Info, #{next := Next0} = State) ->
|
||||
@ -65,7 +117,7 @@ terminate(StreamID, Reason, #{next := Next}) ->
|
||||
cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
|
||||
when Resp::cowboy_stream:resp_command().
|
||||
early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
|
||||
{EvHandler, ReadBodyOpts} = extract_trace_options(Opts),
|
||||
_ = trace_request(PartialReq, EvHandler, ReadBodyOpts),
|
||||
_ = trace_response(PartialReq, Resp, EvHandler),
|
||||
TraceOpts = extract_trace_options(Opts),
|
||||
_ = trace_request(PartialReq, TraceOpts),
|
||||
_ = trace_response(PartialReq, Resp, TraceOpts),
|
||||
cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
|
||||
|
@ -6,6 +6,7 @@
|
||||
-export([get_protocol_handler/2]).
|
||||
-export([get_mod_opts/1]).
|
||||
-export([to_binary/1]).
|
||||
-export([get_rpc_type/2]).
|
||||
-export([get_rpc_reply_type/1]).
|
||||
|
||||
-define(DEFAULT_HANDLER_OPTS, undefined).
|
||||
@ -14,13 +15,15 @@
|
||||
%% Internal API
|
||||
%%
|
||||
-spec get_protocol_handler(woody:role(), map()) ->
|
||||
woody_client_thrift | woody_server_thrift_http_handler | no_return().
|
||||
module() | no_return().
|
||||
get_protocol_handler(_Role, #{protocol_handler_override := Module}) when is_atom(Module) ->
|
||||
Module;
|
||||
get_protocol_handler(Role, Opts) ->
|
||||
Protocol = genlib_map:get(protocol, Opts, thrift),
|
||||
Transport = genlib_map:get(transport, Opts, http),
|
||||
case {Role, Protocol, Transport} of
|
||||
{client, thrift, http} -> woody_client_thrift;
|
||||
{server, thrift, http} -> woody_server_thrift_http_handler;
|
||||
{client, thrift, http} -> woody_client_thrift_v2;
|
||||
{server, thrift, http} -> woody_server_thrift_v2;
|
||||
_ -> error(badarg, [Role, Opts])
|
||||
end.
|
||||
|
||||
@ -44,6 +47,11 @@ to_binary([Part | T], Reason) ->
|
||||
BinPart = genlib:to_binary(Part),
|
||||
to_binary(T, <<Reason/binary, BinPart/binary>>).
|
||||
|
||||
-spec get_rpc_type(woody:service(), woody:func()) ->
|
||||
woody:rpc_type().
|
||||
get_rpc_type({Module, Service}, Function) ->
|
||||
get_rpc_reply_type(Module:function_info(Service, Function, reply_type)).
|
||||
|
||||
-spec get_rpc_reply_type(_ThriftReplyType) ->
|
||||
woody:rpc_type().
|
||||
get_rpc_reply_type(oneway_void) -> cast;
|
||||
|
@ -199,7 +199,7 @@ handle_event(Event, RpcId, Meta, _) ->
|
||||
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) ->
|
||||
{ok, woody:result()}.
|
||||
|
||||
handle_function(get_weapon, [Name, _Data], Context, _Opts) ->
|
||||
handle_function(get_weapon, {Name, _Data}, Context, _Opts) ->
|
||||
_ = assert_common_name([<<"Valid Test Client">>], Context),
|
||||
{ok, #'Weapon'{name = Name, slot_pos = 0}}.
|
||||
|
||||
@ -259,7 +259,7 @@ get_weapon(Id, Gun, SSLOptions) ->
|
||||
]
|
||||
}
|
||||
},
|
||||
woody_client:call({Service, get_weapon, [Gun, <<>>]}, Options, Context).
|
||||
woody_client:call({Service, get_weapon, {Gun, <<>>}}, Options, Context).
|
||||
|
||||
get_service_endpoint('Weapons') ->
|
||||
{
|
||||
|
@ -239,24 +239,17 @@
|
||||
%% tests descriptions
|
||||
%%
|
||||
all() ->
|
||||
[{group, G} || G <- maps:keys(cross_test_groups())] ++
|
||||
[
|
||||
{group, client_server},
|
||||
ids_monotonic_incr_test,
|
||||
{group, contexts},
|
||||
{group, deadlines},
|
||||
{group, woody_resolver}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
SpecTests = [
|
||||
context_add_put_get_meta_ok_test,
|
||||
context_get_meta_by_key_ok_test,
|
||||
context_get_empty_meta_ok_test,
|
||||
context_get_empty_meta_by_key_ok_test,
|
||||
context_given_rpc_id_test,
|
||||
context_given_id_test,
|
||||
context_generated_rpc_id_test,
|
||||
ids_monotonic_incr_test,
|
||||
deadline_reached_test,
|
||||
deadline_to_from_timeout_test,
|
||||
deadline_to_from_binary_test,
|
||||
call_ok_test,
|
||||
call_resolver_nxdomain,
|
||||
call3_ok_test,
|
||||
@ -290,8 +283,23 @@ groups() ->
|
||||
find_multiple_pools_test,
|
||||
calls_with_cache
|
||||
],
|
||||
[{G, [], SpecTests} || G <- maps:keys(cross_test_groups())] ++
|
||||
[
|
||||
{client_server, [], SpecTests},
|
||||
{contexts, [], [
|
||||
context_add_put_get_meta_ok_test,
|
||||
context_get_meta_by_key_ok_test,
|
||||
context_get_empty_meta_ok_test,
|
||||
context_get_empty_meta_by_key_ok_test,
|
||||
context_given_rpc_id_test,
|
||||
context_given_id_test,
|
||||
context_generated_rpc_id_test
|
||||
]},
|
||||
{deadlines, [], [
|
||||
deadline_reached_test,
|
||||
deadline_to_from_timeout_test,
|
||||
deadline_to_from_binary_test
|
||||
]},
|
||||
{woody_resolver, [], [
|
||||
woody_resolver_inet,
|
||||
woody_resolver_inet6,
|
||||
@ -299,6 +307,22 @@ groups() ->
|
||||
]}
|
||||
].
|
||||
|
||||
cross_test_groups() ->
|
||||
#{
|
||||
'client_v1.server_v1' => {
|
||||
#{protocol_handler_override => woody_client_thrift},
|
||||
#{protocol_handler_override => woody_server_thrift_http_handler}
|
||||
},
|
||||
'client_v1.server_v2' => {
|
||||
#{protocol_handler_override => woody_client_thrift},
|
||||
#{}
|
||||
},
|
||||
'client_v2.server_v1' => {
|
||||
#{},
|
||||
#{protocol_handler_override => woody_server_thrift_http_handler}
|
||||
}
|
||||
}.
|
||||
|
||||
%%
|
||||
%% starting/stopping
|
||||
%%
|
||||
@ -351,28 +375,29 @@ init_per_testcase(find_multiple_pools_test, C) ->
|
||||
{ok, Sup} = start_tc_sup(),
|
||||
Pool1 = {swords, 15000, 100},
|
||||
Pool2 = {shields, undefined, 50},
|
||||
ok = start_woody_server_with_pools(woody_ct, Sup, ['Weapons', 'Powerups'], [Pool1, Pool2]),
|
||||
ok = start_woody_server_with_pools(woody_ct, Sup, ['Weapons', 'Powerups'], [Pool1, Pool2], C),
|
||||
[{sup, Sup} | C];
|
||||
init_per_testcase(calls_with_cache, C) ->
|
||||
{ok, Sup} = start_tc_sup(),
|
||||
{ok, _} = start_caching_client(caching_client_ct, Sup),
|
||||
{ok, _} = start_woody_server(woody_ct, Sup, ['Weapons', 'Powerups']),
|
||||
{ok, _} = start_woody_server(woody_ct, Sup, ['Weapons', 'Powerups'], C),
|
||||
[{sup, Sup} | C];
|
||||
init_per_testcase(server_handled_client_timeout_test, C) ->
|
||||
{ok, Sup} = start_tc_sup(),
|
||||
{ok, _} = supervisor:start_child(Sup, server_timeout_event_handler:child_spec()),
|
||||
{ok, _} = start_woody_server(woody_ct, Sup, ['Weapons', 'Powerups'], server_timeout_event_handler),
|
||||
{ok, _} = start_woody_server(woody_ct, Sup, ['Weapons', 'Powerups'], server_timeout_event_handler, C),
|
||||
[{sup, Sup} | C];
|
||||
|
||||
init_per_testcase(_, C) ->
|
||||
{ok, Sup} = start_tc_sup(),
|
||||
{ok, _} = start_woody_server(woody_ct, Sup, ['Weapons', 'Powerups']),
|
||||
{ok, _} = start_woody_server(woody_ct, Sup, ['Weapons', 'Powerups'], C),
|
||||
[{sup, Sup} | C].
|
||||
|
||||
init_per_group(woody_resolver, Config) ->
|
||||
[{env_inet6, inet_db:res_option(inet6)} | Config];
|
||||
init_per_group(_Name, Config) ->
|
||||
Config.
|
||||
init_per_group(Name, Config) ->
|
||||
{ClientOpts, ServerOpts} = maps:get(Name, cross_test_groups(), {#{}, #{}}),
|
||||
[{client_opts, ClientOpts}, {server_opts, ServerOpts} | Config].
|
||||
|
||||
end_per_group(_Name, _Config) ->
|
||||
ok.
|
||||
@ -389,25 +414,33 @@ start_error_server(TC, Sup) ->
|
||||
),
|
||||
supervisor:start_child(Sup, Server).
|
||||
|
||||
start_woody_server(Id, Sup, Services) ->
|
||||
start_woody_server(Id, Sup, Services, ?MODULE).
|
||||
start_woody_server(Id, Sup, Services, C) ->
|
||||
start_woody_server(Id, Sup, Services, ?MODULE, C).
|
||||
|
||||
start_woody_server(Id, Sup, Services, EventHandler) ->
|
||||
Server = woody_server:child_spec(Id, #{
|
||||
handlers => [get_handler(S) || S <- Services],
|
||||
event_handler => EventHandler,
|
||||
ip => ?SERVER_IP,
|
||||
port => ?SERVER_PORT
|
||||
}),
|
||||
start_woody_server(Id, Sup, Services, EventHandler, C) ->
|
||||
Opts = maps:merge(
|
||||
proplists:get_value(server_opts, C, #{}),
|
||||
#{
|
||||
handlers => [get_handler(S) || S <- Services],
|
||||
event_handler => EventHandler,
|
||||
ip => ?SERVER_IP,
|
||||
port => ?SERVER_PORT
|
||||
}
|
||||
),
|
||||
Server = woody_server:child_spec(Id, Opts),
|
||||
supervisor:start_child(Sup, Server).
|
||||
|
||||
start_woody_server_with_pools(Id, Sup, Services, Params) ->
|
||||
Server = woody_server:child_spec(Id, #{
|
||||
handlers => [get_handler(S) || S <- Services],
|
||||
event_handler => ?MODULE,
|
||||
ip => ?SERVER_IP,
|
||||
port => ?SERVER_PORT
|
||||
}),
|
||||
start_woody_server_with_pools(Id, Sup, Services, Params, C) ->
|
||||
Opts = maps:merge(
|
||||
proplists:get_value(server_opts, C, #{}),
|
||||
#{
|
||||
handlers => [get_handler(S) || S <- Services],
|
||||
event_handler => ?MODULE,
|
||||
ip => ?SERVER_IP,
|
||||
port => ?SERVER_PORT
|
||||
}
|
||||
),
|
||||
Server = woody_server:child_spec(Id, Opts),
|
||||
{ok, WoodyServer} = supervisor:start_child(Sup, Server),
|
||||
Specs = [woody_client:child_spec(pool_opts(Pool)) || Pool <- Params],
|
||||
|
||||
@ -586,144 +619,144 @@ deadline_to_from_binary_test(_) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
call_ok_test(_) ->
|
||||
call_ok_test(C) ->
|
||||
Gun = <<"Enforcer">>,
|
||||
gun_test_basic(<<"call_ok">>, Gun, {ok, genlib_map:get(Gun, ?WEAPONS)}, true).
|
||||
gun_test_basic(<<"call_ok">>, Gun, {ok, genlib_map:get(Gun, ?WEAPONS)}, true, C).
|
||||
|
||||
call_resolver_nxdomain(_) ->
|
||||
call_resolver_nxdomain(C) ->
|
||||
Context = make_context(<<"nxdomain">>),
|
||||
?assertError(
|
||||
{woody_error, {internal, resource_unavailable, <<"{resolve_failed,nxdomain}">>}},
|
||||
call(Context, 'The Void', get_weapon, [<<"Enforcer">>, self_to_bin()])
|
||||
call(Context, 'The Void', get_weapon, {<<"Enforcer">>, self_to_bin()}, C)
|
||||
).
|
||||
|
||||
call3_ok_test(_) ->
|
||||
call3_ok_test(C) ->
|
||||
{Url, Service} = get_service_endpoint('Weapons'),
|
||||
Gun = <<"Enforcer">>,
|
||||
Request = {Service, get_weapon, [Gun, self_to_bin()]},
|
||||
Opts = #{url => Url, event_handler => ?MODULE},
|
||||
Request = {Service, get_weapon, {Gun, self_to_bin()}},
|
||||
Opts = mk_client_opts(#{url => Url}, C),
|
||||
Expect = {ok, genlib_map:get(Gun, ?WEAPONS)},
|
||||
Expect = woody_client:call(Request, Opts).
|
||||
|
||||
call3_ok_default_ev_handler_test(_) ->
|
||||
call3_ok_default_ev_handler_test(C) ->
|
||||
{Url, Service} = get_service_endpoint('Weapons'),
|
||||
Gun = <<"Enforcer">>,
|
||||
Request = {Service, get_weapon, [Gun, self_to_bin()]},
|
||||
Opts = #{url => Url, event_handler => {woody_event_handler_default, #{}}},
|
||||
Request = {Service, get_weapon, {Gun, self_to_bin()}},
|
||||
Opts = mk_client_opts(#{url => Url, event_handler => {woody_event_handler_default, #{}}}, C),
|
||||
Expect = {ok, genlib_map:get(Gun, ?WEAPONS)},
|
||||
Expect = woody_client:call(Request, Opts).
|
||||
|
||||
call_business_error_test(_) ->
|
||||
call_business_error_test(C) ->
|
||||
Gun = <<"Bio Rifle">>,
|
||||
gun_test_basic(<<"call_business_error">>, Gun,
|
||||
{exception, ?WEAPON_FAILURE("out of ammo")}, true).
|
||||
{exception, ?WEAPON_FAILURE("out of ammo")}, true, C).
|
||||
|
||||
call_throw_unexpected_test(_) ->
|
||||
call_throw_unexpected_test(C) ->
|
||||
Id = <<"call_throw_unexpected">>,
|
||||
Current = genlib_map:get(<<"Rocket Launcher">>, ?WEAPONS),
|
||||
Context = make_context(Id),
|
||||
?assertError(
|
||||
{woody_error, {external, result_unexpected, _}},
|
||||
call(Context, 'Weapons', switch_weapon, [Current, next, 1, self_to_bin()])
|
||||
call(Context, 'Weapons', switch_weapon, {Current, next, 1, self_to_bin()}, C)
|
||||
).
|
||||
|
||||
call_system_external_error_test(_) ->
|
||||
call_system_external_error_test(C) ->
|
||||
Id = <<"call_system_external_error">>,
|
||||
Gun = <<"The Ultimate Super Mega Destroyer">>,
|
||||
Context = make_context(Id),
|
||||
?assertError(
|
||||
{woody_error, {external, result_unexpected, _}},
|
||||
call(Context, 'Weapons', get_weapon, [Gun, self_to_bin()])
|
||||
call(Context, 'Weapons', get_weapon, {Gun, self_to_bin()}, C)
|
||||
).
|
||||
|
||||
call_client_error_test(_) ->
|
||||
call_client_error_test(C) ->
|
||||
Gun = 'Wrong Type of Mega Destroyer',
|
||||
Context = make_context(<<"call_client_error">>),
|
||||
?assertError(
|
||||
{woody_error, {internal, result_unexpected, <<"client thrift error: ", _/binary>>}},
|
||||
call(Context, 'Weapons', get_weapon, [Gun, self_to_bin()])
|
||||
call(Context, 'Weapons', get_weapon, {Gun, self_to_bin()}, C)
|
||||
).
|
||||
|
||||
call_server_internal_error_test(_) ->
|
||||
call_server_internal_error_test(C) ->
|
||||
Armor = <<"Helmet">>,
|
||||
Context = make_context(<<"call_server_internal_error">>),
|
||||
?assertError(
|
||||
{woody_error, {external, result_unexpected, _}},
|
||||
call(Context, 'Powerups', get_powerup, [Armor, self_to_bin()])
|
||||
call(Context, 'Powerups', get_powerup, {Armor, self_to_bin()}, C)
|
||||
),
|
||||
{ok, _} = receive_msg(Armor, Context).
|
||||
|
||||
call_oneway_void_test(_) ->
|
||||
call_oneway_void_test(C) ->
|
||||
Armor = <<"Helmet">>,
|
||||
Context = make_context(<<"call_oneway_void">>),
|
||||
{ok, ok} = call(Context, 'Powerups', like_powerup, [Armor, self_to_bin()]),
|
||||
{ok, ok} = call(Context, 'Powerups', like_powerup, {Armor, self_to_bin()}, C),
|
||||
{ok, _} = receive_msg(Armor, Context).
|
||||
|
||||
call_sequence_with_context_meta_test(_) ->
|
||||
call_sequence_with_context_meta_test(C) ->
|
||||
Gun = <<"Enforcer">>,
|
||||
Current = genlib_map:get(Gun, ?WEAPONS),
|
||||
Context = woody_context:new(
|
||||
<<"call_seq_with_context_meta">>,
|
||||
#{genlib:to_binary(Current#'Weapon'.slot_pos) => Gun}),
|
||||
Expect = {ok, genlib_map:get(<<"Ripper">>, ?WEAPONS)},
|
||||
Expect = call(Context, 'Weapons', switch_weapon, [Current, next, 1, self_to_bin()]).
|
||||
Expect = call(Context, 'Weapons', switch_weapon, {Current, next, 1, self_to_bin()}, C).
|
||||
|
||||
call_pass_thru_ok_test(_) ->
|
||||
call_pass_thru_ok_test(C) ->
|
||||
Armor = <<"AntiGrav Boots">>,
|
||||
Context = make_context(<<"call_pass_thru_ok">>),
|
||||
Expect = {ok, genlib_map:get(Armor, ?POWERUPS)},
|
||||
Expect = call(Context, 'Powerups', proxy_get_powerup, [Armor, self_to_bin()]),
|
||||
Expect = call(Context, 'Powerups', proxy_get_powerup, {Armor, self_to_bin()}, C),
|
||||
{ok, _} = receive_msg(Armor, Context).
|
||||
|
||||
call_pass_thru_except_test(_) ->
|
||||
call_pass_thru_except_test(C) ->
|
||||
Armor = <<"Shield Belt">>,
|
||||
Id = <<"call_pass_thru_except">>,
|
||||
RpcId = woody_context:new_rpc_id(?ROOT_REQ_PARENT_ID, Id, Id),
|
||||
Context = woody_context:new(RpcId),
|
||||
try call(Context, 'Powerups', proxy_get_powerup, [Armor, self_to_bin()])
|
||||
try call(Context, 'Powerups', proxy_get_powerup, {Armor, self_to_bin()}, C)
|
||||
catch
|
||||
error:{woody_error, {external, result_unexpected, _}} ->
|
||||
ok
|
||||
end,
|
||||
{ok, _} = receive_msg(Armor, Context).
|
||||
|
||||
call_pass_thru_bad_result_test(_) ->
|
||||
call_pass_thru_bad_result_test(C) ->
|
||||
Armor = <<"AntiGrav Boots">>,
|
||||
Context = make_context(<<"call_pass_thru_bad_result">>),
|
||||
?assertError(
|
||||
{woody_error, {external, result_unexpected, _}},
|
||||
call(Context, 'Powerups', bad_proxy_get_powerup, [Armor, self_to_bin()])
|
||||
call(Context, 'Powerups', bad_proxy_get_powerup, {Armor, self_to_bin()}, C)
|
||||
),
|
||||
{ok, _} = receive_msg(Armor, Context).
|
||||
|
||||
call_pass_thru_bad_except_test(_) ->
|
||||
call_pass_thru_bad_except_test(C) ->
|
||||
Armor = <<"Shield Belt">>,
|
||||
Context = make_context(<<"call_pass_thru_bad_except">>),
|
||||
?assertError(
|
||||
{woody_error, {external, result_unexpected, _}},
|
||||
call(Context, 'Powerups', bad_proxy_get_powerup, [Armor, self_to_bin()])
|
||||
call(Context, 'Powerups', bad_proxy_get_powerup, {Armor, self_to_bin()}, C)
|
||||
),
|
||||
{ok, _} = receive_msg(Armor, Context).
|
||||
|
||||
call_pass_thru_result_unexpected_test(_) ->
|
||||
call_pass_thru_result_unexpected_test(C) ->
|
||||
call_pass_thru_error(<<"call_pass_thru_result_unexpected">>, <<"Helmet">>,
|
||||
error, result_unexpected).
|
||||
error, result_unexpected, C).
|
||||
|
||||
call_pass_thru_resource_unavail_test(_) ->
|
||||
call_pass_thru_resource_unavail_test(C) ->
|
||||
call_pass_thru_error(<<"call_pass_thru_resource_unavail">>, <<"Damage Amplifier">>,
|
||||
error, resource_unavailable).
|
||||
error, resource_unavailable, C).
|
||||
|
||||
call_pass_thru_result_unknown_test(_) ->
|
||||
call_pass_thru_result_unknown_test(C) ->
|
||||
call_pass_thru_error(<<"call_pass_thru_result_unknown">>, <<"Invisibility">>,
|
||||
error, result_unknown).
|
||||
error, result_unknown, C).
|
||||
|
||||
call_pass_thru_error(Id, Powerup, ExceptClass, ErrClass) ->
|
||||
call_pass_thru_error(Id, Powerup, ExceptClass, ErrClass, C) ->
|
||||
RpcId = woody_context:new_rpc_id(?ROOT_REQ_PARENT_ID, Id, Id),
|
||||
Context = woody_context:new(RpcId),
|
||||
?assertException(
|
||||
ExceptClass,
|
||||
{woody_error, {external, ErrClass, _}},
|
||||
call(Context, 'Powerups', proxy_get_powerup, [Powerup, self_to_bin()])
|
||||
call(Context, 'Powerups', proxy_get_powerup, {Powerup, self_to_bin()}, C)
|
||||
),
|
||||
{ok, _} = receive_msg(Powerup, Context).
|
||||
|
||||
@ -749,7 +782,7 @@ call_fail_w_no_headers(Id, Class, Code) ->
|
||||
BinCode = integer_to_binary(Code),
|
||||
?assertError(
|
||||
{woody_error, {external, Class, <<"got response with http code ", BinCode:3/binary, _/binary>>}},
|
||||
woody_client:call({Service, get_weapon, [Gun, self_to_bin()]},
|
||||
woody_client:call({Service, get_weapon, {Gun, self_to_bin()}},
|
||||
#{url => Url, event_handler => ?MODULE}, Context)
|
||||
).
|
||||
|
||||
@ -775,23 +808,23 @@ make_thrift_multiplexed_client(Id, ServiceName, {Url, Service}) ->
|
||||
{ok, Client} = thrift_client:new(Protocol1, Service),
|
||||
Client.
|
||||
|
||||
call_deadline_ok_test(_) ->
|
||||
call_deadline_ok_test(C) ->
|
||||
Id = <<"call_deadline_timeout">>,
|
||||
{Url, Service} = get_service_endpoint('Weapons'),
|
||||
Gun = <<"Enforcer">>,
|
||||
Request = {Service, get_weapon, [Gun, self_to_bin()]},
|
||||
Opts = #{url => Url, event_handler => ?MODULE},
|
||||
Request = {Service, get_weapon, {Gun, self_to_bin()}},
|
||||
Opts = mk_client_opts(#{url => Url}, C),
|
||||
Deadline = woody_deadline:from_timeout(3000),
|
||||
Context = woody_context:new(Id, #{<<"sleep">> => <<"100">>}, Deadline),
|
||||
Expect = {ok, genlib_map:get(Gun, ?WEAPONS)},
|
||||
Expect = woody_client:call(Request, Opts, Context).
|
||||
|
||||
call_deadline_reached_on_client_test(_) ->
|
||||
call_deadline_reached_on_client_test(C) ->
|
||||
Id = <<"call_deadline_reached_on_client">>,
|
||||
{Url, Service} = get_service_endpoint('Weapons'),
|
||||
Gun = <<"Enforcer">>,
|
||||
Request = {Service, get_weapon, [Gun, self_to_bin()]},
|
||||
Opts = #{url => Url, event_handler => ?MODULE},
|
||||
Request = {Service, get_weapon, {Gun, self_to_bin()}},
|
||||
Opts = mk_client_opts(#{url => Url}, C),
|
||||
Deadline = woody_deadline:from_timeout(0),
|
||||
Context = woody_context:new(Id, #{<<"sleep">> => <<"1000">>}, Deadline),
|
||||
?assertError(
|
||||
@ -799,11 +832,11 @@ call_deadline_reached_on_client_test(_) ->
|
||||
woody_client:call(Request, Opts, Context)
|
||||
).
|
||||
|
||||
server_handled_client_timeout_test(_) ->
|
||||
server_handled_client_timeout_test(C) ->
|
||||
Id = <<"server_handled_client_timeout">>,
|
||||
{Url, Service} = get_service_endpoint('Weapons'),
|
||||
Request = {Service, get_stuck_looping_weapons, []},
|
||||
Opts = #{url => Url, event_handler => ?MODULE},
|
||||
Request = {Service, get_stuck_looping_weapons, {}},
|
||||
Opts = mk_client_opts(#{url => Url}, C),
|
||||
Deadline = woody_deadline:from_timeout(250),
|
||||
Context = woody_context:new(Id, #{}, Deadline),
|
||||
try
|
||||
@ -816,12 +849,12 @@ server_handled_client_timeout_test(_) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
call_deadline_timeout_test(_) ->
|
||||
call_deadline_timeout_test(C) ->
|
||||
Id = <<"call_deadline_timeout">>,
|
||||
{Url, Service} = get_service_endpoint('Weapons'),
|
||||
Gun = <<"Enforcer">>,
|
||||
Request = {Service, get_weapon, [Gun, self_to_bin()]},
|
||||
Opts = #{url => Url, event_handler => ?MODULE},
|
||||
Request = {Service, get_weapon, {Gun, self_to_bin()}},
|
||||
Opts = mk_client_opts(#{url => Url}, C),
|
||||
Deadline = woody_deadline:from_timeout(500),
|
||||
Context = woody_context:new(Id, #{<<"sleep">> => <<"3000">>}, Deadline),
|
||||
?assertError(
|
||||
@ -883,8 +916,8 @@ try_bad_handler_spec_test(_) ->
|
||||
calls_with_cache(_) ->
|
||||
Id = <<"call_with_cache">>,
|
||||
{_, Service} = get_service_endpoint('Weapons'),
|
||||
Request = {Service, get_weapon, [<<"Enforcer">>, self_to_bin()]},
|
||||
InvalidRequest = {Service, get_weapon, [<<"Bio Rifle">>, self_to_bin()]},
|
||||
Request = {Service, get_weapon, {<<"Enforcer">>, self_to_bin()}},
|
||||
InvalidRequest = {Service, get_weapon, {<<"Bio Rifle">>, self_to_bin()}},
|
||||
Opts = woody_caching_client_options(),
|
||||
Context = woody_context:new(Id),
|
||||
|
||||
@ -963,13 +996,13 @@ init(_) ->
|
||||
%%
|
||||
|
||||
%% Weapons
|
||||
handle_function(switch_weapon, [CurrentWeapon, Direction, Shift, To], Context, #{meta_test_id := AnnotTestId}) ->
|
||||
handle_function(switch_weapon, {CurrentWeapon, Direction, Shift, To}, Context, #{meta_test_id := AnnotTestId}) ->
|
||||
ok = send_msg(To, {woody_context:get_rpc_id(parent_id, Context), CurrentWeapon}),
|
||||
CheckAnnot = is_meta_check_required(AnnotTestId, woody_context:get_rpc_id(trace_id, Context)),
|
||||
ok = check_meta(CheckAnnot, Context, CurrentWeapon),
|
||||
switch_weapon(CurrentWeapon, Direction, Shift, Context, CheckAnnot);
|
||||
switch_weapon(CurrentWeapon, Direction, Shift, Context, CheckAnnot, []);
|
||||
|
||||
handle_function(get_weapon, [Name, To], Context, _Opts) ->
|
||||
handle_function(get_weapon, {Name, To}, Context, _Opts) ->
|
||||
ok = handle_sleep(Context),
|
||||
ok = send_msg(To, {woody_context:get_rpc_id(parent_id, Context), Name}),
|
||||
case genlib_map:get(Name, ?WEAPONS) of
|
||||
@ -980,15 +1013,19 @@ handle_function(get_weapon, [Name, To], Context, _Opts) ->
|
||||
end;
|
||||
|
||||
%% Powerups
|
||||
handle_function(get_powerup, [Name, To], Context, _Opts) ->
|
||||
handle_function(get_powerup, {Name, To}, Context, _Opts) ->
|
||||
ok = send_msg(To, {woody_context:get_rpc_id(parent_id, Context), Name}),
|
||||
{ok, return_powerup(Name)};
|
||||
|
||||
handle_function(ProxyGetPowerup, [Name, To], Context, _Opts) when
|
||||
handle_function(ProxyGetPowerup, {Name, To}, Context, _Opts) when
|
||||
ProxyGetPowerup =:= proxy_get_powerup orelse
|
||||
ProxyGetPowerup =:= bad_proxy_get_powerup
|
||||
->
|
||||
try call(Context, 'Powerups', get_powerup, [Name, self_to_bin()])
|
||||
% NOTE
|
||||
% Client may return `{exception, _}` tuple with some business level exception
|
||||
% here, yet handler expects us to `throw/1` them. This is expected here it
|
||||
% seems though.
|
||||
try call(Context, 'Powerups', get_powerup, {Name, self_to_bin()}, [])
|
||||
catch
|
||||
Class:Reason:Stacktrace ->
|
||||
erlang:raise(Class, Reason, Stacktrace)
|
||||
@ -997,7 +1034,7 @@ handle_function(ProxyGetPowerup, [Name, To], Context, _Opts) when
|
||||
ok = send_msg(To, {woody_context:get_rpc_id(parent_id, Context), Name})
|
||||
end;
|
||||
|
||||
handle_function(like_powerup, [Name, To], Context, _Opts) ->
|
||||
handle_function(like_powerup, {Name, To}, Context, _Opts) ->
|
||||
ok = send_msg(To, {woody_context:get_rpc_id(parent_id, Context), Name}),
|
||||
{ok, ok};
|
||||
|
||||
@ -1063,7 +1100,7 @@ log_event(Event, RpcId, Meta) ->
|
||||
%%
|
||||
init(Req, Code) ->
|
||||
ct:pal("Cowboy fail server received request. Replying: ~p", [Code]),
|
||||
{stop, cowboy_req:reply(Code, Req), Code}.
|
||||
{ok, cowboy_req:reply(Code, Req), Code}.
|
||||
|
||||
terminate(_, _, _) ->
|
||||
ok.
|
||||
@ -1071,10 +1108,10 @@ terminate(_, _, _) ->
|
||||
%%
|
||||
%% internal functions
|
||||
%%
|
||||
gun_test_basic(Id, Gun, Expect, WithMsg) ->
|
||||
gun_test_basic(Id, Gun, Expect, WithMsg, C) ->
|
||||
Context = make_context(Id),
|
||||
{Class, Reason} = get_except(Expect),
|
||||
try call(Context, 'Weapons', get_weapon, [Gun, self_to_bin()]) of
|
||||
try call(Context, 'Weapons', get_weapon, {Gun, self_to_bin()}, C) of
|
||||
Expect -> ok
|
||||
catch
|
||||
Class:Reason -> ok
|
||||
@ -1091,9 +1128,9 @@ get_except(Except = {_Class, _Reason}) ->
|
||||
make_context(ReqId) ->
|
||||
woody_context:new(ReqId).
|
||||
|
||||
call(Context, ServiceName, Function, Args) ->
|
||||
call(Context, ServiceName, Function, Args, C) ->
|
||||
{Url, Service} = get_service_endpoint(ServiceName),
|
||||
woody_client:call({Service, Function, Args}, #{url => Url, event_handler => ?MODULE}, Context).
|
||||
woody_client:call({Service, Function, Args}, mk_client_opts(#{url => Url}, C), Context).
|
||||
|
||||
get_service_endpoint('Weapons') ->
|
||||
{
|
||||
@ -1116,13 +1153,13 @@ check_msg(true, Msg, Context) ->
|
||||
check_msg(false, _, _) ->
|
||||
ok.
|
||||
|
||||
switch_weapon(CurrentWeapon, Direction, Shift, Context, CheckAnnot) ->
|
||||
switch_weapon(CurrentWeapon, Direction, Shift, Context, CheckAnnot, C) ->
|
||||
{NextWName, NextWPos} = next_weapon(CurrentWeapon, Direction, Shift),
|
||||
ContextAnnot = annotate_weapon(CheckAnnot, Context, NextWName, NextWPos),
|
||||
case call(ContextAnnot, 'Weapons', get_weapon, [NextWName, self_to_bin()]) of
|
||||
case call(ContextAnnot, 'Weapons', get_weapon, {NextWName, self_to_bin()}, C) of
|
||||
{exception, #'WeaponFailure'{code = <<"weapon_error">>, reason = <<"out of ammo">>}}
|
||||
->
|
||||
switch_weapon(CurrentWeapon, Direction, Shift + 1, Context, CheckAnnot);
|
||||
switch_weapon(CurrentWeapon, Direction, Shift + 1, Context, CheckAnnot, C);
|
||||
Ok ->
|
||||
Ok
|
||||
end.
|
||||
@ -1155,6 +1192,13 @@ return_powerup(P = #'Powerup'{}) ->
|
||||
return_powerup(P = ?BAD_POWERUP_REPLY) ->
|
||||
P.
|
||||
|
||||
mk_client_opts(TestCaseOpts, C) ->
|
||||
GroupOpts = proplists:get_value(client_opts, C, #{}),
|
||||
lists:foldl(fun maps:merge/2, GroupOpts, [
|
||||
#{event_handler => ?MODULE},
|
||||
TestCaseOpts
|
||||
]).
|
||||
|
||||
self_to_bin() ->
|
||||
genlib:to_binary(pid_to_list(self())).
|
||||
|
||||
|
@ -100,7 +100,7 @@ respects_max_connections(C) ->
|
||||
{ok, ServerPid} = start_woody_server(Handler, TransportOpts, ProtocolOpts, ReadBodyOpts, C),
|
||||
Results = genlib_pmap:map(
|
||||
fun (_) ->
|
||||
woody_client:call({Service, 'get_weapon', [<<"BFG">>, <<>>]}, Client)
|
||||
woody_client:call({Service, 'get_weapon', {<<"BFG">>, <<>>}}, Client)
|
||||
end,
|
||||
lists:seq(1, MaxConns * 10)
|
||||
),
|
||||
@ -176,13 +176,13 @@ stop_woody_server(Pid) ->
|
||||
true = exit(Pid, shutdown),
|
||||
ok.
|
||||
|
||||
handle_function(get_weapon, [Name, _], _Context, {respects_max_connections, Table}) ->
|
||||
handle_function(get_weapon, {Name, _}, _Context, {respects_max_connections, Table}) ->
|
||||
Slot = ets:update_counter(Table, slot, 1),
|
||||
ok = timer:sleep(rand:uniform(10)),
|
||||
_ = ets:update_counter(Table, slot, -1),
|
||||
{ok, #'Weapon'{name = Name, slot_pos = Slot}};
|
||||
|
||||
handle_function(get_powerup, [Name, _], _Context, _) ->
|
||||
handle_function(get_powerup, {Name, _}, _Context, _) ->
|
||||
ok = timer:sleep(2000),
|
||||
{ok, #'Powerup'{name = Name}}.
|
||||
|
||||
@ -191,7 +191,7 @@ handle_event(Event, RpcId, Meta, Opts) ->
|
||||
ct:pal("~p " ++ Format, [Opts] ++ Msg).
|
||||
|
||||
get_powerup(Client, Name, Arg) ->
|
||||
woody_client:call({{woody_test_thrift, 'Powerups'}, 'get_powerup', [Name, Arg]}, Client).
|
||||
woody_client:call({{woody_test_thrift, 'Powerups'}, 'get_powerup', {Name, Arg}}, Client).
|
||||
|
||||
%%
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user