upgrade: +alpine +rebar +dialyzer +deps (#149)

This commit is contained in:
dinama 2021-01-15 15:35:41 +03:00 committed by GitHub
parent 58f56b4624
commit f2cd30883d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 3561 additions and 3365 deletions

View File

@ -7,9 +7,12 @@ UTILS_PATH := build_utils
# with handling of the varriable in build_utils is fixed
TEMPLATES_PATH := .
SERVICE_NAME := woody
BUILD_IMAGE_TAG := 07d3946f8f005782697de20270ac58cdcd18b011
CALL_W_CONTAINER := all submodules compile xref lint test bench dialyze clean distclean
BUILD_IMAGE_NAME := build-erlang
BUILD_IMAGE_TAG := c60896ef07d41e7ae2e5f9b6ce845a60ad79acc7
CALL_W_CONTAINER := all submodules compile xref lint test bench dialyze clean distclean \
check_format format
.PHONY: $(CALL_W_CONTAINER)
@ -36,6 +39,12 @@ xref: submodules
lint: compile
elvis rock
check_format:
$(REBAR) fmt -c
format:
$(REBAR) fmt -w
clean:
$(REBAR) clean
$(REBAR) as test clean

@ -1 +1 @@
Subproject commit 5cb25f049c719a608276a99fc4fbe852187019ca
Subproject commit e1318727d4d0c3e48f5122bf3197158b6695f50e

View File

@ -21,9 +21,9 @@
% Common project dependencies.
{deps, [
{cowboy, "2.7.0"},
{hackney, "1.15.2"},
{hackney, "1.17.0"},
{gproc , "0.8.0"},
{cache , "2.2.0"},
{cache , "2.3.3"},
{thrift, {git, "https://github.com/rbkmoney/thrift_erlang.git", {branch, "master"}}},
{snowflake, {git, "https://github.com/rbkmoney/snowflake.git", {branch, "master"}}},
{genlib, {git, "https://github.com/rbkmoney/genlib.git", {branch, "master"}}}
@ -49,6 +49,7 @@
{profiles, [
{test, [
{extra_src_dirs, [{"test", [{recursive, true}]}]},
{plugins, [
{rebar3_bench, "0.2.1"}
]},
@ -67,17 +68,27 @@
{gen, "erlang:app_prefix=woody"}
]},
{deps, [
{proper, "1.2.0"},
{how_are_you , {git, "https://github.com/rbkmoney/how_are_you.git", {ref, "8f11d17"}}},
{cth_readable, "1.4.9"},
{proper, "1.3.0"},
{how_are_you , {git, "https://github.com/rbkmoney/how_are_you.git", {branch, "master"}}},
{damsel , {git, "https://github.com/rbkmoney/damsel.git", {ref, "8911ac3"}}},
{mg_proto , {git, "https://github.com/rbkmoney/machinegun_proto.git", {ref, "ebae56f"}}}
]},
{dialyzer, [
{plt_extra_apps, [how_are_you, eunit]}
{plt_extra_apps, [how_are_you, eunit, proper, common_test, cth_readable]}
]}
]}
]}.
{plugins, [
{rebar3_thrift_compiler, {git, "https://github.com/rbkmoney/rebar3_thrift_compiler.git", {tag, "0.3.1"}}}
{rebar3_thrift_compiler, {git, "https://github.com/rbkmoney/rebar3_thrift_compiler.git", {branch, "master"}}},
{erlfmt, "0.9.0"}
]}.
{erlfmt, [
{print_width, 120},
{files, [
"{src,include,test}/*.{hrl,erl}",
"test/*/*.{hrl,erl}"
]}
]}.

View File

@ -1,7 +1,7 @@
{"1.1.0",
{"1.2.0",
[{<<"bear">>,{pkg,<<"bear">>,<<"0.8.7">>},2},
{<<"cache">>,{pkg,<<"cache">>,<<"2.2.0">>},0},
{<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.1">>},1},
{<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},0},
{<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.3">>},1},
{<<"cg_mon">>,
{git,"https://github.com/rbkmoney/cg_mon.git",
{ref,"5a87a37694e42b6592d3b4164ae54e0e87e24e18"}},
@ -14,43 +14,58 @@
1},
{<<"genlib">>,
{git,"https://github.com/rbkmoney/genlib.git",
{ref,"54920e768a71f121304a5eda547ee60295398f3c"}},
{ref,"4565a8d73f34a0b78cca32c9cd2b97d298bdadf8"}},
0},
{<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},0},
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.15.2">>},0},
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.17.0">>},0},
{<<"how_are_you">>,
{git,"https://github.com/rbkmoney/how_are_you.git",
{ref,"8f11d17eeb6eb74096da7363a9df272fd3099718"}},
0},
{<<"idna">>,{pkg,<<"idna">>,<<"6.0.0">>},1},
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1},
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1},
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},1},
{<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},2},
{<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},1},
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.7.1">>},1},
{<<"snowflake">>,
{git,"https://github.com/rbkmoney/snowflake.git",
{ref,"7f379ad5e389e1c96389a8d60bae8117965d6a6d"}},
{ref,"de159486ef40cec67074afe71882bdc7f7deab72"}},
0},
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.5">>},1},
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},1},
{<<"thrift">>,
{git,"https://github.com/rbkmoney/thrift_erlang.git",
{ref,"846a0819d9b6d09d0c31f160e33a78dbad2067b4"}},
0},
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.4.1">>},2}]}.
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1}]}.
[
{pkg_hash,[
{<<"bear">>, <<"16264309AE5D005D03718A5C82641FCC259C9E8F09ADEB6FD79CA4271168656F">>},
{<<"cache">>, <<"3C11DBF4CD8FCD5787C95A5FB2A04038E3729CFCA0386016EEA8C953AB48A5AB">>},
{<<"certifi">>, <<"867CE347F7C7D78563450A18A6A28A8090331E77FA02380B4A21962A65D36EE5">>},
{<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>},
{<<"certifi">>, <<"70BDD7E7188C804F3A30EE0E7C99655BC35D8AC41C23E12325F36AB449B70651">>},
{<<"cowboy">>, <<"91ED100138A764355F43316B1D23D7FF6BDB0DE4EA618CB5D8677C93A7A2F115">>},
{<<"cowlib">>, <<"FD0FF1787DB84AC415B8211573E9A30A3EBE71B5CBFF7F720089972B2319C8A4">>},
{<<"gproc">>, <<"CEA02C578589C61E5341FCE149EA36CCEF236CC2ECAC8691FBA408E7EA77EC2F">>},
{<<"hackney">>, <<"07E33C794F8F8964EE86CEBEC1A8ED88DB5070E52E904B8F12209773C1036085">>},
{<<"idna">>, <<"689C46CBCDF3524C44D5F3DDE8001F364CD7608A99556D8FBD8239A5798D4C10">>},
{<<"hackney">>, <<"717EA195FD2F898D9FE9F1CE0AFCC2621A41ECFE137FAE57E7FE6E9484B9AA99">>},
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
{<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>},
{<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>},
{<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>},
{<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>},
{<<"ranch">>, <<"6B1FAB51B49196860B733A49C07604465A47BDB78AA10C1C16A3D199F7F8C881">>},
{<<"ssl_verify_fun">>, <<"6EAF7AD16CB568BB01753DBBD7A95FF8B91C7979482B95F38443FE2C8852A79B">>},
{<<"unicode_util_compat">>, <<"D869E4C68901DD9531385BB0C8C40444EBF624E60B6962D95952775CAC5E90CD">>}]}
{<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>},
{<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]},
{pkg_hash_ext,[
{<<"bear">>, <<"534217DCE6A719D59E54FB0EB7A367900DBFC5F85757E8C1F94269DF383F6D9B">>},
{<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>},
{<<"certifi">>, <<"ED516ACB3929B101208A9D700062D520F3953DA3B6B918D866106FFA980E1C10">>},
{<<"cowboy">>, <<"04FD8C6A39EDC6AAA9C26123009200FC61F92A3A94F3178C527B70B767C6E605">>},
{<<"cowlib">>, <<"79F954A7021B302186A950A32869DBC185523D99D3E44CE430CD1F3289F41ED4">>},
{<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>},
{<<"hackney">>, <<"64C22225F1EA8855F584720C0E5B3CD14095703AF1C9FBC845BA042811DC671C">>},
{<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
{<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>},
{<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>},
{<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>},
{<<"ranch">>, <<"451D8527787DF716D99DC36162FCA05934915DB0B6141BBDAC2EA8D3C7AFC7D7">>},
{<<"ssl_verify_fun">>, <<"BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680">>},
{<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]}
].

View File

@ -6,14 +6,14 @@
%% Types
%% Dapper RPC
-type req_id() :: binary().
-type span_id() :: req_id().
-type trace_id() :: req_id().
-type req_id() :: binary().
-type span_id() :: req_id().
-type trace_id() :: req_id().
-type parent_id() :: req_id().
-type rpc_id() :: #{
span_id => span_id(),
trace_id => trace_id(),
span_id => span_id(),
trace_id => trace_id(),
parent_id => parent_id()
}.
@ -24,49 +24,59 @@
-type millisec() :: woody_deadline:millisec().
-type deadline() :: woody_deadline:deadline().
-export_type([deadline/0, millisec/0]).
-type cert() :: woody_cert:cert().
-type common_name() :: woody_cert:common_name().
-export_type([cert/0, common_name/0]).
%% Thrift
-type service_name() :: atom().
-type service() :: {module(), service_name()}.
-type func() :: atom().
-type args() :: tuple().
-type request() :: {service(), func(), args()}.
-type result() :: _.
-type th_handler() :: {service(), handler(options())}.
-type service() :: {module(), service_name()}.
-type func() :: atom().
-type args() :: tuple().
-type request() :: {service(), func(), args()}.
-type result() :: _.
-type th_handler() :: {service(), handler(options())}.
-export_type([request/0, result/0, service/0, service_name/0, func/0, args/0, th_handler/0]).
-type rpc_type() :: call | cast.
-export_type([rpc_type/0]).
%% Generic
-type options() :: any().
-type options() :: any().
-type handler(Opts) :: {module(), Opts} | module().
-type ev_handler() :: handler(options()).
-type ev_handler() :: handler(options()).
-type ev_handlers() :: ev_handler() | [ev_handler()].
-export_type([handler/1, ev_handler/0, ev_handlers/0, options/0]).
-type role() :: client | server.
-type url() :: binary() | string().
-type path() :: '_' | iodata(). %% cowboy_router:route_match()
-type role() :: client | server.
-type url() :: binary() | string().
%% cowboy_router:route_match()
-type path() :: '_' | iodata().
-type http_handler(Handler) :: {path(), Handler}.
-export_type([role/0, url/0, path/0, http_handler/1]).
-type http_code() :: integer().
-type http_code() :: integer().
-type http_header_name() :: binary().
-type http_header_val() :: binary().
-type http_headers() :: #{http_header_name() => http_header_val()}.
-type http_body() :: binary().
-type http_header_val() :: binary().
-type http_headers() :: #{http_header_name() => http_header_val()}.
-type http_body() :: binary().
-export_type([http_code/0, http_header_name/0, http_header_val/0, http_headers/0, http_body/0]).
%% copy-paste from OTP supervisor
-type sup_ref() :: (Name :: atom())
| {Name :: atom(), Node :: node()}
| {'global', Name :: atom()}
| {'via', Module :: module(), Name :: any()}
| pid().
-type sup_ref() ::
(Name :: atom())
| {Name :: atom(), Node :: node()}
| {'global', Name :: atom()}
| {'via', Module :: module(), Name :: any()}
| pid().
-export_type([sup_ref/0]).

View File

@ -15,6 +15,7 @@
%%%
-module(woody_api_hay).
-behaviour(hay_metrics_handler).
%% how_are_you callbacks
@ -32,7 +33,6 @@
%% Internal types
-type state() :: options().
-type metric() :: how_are_you:metric().
-type metric_key() :: how_are_you:metric_key().
@ -67,15 +67,15 @@ get_ranch_info() ->
get_active_connections() ->
F = fun({Ref, Info}) ->
Nconns = case lists:keyfind(active_connections, 1, Info) of
false -> 0;
{_, N} -> N
end,
Nconns =
case lists:keyfind(active_connections, 1, Info) of
false -> 0;
{_, N} -> N
end,
{Ref, Nconns}
end,
lists:map(F, get_ranch_info()).
-spec gauge(metric_key(), metric_value()) ->
metric().
-spec gauge(metric_key(), metric_value()) -> metric().
gauge(Key, Value) ->
how_are_you:metric_construct(gauge, Key, Value).

View File

