Merge pull request #3 from arentrue/ft/MSPF-15

Ft/mspf 15
This commit is contained in:
Anton Belyaev 2016-05-10 14:21:41 +04:00
commit bf74d46150
21 changed files with 655 additions and 602 deletions

View File

@ -13,7 +13,10 @@ Erlang реализация [Библиотеки RPC вызовов для об
```erlang
1> EventHandler = my_event_handler. %% реализует woody_event_handler behaviour
2> Service = my_money_service. %% реализует thrift_service behaviour (генерируется из .thrift файла)
2> Service = {
2> my_money_thrift, %% имя модуля, сгенерированного из money.thrift файла
2> money %% имя thrift сервиса, заданное в money.thift
2> }.
3> ThriftHandler = my_money_thrift_service_handler. %% реализует woody_server_thrift_handler behaviour
4> Opts = [].
5> Handlers = [{"/v1/thrift_money_service",{Service, ThriftHandler, Opts}}].
@ -41,42 +44,42 @@ Erlang реализация [Библиотеки RPC вызовов для об
9> Function = give_me_money. %% thrift метод
10> Args = [100, <<"rub">>].
11> Request = {Service, Function, Args}.
12> Client = woody_client:new(<<"myUniqRequestID1">>, EventHandler).
13> {{ok, Result}, _NextClient} = woody_client:call(Client, Request, #{url => Url}).
12> Context = woody_client:new_context(<<"myUniqRequestID1">>, EventHandler).
13> {{ok, Result}, _NextContext} = woody_client:call(Context, Request, #{url => Url}).
```
В случае вызова _thrift_ `oneway` функции (_thrift_ реализация _cast_) `woody_client:call/3` вернет `{ok, NextClient}`.
В случае вызова _thrift_ `oneway` функции (_thrift_ реализация _cast_) `woody_client:call/3` вернет `{ok, NextContext}`.
Если сервер бросает `Exception`, описанный в _.thrift_ файле сервиса, `woody_client:call/3` бросит это же исключение в виде: `throw:{{exception, Exception}, NextClient}`, а в случае ошибки RPC вызова: `error:{Reason, NextClient}`.
Если сервер бросает `Exception`, описанный в _.thrift_ файле сервиса, `woody_client:call/3` бросит это же исключение в виде: `throw:{{exception, Exception}, NextContext}`, а в случае ошибки RPC вызова: `error:{Reason, NextContext}`.
`woody_client:call_safe/3` - аналогична `call/3`, но в случае исключений, не бросает их, а возвращает в виде tuple: `{{exception | error, Error}, NextClient}` либо `{{error, Error, Stacktace}, NextClient}`.
`woody_client:call_safe/3` - аналогична `call/3`, но в случае исключений, не бросает их, а возвращает в виде tuple: `{{exception | error, Error}, NextContext}` либо `{{error, Error, Stacktace}, NextContext}`.
```erlang
14> Args1 = [1000000, <<"usd">>].
15> Request1 = {Service, Function, Args1}.
16> Client1 = woody_client:new(<<"myUniqRequestID2">>, EventHandler).
17> {{exception, #take_it_easy{}}, _NextClient1} = woody_client:call_safe(Client1, Request1, #{url => Url}).
16> Context1 = woody_client:new_context(<<"myUniqRequestID2">>, EventHandler).
17> {{exception, #take_it_easy{}}, _NextContext1} = woody_client:call_safe(Context1, Request1, #{url => Url}).
```
`woody_client:call_async/5` позволяет сделать call асинхронно и обработать результат в callback функции. `woody_client:call_async/5` требует также _sup_ref()_ для включения процесса, обрабатывающего RPC вызов, в supervision tree приложения.
```erlang
18> Callback = fun({{ok, Res}, _NextClient2}) -> io:format("Rpc succeeded: ~p~n", [Res]);
18> ({{exception, Error}, _NextClient2}) -> io:format("Service exception: ~p~n", [Error]);
18> ({{error, _} _NextClient2}) -> io:format("Rpc failed")
18> ({{error, _, _} _NextClient2}) -> io:format("Rpc failed")
18> Callback = fun({{ok, Res}, _NextContext2}) -> io:format("Rpc succeeded: ~p~n", [Res]);
18> ({{exception, Error}, _NextContext2}) -> io:format("Service exception: ~p~n", [Error]);
18> ({{error, _} _NextContext2}) -> io:format("Rpc failed")
18> ({{error, _, _} _NextContext2}) -> io:format("Rpc failed")
18> end.
19> Client2 = woody_client:new(<<"myUniqRequestID3">>, EventHandler).
20> {ok, Pid, _NextClient2} = woody_client:call_async(SupRef, Callback, Client2, Request, #{url => Url}).
19> Context2 = woody_client:new_context(<<"myUniqRequestID3">>, EventHandler).
20> {ok, Pid, _NextContext2} = woody_client:call_async(SupRef, Callback, Context2, Request, #{url => Url}).
```
Можно создать пул соединений для thrift клиента: `woody_client_thrift:start_pool/2` и затем использовать его при работе с `woody_client`:
Можно создать пул соединений для thrift клиента (например, для установления _keep alive_ соединений с сервером): `woody_client_thrift:start_pool/2` и затем использовать его при работе с `woody_client`:
```erlang
21> Pool = my_client_pool.
22> ok = woody_client_thrift:start_pool(Pool, 10).
23> Client3 = woody_client:new(<<"myUniqRequestID3">>, EventHandler).
24> {{ok, Result}, _NextClient3} = woody_client:call(Client, Request, #{url => Url, pool => Pool}).
23> Context3 = woody_client:new_context(<<"myUniqRequestID3">>, EventHandler).
24> {{ok, Result}, _NextContext3} = woody_client:call(Context, Request, #{url => Url, pool => Pool}).
```
Закрыть пул можно с помошью `woody_client_thrift:stop_pool/1`.
@ -87,49 +90,53 @@ Erlang реализация [Библиотеки RPC вызовов для об
-module(my_money_thrift_service_handler).
-behaviour(woody_server_thrift_handler).
%% Auto-generated Thrift types for my_money_service
-include("my_money_types.hrl").
%% Auto-generated Thrift types from money.thrift
-include("my_money_thrift.hrl").
-export([handle_function/5, handle_error/5]).
-export([handle_function/4, handle_error/4]).
-spec handle_function(woody_t:func(), woody_server_thrift_handler:args(),
woody_t:rpc_id(), woody_client:client(), woody_server_thrift_handler:handler_opts())
woody_client:context(), woody_server_thrift_handler:handler_opts())
->
{ok, woody_server_thrift_handler:result()} | no_return().
handle_function(give_me_money, Sum = {Amount, Currency}, RpcId, Client, _Opts) ->
handle_function(give_me_money, Sum = {Amount, Currency}, Context, _Opts) ->
Wallet = <<"localhost:8022/v1/thrift_wallet_service">>,
RequestLimits = {my_wallet_service, check_limits, Sum},
%% Используется Client, полученный handle_function,
%% woody_client:new/2 вызывать не надо.
case woody_client:call_safe(Client, RequestLimits, #{url => Wallet}) of
{{ok, ok}, Client1} ->
%% RpcId можно получить из Context, полученного handle_function,
%% для использования при логировании.
RpcId = woody_client:get_rpc_id(Context),
%% Логи следует тэгировать RpcId, полученным handle_function.
%% Используется Context, полученный handle_function.
%% woody_client:new/2 вызывать не надо.
case woody_client:call_safe(Context, RequestLimits, #{url => Wallet}) of
{{ok, ok}, Context1} ->
%% Логи следует тэгировать RpcId.
lager:info("[~p] giving away ~p ~p",
[my_event_handler:format_id(RpcId), Amount, Currency]),
[RpcId, Amount, Currency]),
RequestMoney = {my_wallet_service, get_money, Sum},
%% Используется новое значение Client1, полученное из предыдущего вызова
%% Используется новое значение Context1, полученное из предыдущего вызова
%% woody_client:call_safe/3 (call/3, call_async/5).
{{ok, Money}, _Client2} = woody_client:call(Client1, RequestMoney,
{{ok, Money}, _Context2} = woody_client:call(Context1, RequestMoney,
#{url => Wallet}),
{ok, Money};
{{exception, #over_limits{}}, _Client1} ->
{{exception, #over_limits{}}, _Context1} ->
lager:info("[~p] ~p ~p is too much",
[my_event_handler:format_id(RpcId), Amount, Currency]),
[RpcId, Amount, Currency]),
throw(#take_it_easy{})
end.
-spec handle_error(woody_t:func(), woody_server_thrift_handler:error_reason(),
woody_t:rpc_id(), woody_client:client(), woody_server_thrift_handler:handler_opts())
woody_t:rpc_id(), woody_server_thrift_handler:handler_opts())
-> _.
handle_error(give_me_money, Error, RpcId, _Client, _Opts) ->
handle_error(give_me_money, Error, Context, _Opts) ->
lager:info("[~p] got error from thrift: ~p",
[my_event_handler:format_id(RpcId), Error]).
[woody_client:get_rpc_id(Context), Error]).
```
Показанное в этом наивном примере реализации `my_money_service` использование `Client` и `RpcId` необходимо для корректного логирования _RPC ID_ библиотекой, которое позволяет построить полное дерево RPC вызовов между микросервисами в рамках обработки бизнес сценария.
Показанное в этом наивном примере реализации сервиса `my_money` использование `Context` и `RpcId` необходимо для корректного логирования _RPC ID_ библиотекой, которое позволяет построить полное дерево RPC вызовов между микросервисами в рамках обработки бизнес сценария.
### Woody Event Handler
@ -139,12 +146,13 @@ handle_error(give_me_money, Error, RpcId, _Client, _Opts) ->
-module(my_event_handler).
-behaviour(woody_event_handler).
-export([handle_event/2]).
-export([handle_event/3]).
-spec handle_event(
woody_event_handler:event_type(),
woody_t:rpc_id(),
woody_event_handler:event_meta_type()
) -> _.
handle_event(Event, Meta) ->
lager:info("woody event ~p: ~p", [Event, Meta]).
handle_event(Event, RpcId, Meta) ->
lager:info("[~p] woody event ~p: ~p", [RpcId, Event, Meta]).
```

View File

@ -17,5 +17,5 @@
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.0">>},1},
{<<"thrift">>,
{git,"https://github.com/rbkmoney/thrift_erlang.git",
{ref,"88ded69c8aa9fef4a75c10e2e89061d744bac7b7"}},
{ref,"4950a4cbb2d79f400a54664cf58e843fd8efcd59"}},
0}].

View File

@ -11,7 +11,8 @@
thrift
]},
{env, [
{acceptors_pool_size, 100}
{acceptors_pool_size, 100},
{enable_debug, false}
]},
{modules, []},
{maintainers, [

View File

@ -8,15 +8,16 @@
-include("woody_defs.hrl").
%% API
-export([new/2]).
-export([make_child_client/2]).
-export([next/1]).
-export([new_context/2]).
-export([get_rpc_id/1]).
-export([make_child_context/2]).
-export([call/3]).
-export([call_safe/3]).
-export([call_async/5]).
-export_type([client/0, options/0, result_ok/0, result_error/0]).
-export_type([context/0, options/0, result_ok/0, result_error/0]).
-define(ROOT_REQ_PARENT_ID, <<"undefined">>).
@ -27,30 +28,31 @@
-export([init/1]).
%% behaviour definition
-callback call(client(), request(), options()) -> result_ok() | no_return().
-callback call(context(), request(), options()) -> result_ok() | no_return().
%%
%% API
%%
-type client() :: #{ %% all elements are mandatory
-type context() :: #{ %% all elements are mandatory
root_rpc => boolean(),
span_id => woody_t:req_id() | undefined,
trace_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
event_handler => woody_t:handler(),
seq => non_neg_integer()
seq => non_neg_integer(),
rpc_id => woody_t:rpc_id() | undefined
}.
-type class() :: throw | error | exit.
-type stacktrace() :: list().
-type result_ok() :: {ok | {ok, _Response}, client()}.
-type result_ok() :: {ok | {ok, _Response}, context()}.
-type result_error() ::
{{exception , woody_client_thrift:except_thrift()} , client()} |
{{error , woody_client_thrift:error_protocol()} , client()} |
{{error , woody_client_thrift_http_transport:error()} , client()} |
{{error , {class(), _Reason, stacktrace()}} , client()}.
{{exception , woody_client_thrift:except_thrift()} , context()} |
{{error , woody_client_thrift:error_protocol()} , context()} |
{{error , woody_client_thrift_http_transport:error()} , context()} |
{{error , {class(), _Reason, stacktrace()}} , context()}.
-type request() :: any().
@ -62,61 +64,62 @@
-type callback() :: fun((result_ok() | result_error()) -> _).
-spec new(woody_t:req_id(), woody_t:handler()) -> client().
new(ReqId, EventHandler) ->
-spec new_context(woody_t:req_id(), woody_t:handler()) -> context().
new_context(ReqId, EventHandler) ->
#{
root_rpc => true,
span_id => ReqId,
trace_id => ReqId,
parent_id => ?ROOT_REQ_PARENT_ID,
seq => 0,
event_handler => EventHandler
event_handler => EventHandler,
rpc_id => undefined
}.
-spec make_child_client(woody_t:rpc_id(), woody_t:handler()) -> client().
make_child_client(#{span_id := ReqId, trace_id := TraceId}, EventHandler) ->
-spec make_child_context(woody_t:rpc_id(), woody_t:handler()) -> context().
make_child_context(RpcId = #{span_id := ReqId, trace_id := TraceId}, EventHandler) ->
#{
root_rpc => false,
span_id => undefined,
trace_id => TraceId,
parent_id => ReqId,
seq => 0,
event_handler => EventHandler
event_handler => EventHandler,
rpc_id => RpcId
}.
-spec next(client()) -> client().
next(Client = #{root_rpc := true}) ->
Client;
next(Client = #{root_rpc := false, seq := Seq}) ->
NextSeq = Seq +1,
Client#{span_id => make_req_id(NextSeq), seq => NextSeq}.
-spec get_rpc_id(context()) -> woody_t:rpc_id() | undefined | no_return().
get_rpc_id(#{rpc_id := RpcId}) ->
RpcId;
get_rpc_id(_) ->
error(badarg).
-spec call(client(), request(), options()) -> result_ok() | no_return().
call(Client, Request, Options) ->
-spec call(context(), request(), options()) -> result_ok() | no_return().
call(Context, Request, Options) ->
ProtocolHandler = woody_t:get_protocol_handler(client, Options),
ProtocolHandler:call(next(Client), Request, Options).
ProtocolHandler:call(next(Context), Request, Options).
-spec call_safe(client(), request(), options()) -> result_ok() | result_error().
call_safe(Client, Request, Options) ->
try call(Client, Request, Options)
-spec call_safe(context(), request(), options()) -> result_ok() | result_error().
call_safe(Context, Request, Options) ->
try call(Context, Request, Options)
catch
%% valid thrift exception
throw:{Except = ?except_thrift(_), Client1} ->
{Except, Client1};
throw:{Except = ?except_thrift(_), Context1} ->
{Except, Context1};
%% rpc send failed
error:{TError = ?error_transport(_), Client1} ->
{{error, TError}, Client1};
error:{TError = ?error_transport(_), Context1} ->
{{error, TError}, Context1};
%% thrift protocol error
error:{PError = ?error_protocol(_), Client1} ->
{{error, PError}, Client1};
error:{PError = ?error_protocol(_), Context1} ->
{{error, PError}, Context1};
%% what else could have happened?
Class:Reason ->
{{error, {Class, Reason, erlang:get_stacktrace()}}, Client}
{{error, {Class, Reason, erlang:get_stacktrace()}}, Context}
end.
-spec call_async(woody_t:sup_ref(), callback(), client(), request(), options()) ->
{ok, pid(), client()} | {error, _}.
call_async(Sup, Callback, Client, Request, Options) ->
-spec call_async(woody_t:sup_ref(), callback(), context(), request(), options()) ->
{ok, pid(), context()} | {error, _}.
call_async(Sup, Callback, Context, Request, Options) ->
_ = woody_t:get_protocol_handler(client, Options),
SupervisorSpec = #{
id => {?MODULE, woody_clients_sup},
@ -131,19 +134,19 @@ call_async(Sup, Callback, Client, Request, Options) ->
{error, {already_started, Pid}} -> Pid
end,
supervisor:start_child(ClientSup,
[Callback, Client, Request, Options]).
[Callback, Context, Request, Options]).
%%
%% Internal API
%%
-spec init_call_async(callback(), client(), request(), options()) -> {ok, pid(), client()}.
init_call_async(Callback, Client, Request, Options) ->
proc_lib:start_link(?MODULE, do_call_async, [Callback, Client, Request, Options]).
-spec init_call_async(callback(), context(), request(), options()) -> {ok, pid(), context()}.
init_call_async(Callback, Context, Request, Options) ->
proc_lib:start_link(?MODULE, do_call_async, [Callback, Context, Request, Options]).
-spec do_call_async(callback(), client(), request(), options()) -> _.
do_call_async(Callback, Client, Request, Options) ->
proc_lib:init_ack({ok, self(), next(Client)}),
Callback(call_safe(Client, Request, Options)).
-spec do_call_async(callback(), context(), request(), options()) -> _.
do_call_async(Callback, Context, Request, Options) ->
proc_lib:init_ack({ok, self(), next(Context)}),
Callback(call_safe(Context, Request, Options)).
%%
%% Supervisor callbacks
@ -170,6 +173,13 @@ init(woody_client_sup) ->
%%
%% Internal functions
%%
-spec next(context()) -> context().
next(Context = #{root_rpc := true}) ->
Context;
next(Context = #{root_rpc := false, seq := Seq}) ->
NextSeq = Seq +1,
Context#{span_id => make_req_id(NextSeq), seq => NextSeq}.
-spec make_req_id(non_neg_integer()) -> woody_t:req_id().
make_req_id(Seq) ->
BinSeq = genlib:to_binary(Seq),

View File

@ -20,7 +20,7 @@
-export_type([except_thrift/0, error_protocol/0]).
-define(log_rpc_result(EventHandler, RpcId, Status, Result),
woody_event_handler:handle_event(EventHandler, ?EV_SERVICE_RESULT, RpcId#{
woody_event_handler:handle_event(EventHandler, ?EV_SERVICE_RESULT, RpcId, #{
status => Status, result => Result
})
).
@ -38,14 +38,14 @@ start_pool(Name, PoolSize) when is_integer(PoolSize) ->
stop_pool(Name) ->
woody_client_thrift_http_transport:stop_client_pool(Name).
-spec call(woody_client:client(), request(), woody_client:options()) ->
-spec call(woody_client:context(), request(), woody_client:options()) ->
woody_client:result_ok() | no_return().
call(Client = #{event_handler := EventHandler},
{Service, Function, Args}, TransportOpts)
call(Context = #{event_handler := EventHandler},
{Service = {_, ServiceName}, Function, Args}, TransportOpts)
->
RpcId = maps:with([span_id, trace_id, parent_id], Client),
woody_event_handler:handle_event(EventHandler, ?EV_CALL_SERVICE, RpcId#{
service => Service,
RpcId = maps:with([span_id, trace_id, parent_id], Context),
woody_event_handler:handle_event(EventHandler, ?EV_CALL_SERVICE, RpcId, #{
service => ServiceName,
function => Function,
type => get_rpc_type(Service, Function),
args => Args
@ -54,15 +54,15 @@ call(Client = #{event_handler := EventHandler},
do_call(
make_thrift_client(RpcId, Service, TransportOpts, EventHandler),
Function, Args
), RpcId, Client
), RpcId, Context
).
%%
%% Internal functions
%%
get_rpc_type(Service, Function) ->
try get_rpc_type(Service:function_info(Function, reply_type))
get_rpc_type({Module, Service}, Function) ->
try get_rpc_type(Module:function_info(Service, Function, reply_type))
catch
error:_ ->
error({badarg, {Service,Function}})
@ -91,40 +91,40 @@ do_call(Client, Function, Args) ->
format_return({ok, ok}, RpcId,
Client = #{event_handler := EventHandler})
Context = #{event_handler := EventHandler})
->
?log_rpc_result(EventHandler, RpcId, ok, ?thrift_cast),
{ok, Client};
{ok, Context};
format_return({ok, Result}, RpcId,
Client = #{event_handler := EventHandler})
Context = #{event_handler := EventHandler})
->
?log_rpc_result(EventHandler, RpcId, ok, Result),
{{ok, Result}, Client};
{{ok, Result}, Context};
%% In case a server violates the requirements and sends
%% #TAppiacationException{} with http status code 200.
format_return({exception, Result = #'TApplicationException'{}}, RpcId,
Client = #{event_handler := EventHandler})
Context = #{event_handler := EventHandler})
->
?log_rpc_result(EventHandler, RpcId, error, Result),
error({?error_transport(server_error), Client});
error({?error_transport(server_error), Context});
%% Service threw valid thrift exception
format_return(Exception = ?except_thrift(_), RpcId,
Client = #{event_handler := EventHandler})
Context = #{event_handler := EventHandler})
->
?log_rpc_result(EventHandler, RpcId, ok, Exception),
throw({Exception, Client});
throw({Exception, Context});
format_return({error, Error = ?error_transport(_)}, RpcId,
Client = #{event_handler := EventHandler})
Context = #{event_handler := EventHandler})
->
?log_rpc_result(EventHandler, RpcId, error, Error),
error({Error, Client});
error({Error, Context});
format_return({error, Error}, RpcId,
Client = #{event_handler := EventHandler})
Context = #{event_handler := EventHandler})
->
?log_rpc_result(EventHandler, RpcId, error, Error),
error({?error_protocol(Error), Client}).
error({?error_protocol(Error), Context}).

View File

@ -43,9 +43,9 @@
{error, ?error_transport(Error)}
).
-define(log_response(EventHandler, Status, Meta),
-define(log_response(EventHandler, Status, RpcId, Meta),
woody_event_handler:handle_event(EventHandler, ?EV_CLIENT_RECEIVE,
Meta#{status =>Status})
RpcId, #{status =>Status})
).
-type woody_transport() :: #{
@ -133,7 +133,7 @@ flush(Transport = #{
],
RpcId = maps:with([span_id, trace_id, parent_id], Transport),
woody_event_handler:handle_event(EventHandler, ?EV_CLIENT_SEND,
RpcId#{url => Url}),
RpcId, #{url => Url}),
case send(Url, Headers, WBuffer, Options, RpcId, EventHandler) of
{ok, Response} ->
{Transport#{
@ -156,15 +156,15 @@ send(Url, Headers, WBuffer, Options, RpcId, EventHandler) ->
case hackney:request(post, Url, Headers, WBuffer, maps:to_list(Options)) of
{ok, ResponseCode, _ResponseHeaders, Ref} ->
?log_response(EventHandler, get_response_status(ResponseCode),
RpcId#{code => ResponseCode}),
RpcId, #{code => ResponseCode}),
handle_response(ResponseCode, hackney:body(Ref));
{error, {closed, _}} ->
?log_response(EventHandler, error,
RpcId#{reason => partial_response}),
RpcId, #{reason => partial_response}),
?format_error(partial_response);
{error, Reason} ->
?log_response(EventHandler, error,
RpcId#{reason => Reason}),
RpcId, #{reason => Reason}),
?format_error(Reason)
end.

View File

@ -30,4 +30,5 @@
-define(EV_THRIFT_ERROR , 'thrift error').
-define(EV_INTERNAL_ERROR , 'internal error').
-define(EV_DEBUG , 'trace_event').
-endif.

View File

@ -1,7 +1,7 @@
-module(woody_event_handler).
%% API
-export([handle_event/3]).
-export([handle_event/4]).
-include("woody_defs.hrl").
@ -10,33 +10,34 @@
%%
-export_type([event_type/0, event_meta_type/0, meta_client_send/0, meta_client_receive/0,
meta_server_receive/0, meta_server_send/0, meta_invoke_service_handler/0,
meta_service_handler_result/0, meta_thrift_error/0, meta_internal_error/0
meta_service_handler_result/0, meta_thrift_error/0, meta_internal_error/0,
meta_debug/0
]).
-callback handle_event
%% mandatory
(?EV_CALL_SERVICE , meta_call_service ()) -> _;
(?EV_SERVICE_RESULT , meta_service_result ()) -> _;
(?EV_CLIENT_SEND , meta_client_send ()) -> _;
(?EV_CLIENT_RECEIVE , meta_client_receive ()) -> _;
(?EV_SERVER_RECEIVE , meta_server_receive ()) -> _;
(?EV_SERVER_SEND , meta_server_send ()) -> _;
(?EV_INVOKE_SERVICE_HANDLER , meta_invoke_service_handler ()) -> _;
(?EV_SERVICE_HANDLER_RESULT , meta_service_handler_result ()) -> _;
(?EV_CALL_SERVICE , woody_t:rpc_id(), meta_call_service ()) -> _;
(?EV_SERVICE_RESULT , woody_t:rpc_id(), meta_service_result ()) -> _;
(?EV_CLIENT_SEND , woody_t:rpc_id(), meta_client_send ()) -> _;
(?EV_CLIENT_RECEIVE , woody_t:rpc_id(), meta_client_receive ()) -> _;
(?EV_SERVER_RECEIVE , woody_t:rpc_id(), meta_server_receive ()) -> _;
(?EV_SERVER_SEND , woody_t:rpc_id(), meta_server_send ()) -> _;
(?EV_INVOKE_SERVICE_HANDLER , woody_t:rpc_id(), meta_invoke_service_handler ()) -> _;
(?EV_SERVICE_HANDLER_RESULT , woody_t:rpc_id(), meta_service_handler_result ()) -> _;
%% optional
(?EV_THRIFT_ERROR , meta_thrift_error ()) -> _;
(?EV_INTERNAL_ERROR , meta_internal_error ()) -> _.
(?EV_THRIFT_ERROR , woody_t:rpc_id(), meta_thrift_error ()) -> _;
(?EV_INTERNAL_ERROR , woody_t:rpc_id(), meta_internal_error ()) -> _;
(?EV_DEBUG , woody_t:rpc_id()|undefined, meta_debug()) -> _.
-type event_type() :: ?EV_CALL_SERVICE | ?EV_SERVICE_RESULT | ?EV_CLIENT_SEND | ?EV_CLIENT_RECEIVE |
?EV_SERVER_RECEIVE | ?EV_SERVER_SEND | ?EV_INVOKE_SERVICE_HANDLER | ?EV_SERVICE_HANDLER_RESULT |
?EV_THRIFT_ERROR | ?EV_INTERNAL_ERROR.
?EV_THRIFT_ERROR | ?EV_INTERNAL_ERROR | ?EV_DEBUG.
-type event_meta_type() :: meta_client_send() | meta_client_receive() |
meta_server_receive() | meta_server_send() | meta_invoke_service_handler() |
meta_service_handler_result() | meta_thrift_error() | meta_internal_error().
-type service() :: woody_t:service().
-type service() :: woody_t:service_name().
-type rpc_type() :: call | cast.
-type status() :: ok | error.
-type thrift_stage() :: protocol_read | protocol_write.
@ -44,9 +45,6 @@
-type meta_call_service() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
service => service(),
function => woody_t:func(),
type => rpc_type(),
@ -55,9 +53,6 @@
}.
-type meta_service_result() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
status => status(),
%% optional
result => any()
@ -65,17 +60,11 @@
-type meta_client_send() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
url => woody_t:url()
}.
-type meta_client_receive() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
status => status(),
%% optional
code => any(),
@ -84,9 +73,6 @@
-type meta_server_receive() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
url => woody_t:url(),
status => status(),
%% optional
@ -95,9 +81,6 @@
-type meta_server_send() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
status => status(),
%% optional
code => pos_integer()
@ -105,9 +88,6 @@
-type meta_invoke_service_handler() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
service => service(),
function => woody_t:func(),
%% optional
@ -117,9 +97,6 @@
-type meta_service_handler_result() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
status => status(),
%% optional
result => any(),
@ -131,40 +108,39 @@
-type meta_thrift_error() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
stage => thrift_stage(),
reason => any()
}.
-type meta_internal_error() :: #{
%% mandatory
span_id => woody_t:req_id(),
parent_id => woody_t:req_id(),
trace_id => woody_t:req_id(),
error => any(),
reason => any(),
%% optional
stack => any()
}.
-type meta_debug() :: #{
%% mandatory
event => atom() | binary()
}.
%%
%% API
%%
-spec handle_event
%% mandatory
(woody_t:handler() , ?EV_CALL_SERVICE , meta_call_service ()) -> _;
(woody_t:handler() , ?EV_SERVICE_RESULT , meta_service_result ()) -> _;
(woody_t:handler() , ?EV_CLIENT_SEND , meta_client_send ()) -> _;
(woody_t:handler() , ?EV_CLIENT_RECEIVE , meta_client_receive ()) -> _;
(woody_t:handler() , ?EV_SERVER_RECEIVE , meta_server_receive ()) -> _;
(woody_t:handler() , ?EV_SERVER_SEND , meta_server_send ()) -> _;
(woody_t:handler() , ?EV_INVOKE_SERVICE_HANDLER , meta_invoke_service_handler()) -> _;
(woody_t:handler() , ?EV_SERVICE_HANDLER_RESULT , meta_service_handler_result()) -> _;
(woody_t:handler() , ?EV_CALL_SERVICE , woody_t:rpc_id(), meta_call_service ()) -> _;
(woody_t:handler() , ?EV_SERVICE_RESULT , woody_t:rpc_id(), meta_service_result ()) -> _;
(woody_t:handler() , ?EV_CLIENT_SEND , woody_t:rpc_id(), meta_client_send ()) -> _;
(woody_t:handler() , ?EV_CLIENT_RECEIVE , woody_t:rpc_id(), meta_client_receive ()) -> _;
(woody_t:handler() , ?EV_SERVER_RECEIVE , woody_t:rpc_id(), meta_server_receive ()) -> _;
(woody_t:handler() , ?EV_SERVER_SEND , woody_t:rpc_id(), meta_server_send ()) -> _;
(woody_t:handler() , ?EV_INVOKE_SERVICE_HANDLER , woody_t:rpc_id(), meta_invoke_service_handler()) -> _;
(woody_t:handler() , ?EV_SERVICE_HANDLER_RESULT , woody_t:rpc_id(), meta_service_handler_result()) -> _;
%% optional
(woody_t:handler() , ?EV_THRIFT_ERROR , meta_thrift_error ()) -> _;
(woody_t:handler() , ?EV_INTERNAL_ERROR , meta_internal_error ()) -> _.
handle_event(Handler, Type, Meta) ->
Handler:handle_event(Type, Meta).
(woody_t:handler() , ?EV_THRIFT_ERROR , woody_t:rpc_id(), meta_thrift_error ()) -> _;
(woody_t:handler() , ?EV_INTERNAL_ERROR , woody_t:rpc_id(), meta_internal_error ()) -> _;
(woody_t:handler() , ?EV_DEBUG , woody_t:rpc_id()|undefined, meta_debug()) -> _.
handle_event(Handler, Type, RpcId, Meta) ->
Handler:handle_event(Type, RpcId, Meta).

View File

@ -3,14 +3,9 @@
-module(woody_server).
-behaviour(supervisor).
%% API
-export([child_spec/2]).
%% supervisor callbacks
-export([init/1]).
%%
%% behaviour definition
%%
@ -27,24 +22,4 @@
-spec child_spec(_Id, options()) -> supervisor:child_spec().
child_spec(Id, Options) ->
ProtocolHandler = woody_t:get_protocol_handler(server, Options),
ServerSpec = ProtocolHandler:child_spec(Id, Options),
#{
id => Id,
start => {supervisor, start_link, [?MODULE, {woody_server, ServerSpec}]},
restart => permanent,
shutdown => infinity,
type => supervisor,
modules => [?MODULE]
}.
%%
%% Supervisor callbacks
%%
-spec init({woody_server, supervisor:child_spec()}) -> {ok, {#{}, [#{}, ...]}}.
init({woody_server, ChildSpec}) ->
{ok, {#{
strategy => one_for_one,
intensity => 10,
period => 10},
[ChildSpec]}
}.
ProtocolHandler:child_spec(Id, Options).

View File

@ -1,7 +1,7 @@
-module(woody_server_thrift_handler).
%% API
-export([start/6]).
-export([start/5]).
-include_lib("thrift/include/thrift_constants.hrl").
-include_lib("thrift/include/thrift_protocol.hrl").
@ -16,21 +16,21 @@
-type args() :: tuple().
-export_type([handler_opts/0, args/0, result/0, error_reason/0]).
-callback handle_function(woody_t:func(), args(), woody_t:rpc_id(),
woody_client:client(), handler_opts())
-callback handle_function(woody_t:func(), args(),
woody_client:context(), handler_opts())
->
ok | {ok, result()} | {error, result()} | no_return().
-callback handle_error(woody_t:func(), error_reason(), woody_t:rpc_id(),
woody_client:client(), handler_opts())
-callback handle_error(woody_t:func(), error_reason(),
woody_client:context(), handler_opts())
-> _.
%%
%% API
%%
-define(log_rpc_result(EventHandler, Status, Meta),
-define(log_rpc_result(EventHandler, Status, RpcId, Meta),
woody_event_handler:handle_event(EventHandler, ?EV_SERVICE_HANDLER_RESULT,
Meta#{status => Status})
RpcId, Meta#{status => Status})
).
-define(stage_read , protocol_read).
@ -41,9 +41,8 @@
-define(error_protocol_send , send_error).
-record(state, {
rpc_id :: woody_t:rpc_id(),
woody_client :: woody_client:client(),
service :: module(),
context :: woody_client:context(),
service :: woody_t:service(),
handler :: woody_t:handler(),
handler_opts :: handler_opts(),
protocol :: any(),
@ -58,19 +57,18 @@
-type event_handler() :: woody_t:handler().
-type transport_handler() :: woody_t:handler().
-spec start(thrift_transport:t_transport(), woody_t:rpc_id(), woody_client:client(),
-spec start(thrift_transport:t_transport(), woody_client:context(),
thrift_handler(), event_handler(), transport_handler())
->
ok | noreply | {error, _Reason}.
start(Transport, RpcId, WoodyClient, {Service, Handler, Opts},
{ok | noreply | {error, _Reason}, cowboy_req:req()}.
start(Transport, Context, {Service, Handler, Opts},
EventHandler, TransportHandler)
->
{ok, Protocol} = thrift_binary_protocol:new(Transport,
[{strict_read, true}, {strict_write, true}]
),
{Result, Protocol1} = process(#state{
rpc_id = RpcId,
woody_client = WoodyClient,
context = Context,
service = Service,
handler = Handler,
handler_opts = Opts,
@ -79,8 +77,8 @@ start(Transport, RpcId, WoodyClient, {Service, Handler, Opts},
event_handler = EventHandler,
transport_handler = TransportHandler
}),
thrift_protocol:close_transport(Protocol1),
Result.
{_, Req} = thrift_protocol:close_transport(Protocol1),
{Result, Req}.
%%
@ -123,7 +121,7 @@ get_function_name(Function) ->
end.
get_params_type(Service, Function) ->
try Service:function_info(Function, params_type)
try get_function_info(Service, Function, params_type)
catch
error:badarg -> ?error_unknown_function
end.
@ -154,17 +152,18 @@ try_call_handler(Function, Args, State, SeqId) ->
end.
call_handler(Function,Args, #state{
rpc_id = RpcId,
woody_client = WoodyClient,
context = Context,
handler = Handler,
handler_opts = Opts,
service = {_, ServiceName},
event_handler = EventHandler})
->
woody_event_handler:handle_event(EventHandler, ?EV_INVOKE_SERVICE_HANDLER, RpcId#{
function => Function, args => Args, options => Opts
RpcId = woody_client:get_rpc_id(Context),
woody_event_handler:handle_event(EventHandler, ?EV_INVOKE_SERVICE_HANDLER, RpcId, #{
service => ServiceName, function => Function, args => Args, options => Opts
}),
Result = Handler:handle_function(Function, Args, RpcId, WoodyClient, Opts),
?log_rpc_result(EventHandler, ok, RpcId#{result => Result}),
Result = Handler:handle_function(Function, Args, Context, Opts),
?log_rpc_result(EventHandler, ok, RpcId, #{result => Result}),
Result.
handle_result(ok, State, Function, SeqId) ->
@ -175,40 +174,41 @@ handle_result({error, Error}, State, Function, SeqId) ->
handle_error(State, Function, Error, SeqId).
handle_success(State = #state{service = Service}, Function, Result, SeqId) ->
ReplyType = Service:function_info(Function, reply_type),
ReplyType = get_function_info(Service, Function, reply_type),
StructName = atom_to_list(Function) ++ "_result",
case Result of
ok when ReplyType == {struct, []} ->
ok when ReplyType == {struct, struct, []} ->
send_reply(State, Function, ?tMessageType_REPLY,
{ReplyType, {StructName}}, SeqId);
ok when ReplyType == oneway_void ->
{State, noreply};
ReplyData ->
Reply = {
{struct, [{0, undefined, ReplyType, undefined, undefined}]},
{struct, struct, [{0, undefined, ReplyType, undefined, undefined}]},
{StructName, ReplyData}
},
send_reply(State, Function, ?tMessageType_REPLY, Reply, SeqId)
end.
handle_function_catch(State = #state{
rpc_id = RpcId,
context = Context,
service = Service,
event_handler = EventHandler
}, Function, Class, Reason, Stack, SeqId)
->
ReplyType = Service:function_info(Function, reply_type),
RpcId = woody_client:get_rpc_id(Context),
ReplyType = get_function_info(Service, Function, reply_type),
case {Class, Reason} of
_Error when ReplyType =:= oneway_void ->
?log_rpc_result(EventHandler, error,
RpcId#{class => Class, reason => Reason, ignore => true}),
?log_rpc_result(EventHandler, error, RpcId,
#{class => Class, reason => Reason, ignore => true}),
{State, noreply};
{throw, Exception} when is_tuple(Exception), size(Exception) > 0 ->
?log_rpc_result(EventHandler, error,
RpcId#{class => throw, reason => Exception, ignore => false}),
?log_rpc_result(EventHandler, error, RpcId,
#{class => throw, reason => Exception, ignore => false}),
handle_exception(State, Function, Exception, SeqId);
{error, Reason} ->
?log_rpc_result(EventHandler, error, RpcId#{class => error,
?log_rpc_result(EventHandler, error, RpcId, #{class => error,
reason => Reason, stack => Stack, ignore => false}),
Reason1 = if is_tuple(Reason) -> element(1, Reason); true -> Reason end,
handle_error(State, Function, Reason1, SeqId)
@ -217,7 +217,7 @@ handle_function_catch(State = #state{
handle_exception(State = #state{service = Service, transport_handler = Trans},
Function, Exception, SeqId)
->
{struct, XInfo} = ReplySpec = Service:function_info(Function, exceptions),
{struct, _, XInfo} = ReplySpec = get_function_info(Service, Function, exceptions),
{ExceptionList, FoundExcept} = lists:mapfoldl(
fun(X, A) -> get_except(Exception, X, A) end, undefined, XInfo),
ExceptionTuple = list_to_tuple([Function | ExceptionList]),
@ -230,7 +230,7 @@ handle_exception(State = #state{service = Service, transport_handler = Trans},
{ReplySpec, ExceptionTuple}, SeqId)
end.
get_except(Exception, {_Fid, _, {struct, {Module, Type}}, _, _}, _) when
get_except(Exception, {_Fid, _, {struct, exception, {Module, Type}}, _, _}, _) when
element(1, Exception) =:= Type
->
{Exception, {Module, Type}};
@ -238,7 +238,7 @@ get_except(_, _, TypesModule) ->
{undefined, TypesModule}.
get_except_name(Module, Type) ->
{struct, Fields} = Module:struct_info(Type),
{struct, exception, Fields} = Module:struct_info(Type),
case lists:keyfind(exception_name, 4, Fields) of
false -> Type;
Field -> element(5, Field)
@ -282,27 +282,29 @@ prepare_response({State, {error, Reason}}, FunctionName) ->
{handle_protocol_error(State, FunctionName, Reason), State#state.protocol}.
handle_protocol_error(State = #state{
rpc_id = RpcId,
context = Context,
protocol_stage = Stage,
transport_handler = Trans,
event_handler = EventHandler}, Function, Reason)
->
RpcId = woody_client:get_rpc_id(Context),
call_error_handler(State, Function, Reason),
woody_event_handler:handle_event(EventHandler, ?EV_THRIFT_ERROR,
RpcId#{stage => Stage, reason => Reason}),
RpcId, #{stage => Stage, reason => Reason}),
format_protocol_error(Reason, Trans).
call_error_handler(#state{
rpc_id = RpcId,
woody_client = WoodyClient,
context = Context,
handler = Handler,
handler_opts = Opts,
event_handler = EventHandler}, Function, Reason) ->
event_handler = EventHandler}, Function, Reason)
->
RpcId = woody_client:get_rpc_id(Context),
try
Handler:handle_error(Function, Reason, RpcId, WoodyClient, Opts)
Handler:handle_error(Function, Reason, Context, Opts)
catch
Class:Error ->
woody_event_handler:handle_event(EventHandler, ?EV_INTERNAL_ERROR, RpcId#{
woody_event_handler:handle_event(EventHandler, ?EV_INTERNAL_ERROR, RpcId, #{
error => <<"service error handler failed">>,
class => Class,
reason => Error,
@ -329,6 +331,9 @@ format_protocol_error(_Reason, Trans) ->
mark_error_to_transport(Trans, transport, "bad request"),
{error, bad_request}.
get_function_info({Module, Service}, Function, Info) ->
Module:function_info(Service, Function, Info).
%% Unfortunately there is no proper way to provide additional info to
%% the transport, where the actual send happens: the Protocol object
%% representing thrift protocol and transport in this module is opaque.

View File

@ -42,8 +42,8 @@
-define(THRIFT_ERROR_KEY, {?MODULE, thrift_error}).
-define(log_event(EventHandler, Event, Status, Meta),
woody_event_handler:handle_event(EventHandler, Event, Meta#{
-define(log_event(EventHandler, Event, Status, RpcId, Meta),
woody_event_handler:handle_event(EventHandler, Event, RpcId, Meta#{
status => Status
})
).
@ -67,7 +67,7 @@ child_spec(Id, #{
port := Port,
net_opts := NetOpts
}) ->
_ = check_callback(handle_event, 2, EventHandler),
_ = check_callback(handle_event, 3, EventHandler),
AcceptorsPool = genlib_app:env(woody, acceptors_pool_size,
?DEFAULT_ACCEPTORS_POOLSIZE
),
@ -83,7 +83,7 @@ check_callback(Callback, Module) ->
proplists:get_value(Callback, Module:module_info(exports)).
validate_handler(Handler) when is_atom(Handler) ->
[check_callback(F, 5, Handler) || F <- [handle_function, handle_error]],
[check_callback(F, 4, Handler) || F <- [handle_function, handle_error]],
Handler.
get_socket_transport(Ip, Port, Options) ->
@ -106,11 +106,36 @@ get_cowboy_config(Handlers, EventHandler) ->
} || {PathMatch, {Service, Handler, Opts}} <- Handlers
],
{ok, _} = application:ensure_all_started(cowboy),
[{env, [{dispatch, cowboy_router:compile([{'_', Paths}])}]}].
Debug = enable_debug(genlib_app:env(woody, enable_debug), EventHandler),
[{env, [{dispatch, cowboy_router:compile([{'_', Paths}])}]}] ++ Debug.
config() ->
[{max_body_length, ?MAX_BODY_LENGTH}].
enable_debug(true, EventHandler) ->
[
{onrequest, fun(Req) ->
{Url, Req1} = cowboy_req:url(Req),
{Headers, Req2} = cowboy_req:headers(Req1),
woody_event_handler:handle_event(EventHandler, ?EV_DEBUG, undefined, #{
event => transport_onrequest,
url => Url,
headers => Headers
}),
Req2 end
},
{onresponse, fun(Code, Headers, _Body, Req) ->
woody_event_handler:handle_event(EventHandler, ?EV_DEBUG, undefined, #{
event => transport_onresponse,
code => Code,
headers => Headers
}),
Req end
}
];
enable_debug(_, _) ->
[].
%%
%% thrift_transport callbacks
@ -155,7 +180,7 @@ flush(State = #http_req{
}) ->
{Code, Req1} = add_x_error_header(Req),
?log_event(EventHandler, ?EV_SERVER_SEND, reply_status(Code),
RpcId#{code => Code}),
RpcId, #{code => Code}),
{ok, Req2} = cowboy_req:reply(Code, [{<<"content-type">>, ?CONTENT_TYPE_THRIFT}],
Body, Req1),
{State#http_req{req = Req2, resp_body = <<>>, replied = true}, ok}.
@ -163,9 +188,9 @@ flush(State = #http_req{
reply_status(200) -> ok;
reply_status(_) -> error.
-spec close(state()) -> {state(), ok}.
close(_State) ->
{#http_req{}, ok}.
-spec close(state()) -> {state(), cowboy_req:req()}.
close(#http_req{req = Req}) ->
{#http_req{}, Req}.
-spec mark_thrift_error(logic | transport, _Error) -> _.
mark_thrift_error(Type, Error) ->
@ -185,7 +210,7 @@ init({_Transport, http}, Req, Opts = [EventHandler | _]) ->
check_headers(set_resp_headers(RpcId, Req2), [Url, RpcId | Opts]);
{error, ErrorMeta, Req2} ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, error,
ErrorMeta#{url => Url, reason => bad_rpc_id}),
ErrorMeta, #{url => Url, reason => bad_rpc_id}),
reply_error_early(400, Req2)
end.
@ -194,19 +219,19 @@ init({_Transport, http}, Req, Opts = [EventHandler | _]) ->
handle(Req, [Url, RpcId, EventHandler, ServerOpts, ThriftHandler]) ->
case get_body(Req, ServerOpts) of
{ok, Body, Req1} when byte_size(Body) > 0 ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, ok, RpcId#{url => Url}),
?log_event(EventHandler, ?EV_SERVER_RECEIVE, ok, RpcId, #{url => Url}),
do_handle(RpcId, Body, ThriftHandler, EventHandler, Req1);
{ok, <<>>, Req1} ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, error,
RpcId#{url => Url, reason => body_empty}),
RpcId, #{url => Url, reason => body_empty}),
reply_error(400, Req1);
{error, body_too_large, Req1} ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, error,
RpcId#{url => Url, reason => body_too_large}),
RpcId, #{url => Url, reason => body_too_large}),
reply_error(413, Req1);
{error, Reason, Req1} ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, error,
RpcId#{url => Url, reason => {body_read_error, Reason}}),
RpcId, #{url => Url, reason => {body_read_error, Reason}}),
reply_error(400, Req1)
end.
@ -216,7 +241,7 @@ terminate({normal, _}, _Req, _Status) ->
ok;
terminate(Reason, _Req, [_, RpcId, EventHandler | _]) ->
erlang:erase(?THRIFT_ERROR_KEY),
woody_event_handler:handle_event(EventHandler, ?EV_INTERNAL_ERROR, RpcId#{
woody_event_handler:handle_event(EventHandler, ?EV_INTERNAL_ERROR, RpcId, #{
error => <<"http handler terminated abnormally">>, reason => Reason
}),
ok.
@ -247,14 +272,14 @@ check_method({<<"POST">>, Req}, Opts) ->
check_content_type(cowboy_req:header(<<"content-type">>, Req), Opts);
check_method({Method, Req}, [Url, RpcId, EventHandler | _]) ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, error,
RpcId#{url => Url, reason => {wrong_method, Method}}),
RpcId, #{url => Url, reason => {wrong_method, Method}}),
reply_error_early(405, cowboy_req:set_resp_header(<<"allow">>, <<"POST">>, Req)).
check_content_type({?CONTENT_TYPE_THRIFT, Req}, Opts) ->
check_accept(cowboy_req:header(<<"accept">>, Req), Opts);
check_content_type({BadType, Req}, [Url, RpcId, EventHandler | _]) ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, error,
RpcId#{url => Url, reason => {wrong_content_type, BadType}}),
RpcId, #{url => Url, reason => {wrong_content_type, BadType}}),
reply_error_early(415, Req).
check_accept({Accept, Req}, Opts) when
@ -264,7 +289,7 @@ check_accept({Accept, Req}, Opts) when
{ok, Req, Opts};
check_accept({BadType, Req1}, [Url, RpcId, EventHandler | _]) ->
?log_event(EventHandler, ?EV_SERVER_RECEIVE, error,
RpcId#{url => Url, reason => {wrong_client_accept, BadType}}),
RpcId, #{url => Url, reason => {wrong_client_accept, BadType}}),
reply_error_early(406, Req1).
get_body(Req, ServerOpts) ->
@ -279,17 +304,17 @@ get_body(Req, ServerOpts) ->
end.
do_handle(RpcId, Body, ThriftHander, EventHandler, Req) ->
WoodyClient = woody_client:make_child_client(RpcId, EventHandler),
Context = woody_client:make_child_context(RpcId, EventHandler),
Transport = make_transport(Req, RpcId, Body, EventHandler),
case woody_server_thrift_handler:start(Transport, RpcId, WoodyClient, ThriftHander,
case woody_server_thrift_handler:start(Transport, Context, ThriftHander,
EventHandler, ?MODULE)
of
ok ->
{ok, Req, undefined};
{error, Reason} ->
handle_error(Reason, RpcId, EventHandler, Req);
noreply ->
{ok, Req, undefined}
{ok, Req1} ->
{ok, Req1, undefined};
{{error, Reason}, Req1} ->
handle_error(Reason, RpcId, EventHandler, Req1);
{noreply, Req1} ->
{ok, Req1, undefined}
end.
handle_error(bad_request, RpcId, EventHandler, Req) ->
@ -298,7 +323,7 @@ handle_error(_Error, RpcId, EventHandler, Req) ->
reply_error(500, RpcId, EventHandler, Req).
reply_error(Code, RpcId, EventHandler, Req) ->
?log_event(EventHandler, ?EV_SERVER_SEND, error, RpcId#{code => Code}),
?log_event(EventHandler, ?EV_SERVER_SEND, error, RpcId, #{code => Code}),
{_, Req1} = add_x_error_header(Req),
reply_error(Code, Req1).

View File

@ -18,7 +18,8 @@
-type url() :: binary().
-type role() :: client | server.
-type service() :: handler().
-type service_name() :: atom().
-type service() :: {handler(), service_name()}.
-type func() :: atom().
%% copy-paste from OTP supervsor
@ -28,8 +29,8 @@
| {'via', Module :: module(), Name :: any()}
| pid().
-export_type([req_id/0, rpc_id/0, service/0, func/0, options/0, handler/0,
url/0, role/0, sup_ref/0]).
-export_type([req_id/0, rpc_id/0, service/0, service_name/0, func/0, options/0,
handler/0, url/0, role/0, sup_ref/0]).
%%

View File

@ -1,9 +0,0 @@
%%
%% Autogenerated by Thrift Compiler (1.0.0-dev)
%%
%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
%%
-include("woody_test_types.hrl").

View File

@ -1,48 +0,0 @@
%%
%% Autogenerated by Thrift Compiler (1.0.0-dev)
%%
%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
%%
-module(woody_test_powerups_service).
-behaviour(thrift_service).
-include("woody_test_powerups_service.hrl").
-export([function_info/2]).
-export([struct_info/1]).
-export([function_names/0]).
function_names() ->
[
'get_powerup',
'like_powerup'
].
struct_info(_) -> erlang:error(badarg).
% get_powerup(This, Name, Data)
function_info('get_powerup', params_type) ->
{struct, [
{1, undefined, string, 'name', undefined},
{2, undefined, string, 'data', undefined}
]};
function_info('get_powerup', reply_type) ->
{struct, {woody_test_types, 'powerup'}};
function_info('get_powerup', exceptions) ->
{struct, [
{1, undefined, {struct, {woody_test_types, 'powerup_failure'}}, 'error', #'powerup_failure'{}}
]};
% like_powerup(This, Name, Data)
function_info('like_powerup', params_type) ->
{struct, [
{1, undefined, string, 'name', undefined},
{2, undefined, string, 'data', undefined}
]};
function_info('like_powerup', reply_type) ->
oneway_void;
function_info('like_powerup', exceptions) ->
{struct, []};
function_info(_Func, _Info) -> erlang:error(badarg).

View File

@ -1,5 +0,0 @@
-ifndef(_woody_test_powerups_service_included).
-define(_woody_test_powerups_service_included, yeah).
-include("woody_test_types.hrl").
-endif.

271
test/woody_test_thrift.erl Normal file
View File

@ -0,0 +1,271 @@
%%
%% Autogenerated by Thrift Compiler (1.0.0-dev)
%%
%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
%%
-module(woody_test_thrift).
-include("woody_test_thrift.hrl").
-export([enums/0]).
-export([typedefs/0]).
-export([structs/0]).
-export([services/0]).
-export([typedef_info/1]).
-export([enum_info/1]).
-export([struct_info/1]).
-export([functions/1]).
-export([function_info/3]).
-export_type([typedef_name/0]).
-export_type([enum_name/0]).
-export_type([struct_name/0]).
-export_type([exception_name/0]).
-export_type([service_name/0]).
-export_type([function_name/0]).
-export_type([
'direction'/0
]).
-export_type([
'weapon'/0,
'powerup'/0
]).
-export_type([
'weapon_failure'/0,
'powerup_failure'/0
]).
%%
%% typedefs
%%
-type typedef_name() :: none().
%%
%% enums
%%
-type enum_name() ::
'direction'.
%% enum 'direction'
-type 'direction'() ::
next |
prev.
%%
%% structs, unions and exceptions
%%
-type struct_name() ::
'weapon' |
'powerup'.
-type exception_name() ::
'weapon_failure' |
'powerup_failure'.
%% struct 'weapon'
-type 'weapon'() :: #'weapon'{}.
%% struct 'powerup'
-type 'powerup'() :: #'powerup'{}.
%% exception 'weapon_failure'
-type 'weapon_failure'() :: #'weapon_failure'{}.
%% exception 'powerup_failure'
-type 'powerup_failure'() :: #'powerup_failure'{}.
%%
%% services and functions
%%
-type service_name() ::
'weapons' |
'powerups'.
-type function_name() ::
'weapons_service_functions'() |
'powerups_service_functions'().
-type 'weapons_service_functions'() ::
'switch_weapon' |
'get_weapon'.
-export_type(['weapons_service_functions'/0]).
-type 'powerups_service_functions'() ::
'get_powerup' |
'like_powerup'.
-export_type(['powerups_service_functions'/0]).
-type struct_flavour() :: struct | exception | union.
-type field_num() :: pos_integer().
-type field_name() :: atom().
-type field_req() :: required | optional | undefined.
-type type_ref() :: {module(), atom()}.
-type field_type() ::
bool | byte | i16 | i32 | i64 | string | double |
{enum, type_ref()} |
{struct, struct_flavour(), type_ref()} |
{list, field_type()} |
{set, field_type()} |
{map, field_type(), field_type()}.
-type struct_field_info() ::
{field_num(), field_req(), field_type(), field_name(), any()}.
-type struct_info() ::
{struct, struct_flavour(), [struct_field_info()]}.
-type enum_choice() ::
'direction'().
-type enum_field_info() ::
{enum_choice(), integer()}.
-type enum_info() ::
{enum, [enum_field_info()]}.
-spec typedefs() -> [typedef_name()].
typedefs() ->
[].
-spec enums() -> [enum_name()].
enums() ->
[
'direction'
].
-spec structs() -> [struct_name()].
structs() ->
[
'weapon',
'powerup'
].
-spec services() -> [service_name()].
services() ->
[
'weapons',
'powerups'
].
-spec typedef_info(typedef_name()) -> field_type() | no_return().
typedef_info(_) -> erlang:error(badarg).
-spec enum_info(enum_name()) -> enum_info() | no_return().
enum_info('direction') ->
{enum, [
{next, 1},
{prev, 0}
]};
enum_info(_) -> erlang:error(badarg).
-spec struct_info(struct_name()) -> struct_info() | no_return().
struct_info('weapon') ->
{struct, struct, [
{1, required, string, 'name', undefined},
{2, required, i16, 'slot_pos', undefined},
{3, optional, i16, 'ammo', undefined}
]};
struct_info('powerup') ->
{struct, struct, [
{1, required, string, 'name', undefined},
{2, optional, i16, 'level', undefined},
{3, optional, i16, 'time_left', undefined}
]};
struct_info('weapon_failure') ->
{struct, exception, [
{1, required, string, 'exception_name', <<"weapon failure">>},
{2, required, string, 'code', undefined},
{3, optional, string, 'reason', undefined}
]};
struct_info('powerup_failure') ->
{struct, exception, [
{1, required, string, 'exception_name', <<"powerup failure">>},
{2, required, string, 'code', undefined},
{3, optional, string, 'reason', undefined}
]};
struct_info(_) -> erlang:error(badarg).
-spec functions(service_name()) -> [function_name()] | no_return().
functions('weapons') ->
[
'switch_weapon',
'get_weapon'
];
functions('powerups') ->
[
'get_powerup',
'like_powerup'
];
functions(_) -> error(badarg).
-spec function_info(service_name(), function_name(), params_type | reply_type | exceptions) ->
struct_info() | no_return().
function_info('weapons', 'switch_weapon', params_type) ->
{struct, struct, [
{1, undefined, {struct, struct, {woody_test_thrift, 'weapon'}}, 'current_weapon', undefined},
{2, undefined, {enum, {woody_test_thrift, 'direction'}}, 'direction', undefined},
{3, undefined, i16, 'shift', undefined},
{4, undefined, string, 'data', undefined}
]};
function_info('weapons', 'switch_weapon', reply_type) ->
{struct, struct, {woody_test_thrift, 'weapon'}};
function_info('weapons', 'switch_weapon', exceptions) ->
{struct, struct, [
{1, undefined, {struct, exception, {woody_test_thrift, 'weapon_failure'}}, 'error', undefined}
]};
function_info('weapons', 'get_weapon', params_type) ->
{struct, struct, [
{1, undefined, string, 'name', undefined},
{2, undefined, string, 'data', undefined}
]};
function_info('weapons', 'get_weapon', reply_type) ->
{struct, struct, {woody_test_thrift, 'weapon'}};
function_info('weapons', 'get_weapon', exceptions) ->
{struct, struct, [
{1, undefined, {struct, exception, {woody_test_thrift, 'weapon_failure'}}, 'error', undefined}
]};
function_info('powerups', 'get_powerup', params_type) ->
{struct, struct, [
{1, undefined, string, 'name', undefined},
{2, undefined, string, 'data', undefined}
]};
function_info('powerups', 'get_powerup', reply_type) ->
{struct, struct, {woody_test_thrift, 'powerup'}};
function_info('powerups', 'get_powerup', exceptions) ->
{struct, struct, [
{1, undefined, {struct, exception, {woody_test_thrift, 'powerup_failure'}}, 'error', undefined}
]};
function_info('powerups', 'like_powerup', params_type) ->
{struct, struct, [
{1, undefined, string, 'name', undefined},
{2, undefined, string, 'data', undefined}
]};
function_info('powerups', 'like_powerup', reply_type) ->
oneway_void;
function_info('powerups', 'like_powerup', exceptions) ->
{struct, struct, []};
function_info(_Service, _Function, _InfoType) -> erlang:error(badarg).

View File

@ -1,48 +1,34 @@
-ifndef(_woody_test_types_included).
-define(_woody_test_types_included, yeah).
-ifndef(woody_test_thrift_included__).
-define(woody_test_thrift_included__, yeah).
-define(WOODY_TEST_DIRECTION_NEXT, 1).
-define(WOODY_TEST_DIRECTION_PREV, 0).
%% struct 'weapon'
-record('weapon', {
'name' :: binary(),
'slot_pos' :: integer(),
'ammo' :: integer()
}).
-type 'weapon'() :: #'weapon'{}.
%% struct 'powerup'
-record('powerup', {
'name' :: binary(),
'level' :: integer(),
'time_left' :: integer()
}).
-type 'powerup'() :: #'powerup'{}.
%% struct 'weapon_failure'
%% exception 'weapon_failure'
-record('weapon_failure', {
'exception_name' = <<"weapon failure">> :: binary(),
'code' :: binary(),
'reason' :: binary()
}).
-type 'weapon_failure'() :: #'weapon_failure'{}.
%% struct 'powerup_failure'
%% exception 'powerup_failure'
-record('powerup_failure', {
'exception_name' = <<"powerup failure">> :: binary(),
'code' :: binary(),
'reason' :: binary()
}).
-type 'powerup_failure'() :: #'powerup_failure'{}.
-endif.

View File

@ -1,90 +0,0 @@
%%
%% Autogenerated by Thrift Compiler (1.0.0-dev)
%%
%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
%%
-module(woody_test_types).
-include("woody_test_types.hrl").
-type type_ref() :: {module(), atom()}.
-type field_num() :: pos_integer().
-type field_name() :: atom().
-type field_req() :: required | optional.
-type field_type() ::
bool | byte | i16 | i32 | i64 | string | double |
{enum, type_ref()} |
{struct, type_ref()} |
{list, field_type()} |
{set, field_type()} |
{map, field_type(), field_type()}.
-type struct_field_info() :: {field_num(), field_req(), field_type(), field_name(), any()}.
-type enum_field_info() :: {atom(), integer()}.
-export([enum_names/0]).
-export([struct_names/0]).
-export([enum_info/1]).
-export([struct_info/1]).
-spec struct_names() -> [atom()].
struct_names() ->
[
'weapon',
'powerup',
'weapon_failure',
'powerup_failure'
].
-spec enum_names() -> [atom()].
enum_names() ->
[
'direction'
].
-spec struct_info(atom()) -> {struct, [struct_field_info()]}.
struct_info('weapon') ->
{struct, [
{1, required, string, 'name', undefined},
{2, required, i16, 'slot_pos', undefined},
{3, optional, i16, 'ammo', undefined}
]};
struct_info('powerup') ->
{struct, [
{1, required, string, 'name', undefined},
{2, optional, i16, 'level', undefined},
{3, optional, i16, 'time_left', undefined}
]};
struct_info('weapon_failure') ->
{struct, [
{1, required, string, 'exception_name', <<"weapon failure">>},
{2, required, string, 'code', undefined},
{3, optional, string, 'reason', undefined}
]};
struct_info('powerup_failure') ->
{struct, [
{1, required, string, 'exception_name', <<"powerup failure">>},
{2, required, string, 'code', undefined},
{3, optional, string, 'reason', undefined}
]};
struct_info(_) -> erlang:error(badarg).
-spec enum_info(atom()) -> {enum, [enum_field_info()]}.
enum_info('direction') ->
{enum, [
{next, 1},
{prev, 0}
]};
enum_info(_) -> erlang:error(badarg).

View File

@ -1,52 +0,0 @@
%%
%% Autogenerated by Thrift Compiler (1.0.0-dev)
%%
%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
%%
-module(woody_test_weapons_service).
-behaviour(thrift_service).
-include("woody_test_weapons_service.hrl").
-export([function_info/2]).
-export([struct_info/1]).
-export([function_names/0]).
function_names() ->
[
'switch_weapon',
'get_weapon'
].
struct_info(_) -> erlang:error(badarg).
% switch_weapon(This, Current_weapon, Direction, Shift, Data)
function_info('switch_weapon', params_type) ->
{struct, [
{1, undefined, {struct, {woody_test_types, 'weapon'}}, 'current_weapon', #'weapon'{}},
{2, undefined, {enum, {woody_test_types, 'direction'}}, 'direction', undefined},
{3, undefined, i16, 'shift', undefined},
{4, undefined, string, 'data', undefined}
]};
function_info('switch_weapon', reply_type) ->
{struct, {woody_test_types, 'weapon'}};
function_info('switch_weapon', exceptions) ->
{struct, [
{1, undefined, {struct, {woody_test_types, 'weapon_failure'}}, 'error', #'weapon_failure'{}}
]};
% get_weapon(This, Name, Data)
function_info('get_weapon', params_type) ->
{struct, [
{1, undefined, string, 'name', undefined},
{2, undefined, string, 'data', undefined}
]};
function_info('get_weapon', reply_type) ->
{struct, {woody_test_types, 'weapon'}};
function_info('get_weapon', exceptions) ->
{struct, [
{1, undefined, {struct, {woody_test_types, 'weapon_failure'}}, 'error', #'weapon_failure'{}}
]};
function_info(_Func, _Info) -> erlang:error(badarg).

View File

@ -1,5 +0,0 @@
-ifndef(_woody_test_weapons_service_included).
-define(_woody_test_weapons_service_included, yeah).
-include("woody_test_types.hrl").
-endif.

View File

@ -2,7 +2,7 @@
-include_lib("common_test/include/ct.hrl").
-include("woody_test_types.hrl").
-include("woody_test_thrift.hrl").
-include("src/woody_defs.hrl").
-compile(export_all).
@ -15,15 +15,18 @@
-export([init/1]).
%% woody_server_thrift_handler callbacks
-export([handle_function/5]).
-export([handle_error/5]).
-export([handle_function/4]).
-export([handle_error/4]).
%% woody_event_handler callbacks
-export([handle_event/2]).
-export([handle_event/3]).
%% internal API
-export([call/4, call_safe/4]).
-define(THRIFT_DEFS, woody_test_thrift).
%% Weapons service
-define(SLOTS, #{
1 => <<"Impact Hammer">>,
@ -164,12 +167,12 @@ start_woody_server(Id, Sup, Services) ->
get_handler(powerups) ->
{
?PATH_POWERUPS,
{woody_test_powerups_service, ?MODULE, []}
{{?THRIFT_DEFS, powerups}, ?MODULE, []}
};
get_handler(weapons) ->
{
?PATH_WEAPONS,
{woody_test_weapons_service, ?MODULE, []}
{{?THRIFT_DEFS, weapons}, ?MODULE, []}
}.
end_per_test_case(_,C) ->
@ -212,18 +215,18 @@ call_handler_throw_test(_) ->
call_safe_handler_throw_unexpected_test(_) ->
Id = <<"call_safe_handler_throw_unexpected">>,
Current = genlib_map:get(<<"Rocket Launcher">>, ?WEAPONS),
Client = get_client(Id),
Expect = {{error, ?error_transport(server_error)}, Client},
Expect = call_safe(Client, weapons, switch_weapon,
Context = make_context(Id),
Expect = {{error, ?error_transport(server_error)}, Context},
Expect = call_safe(Context, weapons, switch_weapon,
[Current, next, 1, self_to_bin()]),
{ok, _} = receive_msg({Id, Current}).
call_handler_throw_unexpected_test(_) ->
Id = <<"call_handler_throw_unexpected">>,
Current = genlib_map:get(<<"Rocket Launcher">>, ?WEAPONS),
Client = get_client(Id),
Expect = {?error_transport(server_error), Client},
try call(Client, weapons, switch_weapon, [Current, next, 1, self_to_bin()])
Context = make_context(Id),
Expect = {?error_transport(server_error), Context},
try call(Context, weapons, switch_weapon, [Current, next, 1, self_to_bin()])
catch
error:Expect -> ok
end,
@ -242,25 +245,25 @@ call_handler_error_test(_) ->
call_safe_client_transport_error_test(_) ->
Gun = 'Wrong Type of Mega Destroyer',
Id = <<"call_safe_client_transport_error">>,
Client = get_client(Id),
{{error, ?error_protocol(_)}, Client} = call_safe(Client,
Context = make_context(Id),
{{error, ?error_protocol(_)}, Context} = call_safe(Context,
weapons, get_weapon, [Gun, self_to_bin()]).
call_client_transport_error_test(_) ->
Gun = 'Wrong Type of Mega Destroyer',
Id = <<"call_client_transport_error">>,
Client = get_client(Id),
try call(Client, weapons, get_weapon, [Gun, self_to_bin()])
Context = make_context(Id),
try call(Context, weapons, get_weapon, [Gun, self_to_bin()])
catch
error:{?error_protocol(_), Client} -> ok
error:{?error_protocol(_), Context} -> ok
end.
call_safe_server_transport_error_test(_) ->
Id = <<"call_safe_server_transport_error">>,
Armor = <<"Helmet">>,
Client = get_client(Id),
Expect = {{error, ?error_transport(server_error)}, Client},
Expect = call_safe(Client, powerups, get_powerup,
Context = make_context(Id),
Expect = {{error, ?error_transport(server_error)}, Context},
Expect = call_safe(Context, powerups, get_powerup,
[Armor, self_to_bin()]),
{ok, _} = receive_msg({Id, Armor}).
@ -272,9 +275,9 @@ call_handle_error_fails_test(_) ->
do_call_server_transport_error(Id) ->
Armor = <<"Helmet">>,
Client = get_client(Id),
Expect = {?error_transport(server_error), Client},
try call(Client, powerups, get_powerup, [Armor, self_to_bin()])
Context = make_context(Id),
Expect = {?error_transport(server_error), Context},
try call(Context, powerups, get_powerup, [Armor, self_to_bin()])
catch
error:Expect -> ok
end,
@ -283,9 +286,9 @@ do_call_server_transport_error(Id) ->
call_oneway_void_test(_) ->
Id = <<"call_oneway_void_test">>,
Armor = <<"Helmet">>,
Client = get_client(Id),
Expect = {ok, Client},
Expect = call(Client, powerups, like_powerup, [Armor, self_to_bin()]),
Context = make_context(Id),
Expect = {ok, Context},
Expect = call(Context, powerups, like_powerup, [Armor, self_to_bin()]),
{ok, _} = receive_msg({Id, Armor}).
call_async_ok_test(C) ->
@ -293,28 +296,28 @@ call_async_ok_test(C) ->
Pid = self(),
Callback = fun(Res) -> collect(Res, Pid) end,
Id1 = <<"call_async_ok1">>,
Client1 = get_client(Id1),
{ok, Pid1, Client1} = get_weapon(Client1, Sup, Callback, <<"Impact Hammer">>),
Context1 = make_context(Id1),
{ok, Pid1, Context1} = get_weapon(Context1, Sup, Callback, <<"Impact Hammer">>),
Id2 = <<"call_async_ok2">>,
Client2 = get_client(Id2),
{ok, Pid2, Client2} = get_weapon(Client2, Sup, Callback, <<"Flak Cannon">>),
{ok, Pid1} = receive_msg({Client1,
Context2 = make_context(Id2),
{ok, Pid2, Context2} = get_weapon(Context2, Sup, Callback, <<"Flak Cannon">>),
{ok, Pid1} = receive_msg({Context1,
genlib_map:get(<<"Impact Hammer">>, ?WEAPONS)}),
{ok, Pid2} = receive_msg({Client2,
{ok, Pid2} = receive_msg({Context2,
genlib_map:get(<<"Flak Cannon">>, ?WEAPONS)}).
get_weapon(Client, Sup, Cb, Gun) ->
call_async(Client, weapons, get_weapon, [Gun, <<>>], Sup, Cb).
get_weapon(Context, Sup, Cb, Gun) ->
call_async(Context, weapons, get_weapon, [Gun, <<>>], Sup, Cb).
collect({{ok, Result}, Client}, Pid) ->
send_msg(Pid, {Client, Result}).
collect({{ok, Result}, Context}, Pid) ->
send_msg(Pid, {Context, Result}).
span_ids_sequence_test(_) ->
Id = <<"span_ids_sequence">>,
Current = genlib_map:get(<<"Enforcer">>, ?WEAPONS),
Client = get_client(Id),
Expect = {{ok, genlib_map:get(<<"Ripper">>, ?WEAPONS)}, Client},
Expect = call(Client, weapons, switch_weapon,
Context = make_context(Id),
Expect = {{ok, genlib_map:get(<<"Ripper">>, ?WEAPONS)}, Context},
Expect = call(Context, weapons, switch_weapon,
[Current, next, 1, self_to_bin()]).
call_with_client_pool_test(_) ->
@ -322,11 +325,11 @@ call_with_client_pool_test(_) ->
ok = woody_client_thrift:start_pool(Pool, 10),
Id = <<"call_with_client_pool">>,
Gun = <<"Enforcer">>,
Client = get_client(Id),
Context = make_context(Id),
{Url, Service} = get_service_endpoint(weapons),
Expect = {{ok, genlib_map:get(Gun, ?WEAPONS)}, Client},
Expect = {{ok, genlib_map:get(Gun, ?WEAPONS)}, Context},
Expect = woody_client:call(
Client,
Context,
{Service, get_weapon, [Gun, self_to_bin()]},
#{url => Url, pool => Pool}
),
@ -354,8 +357,8 @@ make_thrift_multiplexed_client(Id, ServiceName, {Url, Service}) ->
[{strict_read, true}, {strict_write, true}]
),
{ok, Protocol1} = thrift_multiplexed_protocol:new(Protocol, ServiceName),
{ok, Client} = thrift_client:new(Protocol1, Service),
Client.
{ok, Context} = thrift_client:new(Protocol1, Service),
Context.
allowed_transport_options_test(_) ->
Id = <<"allowed_transport_options">>,
@ -364,17 +367,17 @@ allowed_transport_options_test(_) ->
{Url, Service} = get_service_endpoint(weapons),
Pool = guns,
ok = woody_client_thrift:start_pool(Pool, 1),
Client = get_client(Id),
Context = make_context(Id),
Options = #{url => Url, pool => Pool, ssl_options => [], connect_timeout => 0},
{{error, ?error_transport(connect_timeout)}, Client} = woody_client:call_safe(
Client,
{{error, ?error_transport(connect_timeout)}, Context} = woody_client:call_safe(
Context,
{Service, get_weapon, Args},
Options
),
BadOpt = #{custom_option => 'fire!'},
ErrorBadOpt = {badarg, {unsupported_options, BadOpt}},
{{error, {error, ErrorBadOpt, _}}, Client} = woody_client:call_safe(
Client,
{{error, {error, ErrorBadOpt, _}}, Context} = woody_client:call_safe(
Context,
{Service, get_weapon, Args},
maps:merge(Options, BadOpt)
),
@ -424,15 +427,15 @@ init(_) ->
%% Weapons
handle_function(switch_weapon, {CurrentWeapon, Direction, Shift, To},
_RpcId = #{span_id := SpanId, trace_id := TraceId},
WoodyClient = #{parent_id := SpanId, trace_id := TraceId}, _Opts)
Context = #{ parent_id := SpanId, trace_id := TraceId,
rpc_id := #{span_id := SpanId, trace_id := TraceId}}, _Opts)
->
send_msg(To, {SpanId, CurrentWeapon}),
switch_weapon(CurrentWeapon, Direction, Shift, WoodyClient);
switch_weapon(CurrentWeapon, Direction, Shift, Context);
handle_function(get_weapon, {Name, To},
#{span_id := SpanId, trace_id := TraceId},
#{parent_id := SpanId, trace_id := TraceId}, _Opts)
_Context = #{ parent_id := SpanId, trace_id := TraceId,
rpc_id := #{span_id := SpanId, trace_id := TraceId}}, _Opts)
->
send_msg(To,{SpanId, Name}),
Res = case genlib_map:get(Name, ?WEAPONS) of
@ -445,26 +448,26 @@ handle_function(get_weapon, {Name, To},
%% Powerups
handle_function(get_powerup, {Name, To},
#{span_id := SpanId, trace_id := TraceId},
#{parent_id := SpanId, trace_id := TraceId}, _Opts)
_Context = #{ parent_id := SpanId, trace_id := TraceId,
rpc_id := #{span_id := SpanId, trace_id := TraceId}}, _Opts)
->
send_msg(To, {SpanId, Name}),
{ok, genlib_map:get(Name, ?POWERUPS, powerup_unknown)};
handle_function(like_powerup, {Name, To},
#{span_id := SpanId, trace_id := TraceId},
#{parent_id := SpanId, trace_id := TraceId}, _Opts)
_Context = #{ parent_id := SpanId, trace_id := TraceId,
rpc_id := #{span_id := SpanId, trace_id := TraceId}}, _Opts)
->
send_msg(To, {SpanId, Name}),
ok.
handle_error(get_powerup, _,
#{trace_id := TraceId, span_id := SpanId = <<"call_handle_error_fails">>},
#{trace_id := TraceId, parent_id := SpanId}, _Opts)
_Context = #{ trace_id := TraceId, span_id := SpanId = <<"call_handle_error_fails">>,
rpc_id := #{trace_id := TraceId, parent_id := SpanId}}, _Opts)
->
error(no_more_powerups);
handle_error(_Function, _Reason,
_RpcId = #{span_id := SpanId, trace_id := TraceId},
_WoodyClient = #{parent_id := SpanId, trace_id := TraceId}, _Opts)
_Context = #{ parent_id := SpanId, trace_id := TraceId,
rpc_id := #{span_id := SpanId, trace_id := TraceId}}, _Opts)
->
ok.
@ -472,33 +475,33 @@ handle_error(_Function, _Reason,
%%
%% woody_event_handler callbacks
%%
handle_event(Type, Meta) ->
ct:pal(info, "woody event ~p: ~p", [Type, Meta]).
handle_event(Type, RpcId, Meta) ->
ct:pal(info, "woody event ~p for RPC ID: ~p~n~p", [Type, RpcId, Meta]).
%%
%% internal functions
%%
get_client(ReqId) ->
woody_client:new(ReqId, ?MODULE).
make_context(ReqId) ->
woody_client:new_context(ReqId, ?MODULE).
call(Client, ServiceName, Function, Args) ->
do_call(call, Client, ServiceName, Function, Args).
call(Context, ServiceName, Function, Args) ->
do_call(call, Context, ServiceName, Function, Args).
call_safe(Client, ServiceName, Function, Args) ->
do_call(call_safe, Client, ServiceName, Function, Args).
call_safe(Context, ServiceName, Function, Args) ->
do_call(call_safe, Context, ServiceName, Function, Args).
do_call(Call, Client, ServiceName, Function, Args) ->
do_call(Call, Context, ServiceName, Function, Args) ->
{Url, Service} = get_service_endpoint(ServiceName),
woody_client:Call(
Client,
Context,
{Service, Function, Args},
#{url => Url}
).
call_async(Client, ServiceName, Function, Args, Sup, Callback) ->
call_async(Context, ServiceName, Function, Args, Sup, Callback) ->
{Url, Service} = get_service_endpoint(ServiceName),
woody_client:call_async(Sup, Callback,
Client,
Context,
{Service, Function, Args},
#{url => Url}
).
@ -506,27 +509,27 @@ call_async(Client, ServiceName, Function, Args, Sup, Callback) ->
get_service_endpoint(weapons) ->
{
genlib:to_binary(?URL_BASE ++ ?PATH_WEAPONS),
woody_test_weapons_service
{?THRIFT_DEFS, weapons}
};
get_service_endpoint(powerups) ->
{
genlib:to_binary(?URL_BASE ++ ?PATH_POWERUPS),
woody_test_powerups_service
{?THRIFT_DEFS, powerups}
}.
gun_test_basic(CallFun, Id, Gun, {ExpectStatus, ExpectRes}, WithMsg) ->
Client = get_client(Id),
Expect = {{ExpectStatus, ExpectRes}, Client},
Expect = ?MODULE:CallFun(Client, weapons, get_weapon, [Gun, self_to_bin()]),
Context = make_context(Id),
Expect = {{ExpectStatus, ExpectRes}, Context},
Expect = ?MODULE:CallFun(Context, weapons, get_weapon, [Gun, self_to_bin()]),
case WithMsg of
true -> {ok, _} = receive_msg({Id, Gun});
_ -> ok
end.
gun_catch_test_basic(Id, Gun, {Class, Exception}, WithMsg) ->
Client = get_client(Id),
Expect = {Exception, Client},
try call(Client, weapons, get_weapon, [Gun, self_to_bin()])
Context = make_context(Id),
Expect = {Exception, Context},
try call(Context, weapons, get_weapon, [Gun, self_to_bin()])
catch
Class:Expect -> ok
end,
@ -535,8 +538,8 @@ gun_catch_test_basic(Id, Gun, {Class, Exception}, WithMsg) ->
_ -> ok
end.
switch_weapon(CurrentWeapon, Direction, Shift, WoodyClient) ->
case call_safe(WoodyClient, weapons, get_weapon,
switch_weapon(CurrentWeapon, Direction, Shift, Context) ->
case call_safe(Context, weapons, get_weapon,
[new_weapon_name(CurrentWeapon, Direction, Shift), self_to_bin()])
of
{{ok, Weapon}, _} ->
@ -544,9 +547,9 @@ switch_weapon(CurrentWeapon, Direction, Shift, WoodyClient) ->
{{exception, #weapon_failure{
code = <<"weapon_error">>,
reason = <<"out of ammo">>
}}, NextClient} ->
ok = validate_next_client(NextClient, WoodyClient),
switch_weapon(CurrentWeapon, Direction, Shift + 1, NextClient)
}}, NextContex} ->
ok = validate_next_context(NextContex, Context),
switch_weapon(CurrentWeapon, Direction, Shift + 1, NextContex)
end.
new_weapon_name(#weapon{slot_pos = Pos}, next, Shift) ->
@ -559,7 +562,7 @@ new_weapon_name(Pos) when is_integer(Pos), Pos >= 0, Pos < 10 ->
new_weapon_name(_) ->
throw(?pos_error).
validate_next_client(#{seq := NextSeq}, #{seq := Seq}) ->
validate_next_context(#{seq := NextSeq}, #{seq := Seq}) ->
NextSeq = Seq + 1,
ok.