mirror of
https://github.com/processone/ejabberd.git
synced 2024-06-12 21:52:07 +02:00
Add http_p1.erl, rest.erl, and oauth2 ReST backend for tokens.
This commit is contained in:
parent
e7787e2f33
commit
ac6f701033
|
@ -225,6 +225,7 @@ start_apps() ->
|
|||
ejabberd:start_app(fast_tls),
|
||||
ejabberd:start_app(fast_xml),
|
||||
ejabberd:start_app(stringprep),
|
||||
http_p1:start(),
|
||||
ejabberd:start_app(cache_tab).
|
||||
|
||||
opt_type(net_ticktime) ->
|
||||
|
|
98
src/ejabberd_oauth_rest.erl
Normal file
98
src/ejabberd_oauth_rest.erl
Normal file
|
@ -0,0 +1,98 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_oauth_rest.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : OAUTH2 REST backend
|
||||
%%% Created : 26 Jul 2016 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_rest).
|
||||
|
||||
-export([init/0,
|
||||
store/1,
|
||||
lookup/1,
|
||||
clean/1,
|
||||
opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_oauth.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
init() ->
|
||||
rest:start(?MYNAME),
|
||||
ok.
|
||||
|
||||
store(R) ->
|
||||
Path = path(<<"store">>),
|
||||
%% Retry 2 times, with a backoff of 500millisec
|
||||
{User, Server} = R#oauth_token.us,
|
||||
SJID = jid:to_string({User, Server, <<"">>}),
|
||||
case rest:with_retry(
|
||||
post,
|
||||
[?MYNAME, Path, [],
|
||||
{[{<<"token">>, R#oauth_token.token},
|
||||
{<<"user">>, SJID},
|
||||
{<<"scope">>, R#oauth_token.scope},
|
||||
{<<"expire">>, R#oauth_token.expire}
|
||||
]}], 2, 500) of
|
||||
{ok, Code, _} when Code == 200 orelse Code == 201 ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to store oauth record ~p: ~p", [R, Err]),
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
lookup(Token) ->
|
||||
Path = path(<<"lookup">>),
|
||||
case rest:with_retry(post, [?MYNAME, Path, [],
|
||||
{[{<<"token">>, Token}]}],
|
||||
2, 500) of
|
||||
{ok, 200, {Data}} ->
|
||||
SJID = proplists:get_value(<<"user">>, Data, <<>>),
|
||||
JID = jid:from_string(SJID),
|
||||
US = {JID#jid.luser, JID#jid.lserver},
|
||||
Scope = proplists:get_value(<<"scope">>, Data, []),
|
||||
Expire = proplists:get_value(<<"expire">>, Data, 0),
|
||||
#oauth_token{token = Token,
|
||||
us = US,
|
||||
scope = Scope,
|
||||
expire = Expire};
|
||||
{ok, 404, _Resp} ->
|
||||
false;
|
||||
Other ->
|
||||
?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]),
|
||||
{error, rest_failed}
|
||||
end.
|
||||
|
||||
clean(_TS) ->
|
||||
ok.
|
||||
|
||||
path(Path) ->
|
||||
Base = ejabberd_config:get_option(ext_api_path_oauth,
|
||||
fun(X) -> iolist_to_binary(X) end,
|
||||
<<"/oauth">>),
|
||||
<<Base/binary, "/", Path/binary>>.
|
||||
|
||||
|
||||
opt_type(ext_api_path_oauth) ->
|
||||
fun (X) -> iolist_to_binary(X) end;
|
||||
opt_type(_) -> [ext_api_path_oauth].
|
358
src/http_p1.erl
Normal file
358
src/http_p1.erl
Normal file
|
@ -0,0 +1,358 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%% File : http_p1.erl
|
||||
%%% Author : Emilio Bustos <ebustos@process-one.net>
|
||||
%%% Purpose : Provide a common API for inets / lhttpc / ibrowse
|
||||
%%% Created : 29 Jul 2010 by Emilio Bustos <ebustos@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(http_p1).
|
||||
|
||||
-author('ebustos@process-one.net').
|
||||
|
||||
-export([start/0, stop/0, get/1, get/2, post/2, post/3,
|
||||
request/3, request/4, request/5,
|
||||
get_pool_size/0, set_pool_size/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
% -define(USE_INETS, 1).
|
||||
-define(USE_LHTTPC, 1).
|
||||
% -define(USE_IBROWSE, 1).
|
||||
% inets used as default if none specified
|
||||
|
||||
-ifdef(USE_IBROWSE).
|
||||
|
||||
start() ->
|
||||
ejabberd:start_app(ibrowse).
|
||||
|
||||
stop() ->
|
||||
application:stop(ibrowse).
|
||||
|
||||
request(Method, URL, Hdrs, Body, Opts) ->
|
||||
TimeOut = proplists:get_value(timeout, Opts, infinity),
|
||||
Options = [{inactivity_timeout, TimeOut}
|
||||
| proplists:delete(timeout, Opts)],
|
||||
case ibrowse:send_req(URL, Hdrs, Method, Body, Options)
|
||||
of
|
||||
{ok, Status, Headers, Response} ->
|
||||
{ok, jlib:binary_to_integer(Status), Headers,
|
||||
Response};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
get_pool_size() ->
|
||||
application:get_env(ibrowse, default_max_sessions, 10).
|
||||
|
||||
set_pool_size(Size) ->
|
||||
application:set_env(ibrowse, default_max_sessions, Size).
|
||||
|
||||
-else.
|
||||
|
||||
-ifdef(USE_LHTTPC).
|
||||
|
||||
start() ->
|
||||
ejabberd:start_app(lhttpc).
|
||||
|
||||
stop() ->
|
||||
application:stop(lhttpc).
|
||||
|
||||
request(Method, URL, Hdrs, Body, Opts) ->
|
||||
{[TO, SO], Rest} = proplists:split(Opts, [timeout, socket_options]),
|
||||
TimeOut = proplists:get_value(timeout, TO, infinity),
|
||||
SockOpt = proplists:get_value(socket_options, SO, []),
|
||||
Options = [{connect_options, SockOpt} | Rest],
|
||||
Result = lhttpc:request(URL, Method, Hdrs, Body, TimeOut, Options),
|
||||
?DEBUG("HTTP request -> response:~n"
|
||||
"** Method = ~p~n"
|
||||
"** URI = ~s~n"
|
||||
"** Body = ~s~n"
|
||||
"** Hdrs = ~p~n"
|
||||
"** Timeout = ~p~n"
|
||||
"** Options = ~p~n"
|
||||
"** Response = ~p",
|
||||
[Method, URL, Body, Hdrs, TimeOut, Options, Result]),
|
||||
case Result of
|
||||
{ok, {{Status, _Reason}, Headers, Response}} ->
|
||||
{ok, Status, Headers, (Response)};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
get_pool_size() ->
|
||||
Opts = proplists:get_value(lhttpc_manager, lhttpc_manager:list_pools()),
|
||||
proplists:get_value(max_pool_size,Opts).
|
||||
|
||||
set_pool_size(Size) ->
|
||||
lhttpc_manager:set_max_pool_size(lhttpc_manager, Size).
|
||||
|
||||
-else.
|
||||
|
||||
start() ->
|
||||
ejabberd:start_app(inets).
|
||||
|
||||
stop() ->
|
||||
application:stop(inets).
|
||||
|
||||
to_list(Str) when is_binary(Str) ->
|
||||
binary_to_list(Str);
|
||||
to_list(Str) ->
|
||||
Str.
|
||||
|
||||
request(Method, URLRaw, HdrsRaw, Body, Opts) ->
|
||||
Hdrs = lists:map(fun({N, V}) ->
|
||||
{to_list(N), to_list(V)}
|
||||
end, HdrsRaw),
|
||||
URL = to_list(URLRaw),
|
||||
|
||||
Request = case Method of
|
||||
get -> {URL, Hdrs};
|
||||
head -> {URL, Hdrs};
|
||||
delete -> {URL, Hdrs};
|
||||
_ -> % post, etc.
|
||||
{URL, Hdrs,
|
||||
to_list(proplists:get_value(<<"content-type">>, HdrsRaw, [])),
|
||||
Body}
|
||||
end,
|
||||
Options = case proplists:get_value(timeout, Opts,
|
||||
infinity)
|
||||
of
|
||||
infinity -> proplists:delete(timeout, Opts);
|
||||
_ -> Opts
|
||||
end,
|
||||
case httpc:request(Method, Request, Options, []) of
|
||||
{ok, {{_, Status, _}, Headers, Response}} ->
|
||||
{ok, Status, Headers, Response};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
get_pool_size() ->
|
||||
{ok, Size} = httpc:get_option(max_sessions),
|
||||
Size.
|
||||
|
||||
set_pool_size(Size) ->
|
||||
httpc:set_option(max_sessions, Size).
|
||||
|
||||
-endif.
|
||||
|
||||
-endif.
|
||||
|
||||
-type({header,
|
||||
{type, 63, tuple,
|
||||
[{type, 63, union,
|
||||
[{type, 63, string, []}, {type, 63, atom, []}]},
|
||||
{type, 63, string, []}]},
|
||||
[]}).
|
||||
|
||||
-type({headers,
|
||||
{type, 64, list, [{type, 64, header, []}]}, []}).
|
||||
|
||||
-type({option,
|
||||
{type, 67, union,
|
||||
[{type, 67, tuple,
|
||||
[{atom, 67, connect_timeout}, {type, 67, timeout, []}]},
|
||||
{type, 68, tuple,
|
||||
[{atom, 68, timeout}, {type, 68, timeout, []}]},
|
||||
{type, 70, tuple,
|
||||
[{atom, 70, send_retry},
|
||||
{type, 70, non_neg_integer, []}]},
|
||||
{type, 71, tuple,
|
||||
[{atom, 71, partial_upload},
|
||||
{type, 71, union,
|
||||
[{type, 71, non_neg_integer, []},
|
||||
{atom, 71, infinity}]}]},
|
||||
{type, 72, tuple,
|
||||
[{atom, 72, partial_download}, {type, 72, pid, []},
|
||||
{type, 72, union,
|
||||
[{type, 72, non_neg_integer, []},
|
||||
{atom, 72, infinity}]}]}]},
|
||||
[]}).
|
||||
|
||||
-type({options,
|
||||
{type, 74, list, [{type, 74, option, []}]}, []}).
|
||||
|
||||
-type({result,
|
||||
{type, 76, union,
|
||||
[{type, 76, tuple,
|
||||
[{atom, 76, ok},
|
||||
{type, 76, tuple,
|
||||
[{type, 76, tuple,
|
||||
[{type, 76, pos_integer, []}, {type, 76, string, []}]},
|
||||
{type, 76, headers, []}, {type, 76, string, []}]}]},
|
||||
{type, 77, tuple,
|
||||
[{atom, 77, error}, {type, 77, atom, []}]}]},
|
||||
[]}).
|
||||
|
||||
%% @spec (URL) -> Result
|
||||
%% URL = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a GET request.
|
||||
%% Would be the same as calling `request(get, URL, [])',
|
||||
%% that is {@link request/3} with an empty header list.
|
||||
%% @end
|
||||
%% @see request/3
|
||||
-spec get(string()) -> result().
|
||||
get(URL) -> request(get, URL, []).
|
||||
|
||||
%% @spec (URL, Hdrs) -> Result
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a GET request.
|
||||
%% Would be the same as calling `request(get, URL, Hdrs)'.
|
||||
%% @end
|
||||
%% @see request/3
|
||||
-spec get(string(), headers()) -> result().
|
||||
get(URL, Hdrs) -> request(get, URL, Hdrs).
|
||||
|
||||
%% @spec (URL, RequestBody) -> Result
|
||||
%% URL = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a POST request with form data.
|
||||
%% Would be the same as calling
|
||||
%% `request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body)'.
|
||||
%% @end
|
||||
%% @see request/4
|
||||
-spec post(string(), string()) -> result().
|
||||
post(URL, Body) ->
|
||||
request(post, URL,
|
||||
[{<<"content-type">>, <<"x-www-form-urlencoded">>}],
|
||||
Body).
|
||||
|
||||
%% @spec (URL, Hdrs, RequestBody) -> Result
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a POST request.
|
||||
%% Would be the same as calling
|
||||
%% `request(post, URL, Hdrs, Body)'.
|
||||
%% @end
|
||||
%% @see request/4
|
||||
-spec post(string(), headers(), string()) -> result().
|
||||
post(URL, Hdrs, Body) ->
|
||||
NewHdrs = case [X
|
||||
|| {X, _} <- Hdrs,
|
||||
str:to_lower(X) == <<"content-type">>]
|
||||
of
|
||||
[] ->
|
||||
[{<<"content-type">>, <<"x-www-form-urlencoded">>}
|
||||
| Hdrs];
|
||||
_ -> Hdrs
|
||||
end,
|
||||
request(post, URL, NewHdrs, Body).
|
||||
|
||||
%% @spec (Method, URL, Hdrs) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request without a body.
|
||||
%% Would be the same as calling `request(Method, URL, Hdrs, [], [])',
|
||||
%% that is {@link request/5} with an empty body.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers()) -> result().
|
||||
request(Method, URL, Hdrs) ->
|
||||
request(Method, URL, Hdrs, [], []).
|
||||
|
||||
%% @spec (Method, URL, Hdrs, RequestBody) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request with a body.
|
||||
%% Would be the same as calling
|
||||
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||
%% with no options.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers(), string()) -> result().
|
||||
request(Method, URL, Hdrs, Body) ->
|
||||
request(Method, URL, Hdrs, Body, []).
|
||||
|
||||
%% @spec (Method, URL, Hdrs, RequestBody, Options) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Options = [Option]
|
||||
%% Option = {timeout, Milliseconds | infinity} |
|
||||
%% {connect_timeout, Milliseconds | infinity} |
|
||||
%% {socket_options, [term()]} |
|
||||
|
||||
%% Milliseconds = integer()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request with a body.
|
||||
%% Would be the same as calling
|
||||
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||
%% with no options.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers(), string(), options()) -> result().
|
||||
|
||||
% ibrowse {response_format, response_format()} |
|
||||
% Options - [option()]
|
||||
% Option - {sync, boolean()} | {stream, StreamTo} | {body_format, body_format()} | {full_result,
|
||||
% boolean()} | {headers_as_is, boolean()}
|
||||
%body_format() = string() | binary()
|
||||
% The body_format option is only valid for the synchronous request and the default is string.
|
||||
% When making an asynchronous request the body will always be received as a binary.
|
||||
% lhttpc: always binary
|
||||
|
181
src/rest.erl
Normal file
181
src/rest.erl
Normal file
|
@ -0,0 +1,181 @@
|
|||
%%%----------------------------------------------------------------------
|
||||
%%% File : rest.erl
|
||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Purpose : Generic REST client
|
||||
%%% Created : 16 Oct 2014 by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(rest).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-export([start/1, stop/1, get/2, get/3, post/4, delete/2,
|
||||
request/6, with_retry/4, opt_type/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(HTTP_TIMEOUT, 10000).
|
||||
-define(CONNECT_TIMEOUT, 8000).
|
||||
|
||||
start(Host) ->
|
||||
http_p1:start(),
|
||||
Pool_size =
|
||||
ejabberd_config:get_option({ext_api_http_pool_size, Host},
|
||||
fun(X) when is_integer(X), X > 0->
|
||||
X
|
||||
end,
|
||||
100),
|
||||
http_p1:set_pool_size(Pool_size).
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
with_retry(Method, Args, MaxRetries, Backoff) ->
|
||||
with_retry(Method, Args, 0, MaxRetries, Backoff).
|
||||
with_retry(Method, Args, Retries, MaxRetries, Backoff) ->
|
||||
case apply(?MODULE, Method, Args) of
|
||||
%% Only retry on timeout errors
|
||||
{error, {http_error,{error,Error}}}
|
||||
when Retries < MaxRetries
|
||||
andalso (Error == 'timeout' orelse Error == 'connect_timeout') ->
|
||||
timer:sleep(round(math:pow(2, Retries)) * Backoff),
|
||||
with_retry(Method, Args, Retries+1, MaxRetries, Backoff);
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
|
||||
get(Server, Path) ->
|
||||
request(Server, get, Path, [], "application/json", <<>>).
|
||||
get(Server, Path, Params) ->
|
||||
request(Server, get, Path, Params, "application/json", <<>>).
|
||||
|
||||
delete(Server, Path) ->
|
||||
request(Server, delete, Path, [], "application/json", <<>>).
|
||||
|
||||
post(Server, Path, Params, Content) ->
|
||||
Data = case catch jiffy:encode(Content) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("HTTP content encodage failed:~n"
|
||||
"** Content = ~p~n"
|
||||
"** Err = ~p",
|
||||
[Content, Reason]),
|
||||
<<>>;
|
||||
Encoded ->
|
||||
Encoded
|
||||
end,
|
||||
request(Server, post, Path, Params, "application/json", Data).
|
||||
|
||||
request(Server, Method, Path, Params, Mime, Data) ->
|
||||
URI = url(Server, Path, Params),
|
||||
Opts = [{connect_timeout, ?CONNECT_TIMEOUT},
|
||||
{timeout, ?HTTP_TIMEOUT}],
|
||||
Hdrs = [{"connection", "keep-alive"},
|
||||
{"content-type", Mime},
|
||||
{"User-Agent", "ejabberd"}],
|
||||
Begin = os:timestamp(),
|
||||
Result = case catch http_p1:request(Method, URI, Hdrs, Data, Opts) of
|
||||
{ok, Code, _, <<>>} ->
|
||||
{ok, Code, []};
|
||||
{ok, Code, _, <<" ">>} ->
|
||||
{ok, Code, []};
|
||||
{ok, Code, _, <<"\r\n">>} ->
|
||||
{ok, Code, []};
|
||||
{ok, Code, _, Body} ->
|
||||
try jiffy:decode(Body) of
|
||||
JSon ->
|
||||
{ok, Code, JSon}
|
||||
catch
|
||||
_:Error ->
|
||||
?ERROR_MSG("HTTP response decode failed:~n"
|
||||
"** URI = ~s~n"
|
||||
"** Body = ~p~n"
|
||||
"** Err = ~p",
|
||||
[URI, Body, Error]),
|
||||
{error, {invalid_json, Body}}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("HTTP request failed:~n"
|
||||
"** URI = ~s~n"
|
||||
"** Err = ~p",
|
||||
[URI, Reason]),
|
||||
{error, {http_error, {error, Reason}}};
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("HTTP request failed:~n"
|
||||
"** URI = ~s~n"
|
||||
"** Err = ~p",
|
||||
[URI, Reason]),
|
||||
{error, {http_error, {error, Reason}}}
|
||||
end,
|
||||
ejabberd_hooks:run(backend_api_call, Server, [Server, Method, Path]),
|
||||
case Result of
|
||||
{ok, _, _} ->
|
||||
End = os:timestamp(),
|
||||
Elapsed = timer:now_diff(End, Begin) div 1000, %% time in ms
|
||||
ejabberd_hooks:run(backend_api_response_time, Server,
|
||||
[Server, Method, Path, Elapsed]);
|
||||
{error, {http_error,{error,timeout}}} ->
|
||||
ejabberd_hooks:run(backend_api_timeout, Server,
|
||||
[Server, Method, Path]);
|
||||
{error, {http_error,{error,connect_timeout}}} ->
|
||||
ejabberd_hooks:run(backend_api_timeout, Server,
|
||||
[Server, Method, Path]);
|
||||
{error, _} ->
|
||||
ejabberd_hooks:run(backend_api_error, Server,
|
||||
[Server, Method, Path])
|
||||
end,
|
||||
Result.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% HTTP helpers
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
base_url(Server, Path) ->
|
||||
Tail = case iolist_to_binary(Path) of
|
||||
<<$/, Ok/binary>> -> Ok;
|
||||
Ok -> Ok
|
||||
end,
|
||||
case Tail of
|
||||
<<"http", _Url/binary>> -> Tail;
|
||||
_ ->
|
||||
Base = ejabberd_config:get_option({ext_api_url, Server},
|
||||
fun(X) ->
|
||||
iolist_to_binary(X)
|
||||
end,
|
||||
<<"http://localhost/api">>),
|
||||
<<Base/binary, "/", Tail/binary>>
|
||||
end.
|
||||
|
||||
url(Server, Path, []) ->
|
||||
binary_to_list(base_url(Server, Path));
|
||||
url(Server, Path, Params) ->
|
||||
Base = base_url(Server, Path),
|
||||
[<<$&, ParHead/binary>> | ParTail] =
|
||||
[<<"&", (iolist_to_binary(Key))/binary, "=",
|
||||
(ejabberd_http:url_encode(Value))/binary>>
|
||||
|| {Key, Value} <- Params],
|
||||
Tail = iolist_to_binary([ParHead | ParTail]),
|
||||
binary_to_list(<<Base/binary, $?, Tail/binary>>).
|
||||
|
||||
opt_type(ext_api_http_pool_size) ->
|
||||
fun (X) when is_integer(X), X > 0 -> X end;
|
||||
opt_type(ext_api_url) ->
|
||||
fun (X) -> iolist_to_binary(X) end;
|
||||
opt_type(_) -> [ext_api_http_pool_size, ext_api_url].
|
Loading…
Reference in New Issue
Block a user