@ -1,14 +1,16 @@
-module(woody_caching_client).
-include("woody_defs.hrl").
%% API
-export_type([cache_control/0]).
-export_type([cache_options/0]).
-export_type([options /0]).
-export_type([options/0]).
-export([child_spec/2]).
-export([start_link/1]).
-export([call /3]).
-export([call /4]).
-export([call/3]).
-export([call/4]).
%% Internal API
-export([call_safe/4]).
@ -16,40 +18,39 @@
%%
%% API
%%
-type cache_control() :: cache | {cache_for, TimeoutMs::non_neg_integer()} | no_cache.
-type cache_control() :: cache | {cache_for, TimeoutMs :: non_neg_integer()} | no_cache.
-type cache_options() :: #{
local_name => atom(),
type => set | ordered_set,
policy => lru | mru,
memory => integer(),
size => integer(),
n => integer(),
ttl => integer(), %% seconds
check => integer(),
stats => function() | {module(), atom()},
heir => atom() | pid()
type => set | ordered_set,
policy => lru | mru,
memory => integer(),
size => integer(),
n => integer(),
%% seconds
ttl => integer(),
check => integer(),
stats => function() | {module(), atom()},
heir => atom() | pid()
}.
-type options() :: #{
workers_name := atom(),
cache := cache_options(),
woody_client := woody_client:options(),
joint_control => joint | no_joint
workers_name := atom(),
cache := cache_options(),
woody_client := woody_client:options(),
joint_control => joint | no_joint
}.
-spec child_spec(atom(), options()) ->
supervisor:child_spec().
-spec child_spec(atom(), options()) -> supervisor:child_spec().
child_spec(ChildID, Options) ->
#{
id => ChildID,
start => {?MODULE, start_link, [Options]},
restart => permanent,
type => supervisor
id => ChildID,
start => {?MODULE, start_link, [Options]},
restart => permanent,
type => supervisor
}.
-spec start_link(options()) ->
genlib_gen:start_ret().
-spec start_link(options()) -> genlib_gen:start_ret().
start_link(Options) ->
genlib_adhoc_supervisor:start_link(
#{strategy => one_for_one},
@ -61,21 +62,20 @@ start_link(Options) ->
).
-spec call(woody:request(), cache_control(), options()) ->
{ok, woody:result()} |
{exception, woody_error:business_error()} |
no_return().
{ok, woody:result()}
| {exception, woody_error:business_error()}
| no_return().
call(Request, CacheControl, Options) ->
call(Request, CacheControl, Options, woody_context:new()).
-spec call(woody:request(), cache_control(), options(), woody_context:ctx()) ->
{ok, woody:result()} |
{exception, woody_error:business_error()} |
no_return().
{ok, woody:result()}
| {exception, woody_error:business_error()}
| no_return().
call(Request, CacheControl, #{joint_control := joint} = Options, Context) ->
Task =
fun(_) ->
call_safe(Request, CacheControl, Options, Context)
end,
Task = fun(_) ->
call_safe(Request, CacheControl, Options, Context)
end,
woody_joint_workers:do(workers_ref(Options), Request, Task, woody_context:get_deadline(Context));
call(Request, CacheControl, Options, Context) ->
call_safe(Request, CacheControl, Options, Context).
@ -85,7 +85,7 @@ call(Request, CacheControl, Options, Context) ->
%%
-spec call_safe(woody:request(), cache_control(), options(), woody_context:ctx()) ->
{ok, woody:result()}
{ok, woody:result()}
| {exception, woody_error:business_error()}.
call_safe(Request, CacheControl, Options, Context) ->
Meta = add_thrift_meta(Request, new_meta(Options, Context)),
@ -97,35 +97,35 @@ call_safe(Request, CacheControl, Options, Context) ->
end.
-spec do_call(woody:request(), cache_control(), options(), woody_context:ctx(), map()) ->
{ok, woody:result()}
{ok, woody:result()}
| {exception, woody_error:business_error()}.
do_call(Request, CacheControl, Options, Context, Meta) ->
Result = case get_from_cache(Request, CacheControl, Options) of
OK={ok, _CacheResult} ->
% cache hit
ok = emit_event(?EV_CLIENT_CACHE_HIT, Meta, Context, Options),
OK;
not_found ->
% cache miss
ok = emit_event(?EV_CLIENT_CACHE_MISS, Meta, Context, Options),
case woody_client:call(Request, woody_client_options(Options), Context) of
{ok, CallResult} ->
% cache update
ok = emit_event(?EV_CLIENT_CACHE_UPDATE, Meta#{result => CallResult}, Context, Options),
ok = update_cache(Request, CallResult, CacheControl, Options),
{ok, CallResult};
Exception = {exception, _} ->
Exception
end
end,
Result =
case get_from_cache(Request, CacheControl, Options) of
OK = {ok, _CacheResult} ->
% cache hit
ok = emit_event(?EV_CLIENT_CACHE_HIT, Meta, Context, Options),
OK;
not_found ->
% cache miss
ok = emit_event(?EV_CLIENT_CACHE_MISS, Meta, Context, Options),
case woody_client:call(Request, woody_client_options(Options), Context) of
{ok, CallResult} ->
% cache update
ok = emit_event(?EV_CLIENT_CACHE_UPDATE, Meta#{result => CallResult}, Context, Options),
ok = update_cache(Request, CallResult, CacheControl, Options),
{ok, CallResult};
Exception = {exception, _} ->
Exception
end
end,
ok = emit_event(?EV_CLIENT_CACHE_RESULT, Meta#{result => Result}, Context, Options),
Result.
%%
%% local
%%
-spec get_from_cache(_Key, cache_control(), options()) ->
not_found | {ok, _Value}.
-spec get_from_cache(_Key, cache_control(), options()) -> not_found | {ok, _Value}.
get_from_cache(_, no_cache, _) ->
not_found;
get_from_cache(Key, CacheControl, Options) ->
@ -139,8 +139,7 @@ get_from_cache(Key, CacheControl, Options) ->
{ok, Value}
end.
-spec update_cache(_Key, _Value, cache_control(), options()) ->
ok.
-spec update_cache(_Key, _Value, cache_control(), options()) -> ok.
update_cache(_, _, no_cache, _) ->
ok;
update_cache(Key, Value, cache, Options) ->
@ -150,80 +149,68 @@ update_cache(Key, Value, {cache_for, LifetimeMs}, Options) ->
%%
-spec workers_reg_name(options()) ->
genlib_gen:reg_name().
-spec workers_reg_name(options()) -> genlib_gen:reg_name().
workers_reg_name(#{workers_name := Name}) ->
{local, Name}.
-spec workers_ref(options()) ->
genlib_gen:ref().
-spec workers_ref(options()) -> genlib_gen:ref().
workers_ref(#{workers_name := Name}) ->
Name.
-spec cache_child_spec(atom(), options()) ->
supervisor:child_spec().
-spec cache_child_spec(atom(), options()) -> supervisor:child_spec().
cache_child_spec(ChildID, Options) ->
#{
id => ChildID,
start => {cache, start_link, [cache_name(Options), cache_options(Options)]},
restart => permanent,
type => supervisor
id => ChildID,
start => {cache, start_link, [cache_name(Options), cache_options(Options)]},
restart => permanent,
type => supervisor
}.
-spec cache_name(options()) ->
atom().
-spec cache_name(options()) -> atom().
cache_name(#{cache := #{local_name := Name}}) ->
Name.
-spec cache_options(options()) ->
list().
-spec cache_options(options()) -> list().
cache_options(#{cache := Options}) ->
maps:to_list(Options).
-spec woody_client_options(options()) ->
woody_client:options().
-spec woody_client_options(options()) -> woody_client:options().
woody_client_options(#{woody_client := Options}) ->
Options.
-spec now_ms() ->
integer().
-spec now_ms() -> integer().
now_ms() ->
% The cache library uses os:timestamp/0 to get the current time, so just do the same
os:system_time(millisecond).
-spec emit_event(woody_event_handler:event(), map(), woody_context:ctx(), options()) ->
ok.
-spec emit_event(woody_event_handler:event(), map(), woody_context:ctx(), options()) -> ok.
emit_event(Event, Meta, #{rpc_id := RPCID}, Options) ->
_ = woody_event_handler:handle_event(woody_event_handler(Options), Event, RPCID, Meta),
ok.
-spec woody_event_handler(options()) ->
woody:ev_handlers().
-spec woody_event_handler(options()) -> woody:ev_handlers().
woody_event_handler(#{woody_client := #{event_handler := EventHandler}}) ->
EventHandler.
-spec url(options()) ->
woody:url().
-spec url(options()) -> woody:url().
url(#{woody_client := #{url := URL}}) ->
URL.
-spec new_meta(options(), woody_context:ctx()) ->
map().
-spec new_meta(options(), woody_context:ctx()) -> map().
new_meta(Options, Context) ->
#{
role => client,
role => client,
metadata => woody_context:get_meta(Context),
url => url(Options)
url => url(Options)
}.
%% FIXME протекающая абстракция
-spec add_thrift_meta(woody:request(), map()) ->
map().
-spec add_thrift_meta(woody:request(), map()) -> map().
add_thrift_meta({Service = {_, ServiceName}, Function, Args}, Meta) ->
Meta#{
service => ServiceName,
service => ServiceName,
service_schema => Service,
function => Function,
type => woody_util:get_rpc_type(Service, Function),
args => Args
function => Function,
type => woody_util:get_rpc_type(Service, Function),
args => Args
}.

View File

@ -6,20 +6,18 @@
-export([get_common_name/1]).
-opaque cert() :: public_key:der_encoded() | #'OTPCertificate'{} | undefined.
-type common_name() :: binary().
-export_type([cert/0, common_name/0]).
%%% API
-spec from_req(cowboy_req:req()) ->
cert().
-spec from_req(cowboy_req:req()) -> cert().
from_req(Req) ->
cowboy_req:cert(Req).
-spec get_common_name(cert()) ->
common_name() | undefined.
-spec get_common_name(cert()) -> common_name() | undefined.
get_common_name(undefined) ->
undefined;
get_common_name(Cert) when is_binary(Cert) ->
@ -35,9 +33,11 @@ get_common_name(#'OTPCertificate'{tbsCertificate = TbsCert}) ->
%%% Internal functions
get_cn_from_rdn({rdnSequence, RDNSeq}) ->
[to_binary(V) ||
ATVs <- RDNSeq,
#'AttributeTypeAndValue'{type = ?'id-at-commonName', value = {_T, V}} <- ATVs];
[
to_binary(V)
|| ATVs <- RDNSeq,
#'AttributeTypeAndValue'{type = ?'id-at-commonName', value = {_T, V}} <- ATVs
];
get_cn_from_rdn(_) ->
[].

View File

@ -7,48 +7,49 @@
%% API
-export([child_spec/1]).
-export([call /2]).
-export([call /3]).
-export([call/2]).
-export([call/3]).
%% Types
-type options() :: #{
url := woody:url(),
event_handler := woody:ev_handlers(),
protocol => thrift,
transport => http,
url := woody:url(),
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]).
%% Internal API
-type result() ::
{ok , woody:result ()} |
{error , woody_error:error()}.
{ok, woody:result()}
| {error, woody_error:error()}.
-export_type([result/0]).
%%
%% API
%%
-spec child_spec(options()) ->
supervisor:child_spec().
-spec child_spec(options()) -> supervisor:child_spec().
child_spec(Options) ->
woody_client_behaviour:child_spec(Options).
-spec call(woody:request(), options()) ->
{ok, woody:result()} |
{exception, woody_error:business_error()} |
no_return().
{ok, woody:result()}
| {exception, woody_error:business_error()}
| no_return().
call(Request, Options) ->
call(Request, Options, woody_context:new()).
-spec call(woody:request(), options(), woody_context:ctx()) ->
{ok, woody:result()} |
{exception, woody_error:business_error()} |
no_return().
{ok, woody:result()}
| {exception, woody_error:business_error()}
| no_return().
call(Request, Options = #{event_handler := EvHandler}, Context) ->
Child = woody_context:new_child(Context),
WoodyState = woody_state:new(client, Child, EvHandler),
@ -64,14 +65,13 @@ call(Request, Options = #{event_handler := EvHandler}, Context) ->
%%
%% Internal functions
%%
-spec call_safe(woody:request(), options(), woody_state:st()) ->
result().
-spec call_safe(woody:request(), options(), woody_state:st()) -> result().
call_safe(Request, Options, WoodyState) ->
_ = woody_event_handler:handle_event(?EV_CLIENT_BEGIN, WoodyState, #{}),
try woody_client_behaviour:call(Request, Options, WoodyState) of
Resp = {ok, _} ->
Resp;
Error = {error, {Type, _}} when Type =:= system ; Type =:= business ->
Error = {error, {Type, _}} when Type =:= system; Type =:= business ->
Error
catch
Class:Reason:Stacktrace ->
@ -85,10 +85,10 @@ call_safe(Request, Options, WoodyState) ->
handle_client_error(Class, Error, Stacktrace, WoodyState) ->
Details = woody_error:format_details(Error),
_ = woody_event_handler:handle_event(?EV_INTERNAL_ERROR, WoodyState, #{
error => woody_util:to_binary([?EV_CALL_SERVICE, " error"]),
class => Class,
reason => Details,
stack => Stacktrace,
final => false
error => woody_util:to_binary([?EV_CALL_SERVICE, " error"]),
class => Class,
reason => Details,
stack => Stacktrace,
final => false
}),
{error, {system, {internal, result_unexpected, <<"client error: ", Details/binary>>}}}.

View File

@ -1,20 +1,18 @@
-module(woody_client_behaviour).
-export([child_spec/1]).
-export([call /3]).
-export([call/3]).
%% Behaviour definition
-callback call(woody:request(), woody_client:options(), woody_state:st()) -> woody_client:result().
-callback call(woody:request(), woody_client:options(), woody_state:st()) -> woody_client:result().
-callback child_spec(woody_client:options()) -> supervisor:child_spec().
-spec child_spec(woody_client:options()) ->
supervisor:child_spec().
-spec child_spec(woody_client:options()) -> supervisor:child_spec().
child_spec(Options) ->
Handler = woody_util:get_protocol_handler(client, Options),
Handler:child_spec(Options).
-spec call(woody:request(), woody_client:options(), woody_state:st()) ->
woody_client:result().
-spec call(woody:request(), woody_client:options(), woody_state:st()) -> woody_client:result().
call(Request, Options, WoodyState) ->
Handler = woody_util:get_protocol_handler(client, Options),
Handler:call(Request, Options, WoodyState).

View File

@ -28,11 +28,11 @@ increment_counter(Key) ->
-spec increment_counter(any(), number()) -> ok | {error, term()}.
increment_counter([hackney, _Host, _], _) ->
ok; % we don't need per host metrics
% we don't need per host metrics
ok;
increment_counter(Key, Value) ->
update_metric(counter, Key, Value).
-spec decrement_counter(any()) -> ok | {error, term()}.
decrement_counter(Key) ->
decrement_counter(Key, 1).
@ -47,7 +47,7 @@ update_histogram(Key, Value) ->
-spec update_gauge(any(), number()) -> ok | {error, term()}.
update_gauge(Key, Value) ->
update_metric(gauge, Key, Value).
update_metric(gauge, Key, Value).
-spec update_meter(any(), number()) -> ok | {error, term()}.
update_meter(Key, Value) ->
@ -72,7 +72,6 @@ update_metric(Type, Key0, Value) ->
tag_key(Key) when is_list(Key) ->
[woody, client | Key].
is_allowed_metric([hackney_pool, _, Metric]) ->
lists:member(Metric, get_allowed_pool_metrics());
is_allowed_metric(Key) ->

View File

@ -3,20 +3,21 @@
-behaviour(woody_client_behaviour).
-include_lib("thrift/include/thrift_constants.hrl").
-include("woody_defs.hrl").
%% woody_client_behaviour callback
-export([call /3]).
-export([call/3]).
-export([child_spec/1]).
%% Types
-type options() :: #{
url := woody:url(),
event_handler := woody:ev_handlers(),
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
resolver_opts => woody_resolver:options(),
protocol => thrift,
transport => http
}.
-type thrift_client() :: term().
@ -26,24 +27,22 @@
%%
%% API
%%
-spec child_spec(options()) ->
supervisor:child_spec().
-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(), options(), woody_state:st()) ->
woody_client:result().
-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 => 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)
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
),
@ -53,8 +52,7 @@ call({Service = {_, ServiceName}, Function, Args}, Opts, WoodyState) ->
%%
%% Internal functions
%%
-spec make_thrift_client(woody:service(), options(), woody_state:st()) ->
thrift_client().
-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(
woody_client_thrift_http_transport:new(
@ -68,20 +66,19 @@ make_thrift_client(Service, Opts = #{url := Url}, WoodyState) ->
{ok, Client} = thrift_client:new(Protocol, Service),
Client.
-spec get_transport_opts(options()) ->
woody_client_thrift_http_transport:transport_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(options()) ->
woody_resolver:options().
-spec get_resolver_opts(options()) -> woody_resolver:options().
get_resolver_opts(Opts) ->
maps:get(resolver_opts, Opts, #{}).
-spec do_call(thrift_client(), woody:func(), woody:args(), woody_state:st()) ->
woody_client:result().
-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, tuple_to_list(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()}}};
@ -105,8 +102,7 @@ log_result({error, {business, ThriftExcept}}, WoodyState) ->
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().
-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 ->

View File

@ -1,23 +1,25 @@
-module(woody_client_thrift_http_transport).
-behaviour(thrift_transport).
-dialyzer(no_undefined_callbacks).
-include("woody_defs.hrl").
-include_lib("hackney/include/hackney_lib.hrl").
%% API
-export([new /4]).
-export([new/4]).
-export([child_spec/1]).
%% Thrift transport callbacks
-export([read/2, write/2, flush/1, close/1]).
%% Types
%% See hackney:request/5 for available options.
-type transport_options() :: map().
-export_type([transport_options/0]).
-define(DEFAULT_TRANSPORT_OPTIONS, #{
@ -31,20 +33,20 @@
}).
-type woody_transport() :: #{
url := woody:url(),
woody_state := woody_state:st(),
url := woody:url(),
woody_state := woody_state:st(),
transport_options := transport_options(),
resolver_options := woody_resolver:options(),
write_buffer := binary(),
read_buffer := binary()
resolver_options := woody_resolver:options(),
write_buffer := binary(),
read_buffer := binary()
}.
-type error() :: {error, {system, woody_error:system_error()}}.
-type error() :: {error, {system, woody_error:system_error()}}.
-type header_parse_value() :: none | woody:http_header_val().
-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(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">>).
%%
%% API
@ -53,17 +55,16 @@
thrift_transport:t_transport() | no_return().
new(Url, Opts, ResOpts, WoodyState) ->
{ok, Transport} = thrift_transport:new(?MODULE, #{
url => Url,
url => Url,
transport_options => Opts,
resolver_options => ResOpts,
woody_state => WoodyState,
write_buffer => <<>>,
read_buffer => <<>>
resolver_options => ResOpts,
woody_state => WoodyState,
write_buffer => <<>>,
read_buffer => <<>>
}),
Transport.
-spec child_spec(transport_options()) ->
supervisor:child_spec().
-spec child_spec(transport_options()) -> supervisor:child_spec().
child_spec(Options) ->
Name = maps:get(pool, Options, undefined),
hackney_pool:child_spec(Name, maps:to_list(Options)).
@ -71,15 +72,11 @@ child_spec(Options) ->
%%
%% Thrift transport callbacks
%%
-spec write(woody_transport(), binary()) ->
{woody_transport(), ok}.
write(Transport = #{write_buffer := WBuffer}, Data) when
is_binary(WBuffer), is_binary(Data)
->
-spec write(woody_transport(), binary()) -> {woody_transport(), ok}.
write(Transport = #{write_buffer := WBuffer}, Data) when is_binary(WBuffer), is_binary(Data) ->
{Transport#{write_buffer => <<WBuffer/binary, Data/binary>>}, ok}.
-spec read(woody_transport(), pos_integer()) ->
{woody_transport(), {ok, binary()}}.
-spec read(woody_transport(), pos_integer()) -> {woody_transport(), {ok, binary()}}.
read(Transport = #{read_buffer := RBuffer}, Len) when is_binary(RBuffer) ->
Give = min(byte_size(RBuffer), Len),
<<Data:Give/binary, RBuffer1/binary>> = RBuffer,
@ -87,25 +84,29 @@ read(Transport = #{read_buffer := RBuffer}, Len) when is_binary(RBuffer) ->
Transport1 = Transport#{read_buffer => RBuffer1},
{Transport1, Response}.
-spec flush(woody_transport()) ->
{woody_transport(), ok | error()}.
flush(Transport = #{
url := Url,
woody_state := WoodyState,
transport_options := Options,
resolver_options := ResOpts,
write_buffer := WBuffer,
read_buffer := RBuffer
}) when is_binary(WBuffer), is_binary(RBuffer) ->
case handle_result(
send(Url, WBuffer, Options, ResOpts, WoodyState),
WoodyState
) of
-spec flush(woody_transport()) -> {woody_transport(), ok | error()}.
flush(
Transport = #{
url := Url,
woody_state := WoodyState,
transport_options := Options,
resolver_options := ResOpts,
write_buffer := WBuffer,
read_buffer := RBuffer
}
) when is_binary(WBuffer), is_binary(RBuffer) ->
case
handle_result(
send(Url, WBuffer, Options, ResOpts, WoodyState),
WoodyState
)
of
{ok, Response} ->
{Transport#{
read_buffer => Response,
write_buffer => <<>>
}, ok};
read_buffer => Response,
write_buffer => <<>>
},
ok};
Error ->
{Transport#{read_buffer => <<>>, write_buffer => <<>>}, Error}
end.
@ -122,7 +123,7 @@ send(Url, Body, Options, ResOpts, WoodyState) ->
% reusing keep-alive connections to dead hosts
case woody_resolver:resolve_url(Url, WoodyState, ResOpts) of
{ok, {OldUrl, NewUrl}} ->
Headers = add_host_header(OldUrl, make_woody_headers(Context)),
Headers = add_host_header(OldUrl, make_woody_headers(Context)),
Options1 = set_defaults(Options),
Options2 = set_timeouts(Options1, Context),
HeaderList = maps:to_list(Headers),
@ -154,15 +155,17 @@ set_timeouts(Options, Context) ->
%% 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)
maps:merge(
#{
connect_timeout => ConnectTimeout,
send_timeout => SendTimeout,
recv_timeout => Timeout
},
Options
)
end.
-spec deadline_to_timeout(woody:deadline()) ->
non_neg_integer().
-spec deadline_to_timeout(woody:deadline()) -> non_neg_integer().
deadline_to_timeout(Deadline) ->
try
woody_deadline:to_timeout(Deadline)
@ -171,14 +174,15 @@ deadline_to_timeout(Deadline) ->
0
end.
-define(DEFAULT_CONNECT_AND_SEND_TIMEOUT, 1000). %% millisec
%% millisec
-define(DEFAULT_CONNECT_AND_SEND_TIMEOUT, 1000).
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 Timeout div 5 of
T when (T*2) > ?DEFAULT_CONNECT_AND_SEND_TIMEOUT ->
T when (T * 2) > ?DEFAULT_CONNECT_AND_SEND_TIMEOUT ->
?DEFAULT_CONNECT_AND_SEND_TIMEOUT;
T ->
T
@ -187,26 +191,25 @@ calc_timeouts(Timeout) ->
is_deadline_reached(Context) ->
woody_deadline:is_reached(woody_context:get_deadline(Context)).
-spec close(woody_transport()) ->
{woody_transport(), ok}.
-spec close(woody_transport()) -> {woody_transport(), ok}.
close(Transport) ->
{Transport#{}, ok}.
%%
%% Internal functions
%%
-spec handle_result(_, woody_state:st()) ->
{ok, woody:http_body()} | error().
-spec handle_result(_, woody_state:st()) -> {ok, woody:http_body()} | error().
handle_result({ok, 200, Headers, Ref}, WoodyState) ->
Meta = case check_error_reason(Headers, 200, WoodyState) of
<<>> -> #{};
Reason -> #{reason => Reason}
end,
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_result({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}),
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, code => Code, reason => Details}),
%% Free the connection
case hackney:skip_body(Ref) of
ok ->
@ -220,25 +223,25 @@ handle_result({error, {closed, _}}, WoodyState) ->
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, reason => Reason}),
{error, {system, {external, result_unknown, Reason}}};
handle_result({error, Reason}, WoodyState) when
Reason =:= timeout ;
Reason =:= econnaborted ;
Reason =:= enetreset ;
Reason =:= econnreset ;
Reason =:= eshutdown ;
Reason =:= etimedout ;
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_result({error, Reason}, WoodyState) when
Reason =:= econnrefused ;
Reason =:= connect_timeout ;
Reason =:= checkout_timeout;
Reason =:= enetdown ;
Reason =:= enetunreach ;
Reason =:= ehostunreach ;
Reason =:= eacces ;
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),
@ -251,8 +254,7 @@ handle_result({error, Reason}, WoodyState) ->
_ = log_event(?EV_CLIENT_RECEIVE, WoodyState, #{status => error, reason => Details}),
{error, {system, {internal, result_unexpected, Details}}}.
-spec get_body({ok, woody:http_body()} | {error, atom()}, woody_state:st()) ->
{ok, woody:http_body()} | error().
-spec get_body({ok, woody:http_body()} | {error, atom()}, woody_state:st()) -> {ok, woody:http_body()} | error().
get_body(B = {ok, _}, _) ->
B;
get_body({error, Reason}, WoodyState) ->
@ -266,8 +268,7 @@ check_error_headers(502, 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().
-spec get_error_class(woody:http_code()) -> woody_error:class().
get_error_class(503) ->
resource_unavailable;
get_error_class(504) ->
@ -290,13 +291,11 @@ 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 check_error_reason(woody:http_headers(), woody:http_code(), woody_state:st()) ->
woody_error:details().
-spec check_error_reason(woody: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().
-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) ->
@ -305,8 +304,7 @@ do_check_error_reason(none, Code, WoodyState) ->
do_check_error_reason(Reason, _, _) ->
Reason.
-spec get_error_class_header_value(woody:http_headers()) ->
header_parse_value().
-spec get_error_class_header_value(woody: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 ->
@ -315,34 +313,30 @@ get_error_class_header_value(Headers) ->
genlib_string:to_lower(Value)
end.
-spec get_header_value(woody:http_header_name(), woody:http_headers()) ->
header_parse_value().
-spec get_header_value(woody:http_header_name(), woody:http_headers()) -> header_parse_value().
get_header_value(Name, Headers) ->
% Couldn't find a way to easily do same with maps
HeaderList = maps:to_list(Headers),
case [V || {K, V} <- HeaderList, Name =:= genlib_string:to_lower(K)] of
[Value] -> Value;
[] -> none
[] -> none
end.
-spec make_woody_headers(woody_context:ctx()) ->
woody:http_headers().
-spec make_woody_headers(woody_context:ctx()) -> woody: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),
<<"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(), woody:http_headers()) ->
woody:http_headers().
-spec add_optional_headers(woody_context:ctx(), woody:http_headers()) -> woody:http_headers().
add_optional_headers(Context, Headers) ->
add_deadline_header(Context, add_metadata_headers(Context, Headers)).
-spec add_metadata_headers(woody_context:ctx(), woody:http_headers()) ->
woody:http_headers().
-spec add_metadata_headers(woody_context:ctx(), woody:http_headers()) -> woody:http_headers().
add_metadata_headers(Context, Headers) ->
maps:fold(fun add_metadata_header/3, Headers, woody_context:get_meta(Context)).

View File

@ -3,26 +3,28 @@
-behaviour(woody_client_behaviour).
-include_lib("thrift/include/thrift_constants.hrl").
-include("woody_defs.hrl").
%% woody_client_behaviour callback
-export([call /3]).
-export([call/3]).
-export([child_spec/1]).
%% Types
-type options() :: #{
url := woody:url(),
event_handler := woody:ev_handlers(),
url := woody:url(),
event_handler := woody:ev_handlers(),
transport_opts => transport_options(),
resolver_opts => woody_resolver:options(),
protocol => thrift,
transport => http
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
%% millisec
-define(DEFAULT_CONNECT_AND_SEND_TIMEOUT, 1000).
-define(DEFAULT_TRANSPORT_OPTIONS, #{
connect_options => [
% Turn TCP_NODELAY on.
@ -36,26 +38,24 @@
%%
%% API
%%
-spec child_spec(options()) ->
supervisor:child_spec().
-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().
-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 => 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)
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
),
@ -72,9 +72,9 @@ call({Service = {_, ServiceName}, Function, Args}, Opts, WoodyState) ->
-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(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, <<
@ -83,36 +83,33 @@ call({Service = {_, ServiceName}, Function, Args}, Opts, WoodyState) ->
>>}
).
-spec get_transport_opts(options()) ->
woody_client_thrift_http_transport:transport_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(options()) ->
woody_resolver:options().
-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().
-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,
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.
-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
@ -134,9 +131,11 @@ handle_result(Service, Function, Response) ->
Error
end.
%% erlfmt-ignore
-spec send_call(?CODEC:buffer(), options(), woody_state:st()) ->
{ok, binary()} | {error, {system, _}}.
send_call(Buffer, Opts = #{url := Url}, WoodyState) ->
send_call(Buffer, #{url := Url} = Opts, WoodyState) ->
Context = woody_state:get_context(WoodyState),
TransportOpts = get_transport_opts(Opts),
ResolverOpts = get_resolver_opts(Opts),
@ -177,11 +176,14 @@ set_timeouts(Options, Context) ->
%% 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)
maps:merge(
#{
connect_timeout => ConnectTimeout,
send_timeout => SendTimeout,
recv_timeout => Timeout
},
Options
)
end.
calc_timeouts(Timeout) ->
@ -189,30 +191,27 @@ calc_timeouts(Timeout) ->
%% 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 ->
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().
-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)}
{<<"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().
-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().
-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)).
@ -232,13 +231,13 @@ do_add_deadline_header(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()}}.
-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,
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) ->
@ -257,25 +256,25 @@ handle_response({error, {closed, _}}, WoodyState) ->
_ = 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 =:= 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 ;
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),
@ -288,13 +287,11 @@ handle_response({error, Reason}, WoodyState) ->
_ = 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().
-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().
-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) ->
@ -310,8 +307,7 @@ check_error_headers(502, 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().
-spec get_error_class(woody:http_code()) -> woody_error:class().
get_error_class(503) ->
resource_unavailable;
get_error_class(504) ->
@ -342,8 +338,7 @@ check_502_error_class(Bad, _, WoodyState) ->
),
{result_unexpected, ?BAD_RESP_HEADER}.
-spec get_error_class_header_value(http_headers()) ->
header_parse_value().
-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 ->
@ -352,12 +347,11 @@ get_error_class_header_value(Headers) ->
genlib_string:to_lower(Value)
end.
-spec get_header_value(woody:http_header_name(), http_headers()) ->
header_parse_value().
-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
case lists:dropwhile(fun({K, _}) -> Name /= genlib_string:to_lower(K) end, Headers) of
[{_, Value} | _] -> Value;
[] -> none
[] -> none
end.
-spec get_body({ok, woody:http_body()} | {error, atom()}, woody_state:st()) ->
@ -375,8 +369,7 @@ log_result({error, {business, ThriftExcept}}, WoodyState) ->
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().
-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 ->

