|
@@ -21,6 +21,8 @@
|
|
|
-include_lib("typerefl/include/types.hrl").
|
|
-include_lib("typerefl/include/types.hrl").
|
|
|
-include_lib("emqx_resource/include/emqx_resource_behaviour.hrl").
|
|
-include_lib("emqx_resource/include/emqx_resource_behaviour.hrl").
|
|
|
|
|
|
|
|
|
|
+-include_lib("emqx/include/logger.hrl").
|
|
|
|
|
+
|
|
|
%% callbacks of behaviour emqx_resource
|
|
%% callbacks of behaviour emqx_resource
|
|
|
-export([ on_start/2
|
|
-export([ on_start/2
|
|
|
, on_stop/2
|
|
, on_stop/2
|
|
@@ -38,7 +40,7 @@
|
|
|
|
|
|
|
|
-export([ check_ssl_opts/2 ]).
|
|
-export([ check_ssl_opts/2 ]).
|
|
|
|
|
|
|
|
--type connect_timeout() :: non_neg_integer() | infinity.
|
|
|
|
|
|
|
+-type connect_timeout() :: emqx_schema:duration() | infinity.
|
|
|
-type pool_type() :: random | hash.
|
|
-type pool_type() :: random | hash.
|
|
|
|
|
|
|
|
-reflect_type([ connect_timeout/0
|
|
-reflect_type([ connect_timeout/0
|
|
@@ -50,6 +52,22 @@
|
|
|
roots() ->
|
|
roots() ->
|
|
|
[{config, #{type => hoconsc:ref(?MODULE, config)}}].
|
|
[{config, #{type => hoconsc:ref(?MODULE, config)}}].
|
|
|
|
|
|
|
|
|
|
+fields("http_request") ->
|
|
|
|
|
+ [ {subscribe_local_topic, hoconsc:mk(binary())}
|
|
|
|
|
+ , {method, hoconsc:mk(method(), #{default => post})}
|
|
|
|
|
+ , {path, hoconsc:mk(binary(), #{default => <<"">>})}
|
|
|
|
|
+ , {headers, hoconsc:mk(map(),
|
|
|
|
|
+ #{default => #{
|
|
|
|
|
+ <<"accept">> => <<"application/json">>,
|
|
|
|
|
+ <<"cache-control">> => <<"no-cache">>,
|
|
|
|
|
+ <<"connection">> => <<"keep-alive">>,
|
|
|
|
|
+ <<"content-type">> => <<"application/json">>,
|
|
|
|
|
+ <<"keep-alive">> => <<"timeout=5">>}})
|
|
|
|
|
+ }
|
|
|
|
|
+ , {body, hoconsc:mk(binary(), #{default => <<"${payload}">>})}
|
|
|
|
|
+ , {request_timeout, hoconsc:mk(emqx_schema:duration_ms(), #{default => <<"30s">>})}
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
fields(config) ->
|
|
fields(config) ->
|
|
|
[ {base_url, fun base_url/1}
|
|
[ {base_url, fun base_url/1}
|
|
|
, {connect_timeout, fun connect_timeout/1}
|
|
, {connect_timeout, fun connect_timeout/1}
|
|
@@ -60,6 +78,13 @@ fields(config) ->
|
|
|
, {enable_pipelining, fun enable_pipelining/1}
|
|
, {enable_pipelining, fun enable_pipelining/1}
|
|
|
] ++ emqx_connector_schema_lib:ssl_fields().
|
|
] ++ emqx_connector_schema_lib:ssl_fields().
|
|
|
|
|
|
|
|
|
|
+method() ->
|
|
|
|
|
+ hoconsc:union([ typerefl:atom(post)
|
|
|
|
|
+ , typerefl:atom(put)
|
|
|
|
|
+ , typerefl:atom(get)
|
|
|
|
|
+ , typerefl:atom(delete)
|
|
|
|
|
+ ]).
|
|
|
|
|
+
|
|
|
validations() ->
|
|
validations() ->
|
|
|
[ {check_ssl_opts, fun check_ssl_opts/1} ].
|
|
[ {check_ssl_opts, fun check_ssl_opts/1} ].
|
|
|
|
|
|
|
@@ -79,7 +104,7 @@ max_retries(type) -> non_neg_integer();
|
|
|
max_retries(default) -> 5;
|
|
max_retries(default) -> 5;
|
|
|
max_retries(_) -> undefined.
|
|
max_retries(_) -> undefined.
|
|
|
|
|
|
|
|
-retry_interval(type) -> emqx_schema:duration_ms();
|
|
|
|
|
|
|
+retry_interval(type) -> emqx_schema:duration();
|
|
|
retry_interval(default) -> "1s";
|
|
retry_interval(default) -> "1s";
|
|
|
retry_interval(_) -> undefined.
|
|
retry_interval(_) -> undefined.
|
|
|
|
|
|
|
@@ -111,7 +136,7 @@ on_start(InstId, #{base_url := #{scheme := Scheme,
|
|
|
{tcp, []};
|
|
{tcp, []};
|
|
|
https ->
|
|
https ->
|
|
|
SSLOpts = emqx_plugin_libs_ssl:save_files_return_opts(
|
|
SSLOpts = emqx_plugin_libs_ssl:save_files_return_opts(
|
|
|
- maps:get(ssl_opts, Config), "connectors", InstId),
|
|
|
|
|
|
|
+ maps:get(ssl, Config), "connectors", InstId),
|
|
|
{tls, SSLOpts}
|
|
{tls, SSLOpts}
|
|
|
end,
|
|
end,
|
|
|
NTransportOpts = emqx_misc:ipv6_probe(TransportOpts),
|
|
NTransportOpts = emqx_misc:ipv6_probe(TransportOpts),
|
|
@@ -126,16 +151,32 @@ on_start(InstId, #{base_url := #{scheme := Scheme,
|
|
|
, {transport, Transport}
|
|
, {transport, Transport}
|
|
|
, {transport_opts, NTransportOpts}],
|
|
, {transport_opts, NTransportOpts}],
|
|
|
PoolName = emqx_plugin_libs_pool:pool_name(InstId),
|
|
PoolName = emqx_plugin_libs_pool:pool_name(InstId),
|
|
|
- {ok, _} = ehttpc_sup:start_pool(PoolName, PoolOpts),
|
|
|
|
|
- {ok, #{pool_name => PoolName,
|
|
|
|
|
- host => Host,
|
|
|
|
|
- port => Port,
|
|
|
|
|
- base_path => BasePath}}.
|
|
|
|
|
|
|
+ State = #{
|
|
|
|
|
+ pool_name => PoolName,
|
|
|
|
|
+ host => Host,
|
|
|
|
|
+ port => Port,
|
|
|
|
|
+ base_path => BasePath,
|
|
|
|
|
+ channels => preproc_channels(InstId, Config)
|
|
|
|
|
+ },
|
|
|
|
|
+ case ehttpc_sup:start_pool(PoolName, PoolOpts) of
|
|
|
|
|
+ {ok, _} -> {ok, State};
|
|
|
|
|
+ {error, {already_started, _}} -> {ok, State};
|
|
|
|
|
+ {error, Reason} ->
|
|
|
|
|
+ {error, Reason}
|
|
|
|
|
+ end.
|
|
|
|
|
|
|
|
on_stop(InstId, #{pool_name := PoolName}) ->
|
|
on_stop(InstId, #{pool_name := PoolName}) ->
|
|
|
logger:info("stopping http connector: ~p", [InstId]),
|
|
logger:info("stopping http connector: ~p", [InstId]),
|
|
|
ehttpc_sup:stop_pool(PoolName).
|
|
ehttpc_sup:stop_pool(PoolName).
|
|
|
|
|
|
|
|
|
|
+on_query(InstId, {send_message, ChannelId, Msg}, AfterQuery, #{channels := Channels} = State) ->
|
|
|
|
|
+ case maps:find(ChannelId, Channels) of
|
|
|
|
|
+ error -> ?SLOG(error, #{msg => "channel not found", channel_id => ChannelId});
|
|
|
|
|
+ {ok, ChannConf} ->
|
|
|
|
|
+ #{method := Method, path := Path, body := Body, headers := Headers,
|
|
|
|
|
+ request_timeout := Timeout} = proc_channel_conf(ChannConf, Msg),
|
|
|
|
|
+ on_query(InstId, {Method, {Path, Headers, Body}, Timeout}, AfterQuery, State)
|
|
|
|
|
+ end;
|
|
|
on_query(InstId, {Method, Request}, AfterQuery, State) ->
|
|
on_query(InstId, {Method, Request}, AfterQuery, State) ->
|
|
|
on_query(InstId, {undefined, Method, Request, 5000}, AfterQuery, State);
|
|
on_query(InstId, {undefined, Method, Request, 5000}, AfterQuery, State);
|
|
|
on_query(InstId, {Method, Request, Timeout}, AfterQuery, State) ->
|
|
on_query(InstId, {Method, Request, Timeout}, AfterQuery, State) ->
|
|
@@ -169,6 +210,52 @@ on_health_check(_InstId, #{host := Host, port := Port} = State) ->
|
|
|
%% Internal functions
|
|
%% Internal functions
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
+preproc_channels(<<"bridge:", BridgeId/binary>>, Config) ->
|
|
|
|
|
+ {BridgeType, BridgeName} = emqx_bridge:parse_bridge_id(BridgeId),
|
|
|
|
|
+ maps:fold(fun(ChannName, ChannConf, Acc) ->
|
|
|
|
|
+ Acc#{emqx_bridge:channel_id(BridgeType, BridgeName, egress_channels, ChannName) =>
|
|
|
|
|
+ preproc_channel_conf(ChannConf)}
|
|
|
|
|
+ end, #{}, maps:get(egress_channels, Config, #{})).
|
|
|
|
|
+
|
|
|
|
|
+preproc_channel_conf(#{
|
|
|
|
|
+ method := Method,
|
|
|
|
|
+ path := Path,
|
|
|
|
|
+ body := Body,
|
|
|
|
|
+ headers := Headers} = Conf) ->
|
|
|
|
|
+ Conf#{ method => emqx_plugin_libs_rule:preproc_tmpl(bin(Method))
|
|
|
|
|
+ , path => emqx_plugin_libs_rule:preproc_tmpl(Path)
|
|
|
|
|
+ , body => emqx_plugin_libs_rule:preproc_tmpl(Body)
|
|
|
|
|
+ , headers => preproc_headers(Headers)
|
|
|
|
|
+ }.
|
|
|
|
|
+
|
|
|
|
|
+preproc_headers(Headers) ->
|
|
|
|
|
+ maps:fold(fun(K, V, Acc) ->
|
|
|
|
|
+ Acc#{emqx_plugin_libs_rule:preproc_tmpl(bin(K)) =>
|
|
|
|
|
+ emqx_plugin_libs_rule:preproc_tmpl(bin(V))}
|
|
|
|
|
+ end, #{}, Headers).
|
|
|
|
|
+
|
|
|
|
|
+proc_channel_conf(#{
|
|
|
|
|
+ method := MethodTks,
|
|
|
|
|
+ path := PathTks,
|
|
|
|
|
+ body := BodyTks,
|
|
|
|
|
+ headers := HeadersTks} = Conf, Msg) ->
|
|
|
|
|
+ Conf#{ method => make_method(emqx_plugin_libs_rule:proc_tmpl(MethodTks, Msg))
|
|
|
|
|
+ , path => emqx_plugin_libs_rule:proc_tmpl(PathTks, Msg)
|
|
|
|
|
+ , body => emqx_plugin_libs_rule:proc_tmpl(BodyTks, Msg)
|
|
|
|
|
+ , headers => maps:to_list(proc_headers(HeadersTks, Msg))
|
|
|
|
|
+ }.
|
|
|
|
|
+
|
|
|
|
|
+proc_headers(HeaderTks, Msg) ->
|
|
|
|
|
+ maps:fold(fun(K, V, Acc) ->
|
|
|
|
|
+ Acc#{emqx_plugin_libs_rule:proc_tmpl(K, Msg) =>
|
|
|
|
|
+ emqx_plugin_libs_rule:proc_tmpl(V, Msg)}
|
|
|
|
|
+ end, #{}, HeaderTks).
|
|
|
|
|
+
|
|
|
|
|
+make_method(M) when M == <<"POST">>; M == <<"post">> -> post;
|
|
|
|
|
+make_method(M) when M == <<"PUT">>; M == <<"put">> -> put;
|
|
|
|
|
+make_method(M) when M == <<"GET">>; M == <<"get">> -> get;
|
|
|
|
|
+make_method(M) when M == <<"DELETE">>; M == <<"delete">> -> delete.
|
|
|
|
|
+
|
|
|
check_ssl_opts(Conf) ->
|
|
check_ssl_opts(Conf) ->
|
|
|
check_ssl_opts("base_url", Conf).
|
|
check_ssl_opts("base_url", Conf).
|
|
|
|
|
|
|
@@ -185,3 +272,10 @@ update_path(BasePath, {Path, Headers}) ->
|
|
|
{filename:join(BasePath, Path), Headers};
|
|
{filename:join(BasePath, Path), Headers};
|
|
|
update_path(BasePath, {Path, Headers, Body}) ->
|
|
update_path(BasePath, {Path, Headers, Body}) ->
|
|
|
{filename:join(BasePath, Path), Headers, Body}.
|
|
{filename:join(BasePath, Path), Headers, Body}.
|
|
|
|
|
+
|
|
|
|
|
+bin(Bin) when is_binary(Bin) ->
|
|
|
|
|
+ Bin;
|
|
|
|
|
+bin(Str) when is_list(Str) ->
|
|
|
|
|
+ list_to_binary(Str);
|
|
|
|
|
+bin(Atom) when is_atom(Atom) ->
|
|
|
|
|
+ atom_to_binary(Atom, utf8).
|