View File

@ -33,54 +33,46 @@
-export_type([meta_key/0]).
-type ctx() :: #{
rpc_id := woody:rpc_id(),
deadline := woody:deadline(),
meta => meta(),
cert => woody:cert()
rpc_id := woody:rpc_id(),
deadline := woody:deadline(),
meta => meta(),
cert => woody:cert()
}.
-type meta_value() :: binary().
-type meta_key() :: binary().
-type meta() :: #{meta_key() => meta_value()}.
-type meta_key() :: binary().
-type meta() :: #{meta_key() => meta_value()}.
-define(ROOT_REQ_PARENT_ID, <<"undefined">>).
%%
%% API
%%
-spec new() ->
ctx().
-spec new() -> ctx().
new() ->
new(new_req_id()).
-spec new(woody:req_id() | woody:rpc_id()) ->
ctx().
-spec new(woody:req_id() | woody:rpc_id()) -> ctx().
new(Id) ->
new(Id, undefined).
-spec new(woody:rpc_id() | woody:trace_id(), meta() | undefined) ->
ctx().
-spec new(woody:rpc_id() | woody:trace_id(), meta() | undefined) -> ctx().
new(Id, Meta) ->
new(Id, Meta, undefined).
-spec new(woody:rpc_id() | woody:trace_id(), meta() | undefined, woody:deadline()) ->
ctx().
-spec new(woody:rpc_id() | woody:trace_id(), meta() | undefined, woody:deadline()) -> ctx().
new(Id, Meta, Deadline) ->
make_ctx(expand_rpc_id(Id), Meta, Deadline).
-spec new_child(ctx()) ->
ctx().
-spec new_child(ctx()) -> ctx().
new_child(Context = #{rpc_id := #{trace_id := TraceId, span_id := SpanId}}) ->
Context#{rpc_id => new_rpc_id(SpanId, TraceId, new_req_id())}.
-spec add_meta(ctx(), meta()) ->
ctx().
-spec add_meta(ctx(), meta()) -> ctx().
add_meta(Context, Meta) ->
Context#{meta => append_meta(get_meta(Context), Meta)}.
-spec get_meta(ctx()) ->
meta().
-spec get_meta(ctx()) -> meta().
get_meta(Context) ->
case maps:get(meta, Context, undefined) of
undefined ->
@ -89,45 +81,37 @@ get_meta(Context) ->
Meta
end.
-spec get_meta(meta_key(), ctx()) ->
binary() | undefined.
-spec get_meta(meta_key(), ctx()) -> binary() | undefined.
get_meta(MetaKey, Context) ->
maps:get(MetaKey, maps:get(meta, Context, #{}), undefined).
-spec get_rpc_id(ctx()) ->
woody:rpc_id() | no_return().
-spec get_rpc_id(ctx()) -> woody:rpc_id() | no_return().
get_rpc_id(#{rpc_id := RpcId}) ->
RpcId;
get_rpc_id( _) ->
get_rpc_id(_) ->
error(badarg).
-spec get_rpc_id(woody:dapper_id(), ctx()) ->
woody:req_id() | undefined | no_return().
-spec get_rpc_id(woody:dapper_id(), ctx()) -> woody:req_id() | undefined | no_return().
get_rpc_id(Key, Context) ->
maps:get(Key, get_rpc_id(Context), undefined).
-spec new_rpc_id(woody:span_id()) ->
woody:rpc_id().
-spec new_rpc_id(woody:span_id()) -> woody:rpc_id().
new_rpc_id(SpanId) ->
new_rpc_id(?ROOT_REQ_PARENT_ID, new_req_id(), SpanId).
-spec new_rpc_id(woody:parent_id(), woody:trace_id(), woody:span_id()) ->
woody:rpc_id().
-spec new_rpc_id(woody:parent_id(), woody:trace_id(), woody:span_id()) -> woody:rpc_id().
new_rpc_id(ParentId, TraceId, SpanId) ->
#{
parent_id => ParentId,
trace_id => TraceId,
span_id => SpanId
trace_id => TraceId,
span_id => SpanId
}.
-spec new_req_id() ->
woody:req_id().
-spec new_req_id() -> woody:req_id().
new_req_id() ->
genlib:to_binary(new_unique_int()).
-spec new_unique_int() ->
pos_integer().
-spec new_unique_int() -> pos_integer().
new_unique_int() ->
try snowflake:new(?MODULE) of
<<Id:64>> ->
@ -141,45 +125,38 @@ new_unique_int() ->
woody_error:raise(system, {internal, resource_unavailable, BinReason})
end.
-spec set_deadline(woody:deadline(), ctx()) ->
ctx().
-spec set_deadline(woody:deadline(), ctx()) -> ctx().
set_deadline(Deadline, Context) ->
Context#{deadline => Deadline}.
-spec get_deadline(ctx()) ->
woody:deadline().
-spec get_deadline(ctx()) -> woody:deadline().
get_deadline(#{deadline := Deadline}) ->
Deadline.
-spec set_cert(woody:cert(), ctx()) ->
ctx().
-spec set_cert(woody:cert(), ctx()) -> ctx().
set_cert(Cert, Context) ->
Context#{cert => Cert}.
-spec get_cert(ctx()) ->
woody:cert().
-spec get_cert(ctx()) -> woody:cert().
get_cert(#{cert := Cert}) ->
Cert;
get_cert(_) ->
undefined.
-spec get_common_name(ctx()) ->
woody:common_name() | undefined.
-spec get_common_name(ctx()) -> woody:common_name() | undefined.
get_common_name(Ctx) ->
woody_cert:get_common_name(get_cert(Ctx)).
%%
%% Internal functions
%%
-spec expand_rpc_id(woody:rpc_id() | woody:trace_id()) ->
woody:rpc_id().
-spec expand_rpc_id(woody:rpc_id() | woody:trace_id()) -> woody:rpc_id().
expand_rpc_id(RpcId = #{}) ->
RpcId;
expand_rpc_id(TraceId) ->
new_rpc_id(TraceId).
-spec make_ctx(woody:rpc_id(), meta() | undefined, woody:deadline()) ->
ctx() | no_return().
-spec make_ctx(woody:rpc_id(), meta() | undefined, woody:deadline()) -> ctx() | no_return().
make_ctx(RpcId = #{span_id := _, parent_id := _, trace_id := _}, Meta, Deadline) ->
_ = genlib_map:foreach(fun check_req_id_limit/2, RpcId),
init_meta(#{rpc_id => RpcId, deadline => Deadline}, Meta);
@ -197,8 +174,7 @@ init_meta(Context, undefined) ->
init_meta(Context, Meta) ->
Context#{meta => Meta}.
-spec append_meta(meta(), map()) ->
meta() | no_return().
-spec append_meta(meta(), map()) -> meta() | no_return().
append_meta(MetaBase, MetaNew) ->
Meta = maps:merge(MetaNew, MetaBase),
SizeSum = maps:size(MetaBase) + maps:size(MetaNew),

View File

@ -15,22 +15,22 @@
%% Types
-type millisec() :: 0..1000.
-type deadline() :: {calendar:datetime(), millisec()} | undefined. %% deadline may be not set for a request,
%% that's why 'undefined' is here as well.
%% deadline may be not set for a request,
-type deadline() :: {calendar:datetime(), millisec()} | undefined.
%% that's why 'undefined' is here as well.
-export_type([deadline/0, millisec/0]).
%%
%% API
%%
-spec is_reached(deadline()) ->
boolean().
-spec is_reached(deadline()) -> boolean().
is_reached(undefined) ->
false;
is_reached(Deadline) ->
unow() >= to_unixtime_ms(Deadline).
-spec to_timeout(deadline()) ->
timeout().
-spec to_timeout(deadline()) -> timeout().
to_timeout(undefined) ->
infinity;
to_timeout(Deadline) ->
@ -41,16 +41,14 @@ to_timeout(Deadline) ->
erlang:error(deadline_reached, [Deadline])
end.
-spec from_timeout(timeout()) ->
deadline().
-spec from_timeout(timeout()) -> deadline().
from_timeout(infinity) ->
undefined;
from_timeout(TimeoutMillisec) ->
DeadlineMillisec = unow() + TimeoutMillisec,
from_unixtime_ms(DeadlineMillisec).
-spec to_binary(deadline()) ->
binary().
-spec to_binary(deadline()) -> binary().
to_binary(Deadline = undefined) ->
erlang:error(bad_deadline, [Deadline]);
to_binary(Deadline) ->
@ -63,8 +61,7 @@ to_binary(Deadline) ->
erlang:error({bad_deadline, {Error, Stacktrace}}, [Deadline])
end.
-spec from_binary(binary()) ->
deadline().
-spec from_binary(binary()) -> deadline().
from_binary(Bin) ->
ok = assert_is_utc(Bin),
Str = erlang:binary_to_list(Bin),
@ -89,8 +86,7 @@ from_unixtime_ms(DeadlineMillisec) ->
%% Internal functions
%%
-spec assert_is_utc(binary()) ->
ok | no_return().
-spec assert_is_utc(binary()) -> ok | no_return().
assert_is_utc(Bin) ->
Size0 = erlang:byte_size(Bin),
Size1 = Size0 - 1,
@ -106,8 +102,7 @@ assert_is_utc(Bin) ->
erlang:error({bad_deadline, not_utc}, [Bin])
end.
-spec unow() ->
millisec().
-spec unow() -> millisec().
unow() ->
% We must use OS time for communications with external systems
% erlang:system_time/1 may have a various difference with global time to prevent time warp.

View File

@ -2,44 +2,44 @@
-define(_woody_defs_included, yeah).
%% HTTP headers
-define(CONTENT_TYPE_THRIFT , <<"application/x-thrift">>).
-define(CONTENT_TYPE_THRIFT, <<"application/x-thrift">>).
%% Woody-specific HTTP headers
-define(HEADER_PREFIX , <<"woody.">>).
-define(HEADER_RPC_ID , <<?HEADER_PREFIX/binary, "span-id">>).
-define(HEADER_RPC_PARENT_ID , <<?HEADER_PREFIX/binary, "parent-id">>).
-define(HEADER_RPC_ROOT_ID , <<?HEADER_PREFIX/binary, "trace-id">>).
-define(HEADER_E_CLASS , <<?HEADER_PREFIX/binary, "error-class">>).
-define(HEADER_E_REASON , <<?HEADER_PREFIX/binary, "error-reason">>).
-define(HEADER_DEADLINE , <<?HEADER_PREFIX/binary, "deadline">>).
-define(HEADER_META_PREFIX , <<?HEADER_PREFIX/binary, "meta.">>).
-define(HEADER_META_RE , <<"woody\\.meta\\.">>).
-define(HEADER_PREFIX, <<"woody.">>).
-define(HEADER_RPC_ID, <<?HEADER_PREFIX/binary, "span-id">>).
-define(HEADER_RPC_PARENT_ID, <<?HEADER_PREFIX/binary, "parent-id">>).
-define(HEADER_RPC_ROOT_ID, <<?HEADER_PREFIX/binary, "trace-id">>).
-define(HEADER_E_CLASS, <<?HEADER_PREFIX/binary, "error-class">>).
-define(HEADER_E_REASON, <<?HEADER_PREFIX/binary, "error-reason">>).
-define(HEADER_DEADLINE, <<?HEADER_PREFIX/binary, "deadline">>).
-define(HEADER_META_PREFIX, <<?HEADER_PREFIX/binary, "meta.">>).
-define(HEADER_META_RE, <<"woody\\.meta\\.">>).
%% Events
-define(EV_CALL_SERVICE , 'call service').
-define(EV_SERVICE_RESULT , 'service result').
-define(EV_CALL_SERVICE, 'call service').
-define(EV_SERVICE_RESULT, 'service result').
-define(EV_CLIENT_BEGIN , 'client begin').
-define(EV_CLIENT_SEND , 'client send').
-define(EV_CLIENT_RESOLVE_BEGIN , 'client resolve begin').
-define(EV_CLIENT_RESOLVE_RESULT , 'client resolve result').
-define(EV_CLIENT_RECEIVE , 'client receive').
-define(EV_CLIENT_END , 'client end').
-define(EV_CLIENT_BEGIN, 'client begin').
-define(EV_CLIENT_SEND, 'client send').
-define(EV_CLIENT_RESOLVE_BEGIN, 'client resolve begin').
-define(EV_CLIENT_RESOLVE_RESULT, 'client resolve result').
-define(EV_CLIENT_RECEIVE, 'client receive').
-define(EV_CLIENT_END, 'client end').
-define(EV_CLIENT_CACHE_BEGIN , 'client cache begin').
-define(EV_CLIENT_CACHE_HIT , 'client cache hit').
-define(EV_CLIENT_CACHE_MISS , 'client cache miss').
-define(EV_CLIENT_CACHE_UPDATE , 'client cache update').
-define(EV_CLIENT_CACHE_RESULT , 'client cache result').
-define(EV_CLIENT_CACHE_END , 'client cache end').
-define(EV_CLIENT_CACHE_BEGIN, 'client cache begin').
-define(EV_CLIENT_CACHE_HIT, 'client cache hit').
-define(EV_CLIENT_CACHE_MISS, 'client cache miss').
-define(EV_CLIENT_CACHE_UPDATE, 'client cache update').
-define(EV_CLIENT_CACHE_RESULT, 'client cache result').
-define(EV_CLIENT_CACHE_END, 'client cache end').
-define(EV_SERVER_RECEIVE , 'server receive').
-define(EV_SERVER_SEND , 'server send').
-define(EV_SERVER_RECEIVE, 'server receive').
-define(EV_SERVER_SEND, 'server send').
-define(EV_INVOKE_SERVICE_HANDLER , 'invoke service handler').
-define(EV_SERVICE_HANDLER_RESULT , 'service handler result').
-define(EV_INVOKE_SERVICE_HANDLER, 'invoke service handler').
-define(EV_SERVICE_HANDLER_RESULT, 'service handler result').
-define(EV_INTERNAL_ERROR , 'internal error').
-define(EV_TRACE , 'trace event').
-define(EV_INTERNAL_ERROR, 'internal error').
-define(EV_TRACE, 'trace event').
-endif.

View File

@ -14,17 +14,16 @@
-type type() :: business | system.
-type error() ::
{business , business_error()} |
{system , system_error ()}.
{business, business_error()}
| {system, system_error()}.
-type business_error() :: _Error.
-type system_error () :: {source(), class(), details()}.
-type system_error() :: {source(), class(), details()}.
-type source () :: internal | external.
-type class () :: resource_unavailable | result_unexpected | result_unknown.
-type source() :: internal | external.
-type class() :: resource_unavailable | result_unexpected | result_unknown.
-type details() :: binary().
-type erlang_except() :: throw | error | exit.
-type stack() :: list().
@ -33,15 +32,12 @@
%%
%% API
%%
-spec raise(type(), business_error() | system_error()) ->
no_return().
-spec raise(type(), business_error() | system_error()) -> no_return().
raise(business, Except) ->
erlang:throw(Except);
raise(system, {Source, Class, Details}) ->
erlang:error({woody_error, {Source, Class, Details}}).
-spec format_details(term()) ->
details().
-spec format_details(term()) -> details().
format_details(Error) ->
genlib:to_binary(io_lib:format("~9999p", [Error])).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
-type options() :: #{
formatter_opts => woody_event_formatter:opts()
}.
-export_type([options/0]).
%%
@ -16,8 +17,8 @@
-spec handle_event(Event, RpcId, Meta, Opts) -> ok when
Event :: woody_event_handler:event(),
RpcId :: woody:rpc_id() | undefined,
Meta :: woody_event_handler:event_meta(),
Opts :: options().
Meta :: woody_event_handler:event_meta(),
Opts :: options().
handle_event(Event, RpcId, Meta, Opts) ->
EHOpts = get_event_handler_opts(Opts),
{Level, {Format, Msg}} = woody_event_handler:format_event(Event, Meta, RpcId, EHOpts),
@ -25,12 +26,12 @@ handle_event(Event, RpcId, Meta, Opts) ->
_ = error_logger:Function(Format, Msg),
ok.
get_logger_function(Level) when Level =:= debug ; Level =:= info ->
get_logger_function(Level) when Level =:= debug; Level =:= info ->
info_msg;
get_logger_function(warning) ->
warning_msg;
get_logger_function(error) ->
get_logger_function(error) ->
error_msg.
get_event_handler_opts(Opts) ->
Opts.
Opts.

View File

@ -24,26 +24,24 @@
-export([worker_start_link/3]).
-export([worker_init/4]).
-type deadline() :: woody_deadline:deadline().
-type deadline() :: woody_deadline:deadline().
-type task(Result) :: fun((deadline()) -> Result).
-type id() :: _.
-type exception() :: {atom(), term(), list()}.
-type id() :: _.
-type exception() :: {atom(), term(), list()}.
%%
%% API
%%
-spec child_spec(atom(), genlib_gen:reg_name()) ->
supervisor:child_spec().
-spec child_spec(atom(), genlib_gen:reg_name()) -> supervisor:child_spec().
child_spec(ChildID, RegName) ->
#{
id => ChildID,
start => {?MODULE, start_link, [RegName]},
restart => permanent,
type => supervisor
id => ChildID,
start => {?MODULE, start_link, [RegName]},
restart => permanent,
type => supervisor
}.
-spec start_link(genlib_gen:reg_name()) ->
genlib_gen:start_ret().
-spec start_link(genlib_gen:reg_name()) -> genlib_gen:start_ret().
start_link(RegName) ->
genlib_adhoc_supervisor:start_link(
RegName,
@ -51,31 +49,27 @@ start_link(RegName) ->
[worker_child_spec(worker)]
).
-spec do(genlib_gen:ref(), id(), task(Result), deadline()) ->
Result.
-spec do(genlib_gen:ref(), id(), task(Result), deadline()) -> Result.
do(Ref, ID, Task, Deadline) ->
do(Ref, ID, Task, Deadline, 10).
%%
%% Internal API
%%
-spec worker_child_spec(atom()) ->
supervisor:child_spec().
-spec worker_child_spec(atom()) -> supervisor:child_spec().
worker_child_spec(ChildID) ->
#{
id => ChildID,
start => {?MODULE, worker_start_link, []},
restart => temporary,
type => worker
id => ChildID,
start => {?MODULE, worker_start_link, []},
restart => temporary,
type => worker
}.
-spec worker_start_link(id(), task(_), deadline()) ->
genlib_gen:start_ret().
-spec worker_start_link(id(), task(_), deadline()) -> genlib_gen:start_ret().
worker_start_link(ID, Task, Deadline) ->
proc_lib:start_link(?MODULE, worker_init, [ID, self(), Task, Deadline], deadline_to_timeout(Deadline)).
-spec worker_init(id(), pid(), task(_), deadline()) ->
ok.
-spec worker_init(id(), pid(), task(_), deadline()) -> ok.
worker_init(ID, Parent, Task, Deadline) ->
Self = self(),
case gproc:reg_or_locate({n, l, ID}) of
@ -92,8 +86,7 @@ worker_init(ID, Parent, Task, Deadline) ->
%%
%% local
%%
-spec do(genlib_gen:ref(), id(), task(Result), deadline(), non_neg_integer()) ->
Result.
-spec do(genlib_gen:ref(), id(), task(Result), deadline(), non_neg_integer()) -> Result.
do(Ref, ID, Task, Deadline, 0) ->
erlang:error(fatal_retrying_error, [Ref, ID, Task, Deadline]);
do(Ref, ID, Task, Deadline, Attempts) ->
@ -108,7 +101,7 @@ do(Ref, ID, Task, Deadline, Attempts) ->
% когда соединяются запросы с разными дедлайнами
case woody_deadline:is_reached(Deadline) of
false -> do(Ref, ID, Task, Deadline);
true -> erlang:error(deadline_reached, [Ref, ID, Task, Deadline])
true -> erlang:error(deadline_reached, [Ref, ID, Task, Deadline])
end;
{error, {exception, {Class, Error, Stacktrace}}} ->
erlang:Class({Error, Stacktrace});
@ -117,7 +110,7 @@ do(Ref, ID, Task, Deadline, Attempts) ->
end.
-spec wait_for_result(pid(), deadline()) ->
{ok, _Result}
{ok, _Result}
| {error, deadline_reached | race_detected | {worker_error, _Reason} | {exception, exception()}}.
wait_for_result(Pid, Deadline) ->
Timeout = deadline_to_timeout(Deadline),
@ -131,8 +124,7 @@ wait_for_result(Pid, Deadline) ->
erlang:demonitor(MRef, [flush]),
{error, {exception, Exception}};
%% произошла гонка
{'DOWN', MRef, process, Pid, Reason}
when Reason =:= normal; Reason =:= noproc ->
{'DOWN', MRef, process, Pid, Reason} when Reason =:= normal; Reason =:= noproc ->
{error, race_detected};
%% упал воркер по таймауту
{'DOWN', MRef, process, Pid, deadline_reached} ->
@ -145,30 +137,25 @@ wait_for_result(Pid, Deadline) ->
{error, deadline_reached}
end.
-spec broadcast_result(_Result) ->
ok.
-spec broadcast_result(_Result) -> ok.
broadcast_result(Result) ->
receive
{?MODULE, wait_for_result, MRef, Pid} ->
Pid ! {?MODULE, broadcast_result, MRef, Result},
broadcast_result(Result)
after 0 ->
ok
after 0 -> ok
end.
-spec sync_with_employer(timeout()) ->
ok.
-spec sync_with_employer(timeout()) -> ok.
sync_with_employer(Timeout) ->
receive
{?MODULE, wait_for_result, MRef, Pid} ->
self() ! {?MODULE, wait_for_result, MRef, Pid},
ok
after Timeout ->
exit(deadline_reached)
after Timeout -> exit(deadline_reached)
end.
-spec deadline_to_timeout(deadline()) ->
timeout().
-spec deadline_to_timeout(deadline()) -> timeout().
deadline_to_timeout(Deadline) ->
try
woody_deadline:to_timeout(Deadline)
@ -177,16 +164,17 @@ deadline_to_timeout(Deadline) ->
0
end.
-spec set_worker_deadline_timer(deadline()) ->
ok.
-spec set_worker_deadline_timer(deadline()) -> ok.
set_worker_deadline_timer(Deadline) ->
case deadline_to_timeout(Deadline) of
infinity -> ok;
Timeout -> _ = genlib:unwrap(timer:exit_after(Timeout, self(), deadline_reached)), ok
infinity ->
ok;
Timeout ->
_ = genlib:unwrap(timer:exit_after(Timeout, self(), deadline_reached)),
ok
end.
-spec do_task_safe(task(Result), deadline()) ->
{ok, Result} | {exception, exception()}.
-spec do_task_safe(task(Result), deadline()) -> {ok, Result} | {exception, exception()}.
do_task_safe(Task, Deadline) ->
try
{ok, Task(Deadline)}

View File

@ -1,4 +1,5 @@
-module(woody_monitor_h).
-behaviour(cowboy_stream).
-include("woody_defs.hrl").
@ -30,20 +31,20 @@ set_event(Event, Req) ->
%% callbacks
-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
-> {cowboy_stream:commands(), state()}.
-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) -> {cowboy_stream:commands(), state()}.
init(StreamID, Req, Opts) ->
{Commands0, Next} = cowboy_stream:init(StreamID, Req, Opts),
{Commands0, #{next => Next, event_to_emit => ?EV_SERVER_RECEIVE}}.
-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
-> {cowboy_stream:commands(), State} when State::state().
-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State) ->
{cowboy_stream:commands(), State}
when
State :: state().
data(StreamID, IsFin, Data, #{next := Next0} = State) ->
{Commands0, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
{Commands0, State#{next => Next}}.
-spec info(cowboy_stream:streamid(), any(), State)
-> {cowboy_stream:commands(), State} when State::state().
-spec info(cowboy_stream:streamid(), any(), State) -> {cowboy_stream:commands(), State} when State :: state().
info(StreamID, {woody_state, WoodyState} = Info, #{next := Next0} = State) ->
{Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),
{Commands, State#{next => Next, woody_state => WoodyState}};
@ -61,7 +62,8 @@ terminate(
{socket_error, _, HumanReadable} = Reason,
#{woody_state := WoodyState, next := Next, event_to_emit := Event = ?EV_SERVER_RECEIVE}
) ->
woody_event_handler:handle_event(Event,
woody_event_handler:handle_event(
Event,
WoodyState,
#{status => error, reason => woody_util:to_binary(HumanReadable)}
),
@ -71,7 +73,8 @@ terminate(
{socket_error, _, HumanReadable} = Reason,
#{woody_state := WoodyState, next := Next, event_to_emit := Event = ?EV_SERVICE_HANDLER_RESULT}
) ->
woody_event_handler:handle_event(Event,
woody_event_handler:handle_event(
Event,
WoodyState,
#{status => error, class => system, result => woody_util:to_binary(HumanReadable)}
),
@ -79,9 +82,14 @@ terminate(
terminate(StreamID, Reason, #{next := Next}) ->
cowboy_stream:terminate(StreamID, Reason, Next).
-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
when Resp::cowboy_stream:resp_command().
-spec early_error(
cowboy_stream:streamid(),
cowboy_stream:reason(),
cowboy_stream:partial_req(),
Resp,
cowboy:opts()
) -> Resp when
Resp :: cowboy_stream:resp_command().
early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
% We can't really do anything about it
cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).

View File

@ -8,8 +8,8 @@
-type url() :: woody:url().
-type parsed_url() :: #hackney_url{}.
-type resolve_result() :: {Old::parsed_url(), New::parsed_url()}.
-type resolve_error() :: einval | nxdomain | timeout | {unsupported_url_scheme, woody:url()}.
-type resolve_result() :: {Old :: parsed_url(), New :: parsed_url()}.
-type resolve_error() :: einval | nxdomain | timeout | {unsupported_url_scheme, woody:url()}.
-type options() :: #{
ip_picker => ip_picker(),
@ -18,8 +18,8 @@
-type ip_picker() :: {module(), atom()} | predefined_ip_picker().
-type predefined_ip_picker() ::
random |
first.
random
| first.
-export_type([options/0]).
-export_type([ip_picker/0]).
@ -36,14 +36,14 @@
%%
-spec resolve_url(url(), woody_state:st()) ->
{ok, resolve_result()} |
{error, resolve_error()}.
{ok, resolve_result()}
| {error, resolve_error()}.
resolve_url(Url, WoodyState) ->
resolve_url(Url, WoodyState, #{}).
-spec resolve_url(url(), woody_state:st(), options()) ->
{ok, resolve_result()} |
{error, resolve_error()}.
{ok, resolve_result()}
| {error, resolve_error()}.
resolve_url(Url, WoodyState, Opts) when is_list(Url) ->
resolve_url(unicode:characters_to_binary(Url), WoodyState, Opts);
resolve_url(<<"https://", _Rest/binary>> = Url, WoodyState, Opts) ->
@ -60,7 +60,8 @@ parse_url(Url) ->
resolve_parsed_url(ParsedUrl = #hackney_url{}, WoodyState, Opts) ->
case inet:parse_address(ParsedUrl#hackney_url.host) of
{ok, _} -> {ok, {ParsedUrl, ParsedUrl}}; % url host is already an ip, move on
% url host is already an ip, move on
{ok, _} -> {ok, {ParsedUrl, ParsedUrl}};
{error, _} -> do_resolve_url(ParsedUrl, WoodyState, Opts)
end.
@ -138,8 +139,7 @@ apply_ip_picker({M, F}, AddrList) ->
get_ip_family(HostEnt) ->
HostEnt#hostent.h_addrtype.
-spec get_ip_family_preference() ->
[inet:address_family()].
-spec get_ip_family_preference() -> [inet:address_family()].
get_ip_family_preference() ->
case inet_db:res_option(inet6) of
true -> [inet6, inet];
@ -149,4 +149,4 @@ get_ip_family_preference() ->
%%
log_event(Event, WoodyState, ExtraMeta) ->
woody_event_handler:handle_event(Event, WoodyState, ExtraMeta).
woody_event_handler:handle_event(Event, WoodyState, ExtraMeta).

View File

@ -9,15 +9,16 @@
%% Types
-type options() :: #{
event_handler := woody:ev_handlers(),
protocol => thrift,
transport => http,
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
@ -27,14 +28,12 @@
%%
%% API
%%
-spec child_spec(_Id, options()) ->
supervisor:child_spec().
-spec child_spec(_Id, options()) -> supervisor:child_spec().
child_spec(Id, Options) ->
ProtocolHandler = woody_util:get_protocol_handler(server, Options),
ProtocolHandler:child_spec(Id, Options).
-spec get_addr(_Id, options()) ->
{inet:ip_address(), inet:port_number()}.
-spec get_addr(_Id, options()) -> {inet:ip_address(), inet:port_number()}.
get_addr(Id, Options) ->
ProtocolHandler = woody_util:get_protocol_handler(server, Options),
ProtocolHandler:get_addr(Id).

View File

@ -1,8 +1,9 @@
-module(woody_server_http_drainer).
-behaviour(gen_server).
-type options() :: #{
shutdown := timeout(),
shutdown := timeout(),
ranch_ref := ranch:ref()
}.
@ -16,9 +17,7 @@
%% API
-spec child_spec(options()) ->
supervisor:child_spec().
-spec child_spec(options()) -> supervisor:child_spec().
child_spec(Opts) ->
RanchRef = maps:get(ranch_ref, Opts),
Shutdown = get_shutdown_param(Opts),
@ -28,36 +27,26 @@ child_spec(Opts) ->
shutdown => Shutdown
}.
-spec start_link(ranch:ref()) ->
genlib_gen:start_ret().
-spec start_link(ranch:ref()) -> genlib_gen:start_ret().
start_link(RanchRef) ->
gen_server:start_link(?MODULE, RanchRef, []).
%% supervisor callbacks
-spec init(ranch:ref()) ->
{ok, ranch:ref()}.
-spec init(ranch:ref()) -> {ok, ranch:ref()}.
init(RanchRef) ->
process_flag(trap_exit, true),
{ok, RanchRef}.
-spec handle_call(_, _, ranch:ref()) ->
{noreply, ranch:ref()}.
-spec handle_call(_, _, ranch:ref()) -> {noreply, ranch:ref()}.
handle_call(_Call, _From, St) ->
{noreply, St}.
-spec handle_cast(_, ranch:ref()) ->
{noreply, ranch:ref()}.
-spec handle_cast(_, ranch:ref()) -> {noreply, ranch:ref()}.
handle_cast(_Call, St) ->
{noreply, St}.
-spec terminate(_, ranch:ref()) ->
ok.
-spec terminate(_, ranch:ref()) -> ok.
terminate(shutdown, Ref) ->
ok = ranch:suspend_listener(Ref),
ok = ranch:wait_for_connections(Ref, '==', 0);

View File

@ -8,45 +8,48 @@
-include_lib("thrift/include/thrift_constants.hrl").
-include_lib("thrift/include/thrift_protocol.hrl").
-include("woody_defs.hrl").
%% Types
-type client_error() :: {client, woody_error:details()}.
-export_type([client_error/0]).
-type state() :: #{
woody_state := woody_state:st(),
handler := woody:handler(woody:options()),
service := woody:service(),
th_proto := term(),
function => woody:func(),
args => woody:args(),
th_seqid => term(),
woody_state := woody_state:st(),
handler := woody:handler(woody:options()),
service := woody:service(),
th_proto := term(),
function => woody:func(),
args => woody:args(),
th_seqid => term(),
th_param_type => term(),
th_msg_type => thrift_msg_type(),
th_msg_type => thrift_msg_type(),
th_reply_type => thrift_reply_type()
}.
-export_type([state/0]).
-type thrift_msg_type() ::
?tMessageType_CALL |
?tMessageType_ONEWAY |
?tMessageType_REPLY |
?tMessageType_EXCEPTION.
?tMessageType_CALL
| ?tMessageType_ONEWAY
| ?tMessageType_REPLY
| ?tMessageType_EXCEPTION.
-type thrift_reply_type() :: oneway_void | tuple().
-type reply_type() :: oneway_void | call.
-export_type([reply_type/0]).
-type builtin_thrift_error() :: bad_binary_protocol_version | no_binary_protocol_version | _OtherError.
-type thrift_error() :: unknown_function | multiplexed_request | builtin_thrift_error().
-type thrift_error() :: unknown_function | multiplexed_request | builtin_thrift_error().
%% Behaviour definition
-callback handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) ->
{ok, woody:result()} | no_return().
%%
%% API
%%
@ -54,22 +57,27 @@
{ok, reply_type(), state()} | {error, client_error()}.
init_handler(Request, {Service, Handler}, WoodyState) ->
{ok, Transport} = thrift_membuffer_transport:new(Request),
{ok, Proto} = thrift_binary_protocol:new(Transport,
{ok, Proto} = thrift_binary_protocol:new(
Transport,
[{strict_read, true}, {strict_write, true}]
),
try handle_decode_result(decode_request(decode_message_begin(#{
woody_state => WoodyState,
service => Service,
handler => Handler,
th_proto => Proto
})))
try
handle_decode_result(
decode_request(
decode_message_begin(#{
woody_state => WoodyState,
service => Service,
handler => Handler,
th_proto => Proto
})
)
)
catch
throw:{woody_decode_error, Error} ->
handle_decode_error(Error, WoodyState)
end.
-spec invoke_handler(state()) ->
{ok, binary()} | {error, woody_error:error()}.
-spec invoke_handler(state()) -> {ok, binary()} | {error, woody_error:error()}.
invoke_handler(State) ->
{Result, #{th_proto := Proto, th_reply_type := MsgType}} = call_handler_safe(State),
{_, {ok, Reply}} = thrift_protocol:close_transport(Proto),
@ -87,18 +95,19 @@ handle_function(Handler, Function, Args, WoodyState) ->
%%
%% Decode request
-spec decode_message_begin(state()) ->
state() | no_return().
-spec decode_message_begin(state()) -> state() | no_return().
decode_message_begin(State = #{th_proto := Proto}) ->
case thrift_protocol:read(Proto, message_begin) of
{Proto1, #protocol_message_begin{name = Function, type = Type, seqid = SeqId}} when
Type =:= ?tMessageType_CALL orelse
Type =:= ?tMessageType_ONEWAY
Type =:= ?tMessageType_ONEWAY
->
match_reply_type(get_params_type(
get_function_name(Function),
State#{th_proto := Proto1, th_msg_type => Type, th_seqid => SeqId}
));
match_reply_type(
get_params_type(
get_function_name(Function),
State#{th_proto := Proto1, th_msg_type => Type, th_seqid => SeqId}
)
);
{_, {error, Reason}} ->
throw_decode_error(Reason)
end.
@ -108,14 +117,14 @@ get_function_name(Function) ->
[_ServiceName, _FunctionName] ->
throw_decode_error(multiplexed_request);
_ ->
try list_to_existing_atom(Function)
try
list_to_existing_atom(Function)
catch
error:badarg -> throw_decode_error(unknown_function)
end
end.
-spec get_params_type(woody:func() , state()) ->
state() | no_return().
-spec get_params_type(woody:func(), state()) -> state() | no_return().
get_params_type(Function, State = #{service := Service}) ->
try get_function_info(Service, Function, params_type) of
ParamsType ->
@ -124,21 +133,24 @@ get_params_type(Function, State = #{service := Service}) ->
error:badarg -> throw_decode_error(unknown_function)
end.
-spec match_reply_type(state()) ->
state() | no_return().
match_reply_type(State = #{
service := Service,
function := Function,
th_msg_type := ReqType,
woody_state := WoodyState
}) ->
-spec match_reply_type(state()) -> state() | no_return().
match_reply_type(
State = #{
service := Service,
function := Function,
th_msg_type := ReqType,
woody_state := WoodyState
}
) ->
ReplyType = get_function_info(Service, Function, reply_type),
ok = match_reply_type(ReplyType, ReqType),
State#{th_reply_type => ReplyType, woody_state := add_ev_meta(WoodyState, Service, Function, ReplyType)}.
match_reply_type(ReplyType, ReqType) when
ReplyType =:= oneway_void , ReqType =/= ?tMessageType_ONEWAY orelse
ReplyType =/= oneway_void , ReqType =:= ?tMessageType_ONEWAY
ReplyType =:= oneway_void,
ReqType =/= ?tMessageType_ONEWAY orelse
ReplyType =/= oneway_void,
ReqType =:= ?tMessageType_ONEWAY
->
throw_decode_error(request_reply_type_mismatch);
match_reply_type(_, _) ->
@ -148,16 +160,18 @@ add_ev_meta(WoodyState, Args) ->
woody_state:add_ev_meta(#{args => Args}, WoodyState).
add_ev_meta(WoodyState, Service = {_, ServiceName}, Function, ReplyType) ->
woody_state:add_ev_meta(#{
service => ServiceName,
service_schema => Service,
function => Function,
type => woody_util:get_rpc_reply_type(ReplyType),
deadline => woody_context:get_deadline(woody_state:get_context(WoodyState))
}, WoodyState).
woody_state:add_ev_meta(
#{
service => ServiceName,
service_schema => Service,
function => Function,
type => woody_util:get_rpc_reply_type(ReplyType),
deadline => woody_context:get_deadline(woody_state:get_context(WoodyState))
},
WoodyState
).
-spec decode_request(state()) ->
state() | no_return().
-spec decode_request(state()) -> state() | no_return().
decode_request(State = #{th_proto := Proto, th_param_type := ParamsType, woody_state := WoodyState}) ->
case thrift_protocol:read(Proto, ParamsType) of
{Proto1, {ok, Args}} ->
@ -166,24 +180,21 @@ decode_request(State = #{th_proto := Proto, th_param_type := ParamsType, woody_s
throw_decode_error(Error)
end.
-spec handle_decode_result(state()) ->
{ok, reply_type(), state()}.
-spec handle_decode_result(state()) -> {ok, reply_type(), state()}.
handle_decode_result(State = #{th_reply_type := oneway_void}) ->
{ok, oneway_void, State};
handle_decode_result(State) ->
{ok, call, State}.
-spec handle_decode_error(thrift_error(), woody_state:st()) ->
{error, client_error()}.
-spec handle_decode_error(thrift_error(), woody_state:st()) -> {error, client_error()}.
handle_decode_error(Error, WoodyState) ->
_ = woody_event_handler:handle_event(?EV_INTERNAL_ERROR, WoodyState, #{
error => <<"thrift protocol read failed">>,
reason => woody_error:format_details(Error)
}),
error => <<"thrift protocol read failed">>,
reason => woody_error:format_details(Error)
}),
{error, client_error(Error)}.
-spec client_error(thrift_error()) ->
client_error().
-spec client_error(thrift_error()) -> client_error().
client_error({bad_binary_protocol_version, Version}) ->
BinVersion = genlib:to_binary(Version),
{client, <<"thrift: bad binary protocol version: ", BinVersion/binary>>};
@ -198,38 +209,38 @@ client_error(request_reply_type_mismatch) ->
client_error(Reason) ->
{client, woody_util:to_binary(["thrift decode error: ", woody_error:format_details(Reason)])}.
-spec throw_decode_error(_) ->
no_return().
-spec throw_decode_error(_) -> no_return().
throw_decode_error(Error) ->
throw({woody_decode_error, Error}).
%% Handle request
-spec call_handler_safe(state()) ->
{ok | {error, woody_error:error()}, state()}.
-spec call_handler_safe(state()) -> {ok | {error, woody_error:error()}, state()}.
call_handler_safe(State) ->
try handle_success(call_handler(State), State)
try
handle_success(call_handler(State), State)
catch
Class:Reason:Stacktrace ->
handle_function_catch(Class, Reason, Stacktrace, State)
end.
-spec call_handler(state()) ->
{ok, woody:result()} | no_return().
-spec call_handler(state()) -> {ok, woody:result()} | no_return().
call_handler(#{
woody_state := WoodyState,
handler := Handler,
function := Function,
args := Args})
->
handler := Handler,
function := Function,
args := Args
}) ->
handle_function(Handler, Function, Args, WoodyState).
-spec handle_success({ok, woody:result()}, state()) ->
{ok | {error, {system, woody_error:system_error()}}, state()}.
handle_success(Result, State = #{
function := Function,
th_reply_type := ReplyType,
woody_state := WoodyState
}) ->
-spec handle_success({ok, woody:result()}, state()) -> {ok | {error, {system, woody_error:system_error()}}, state()}.
handle_success(
Result,
State = #{
function := Function,
th_reply_type := ReplyType,
woody_state := WoodyState
}
) ->
_ = log_handler_result(ok, WoodyState, #{result => Result}),
StructName = atom_to_list(Function) ++ "_result",
case Result of
@ -245,42 +256,53 @@ handle_success(Result, State = #{
encode_reply(ok, Reply, State#{th_msg_type => ?tMessageType_REPLY})
end.
-spec handle_function_catch(woody_error:erlang_except(), _Except,
woody_error:stack(), state())
->
{{error, woody_error:error()}, state()}.
-spec handle_function_catch(
woody_error:erlang_except(),
_Except,
woody_error:stack(),
state()
) -> {{error, woody_error:error()}, state()}.
handle_function_catch(throw, Except, Stack, State) ->
handle_exception(Except, Stack, State);
handle_function_catch(error, {woody_error, Error = {_, _, _}}, _Stack, State) ->
handle_woody_error(Error, State);
handle_function_catch(Class, Error, Stack, State) when
Class =:= error orelse Class =:= exit
->
handle_function_catch(Class, Error, Stack, State) when Class =:= error orelse Class =:= exit ->
handle_internal_error(Error, Class, Stack, State).
-spec handle_exception(woody_error:business_error() | _Throw, woody_error:stack(), state())
->
-spec handle_exception(woody_error:business_error() | _Throw, woody_error:stack(), state()) ->
{{error, woody_error:error()}, state()}.
handle_exception(Except, Stack, State = #{
service := Service,
function := Function,
th_reply_type := ReplyType,
woody_state := WoodyState
}) ->
handle_exception(
Except,
Stack,
State = #{
service := Service,
function := Function,
th_reply_type := ReplyType,
woody_state := WoodyState
}
) ->
{struct, _, XInfo} = ReplySpec = get_function_info(Service, Function, exceptions),
{ExceptionList, FoundExcept} = lists:mapfoldl(
fun(X, A) -> get_except(Except, X, A) end, undefined, XInfo),
fun(X, A) -> get_except(Except, X, A) end,
undefined,
XInfo
),
case {FoundExcept, ReplyType} of
{undefined, _} ->
handle_internal_error(Except, throw, Stack, State);
{{_Module, _Type}, oneway_void} ->
log_handler_result(error, WoodyState,
#{class => business, result => Except, ignore => true}),
log_handler_result(
error,
WoodyState,
#{class => business, result => Except, ignore => true}
),
{{error, {business, ignore}}, State};
{{Module, Type}, _} ->
log_handler_result(error, WoodyState,
#{class => business, result => Except, ignore => false}),
log_handler_result(
error,
WoodyState,
#{class => business, result => Except, ignore => false}
),
ExceptTuple = list_to_tuple([Function | ExceptionList]),
encode_reply(
{error, {business, genlib:to_binary(get_except_name(Module, Type))}},
@ -316,27 +338,42 @@ handle_woody_error(Error, State = #{woody_state := WoodyState}) ->
-spec handle_internal_error(_Error, woody_error:erlang_except(), woody_error:stack(), state()) ->
{{error, {system, {internal, woody_error:source(), woody_error:details()}}}, state()}.
handle_internal_error(Error, ExcClass, Stack, State = #{woody_state := WoodyState, th_reply_type := oneway_void}) ->
log_handler_result(error, WoodyState,
#{class => system, result => Error, except_class => ExcClass, stack => Stack, ignore => true}),
log_handler_result(
error,
WoodyState,
#{class => system, result => Error, except_class => ExcClass, stack => Stack, ignore => true}
),
{{error, {system, {internal, result_unexpected, <<>>}}}, State};
handle_internal_error(Error, ExcClass, Stack, State = #{woody_state := WoodyState}) ->
log_handler_result(error, WoodyState,
#{class => system, result => Error, except_class => ExcClass, stack => Stack, ignore => false}),
{{error, {system, {internal, result_unexpected,
format_unexpected_error(ExcClass, woody_error:format_details(Error), Stack)}}}, State}.
log_handler_result(
error,
WoodyState,
#{class => system, result => Error, except_class => ExcClass, stack => Stack, ignore => false}
),
{{error,
{system,
{internal, result_unexpected,
format_unexpected_error(ExcClass, woody_error:format_details(Error), Stack)}}},
State}.
-spec encode_reply(ok | {error, woody_error:business_error()}, _Result, state()) ->
{ok | {error, woody_error:error()}, state()}.
encode_reply(Status, Reply, State = #{
th_proto := Proto,
function := Function,
th_msg_type := ReplyMessageType,
th_seqid := SeqId,
woody_state := WoodyState
}) ->
encode_reply(
Status,
Reply,
State = #{
th_proto := Proto,
function := Function,
th_msg_type := ReplyMessageType,
th_seqid := SeqId,
woody_state := WoodyState
}
) ->
try
StartMessage = #protocol_message_begin{
name = atom_to_list(Function), type = ReplyMessageType, seqid = SeqId
name = atom_to_list(Function),
type = ReplyMessageType,
seqid = SeqId
},
{Protocol1, ok} = thrift_protocol:write(Proto, StartMessage),
{Protocol2, ok} = thrift_protocol:write(Protocol1, Reply),
@ -347,11 +384,11 @@ encode_reply(Status, Reply, State = #{
error:{badmatch, {_, {error, Error}}}:Stack ->
Reason = woody_error:format_details(Error),
_ = woody_event_handler:handle_event(?EV_INTERNAL_ERROR, WoodyState, #{
error => <<"thrift protocol write failed">>,
reason => Reason,
class => error,
stack => Stack
}),
error => <<"thrift protocol write failed">>,
reason => Reason,
class => error,
stack => Stack
}),
{{error, {system, {internal, result_unexpected, format_unexpected_error(error, Reason, Stack)}}}, State}
end.

View File

@ -20,25 +20,29 @@
%% Types
-type handler_limits() :: #{
max_heap_size => integer() %% process words, see erlang:process_flag(max_heap_size, MaxHeapSize) for details.
%% process words, see erlang:process_flag(max_heap_size, MaxHeapSize) for details.
max_heap_size => integer()
}.
-export_type([handler_limits/0]).
-type transport_opts() :: #{
connection_type => worker | supervisor,
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
max_connections => ranch:max_conns(),
logger => module(),
num_acceptors => pos_integer(),
shutdown => timeout() | brutal_kill,
socket => any(),
socket_opts => any(),
% ranch_tcp | ranch_ssl
transport => module()
}.
-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().
@ -47,47 +51,50 @@
%% 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(_)],
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()
shutdown_timeout => timeout()
}.
-export_type([options/0]).
-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()
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()
}.
-export_type([route_opts/0]).
-type re_mp() :: tuple(). %% fuck otp for hiding the types.
%% fuck otp for hiding the types.
-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(),
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()
regexp_meta := re_mp(),
url => woody:url(),
woody_state => woody_state:st()
}.
-type cowboy_init_result() ::
@ -99,7 +106,7 @@
| {stop, cowboy_req:req(), undefined}.
-define(DEFAULT_ACCEPTORS_POOLSIZE, 100).
-define(DEFAULT_SHUTDOWN_TIMEOUT, 0).
-define(DEFAULT_SHUTDOWN_TIMEOUT, 0).
%% nginx should be configured to take care of various limits.
@ -108,8 +115,7 @@
%%
%% woody_server callback
%%
-spec child_spec(atom(), options()) ->
supervisor:child_spec().
-spec child_spec(atom(), options()) -> supervisor:child_spec().
child_spec(Id, Opts) ->
{Transport, TransportOpts} = get_socket_transport(Opts),
CowboyOpts = get_cowboy_config(Opts),
@ -118,8 +124,7 @@ child_spec(Id, Opts) ->
RanchSpec = ranch:child_spec(RanchRef, Transport, TransportOpts, cowboy_clear, CowboyOpts),
make_server_childspec(Id, [RanchSpec, DrainSpec]).
-spec get_addr(atom()) ->
{inet:ip_address(), inet:port_number()}.
-spec get_addr(atom()) -> {inet:ip_address(), inet:port_number()}.
get_addr(Id) ->
ranch:get_addr(create_ranch_ref(Id)).
@ -140,75 +145,72 @@ make_server_childspec(Id, Children) ->
}.
get_socket_transport(Opts = #{ip := Ip, port := Port}) ->
Defaults = #{num_acceptors => ?DEFAULT_ACCEPTORS_POOLSIZE},
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 = 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().
-spec get_cowboy_config(options()) -> protocol_opts().
get_cowboy_config(Opts = #{event_handler := EvHandler}) ->
ok = validate_event_handler(EvHandler),
Dispatch = get_dispatch(Opts),
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),
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).
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),
end,
Handlers
),
ok;
validate_event_handler(Handler) ->
validate_event_handler([Handler]).
-spec get_dispatch(options())->
cowboy_router:dispatch_rules().
-spec get_dispatch(options()) -> cowboy_router:dispatch_rules().
get_dispatch(Opts) ->
cowboy_router:compile([{'_', get_all_routes(Opts)}]).
-spec get_all_routes(options())->
[route(_)].
-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(RouteOpts).
-spec get_routes(route_opts()) ->
[route(state())].
-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()).
-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().
-spec init_state(route_opts()) -> state().
init_state(Opts = #{}) ->
#{
ev_handler => maps:get(event_handler, 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()
regexp_meta => compile_filter_meta()
}.
-spec compile_filter_meta() ->
re_mp().
-spec compile_filter_meta() -> re_mp().
compile_filter_meta() ->
{ok, Re} = re:compile(?HEADER_META_RE, [unicode, caseless]),
Re.
@ -216,8 +218,7 @@ compile_filter_meta() ->
%%
%% cowboy_http_handler callbacks
%%
-spec init(cowboy_req:req(), state()) ->
cowboy_init_result().
-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)),
@ -228,101 +229,92 @@ init(Req, Opts = #{ev_handler := EvHandler, handler_limits := Limits}) ->
{stop, Req1, State} -> {ok, Req1, State}
end.
-spec set_handler_limits(handler_limits()) ->
ok.
-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,
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 ->
_ = woody_event_handler: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,
-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 ->
_ = woody_event_handler: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,
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.
-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
}),
error => <<"http handler terminated abnormally">>,
reason => woody_error:format_details(Reason),
class => undefined,
final => true
}),
ok.
%% init functions
%% 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().
-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().
-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().
-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
->
-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().
-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} ->
@ -334,23 +326,27 @@ check_woody_headers(Req, State = #{woody_state := WoodyState0}) ->
);
{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)
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()}.
-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
}
)).
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
@ -378,13 +374,15 @@ check_deadline_header(DeadlineBin, Req, State) ->
reply_bad_header(400, ErrorDescription, Req, State)
end.
-spec check_deadline(woody:deadline(), cowboy_req:req(), state()) ->
check_result().
-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 ->
_ = woody_event_handler:handle_event(?EV_SERVER_RECEIVE, WoodyState,
#{url => Url, status => error, reason => <<"Deadline reached">>}),
_ = woody_event_handler: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 ->
@ -393,51 +391,49 @@ check_deadline(Deadline, Req, State = #{url := Url, woody_state := WoodyState})
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().
-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().
-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
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).
#{},
Headers
).
-spec set_rpc_id(woody:rpc_id(), woody_state:st()) ->
woody_state:st().
-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().
-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().
-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().
-spec set_metadata(woody_context:meta(), woody_state:st()) -> woody_state:st().
set_metadata(Meta, WoodyState) ->
woody_state:add_context_meta(Meta, WoodyState).
@ -447,16 +443,17 @@ 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().
-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}) ->
_ = woody_event_handler:handle_event(?EV_SERVER_RECEIVE, WoodyState,
#{url => Url, status => error, reason => Reason}),
_ = woody_event_handler:handle_event(
?EV_SERVER_RECEIVE,
WoodyState,
#{url => Url, status => error, reason => Reason}
),
reply(Code, set_error_headers(<<"Result Unexpected">>, Reason, Req), WoodyState).
%% handle functions
-spec get_body(cowboy_req:req(), read_body_opts()) ->
{ok, woody:http_body(), cowboy_req:req()}.
-spec get_body(cowboy_req:req(), read_body_opts()) -> {ok, woody:http_body(), cowboy_req:req()}.
get_body(Req, ReadBodyOpts) ->
do_get_body(<<>>, Req, ReadBodyOpts).
@ -468,8 +465,7 @@ do_get_body(Body, Req, Opts) ->
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().
-spec handle_request(woody:http_body(), woody:th_handler(), woody_state:st(), cowboy_req:req()) -> cowboy_req:req().
handle_request(Body, ThriftHander, WoodyState, Req) ->
ok = woody_monitor_h:set_event(?EV_SERVICE_HANDLER_RESULT, Req),
case woody_server_thrift_handler:init_handler(Body, ThriftHander, WoodyState) of
@ -509,8 +505,7 @@ handle_error({system, {external, resource_unavailable, Details}}, Req, WoodyStat
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().
-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,
@ -518,8 +513,7 @@ set_error_headers(Class, Reason, Req) ->
},
cowboy_req:set_resp_headers(Headers, Req).
-spec reply(woody:http_code(), cowboy_req:req(), woody_state:st()) ->
cowboy_req: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) ->

View File

@ -15,30 +15,35 @@
%% 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.
%% process words, see erlang:process_flag(max_heap_size, MaxHeapSize) for details.
max_heap_size => integer()
}.
-export_type([handler_limits/0]).
-type transport_opts() :: #{
connection_type => worker | supervisor,
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
max_connections => ranch:max_conns(),
logger => module(),
num_acceptors => pos_integer(),
shutdown => timeout() | brutal_kill,
socket => any(),
socket_opts => any(),
% ranch_tcp | ranch_ssl
transport => module()
}.
-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().
@ -47,33 +52,36 @@
%% 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(_)],
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()
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()
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,
@ -89,13 +97,13 @@
-export_type([protocol_opts/0]).
-type state() :: #{
th_handler => woody:th_handler(),
ev_handler := woody:ev_handlers(),
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()
regexp_meta := re_mp(),
url => woody:url(),
woody_state => woody_state:st()
}.
-type cowboy_init_result() ::
@ -107,7 +115,7 @@
| {stop, cowboy_req:req(), undefined}.
-define(DEFAULT_ACCEPTORS_POOLSIZE, 100).
-define(DEFAULT_SHUTDOWN_TIMEOUT, 0).
-define(DEFAULT_SHUTDOWN_TIMEOUT, 0).
%% nginx should be configured to take care of various limits.
@ -116,8 +124,7 @@
%%
%% woody_server callback
%%
-spec child_spec(atom(), options()) ->
supervisor:child_spec().
-spec child_spec(atom(), options()) -> supervisor:child_spec().
child_spec(Id, Opts) ->
{Transport, TransportOpts} = get_socket_transport(Opts),
CowboyOpts = get_cowboy_config(Opts),
@ -126,8 +133,7 @@ child_spec(Id, Opts) ->
RanchSpec = ranch:child_spec(RanchRef, Transport, TransportOpts, cowboy_clear, CowboyOpts),
make_server_childspec(Id, [RanchSpec, DrainSpec]).
-spec get_addr(atom()) ->
{inet:ip_address(), inet:port_number()}.
-spec get_addr(atom()) -> {inet:ip_address(), inet:port_number()}.
get_addr(Id) ->
ranch:get_addr(create_ranch_ref(Id)).
@ -148,74 +154,71 @@ make_server_childspec(Id, Children) ->
}.
get_socket_transport(Opts = #{ip := Ip, port := Port}) ->
Defaults = #{num_acceptors => ?DEFAULT_ACCEPTORS_POOLSIZE},
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 = 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().
-spec get_cowboy_config(options()) -> protocol_opts().
get_cowboy_config(Opts = #{event_handler := EvHandler}) ->
ok = validate_event_handler(EvHandler),
Dispatch = get_dispatch(Opts),
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),
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).
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),
end,
Handlers
),
ok;
validate_event_handler(Handler) ->
validate_event_handler([Handler]).
-spec get_dispatch(options())->
cowboy_router:dispatch_rules().
-spec get_dispatch(options()) -> cowboy_router:dispatch_rules().
get_dispatch(Opts) ->
cowboy_router:compile([{'_', get_all_routes(Opts)}]).
-spec get_all_routes(options())->
[route(_)].
-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())].
-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()).
-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().
-spec init_state(route_opts()) -> state().
init_state(Opts = #{}) ->
#{
ev_handler => maps:get(event_handler, 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()
regexp_meta => compile_filter_meta()
}.
-spec compile_filter_meta() ->
re_mp().
-spec compile_filter_meta() -> re_mp().
compile_filter_meta() ->
{ok, Re} = re:compile(?HEADER_META_RE, [unicode, caseless]),
Re.
@ -223,8 +226,7 @@ compile_filter_meta() ->
%%
%% cowboy_http_handler callbacks
%%
-spec init(cowboy_req:req(), state()) ->
cowboy_init_result().
-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)),
@ -235,61 +237,61 @@ init(Req, Opts = #{ev_handler := EvHandler, handler_limits := Limits}) ->
{stop, Req1, State} -> {ok, Req1, State}
end.
-spec set_handler_limits(handler_limits()) ->
ok.
-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,
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,
-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,
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.
-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
}),
error => <<"http handler terminated abnormally">>,
reason => woody_error:format_details(Reason),
class => undefined,
final => true
}),
ok.
%% init functions
-define(CODEC, thrift_strict_binary_codec).
@ -297,41 +299,32 @@ terminate(Reason, _Req, #{ev_handler := EvHandler} = Opts) ->
%% 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().
-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().
-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().
-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
->
-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().
-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} ->
@ -343,23 +336,27 @@ check_woody_headers(Req, State = #{woody_state := WoodyState0}) ->
);
{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)
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()}.
-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
}
)).
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
@ -387,8 +384,7 @@ check_deadline_header(DeadlineBin, Req, State) ->
reply_bad_header(400, ErrorDescription, Req, State)
end.
-spec check_deadline(woody:deadline(), cowboy_req:req(), state()) ->
check_result().
-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 ->
@ -405,51 +401,49 @@ check_deadline(Deadline, Req, State = #{url := Url, woody_state := WoodyState})
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().
-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().
-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
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).
#{},
Headers
).
-spec set_rpc_id(woody:rpc_id(), woody_state:st()) ->
woody_state:st().
-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().
-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().
-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().
-spec set_metadata(woody_context:meta(), woody_state:st()) -> woody_state:st().
set_metadata(Meta, WoodyState) ->
woody_state:add_context_meta(Meta, WoodyState).
@ -459,8 +453,7 @@ 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().
-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,
@ -469,8 +462,7 @@ reply_client_error(Code, Reason, Req, #{url := Url, woody_state := WoodyState})
),
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()}.
-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).
@ -482,8 +474,7 @@ do_get_body(Body, Req, Opts) ->
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().
-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),
@ -499,21 +490,19 @@ handle_request(Body, ThriftHandler = {Service, _}, WoodyState, Req) ->
handle_decode_error(Reason, Req, WoodyState)
end.
-spec handle_decode_error(_Reason, cowboy_req:req(), woody_state:st()) ->
cowboy_req:req().
-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">>,
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()}.
-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>>};
@ -530,7 +519,9 @@ 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()}.
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
@ -544,13 +535,12 @@ handle_invocation(SeqId, {ReplyType, Function, Args}, {Service, Handler}, Req, W
end.
-type call_result() ::
ok |
{reply, woody:result()} |
{exception, _TypeName, _Exception} |
{error, {system, woody_error:system_error()}}.
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().
-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),
@ -576,8 +566,8 @@ 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()}}.
{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} ->
@ -609,11 +599,9 @@ process_handler_error(Class, Reason, Stack, WoodyState) ->
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
->
-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} ->
@ -640,14 +628,13 @@ get_exception_name({{struct, exception, {_Mod, 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().
-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">>,
error => <<"thrift protocol write failed">>,
reason => woody_error:format_details(Reason)
}
),
@ -655,14 +642,17 @@ handle_encode_error(Reason, Req, WoodyState) ->
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).
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.
@ -691,8 +681,7 @@ handle_error({system, {external, resource_unavailable, Details}}, Req, WoodyStat
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().
-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,
@ -700,8 +689,7 @@ set_error_headers(Class, Reason, Req) ->
},
cowboy_req:set_resp_headers(Headers, Req).
-spec reply(woody:http_code(), cowboy_req:req(), woody_state:st()) ->
cowboy_req: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) ->

View File

@ -16,17 +16,17 @@
%% Types
-type st() :: #{
context := woody_context:ctx(),
context := woody_context:ctx(),
ev_handler := woody:ev_handlers(),
ev_meta := woody_event_handler:meta()
ev_meta := woody_event_handler:meta()
}.
-export_type([st/0]).
%%
%% API
%%
-spec new(woody:role(), woody_context:ctx(), woody:ev_handlers()) ->
st().
-spec new(woody:role(), woody_context:ctx(), woody:ev_handlers()) -> st().
new(Role, Context, EvHandler) ->
Deadline = woody_context:get_deadline(Context),
Metadata = woody_context:get_meta(Context),
@ -35,73 +35,62 @@ new(Role, Context, EvHandler) ->
add_metadata_to_ev_meta(
Metadata,
#{
context => Context,
context => Context,
ev_handler => EvHandler,
ev_meta => #{role => Role, execution_start_time => os:system_time(millisecond)}
ev_meta => #{role => Role, execution_start_time => os:system_time(millisecond)}
}
)
).
-spec get_context(st()) ->
woody_context:ctx().
-spec get_context(st()) -> woody_context:ctx().
get_context(#{context := Context}) ->
Context.
-spec get_ev_handler(st()) ->
woody:ev_handlers().
-spec get_ev_handler(st()) -> woody:ev_handlers().
get_ev_handler(#{ev_handler := Handler}) ->
Handler.
-spec get_ev_meta(st()) ->
woody_event_handler:meta().
-spec get_ev_meta(st()) -> woody_event_handler:meta().
get_ev_meta(#{ev_meta := Meta}) ->
Meta.
-spec add_ev_meta(woody_event_handler:meta(), st()) ->
st().
-spec add_ev_meta(woody_event_handler:meta(), st()) -> st().
add_ev_meta(ExtraMeta, State = #{ev_meta := Meta}) ->
State#{ev_meta => maps:merge(Meta, ExtraMeta)}.
-spec update_context(woody_context:ctx(), st()) ->
st().
-spec update_context(woody_context:ctx(), st()) -> st().
update_context(NewContext, State) ->
State#{context => NewContext}.
-spec add_context_meta(woody_context:meta(), st()) ->
st().
-spec add_context_meta(woody_context:meta(), st()) -> st().
add_context_meta(ContextMeta, State) ->
add_metadata_to_ev_meta(ContextMeta, add_metadata_to_context(ContextMeta, State)).
-spec add_context_deadline(woody:deadline(), st()) ->
st().
-spec add_context_deadline(woody:deadline(), st()) -> st().
add_context_deadline(Deadline, State) ->
add_deadline_to_ev_meta(Deadline, add_deadline_to_context(Deadline, State)).
%%
%% Internal functions
%%
-spec add_metadata_to_context(woody_context:meta(), st()) ->
st().
-spec add_metadata_to_context(woody_context:meta(), st()) -> st().
add_metadata_to_context(ContextMeta, State) ->
update_context(
woody_context:add_meta(get_context(State), ContextMeta),
State
).
-spec add_deadline_to_context(woody:deadline(), st()) ->
st().
-spec add_deadline_to_context(woody:deadline(), st()) -> st().
add_deadline_to_context(Deadline, State) ->
update_context(
woody_context:set_deadline(Deadline, get_context(State)),
State
).
-spec add_metadata_to_ev_meta(woody_context:meta(), st()) ->
st().
-spec add_metadata_to_ev_meta(woody_context:meta(), st()) -> st().
add_metadata_to_ev_meta(ContextMeta, State) ->
add_ev_meta(#{metadata => ContextMeta}, State).
-spec add_deadline_to_ev_meta(woody:deadline(), st()) ->
st().
-spec add_deadline_to_ev_meta(woody:deadline(), st()) -> st().
add_deadline_to_ev_meta(Deadline, State) ->
add_ev_meta(#{deadline => Deadline}, State).

View File

@ -7,6 +7,7 @@
-export([env/1]).
-behaviour(cowboy_stream).
-export([init/3]).
-export([data/4]).
-export([info/3]).
@ -25,8 +26,7 @@
event_handler := woody:ev_handlers()
}.
-spec env(options()) ->
cowboy_middleware:env().
-spec env(options()) -> cowboy_middleware:env().
env(Opts = #{}) ->
EvHandler = maps:get(event_handler, Opts),
#{?MODULE => EvHandler}.
@ -42,8 +42,7 @@ trace_request(Req, EvHandler) ->
trace_response(Req, {response, Code, Headers, Body}, EvHandler) ->
trace_resp(genlib_app:env(woody, ?TRACER), Req, Code, Headers, Body, EvHandler).
-spec trace_req(true, cowboy_req:req(), woody:ev_handlers()) ->
cowboy_req:req().
-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),
@ -51,9 +50,9 @@ trace_req(true, Req, EvHandler) ->
% 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,
role => server,
event => <<"http request received">>,
url => Url,
headers => Headers
},
_ = woody_event_handler:handle_event(EvHandler, ?EV_TRACE, undefined, Meta),
@ -68,15 +67,14 @@ trace_req(_, Req, _) ->
woody:http_headers(),
woody:http_body(),
woody:ev_handlers()
) ->
cowboy_req:req().
) -> 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,
role => server,
event => <<"http response send">>,
code => Code,
headers => Headers,
body => Body
body => Body
}),
Req;
trace_resp(_, Req, _, _, _, _) ->
@ -84,22 +82,22 @@ trace_resp(_, Req, _, _, _, _) ->
%% callbacks
-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
-> {cowboy_stream:commands(), state()}.
-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) -> {cowboy_stream:commands(), state()}.
init(StreamID, Req, Opts) ->
TraceOpts = extract_trace_options(Opts),
_ = trace_request(Req, TraceOpts),
{Commands0, Next} = cowboy_stream:init(StreamID, Req, Opts),
{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().
-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State) ->
{cowboy_stream:commands(), State}
when
State :: state().
data(StreamID, IsFin, Data, #{next := Next0} = State) ->
{Commands0, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
{Commands0, State#{next => Next}}.
-spec info(cowboy_stream:streamid(), any(), State)
-> {cowboy_stream:commands(), State} when State::state().
-spec info(cowboy_stream:streamid(), any(), State) -> {cowboy_stream:commands(), State} when State :: state().
info(StreamID, {response, _, _, _} = Info, #{next := Next0, req := Req, opts := TraceOpts} = State) ->
_ = trace_response(Req, Info, TraceOpts),
{Commands0, Next} = cowboy_stream:info(StreamID, Info, Next0),
@ -113,9 +111,14 @@ terminate(StreamID, Reason, #{next := Next}) ->
cowboy_stream:terminate(StreamID, Reason, Next).
%% At this point we have both request (it's part actually) and response, so we might track them in one place
-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
when Resp::cowboy_stream:resp_command().
-spec early_error(
cowboy_stream:streamid(),
cowboy_stream:reason(),
cowboy_stream:partial_req(),
Resp,
cowboy:opts()
) -> Resp when
Resp :: cowboy_stream:resp_command().
early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
TraceOpts = extract_trace_options(Opts),
_ = trace_request(PartialReq, TraceOpts),

View File

@ -14,28 +14,25 @@
%%
%% Internal API
%%
-spec get_protocol_handler(woody:role(), map()) ->
module() | no_return().
-spec get_protocol_handler(woody:role(), map()) -> 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),
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_v2;
{server, thrift, http} -> woody_server_thrift_v2;
_ -> error(badarg, [Role, Opts])
_ -> error(badarg, [Role, Opts])
end.
-spec get_mod_opts(woody:handler(woody:options())) ->
{module(), woody:options()}.
-spec get_mod_opts(woody:handler(woody:options())) -> {module(), woody:options()}.
get_mod_opts(Handler = {Mod, _Opts}) when is_atom(Mod) ->
Handler;
get_mod_opts(Mod) when is_atom(Mod) ->
{Mod, ?DEFAULT_HANDLER_OPTS}.
-spec to_binary(atom() | list() | binary()) ->
binary().
-spec to_binary(atom() | list() | binary()) -> binary().
to_binary(Reason) when is_list(Reason) ->
to_binary(Reason, <<>>);
to_binary(Reason) ->
@ -47,12 +44,10 @@ 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().
-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().
-spec get_rpc_reply_type(_ThriftReplyType) -> woody:rpc_type().
get_rpc_reply_type(oneway_void) -> cast;
get_rpc_reply_type(_) -> call.

View File

@ -8,105 +8,98 @@
bench_thrift_formatter/2
]).
-type input() :: {woody_thrift_formatter:event_meta(), woody:rpc_id()}.
-type input() :: {woody_event_handler:event_meta(), woody:rpc_id()}.
-spec input() ->
input().
-spec input() -> input().
input() ->
Meta = #{
args => [{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,
110, 116, 104, 2, 100, 0, 15, 80, 97, 114, 116, 121, 77, 97, 110, 97, 103,
101, 109, 101, 110, 116, 100, 0, 11, 67, 114, 101, 97, 116, 101, 67, 108, 97,
105, 109, 109, 0, 0, 2, 145, 11, 0, 2, 0, 0, 0, 11, 49, 67, 83, 72, 84, 104, 84,
69, 74, 56, 52, 15, 0, 3, 12, 0, 0, 0, 4, 12, 0, 4, 11, 0, 1, 0, 0, 0, 11, 49, 67,
83, 72, 84, 106, 75, 108, 51, 52, 75, 12, 0, 2, 12, 0, 1, 12, 0, 2, 8, 0, 1, 0, 0,
0, 1, 0, 12, 0, 3, 8, 0, 1, 0, 0, 0, 1, 0, 12, 0, 1, 12, 0, 1, 12, 0, 1, 11, 0, 1, 0, 0,
0, 18, 72, 111, 111, 102, 115, 32, 38, 32, 72, 111, 114, 110, 115, 32, 79, 74,
83, 67, 11, 0, 2, 0, 0, 0, 10, 49, 50, 51, 52, 53, 48, 57, 56, 55, 54, 11, 0, 3, 0,
0, 0, 13, 49, 50, 49, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 11, 0, 4, 0, 0, 0,
48, 78, 101, 122, 97, 104, 117, 97, 108, 99, 111, 121, 111, 116, 108, 32, 49,
48, 57, 32, 80, 105, 115, 111, 32, 56, 44, 32, 67, 101, 110, 116, 114, 111,
44, 32, 48, 54, 48, 56, 50, 44, 32, 77, 69, 88, 73, 67, 79, 11, 0, 5, 0, 0, 0, 3,
78, 97, 78, 11, 0, 6, 0, 0, 0, 8, 68, 105, 114, 101, 99, 116, 111, 114, 11, 0, 7,
0, 0, 0, 7, 83, 111, 109, 101, 111, 110, 101, 11, 0, 8, 0, 0, 0, 13, 49, 48, 48,
36, 32, 98, 97, 110, 107, 110, 111, 116, 101, 12, 0, 9, 11, 0, 1, 0, 0, 0, 19,
52, 50, 55, 54, 51, 48, 48, 48, 49, 48, 57, 48, 56, 51, 49, 50, 56, 57, 51, 11,
0, 2, 0, 0, 0, 8, 83, 111, 109, 101, 66, 97, 110, 107, 11, 0, 3, 0, 0, 0, 9, 49,
50, 51, 49, 50, 57, 56, 55, 54, 11, 0, 4, 0, 0, 0, 8, 54, 54, 54, 52, 50, 54, 54,
54, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 4, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106,
75, 108, 51, 52, 75, 12, 0, 2, 12, 0, 4, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84,
106, 75, 108, 51, 52, 76, 12, 0, 2, 12, 0, 1, 12, 0, 1, 11, 0, 1, 0, 0, 0, 3, 82,
85, 66, 0, 12, 0, 2, 12, 0, 1, 11, 0, 1, 0, 0, 0, 19, 52, 50, 55, 54, 51, 48, 48,
48, 49, 48, 57, 48, 56, 51, 49, 50, 56, 57, 51, 11, 0, 2, 0, 0, 0, 8, 83, 111,
109, 101, 66, 97, 110, 107, 11, 0, 3, 0, 0, 0, 9, 49, 50, 51, 49, 50, 57, 56, 55,
54, 11, 0, 4, 0, 0, 0, 8, 54, 54, 54, 52, 50, 54, 54, 54, 0, 0, 0, 0, 0, 0, 0, 0, 12,
0, 6, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106, 75, 108, 51, 52, 77, 12, 0,
2, 12, 0, 5, 12, 0, 1, 8, 0, 1, 0, 0, 0, 1, 0, 12, 0, 6, 11, 0, 1, 0, 0, 0, 0, 0, 12, 0,
2, 11, 0, 1, 0, 0, 0, 17, 66, 97, 116, 116, 108, 101, 32, 82, 101, 97, 100, 121,
32, 83, 104, 111, 112, 0, 11, 0, 3, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106, 75,
108, 51, 52, 75, 11, 0, 4, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106, 75, 108, 51, 52,
76, 0, 0, 0, 0, 12, 0, 6, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106, 75, 108,
51, 52, 77, 12, 0, 2, 12, 0, 12, 12, 0, 1, 11, 0, 1, 0, 0, 0, 3, 82, 85, 66, 0, 0, 0,
0, 0, 0>>},
{mg_stateproc_Machine, <<"party">>, <<"1CSHThTEJ84">>,
[{mg_stateproc_Event, 1, <<"2019-08-13T07:52:11.080519Z">>,
undefined,
{arr,
[{obj,
#{{str, <<"ct">>} =>
{str, <<"application/x-erlang-binary">>},
{str, <<"vsn">>} => {i, 6}}},
{bin,
<<131, 104, 2, 100, 0, 13, 112, 97, 114, 116, 121, 95,
99, 104, 97, 110, 103, 101, 115, 108, 0, 0, 0, 2, 104,
2, 100, 0, 13, 112, 97, 114, 116, 121, 95, 99, 114,
101, 97, 116, 101, 100, 104, 4, 100, 0, 20, 112, 97,
121, 112, 114, 111, 99, 95, 80, 97, 114, 116, 121, 67,
114, 101, 97, 116, 101, 100, 109, 0, 0, 0, 11, 49, 67,
83, 72, 84, 104, 84, 69, 74, 56, 52, 104, 2, 100, 0, 23,
100, 111, 109, 97, 105, 110, 95, 80, 97, 114, 116,
121, 67, 111, 110, 116, 97, 99, 116, 73, 110, 102,
111, 109, 0, 0, 0, 12, 104, 103, 95, 99, 116, 95, 104,
101, 108, 112, 101, 114, 109, 0, 0, 0, 27, 50, 48, 49,
57, 45, 48, 56, 45, 49, 51, 84, 48, 55, 58, 53, 50, 58,
49, 49, 46, 48, 55, 50, 56, 51, 53, 90, 104, 2, 100, 0,
16, 114, 101, 118, 105, 115, 105, 111, 110, 95, 99,
104, 97, 110, 103, 101, 100, 104, 3, 100, 0, 28, 112,
97, 121, 112, 114, 111, 99, 95, 80, 97, 114, 116, 121,
82, 101, 118, 105, 115, 105, 111, 110, 67, 104, 97,
110, 103, 101, 100, 109, 0, 0, 0, 27, 50, 48, 49, 57,
45, 48, 56, 45, 49, 51, 84, 48, 55, 58, 53, 50, 58, 49,
49, 46, 48, 55, 50, 56, 51, 53, 90, 97, 0, 106>>}]}}],
{mg_stateproc_HistoryRange, undefined, 10, backward},
{mg_stateproc_Content, undefined,
{obj,
#{{str, <<"aux_state">>} =>
{bin,
<<131, 116, 0, 0, 0, 2, 100, 0, 20, 112, 97, 114, 116,
121, 95, 114, 101, 118, 105, 115, 105, 111, 110, 95,
105, 110, 100, 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">>}}}},
undefined,
{obj,
#{{str, <<"aux_state">>} =>
args => {
{mg_stateproc_CallArgs,
{bin,
<<131, 116, 0, 0, 0, 2, 100, 0, 20, 112, 97, 114, 116, 121, 95,
114, 101, 118, 105, 115, 105, 111, 110, 95, 105, 110, 100,
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">>}}}}}],
<<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, 110, 116, 104, 2, 100, 0, 15, 80, 97,
114, 116, 121, 77, 97, 110, 97, 103, 101, 109, 101, 110, 116, 100, 0, 11, 67, 114, 101, 97, 116,
101, 67, 108, 97, 105, 109, 109, 0, 0, 2, 145, 11, 0, 2, 0, 0, 0, 11, 49, 67, 83, 72, 84, 104,
84, 69, 74, 56, 52, 15, 0, 3, 12, 0, 0, 0, 4, 12, 0, 4, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72,
84, 106, 75, 108, 51, 52, 75, 12, 0, 2, 12, 0, 1, 12, 0, 2, 8, 0, 1, 0, 0, 0, 1, 0, 12, 0, 3, 8,
0, 1, 0, 0, 0, 1, 0, 12, 0, 1, 12, 0, 1, 12, 0, 1, 11, 0, 1, 0, 0, 0, 18, 72, 111, 111, 102,
115, 32, 38, 32, 72, 111, 114, 110, 115, 32, 79, 74, 83, 67, 11, 0, 2, 0, 0, 0, 10, 49, 50, 51,
52, 53, 48, 57, 56, 55, 54, 11, 0, 3, 0, 0, 0, 13, 49, 50, 49, 51, 52, 53, 54, 55, 56, 57, 48,
49, 50, 11, 0, 4, 0, 0, 0, 48, 78, 101, 122, 97, 104, 117, 97, 108, 99, 111, 121, 111, 116, 108,
32, 49, 48, 57, 32, 80, 105, 115, 111, 32, 56, 44, 32, 67, 101, 110, 116, 114, 111, 44, 32, 48,
54, 48, 56, 50, 44, 32, 77, 69, 88, 73, 67, 79, 11, 0, 5, 0, 0, 0, 3, 78, 97, 78, 11, 0, 6, 0,
0, 0, 8, 68, 105, 114, 101, 99, 116, 111, 114, 11, 0, 7, 0, 0, 0, 7, 83, 111, 109, 101, 111,
110, 101, 11, 0, 8, 0, 0, 0, 13, 49, 48, 48, 36, 32, 98, 97, 110, 107, 110, 111, 116, 101, 12,
0, 9, 11, 0, 1, 0, 0, 0, 19, 52, 50, 55, 54, 51, 48, 48, 48, 49, 48, 57, 48, 56, 51, 49, 50, 56,
57, 51, 11, 0, 2, 0, 0, 0, 8, 83, 111, 109, 101, 66, 97, 110, 107, 11, 0, 3, 0, 0, 0, 9, 49, 50,
51, 49, 50, 57, 56, 55, 54, 11, 0, 4, 0, 0, 0, 8, 54, 54, 54, 52, 50, 54, 54, 54, 0, 0, 0, 0, 0,
0, 0, 0, 12, 0, 4, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106, 75, 108, 51, 52, 75, 12, 0,
2, 12, 0, 4, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106, 75, 108, 51, 52, 76, 12, 0, 2, 12,
0, 1, 12, 0, 1, 11, 0, 1, 0, 0, 0, 3, 82, 85, 66, 0, 12, 0, 2, 12, 0, 1, 11, 0, 1, 0, 0, 0, 19,
52, 50, 55, 54, 51, 48, 48, 48, 49, 48, 57, 48, 56, 51, 49, 50, 56, 57, 51, 11, 0, 2, 0, 0, 0,
8, 83, 111, 109, 101, 66, 97, 110, 107, 11, 0, 3, 0, 0, 0, 9, 49, 50, 51, 49, 50, 57, 56, 55,
54, 11, 0, 4, 0, 0, 0, 8, 54, 54, 54, 52, 50, 54, 54, 54, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 6, 11,
0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106, 75, 108, 51, 52, 77, 12, 0, 2, 12, 0, 5, 12, 0, 1,
8, 0, 1, 0, 0, 0, 1, 0, 12, 0, 6, 11, 0, 1, 0, 0, 0, 0, 0, 12, 0, 2, 11, 0, 1, 0, 0, 0, 17, 66,
97, 116, 116, 108, 101, 32, 82, 101, 97, 100, 121, 32, 83, 104, 111, 112, 0, 11, 0, 3, 0, 0, 0,
11, 49, 67, 83, 72, 84, 106, 75, 108, 51, 52, 75, 11, 0, 4, 0, 0, 0, 11, 49, 67, 83, 72, 84,
106, 75, 108, 51, 52, 76, 0, 0, 0, 0, 12, 0, 6, 11, 0, 1, 0, 0, 0, 11, 49, 67, 83, 72, 84, 106,
75, 108, 51, 52, 77, 12, 0, 2, 12, 0, 12, 12, 0, 1, 11, 0, 1, 0, 0, 0, 3, 82, 85, 66, 0, 0, 0,
0, 0, 0>>},
{mg_stateproc_Machine, <<"party">>, <<"1CSHThTEJ84">>,
[
{mg_stateproc_Event, 1, <<"2019-08-13T07:52:11.080519Z">>, undefined,
{arr, [
{obj, #{
{str, <<"ct">>} =>
{str, <<"application/x-erlang-binary">>},
{str, <<"vsn">>} => {i, 6}
}},
{bin,
<<131, 104, 2, 100, 0, 13, 112, 97, 114, 116, 121, 95, 99, 104, 97, 110, 103, 101,
115, 108, 0, 0, 0, 2, 104, 2, 100, 0, 13, 112, 97, 114, 116, 121, 95, 99, 114,
101, 97, 116, 101, 100, 104, 4, 100, 0, 20, 112, 97, 121, 112, 114, 111, 99, 95,
80, 97, 114, 116, 121, 67, 114, 101, 97, 116, 101, 100, 109, 0, 0, 0, 11, 49,
67, 83, 72, 84, 104, 84, 69, 74, 56, 52, 104, 2, 100, 0, 23, 100, 111, 109, 97,
105, 110, 95, 80, 97, 114, 116, 121, 67, 111, 110, 116, 97, 99, 116, 73, 110,
102, 111, 109, 0, 0, 0, 12, 104, 103, 95, 99, 116, 95, 104, 101, 108, 112, 101,
114, 109, 0, 0, 0, 27, 50, 48, 49, 57, 45, 48, 56, 45, 49, 51, 84, 48, 55, 58,
53, 50, 58, 49, 49, 46, 48, 55, 50, 56, 51, 53, 90, 104, 2, 100, 0, 16, 114,
101, 118, 105, 115, 105, 111, 110, 95, 99, 104, 97, 110, 103, 101, 100, 104, 3,
100, 0, 28, 112, 97, 121, 112, 114, 111, 99, 95, 80, 97, 114, 116, 121, 82, 101,
118, 105, 115, 105, 111, 110, 67, 104, 97, 110, 103, 101, 100, 109, 0, 0, 0, 27,
50, 48, 49, 57, 45, 48, 56, 45, 49, 51, 84, 48, 55, 58, 53, 50, 58, 49, 49, 46,
48, 55, 50, 56, 51, 53, 90, 97, 0, 106>>}
]}}
],
{mg_stateproc_HistoryRange, undefined, 10, backward},
{mg_stateproc_Content, undefined,
{obj, #{
{str, <<"aux_state">>} =>
{bin,
<<131, 116, 0, 0, 0, 2, 100, 0, 20, 112, 97, 114, 116, 121, 95, 114, 101, 118, 105,
115, 105, 111, 110, 95, 105, 110, 100, 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">>}
}}},
undefined,
{obj, #{
{str, <<"aux_state">>} =>
{bin,
<<131, 116, 0, 0, 0, 2, 100, 0, 20, 112, 97, 114, 116, 121, 95, 114, 101, 118, 105, 115,
105, 111, 110, 95, 105, 110, 100, 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">>}
}}}}
},
deadline => {{{2019, 8, 13}, {7, 52, 41}}, 105},
execution_start_time => 1565682731109,
function => 'ProcessCall',
metadata =>
#{<<"user-identity.id">> => <<"1CSHThTEJ84">>,
<<"user-identity.realm">> => <<"external">>},
metadata => #{
<<"user-identity.id">> => <<"1CSHThTEJ84">>,
<<"user-identity.realm">> => <<"external">>
},
role => server,
service => 'Processor',
service_schema => {mg_proto_state_processing_thrift, 'Processor'},
@ -119,26 +112,22 @@ input() ->
},
{Meta, RpcID}.
-spec iolib_formatter({input, _State}) ->
input().
-spec iolib_formatter({input, _State}) -> input().
iolib_formatter({input, _}) ->
input().
-spec thrift_formatter({input, _State}) ->
input().
-spec thrift_formatter({input, _State}) -> input().
thrift_formatter({input, _}) ->
input().
-spec bench_iolib_formatter(input(), _State) ->
term().
-spec bench_iolib_formatter(input(), _State) -> term().
bench_iolib_formatter({Meta, RpcID}, _) ->
format_msg(format_event_iolib(Meta, RpcID)).
format_event_iolib(#{service := Service, function := Function, args := Args}, _RpcID) ->
{info, {"calling ~0p:~0p(~0tp)", [Service, Function, Args]}}.
-spec bench_thrift_formatter(input(), _State) ->
term().
-spec bench_thrift_formatter(input(), _State) -> term().
bench_thrift_formatter({Meta, RpcID}, _) ->
format_msg(woody_event_handler:format_event('call service', Meta, RpcID)).

View File

@ -10,8 +10,7 @@
-type input() :: term().
-spec input() ->
input().
-spec input() -> input().
input() ->
%% NOTE
%% You will need some reasonably complex term following `domain_config.Snapshot` thrift schema
@ -20,23 +19,19 @@ input() ->
{ok, Bin} = file:read_file("test/snapshot.term"),
erlang:binary_to_term(Bin).
-spec iolib_formatter({input, _State}) ->
input().
-spec iolib_formatter({input, _State}) -> input().
iolib_formatter({input, _}) ->
input().
-spec thrift_formatter({input, _State}) ->
input().
-spec thrift_formatter({input, _State}) -> input().
thrift_formatter({input, _}) ->
input().
-spec bench_iolib_formatter(input(), _State) ->
term().
-spec bench_iolib_formatter(input(), _State) -> term().
bench_iolib_formatter(Snapshot, _) ->
format_msg({"~0tp", [Snapshot]}).
-spec bench_thrift_formatter(input(), _State) ->
term().
-spec bench_thrift_formatter(input(), _State) -> term().
bench_thrift_formatter(Snapshot, _) ->
Service = dmsl_domain_config_thrift,
format_msg(woody_event_formatter:format_reply(Service, 'Repository', 'Checkout', Snapshot, #{})).

View File

@ -2,8 +2,7 @@
-export([run/0]).
-spec run() ->
ok.
-spec run() -> ok.
run() ->
Input = input(),
Opts = #{iterations => 10},
@ -11,14 +10,13 @@ run() ->
_ = run(thrift, mk_thrift_runner(Input), Opts),
ok.
-spec run(atom(), meter_memory_pressure:runner(), meter_memory_pressure:opts()) ->
ok.
-spec run(atom(), meter_memory_pressure:runner(), meter_memory_pressure:opts()) -> ok.
run(Name, Runner, Opts) ->
_ = io:format("Benchmarking '~s' memory pressure...~n", [Name]),
_ = io:format("====================================~n", []),
Metrics = meter_memory_pressure:measure(Runner, Opts),
lists:foreach(
fun (Metric) ->
fun(Metric) ->
io:format("~24s = ~-16b~n", [Metric, maps:get(Metric, Metrics)])
end,
[
@ -35,8 +33,7 @@ run(Name, Runner, Opts) ->
_ = io:format("====================================~n~n", []),
ok.
-spec input() ->
term().
-spec input() -> term().
input() ->
%% NOTE
%% You will need some reasonably complex term following `domain_config.Snapshot` thrift schema
@ -45,16 +42,14 @@ input() ->
{ok, Binary} = file:read_file("test/snapshot.term"),
erlang:binary_to_term(Binary).
-spec mk_iolib_runner(term()) ->
meter_memory_pressure:runner().
-spec mk_iolib_runner(term()) -> meter_memory_pressure:runner().
mk_iolib_runner(Snapshot) ->
fun () ->
fun() ->
bench_woody_formatter:bench_iolib_formatter(Snapshot, [])
end.
-spec mk_thrift_runner(term()) ->
meter_memory_pressure:runner().
-spec mk_thrift_runner(term()) -> meter_memory_pressure:runner().
mk_thrift_runner(Snapshot) ->
fun () ->
fun() ->
bench_woody_formatter:bench_thrift_formatter(Snapshot, [])
end.

View File

@ -22,16 +22,15 @@
-type runner() :: fun(() -> _).
-type opts() :: #{
iterations => pos_integer(),
spawn_opts => [{atom(), _}],
iterations => pos_integer(),
spawn_opts => [{atom(), _}],
dump_traces => file:filename()
}.
-export_type([runner/0]).
-export_type([opts/0]).
-spec measure(runner(), opts()) ->
metrics().
-spec measure(runner(), opts()) -> metrics().
measure(Runner, Opts0) ->
Opts = maps:merge(get_default_opts(), Opts0),
Token = make_ref(),
@ -49,7 +48,7 @@ get_default_opts() ->
run(Runner, Tracer, Opts) ->
SpawnOpts = [monitor, {priority, high}] ++ maps:get(spawn_opts, Opts),
{Staging, MRef} = erlang:spawn_opt(
fun () -> run_staging(Runner, Tracer, Opts) end,
fun() -> run_staging(Runner, Tracer, Opts) end,
SpawnOpts
),
receive
@ -73,7 +72,7 @@ iterate(_Runner, 0) ->
start_tracer(Token, Opts) ->
Self = self(),
erlang:spawn_link(fun () -> run_tracer(Self, Token, Opts) end).
erlang:spawn_link(fun() -> run_tracer(Self, Token, Opts) end).
collect_metrics(Tracer, Token) ->
_ = Tracer ! Token,
@ -83,7 +82,10 @@ collect_metrics(Tracer, Token) ->
end.
run_tracer(MeterPid, Token, Opts) ->
_ = receive Token -> ok end,
_ =
receive
Token -> ok
end,
Traces = collect_traces(),
Metrics = analyze_traces(Traces),
ok = maybe_dump_traces(Traces, Opts),
@ -91,15 +93,14 @@ run_tracer(MeterPid, Token, Opts) ->
collect_traces() ->
collect_traces([]).
collect_traces(Acc) ->
receive
{trace_ts, _Pid, Trace, Info, Clock} ->
collect_traces([{Trace, Info, Clock} | Acc]);
Unexpected ->
error({unexpected, Unexpected})
after
0 ->
lists:reverse(Acc)
after 0 -> lists:reverse(Acc)
end.
maybe_dump_traces(Traces, #{dump_traces := Filename}) ->
@ -136,7 +137,7 @@ analyze_gc(InfoStart, InfoEnd, M0) ->
M4.
difference(Name, Info1, Info2) ->
combine(Name, fun (V1, V2) -> erlang:max(0, V2 - V1) end, Info1, Info2).
combine(Name, fun(V1, V2) -> erlang:max(0, V2 - V1) end, Info1, Info2).
min(Name, Info1, Info2) ->
combine(Name, fun erlang:min/2, Info1, Info2).
@ -153,15 +154,14 @@ increment(Name, Metrics) ->
increment(Name, 1, Metrics).
increment(Name, Delta, Metrics) ->
maps:update_with(Name, fun (V) -> V + Delta end, Metrics).
maps:update_with(Name, fun(V) -> V + Delta end, Metrics).
update(Name, Fun, I, Metrics) ->
maps:update_with(Name, fun (V) -> Fun(V, I) end, I, Metrics).
maps:update_with(Name, fun(V) -> Fun(V, I) end, I, Metrics).
%%
-spec export(file:filename(), file:filename(), csv) -> ok.
export(FilenameIn, FilenameOut, Format) ->
{ok, Content} = file:read_file(FilenameIn),
Traces = erlang:binary_to_term(Content),
@ -171,7 +171,7 @@ export(FilenameIn, FilenameOut, Format) ->
format_traces(Traces, csv, FileOut) ->
_ = format_csv_header(FileOut),
_ = lists:foreach(fun (T) -> format_csv_trace(T, FileOut) end, Traces),
_ = lists:foreach(fun(T) -> format_csv_trace(T, FileOut) end, Traces),
ok.
format_csv_header(Out) ->
@ -209,7 +209,8 @@ format_csv_trace({Event, Info, Clock}, Out) ->
]).
get_info(Name, Info) ->
{_Name, V} = lists:keyfind(Name, 1, Info), V.
{_Name, V} = lists:keyfind(Name, 1, Info),
V.
clock_to_mcs({MSec, Sec, USec}) ->
(MSec * 1000000 + Sec) * 1000000 + USec.

View File

@ -1,4 +1,5 @@
-module(woody_joint_workers_pt).
-include_lib("proper/include/proper.hrl").
-export([
@ -20,11 +21,11 @@
-type id_t() :: non_neg_integer().
-spec prop_test() -> any().
-spec start_workers() -> genlib_gen:start_ret().
-spec stop_workers(any()) -> ok.
-spec start_workers() -> pid().
-spec stop_workers(pid()) -> ok.
-spec do(id_t(), successfulness()) -> any().
-spec task_timeouts(successfulness()) -> {timeout(), timeout()}.
-spec id() -> id_t().
-spec id() -> proper_types:type().
-spec command(any()) -> any().
-spec initial_state() -> state().
-spec precondition(any(), any()) -> boolean().
@ -49,7 +50,6 @@ prop_test() ->
end
).
start_workers() ->
genlib:unwrap(woody_joint_workers:start_link({local, workers})).
@ -64,15 +64,15 @@ do(ID, Successfulness) ->
% тестовый таск спит небольшое время
% дедлайн ставится либо до, либо после него
{TaskSleepTimeout, WorkerTimeout} = task_timeouts(Successfulness),
Task =
fun(_) ->
ok = timer:sleep(TaskSleepTimeout),
{ok, ID}
end,
Task = fun(_) ->
ok = timer:sleep(TaskSleepTimeout),
{ok, ID}
end,
catch woody_joint_workers:do(workers, {ID, Successfulness}, Task, woody_deadline:from_timeout(WorkerTimeout)).
% если уменьшать, то могут быть ложные срабатывания
-define(timeout_k, 20).
task_timeouts(success) ->
{?timeout_k * 1, ?timeout_k * 3};
task_timeouts(fail) ->
@ -84,7 +84,7 @@ id() ->
command(_) ->
frequency([
{10, {call, ?MODULE, do, [id(), success]}},
{1 , {call, ?MODULE, do, [id(), fail ]}}
{1, {call, ?MODULE, do, [id(), fail]}}
]).
initial_state() ->

View File

@ -13,8 +13,9 @@
-export([get_socket_errors_caught/0]).
-type state() :: #{
socket_errors_caught => pos_integer()
socket_errors_caught => non_neg_integer()
}.
-type event() :: woody_event_handler:event().
-type rpc_id() :: woody:rpc_id().
-type event_meta() :: woody_event_handler:event_meta().
@ -25,18 +26,17 @@
-define(SOCKET_CLOSED, <<"The socket has been closed.">>).
-spec get_socket_errors_caught() -> pos_integer().
get_socket_errors_caught() ->
{ok, N} = gen_server:call(?MODULE, get_number_of_events),
N.
-spec child_spec() -> supervisor:child_spec().
child_spec() -> #{
id => ?MODULE,
start => {?MODULE, start_link, []},
type => worker
}.
child_spec() ->
#{
id => ?MODULE,
start => {?MODULE, start_link, []},
type => worker
}.
%% woody_event_handler callbaacks
@ -46,45 +46,46 @@ child_spec() -> #{
event_meta(),
options()
) -> _.
handle_event(Event, RpcId, Meta, Opts) ->
gen_server:call(?MODULE, {Event, RpcId, Meta, Opts}).
%% gen_server callbacks
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec init(_) -> {ok, state()}.
init(_) ->
{ok, #{socket_errors_caught => 0}}.
-spec handle_call({event(), rpc_id(), event_meta(), options()}, _, state()) ->
{reply, ok | {ok, pos_integer()}, state()}.
handle_call({Event = ?EV_SERVICE_HANDLER_RESULT, Rpc,
#{status := error, class := system, result := ?SOCKET_CLOSED} = Meta, Opts}, _, #{
socket_errors_caught := Caught
} = State) ->
-spec handle_call(get_number_of_events | {event(), rpc_id(), event_meta(), options()}, _, state()) ->
{reply, ok | {ok, non_neg_integer()}, state()}.
handle_call(
{Event = ?EV_SERVICE_HANDLER_RESULT, Rpc, #{status := error, class := system, result := ?SOCKET_CLOSED} = Meta,
Opts},
_,
#{
socket_errors_caught := Caught
} = State
) ->
woody_tests_SUITE:handle_event(Event, Rpc, Meta, Opts),
{reply, ok, State#{socket_errors_caught => Caught + 1}};
handle_call({Event = ?EV_SERVER_RECEIVE, Rpc, #{status := error, reason := ?SOCKET_CLOSED} = Meta, Opts}, _, #{
socket_errors_caught := Caught
} = State) ->
handle_call(
{Event = ?EV_SERVER_RECEIVE, Rpc, #{status := error, reason := ?SOCKET_CLOSED} = Meta, Opts},
_,
#{
socket_errors_caught := Caught
} = State
) ->
woody_tests_SUITE:handle_event(Event, Rpc, Meta, Opts),
{reply, ok, State#{socket_errors_caught => Caught + 1}};
handle_call({Event, Rpc, Meta, Opts}, _, State) ->
woody_tests_SUITE:handle_event(Event, Rpc, Meta, Opts),
{reply, ok, State};
handle_call(get_number_of_events, _, #{socket_errors_caught := N} = State) ->
{reply, {ok, N}, State}.
-spec handle_cast(_, state()) -> {noreply, state()}.
handle_cast(_, S) ->
{noreply, S}.

View File

@ -1,4 +1,5 @@
-module(woody_joint_workers_SUITE).
-include_lib("common_test/include/ct.hrl").
-export([
@ -33,7 +34,7 @@ init_per_suite(C) ->
% dbg:tpl({woody_joint_workers, do, 4}, x),
{ok, Apps} = application:ensure_all_started(woody),
[{apps, Apps}|C].
[{apps, Apps} | C].
end_per_suite(C) ->
[application:stop(App) || App <- ?config(apps, C)].
@ -43,10 +44,11 @@ end_per_suite(C) ->
%%
prop_test(_C) ->
R = proper:quickcheck(
woody_joint_workers_pt:prop_test(),
[noshrink] % default options
),
woody_joint_workers_pt:prop_test(),
% default options
[noshrink]
),
case R of
true -> ok;
true -> ok;
Error -> exit(Error)
end.

View File

@ -3,6 +3,7 @@
-include_lib("public_key/include/public_key.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").
-include("woody_test_thrift.hrl").
-behaviour(supervisor).
@ -35,7 +36,7 @@
%% supervisor callback
-export([init/1]).
-type config() :: [{atom(), any()}].
-type config() :: [{atom(), any()}].
-type group_name() :: atom().
-type case_name() :: atom().
@ -56,9 +57,7 @@
%%% CT callbacks
%%%
-spec all() ->
[case_name()].
-spec all() -> [{group, group_name()}].
all() ->
[
{group, 'tlsv1.3'},
@ -66,9 +65,7 @@ all() ->
{group, 'tlsv1.1'}
].
-spec groups() ->
[{group_name(), list(), [case_name()]}].
-spec groups() -> [{group_name(), list(), [case_name()]}].
groups() ->
TestGroup = [
client_wo_cert_test,
@ -81,34 +78,26 @@ groups() ->
{'tlsv1.3', [parallel], TestGroup}
].
-spec init_per_suite(config()) ->
config().
-spec init_per_suite(config()) -> config().
init_per_suite(C) ->
{ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
true = erlang:unlink(Sup),
{ok, Apps} = application:ensure_all_started(woody),
{ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
true = erlang:unlink(Sup),
{ok, Apps} = application:ensure_all_started(woody),
[{sup, Sup}, {apps, Apps} | C].
-spec end_per_suite(config()) ->
ok.
-spec end_per_suite(config()) -> ok.
end_per_suite(C) ->
Sup = ?config(sup, C),
ok = proc_lib:stop(Sup),
[application:stop(App) || App <- proplists:get_value(apps, C)],
ok = proc_lib:stop(Sup),
_ = [application:stop(App) || App <- proplists:get_value(apps, C)],
ok.
-spec init_per_group(group_name(), config()) ->
config().
-spec init_per_group(group_name(), config()) -> config().
init_per_group(Name, C) ->
{ok, WoodyServer} = start_woody_server(Name, C),
[{woody_server, WoodyServer}, {group_name, Name} | C].
-spec end_per_group(group_name(), config()) ->
any().
-spec end_per_group(group_name(), config()) -> any().
end_per_group(_Name, C) ->
stop_woody_server(C).
@ -117,12 +106,11 @@ end_per_group(_Name, C) ->
%%%
-spec client_wo_cert_test(config()) -> _.
client_wo_cert_test(C) ->
Vsn = ?config(group_name, C),
SSLOptions = [{cacertfile, ?ca_cert(C)} | client_ssl_opts(Vsn)],
try
get_weapon(?FUNCTION_NAME, <<"BFG">>, SSLOptions),
_ = get_weapon(?FUNCTION_NAME, <<"BFG">>, SSLOptions),
error(unreachable)
catch
% NOTE
@ -136,19 +124,17 @@ client_wo_cert_test(C) ->
end.
-spec valid_client_cert_test(config()) -> _.
valid_client_cert_test(C) ->
Vsn = ?config(group_name, C),
SSLOptions = [{cacertfile, ?ca_cert(C)}, {certfile, ?client_cert(C)} | client_ssl_opts(Vsn)],
{ok, #'Weapon'{}} = get_weapon(?FUNCTION_NAME, <<"BFG">>, SSLOptions).
-spec invalid_client_cert_test(config()) -> _.
invalid_client_cert_test(C) ->
Vsn = ?config(group_name, C),
SSLOptions = [{cacertfile, ?ca_cert(C)}, {certfile, ?invalid_client_cert(C)} | client_ssl_opts(Vsn)],
try
get_weapon(?FUNCTION_NAME, <<"BFG">>, SSLOptions),
_ = get_weapon(?FUNCTION_NAME, <<"BFG">>, SSLOptions),
error(unreachable)
catch
% NOTE
@ -159,9 +145,7 @@ invalid_client_cert_test(C) ->
{match, _} = re:run(Reason, <<"^{tls_alert,[\"\{]unknown[ _]ca.*$">>, [])
end.
-spec client_ssl_opts(atom()) ->
[ssl:tls_client_option()].
-spec client_ssl_opts(atom()) -> [ssl:tls_client_option()].
client_ssl_opts('tlsv1.3') ->
% NOTE
% We need at least an extra TLSv1.2 here and default OTP cipher suites,
@ -183,12 +167,26 @@ client_ssl_opts(Vsn) ->
woody_event_handler:event_meta(),
woody:options()
) -> _.
handle_event(Event, RpcId, Meta, _) ->
{_Severity, {Format, Msg}, EvMeta} = woody_event_handler:format_event_and_meta(
Event, Meta, RpcId,
[event, role, service, service_schema, function, type, args, metadata,
deadline, status, url, code, result, execution_time]
Event,
Meta,
RpcId,
[
event,
role,
service,
service_schema,
function,
type,
args,
metadata,
deadline,
status,
url,
code,
result
]
),
ct:pal(Format ++ "~nmeta: ~p", Msg ++ [EvMeta]).
@ -196,9 +194,7 @@ handle_event(Event, RpcId, Meta, _) ->
%%% woody_server_thrift_handler callback
%%%
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) ->
{ok, woody:result()}.
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}.
handle_function(get_weapon, {Name, _Data}, Context, _Opts) ->
_ = assert_common_name([<<"Valid Test Client">>], Context),
{ok, #'Weapon'{name = Name, slot_pos = 0}}.
@ -207,12 +203,13 @@ handle_function(get_weapon, {Name, _Data}, Context, _Opts) ->
%%% Supervisor callback
%%%
-spec init(_) -> _.
-spec init(_) -> genlib_gen:supervisor_ret().
init(_) ->
{ok, {
{one_for_one, 1, 1}, []
}}.
{ok,
{
{one_for_one, 1, 1},
[]
}}.
%%%
%%% Internal functions
@ -221,26 +218,24 @@ init(_) ->
start_woody_server(Vsn, C) ->
Sup = ?config(sup, C),
Server = woody_server:child_spec(?MODULE, #{
handlers => [{?PATH, {{?THRIFT_DEFS, 'Weapons'}, ?MODULE}}],
event_handler => ?MODULE,
ip => {0, 0, 0, 0},
port => 8043,
handlers => [{?PATH, {{?THRIFT_DEFS, 'Weapons'}, ?MODULE}}],
event_handler => ?MODULE,
ip => {0, 0, 0, 0},
port => 8043,
transport_opts => #{
transport => ranch_ssl,
transport => ranch_ssl,
socket_opts => [
{cacertfile, ?ca_cert(C)},
{certfile, ?server_cert(C)},
{verify, verify_peer},
{cacertfile, ?ca_cert(C)},
{certfile, ?server_cert(C)},
{verify, verify_peer},
{fail_if_no_peer_cert, true},
{versions, [Vsn]}
{versions, [Vsn]}
]
}
}),
supervisor:start_child(Sup, Server).
-spec stop_woody_server(config()) ->
ok.
-spec stop_woody_server(config()) -> ok.
stop_woody_server(C) ->
ok = supervisor:terminate_child(?config(sup, C), ?MODULE),
ok = supervisor:delete_child(?config(sup, C), ?MODULE).
@ -254,8 +249,8 @@ get_weapon(Id, Gun, SSLOptions) ->
transport_opts => #{
ssl_options => [
{server_name_indication, "Test Server"},
{verify, verify_peer} |
SSLOptions
{verify, verify_peer}
| SSLOptions
]
}
},
@ -268,9 +263,7 @@ get_service_endpoint('Weapons') ->
}.
to_binary(Atom) when is_atom(Atom) ->
erlang:atom_to_binary(Atom, utf8);
to_binary(Binary) when is_binary(Binary) ->
Binary.
erlang:atom_to_binary(Atom, utf8).
assert_common_name(CNs, Context) ->
CN = woody_context:get_common_name(Context),

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,12 @@
%% woody_server_thrift_handler callbacks
-behaviour(woody_server_thrift_handler).
-export([handle_function/4]).
%% woody_event_handler callbacks
-behaviour(woody_event_handler).
-export([handle_event/4]).
-export([all/0]).
@ -32,8 +34,7 @@
-spec respects_max_connections(config()) -> any().
-spec shuts_down_gracefully(config()) -> any().
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) ->
{ok, woody:result()}.
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}.
-spec handle_event(
woody_event_handler:event(),
@ -65,22 +66,24 @@ init_per_testcase(Name, C) ->
Port = get_random_port(),
[
{client, #{
url => iolist_to_binary(["http://localhost:", integer_to_list(Port), "/"]),
event_handler => {?MODULE, {client, Name}}
url => iolist_to_binary(["http://localhost:", integer_to_list(Port), "/"]),
event_handler => {?MODULE, {client, Name}}
}},
{server , #{
ip => {127, 0, 0, 1},
port => Port,
event_handler => [{?MODULE, {server, Name}}],
{server, #{
ip => {127, 0, 0, 1},
port => Port,
event_handler => [{?MODULE, {server, Name}}],
shutdown_timeout => 5000
}},
{testcase, Name} | C
{testcase, Name}
| C
].
%%
respects_max_connections(C) ->
MaxConns = 10 + rand:uniform(10), % (10; 20]
% (10; 20]
MaxConns = 10 + rand:uniform(10),
Table = ets:new(?MODULE, [public, {read_concurrency, true}, {write_concurrency, true}]),
true = ets:insert_new(Table, [{slot, 0}]),
Service = {woody_test_thrift, 'Weapons'},
@ -99,19 +102,24 @@ respects_max_connections(C) ->
ReadBodyOpts = #{},
{ok, ServerPid} = start_woody_server(Handler, TransportOpts, ProtocolOpts, ReadBodyOpts, C),
Results = genlib_pmap:map(
fun (_) ->
fun(_) ->
woody_client:call({Service, 'get_weapon', {<<"BFG">>, <<>>}}, Client)
end,
lists:seq(1, MaxConns * 10)
),
Slots = lists:map(
fun ({ok, #'Weapon'{slot_pos = Slot}}) -> Slot end,
fun({ok, #'Weapon'{slot_pos = Slot}}) -> Slot end,
Results
),
?assert(lists:max(Slots) =< MaxConns),
ok = stop_woody_server(ServerPid).
-define(receive_or_timeout(Msg, Timeout), receive Msg -> ok after Timeout -> timeout end).
-define(receive_or_timeout(Msg, Timeout),
receive
Msg -> ok
after Timeout -> timeout
end
).
shuts_down_gracefully(C) ->
Client = ?config(client, C),
@ -124,12 +132,12 @@ shuts_down_gracefully(C) ->
ParentPid = self(),
%% send a shutdown signal to the server in 1000ms
%% then try making a new connection with it, expect econnrefused
TestPid = spawn_link(fun() -> process_econnrefused_test(Client, ParentPid) end),
_ = spawn_link(fun() -> process_delayed_kill(ServerPid, TestPid, 1000) end),
TestPid = spawn_link(fun() -> process_econnrefused_test(Client, ParentPid) end),
_ = spawn_link(fun() -> process_delayed_kill(ServerPid, TestPid, 1000) end),
%% fire some requests and expect them to finish successfuly
%% even when server is shutting down in the meantime
_ = genlib_pmap:map(
fun (_) ->
fun(_) ->
?assertEqual(
{ok, #'Powerup'{name = <<"Warbanner">>}},
get_powerup(Client, <<"Warbanner">>, <<>>)
@ -161,13 +169,13 @@ process_delayed_kill(ServerPid, TestPid, Timeout) ->
start_woody_server(Handler, TransportOpts, ProtocolOpts, ReadBodyOpts, C) ->
ServerOpts0 = ?config(server, C),
SupervisorOpts = woody_server:child_spec(
{?MODULE, ?config(testcase, C)},
ServerOpts0#{
handlers => [Handler],
read_body_opts => ReadBodyOpts,
transport_opts => TransportOpts,
protocol_opts => ProtocolOpts
}
{?MODULE, ?config(testcase, C)},
ServerOpts0#{
handlers => [Handler],
read_body_opts => ReadBodyOpts,
transport_opts => TransportOpts,
protocol_opts => ProtocolOpts
}
),
genlib_adhoc_supervisor:start_link(#{}, [SupervisorOpts]).
@ -181,7 +189,6 @@ handle_function(get_weapon, {Name, _}, _Context, {respects_max_connections, Tabl
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, _) ->
ok = timer:sleep(2000),
{ok, #'Powerup'{name = Name}}.