aboutsummaryrefslogtreecommitdiff
path: root/src/mod_bosh.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_bosh.erl')
-rw-r--r--src/mod_bosh.erl342
1 files changed, 342 insertions, 0 deletions
diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl
new file mode 100644
index 000000000..9803e941c
--- /dev/null
+++ b/src/mod_bosh.erl
@@ -0,0 +1,342 @@
+%%%-------------------------------------------------------------------
+%%% File : mod_bosh.erl
+%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Purpose : This module acts as a bridge to ejabberd_bosh which implements
+%%% the real stuff, this is to handle the new pluggable architecture
+%%% for extending ejabberd's http service.
+%%% Created : 20 Jul 2011 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2019 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(mod_bosh).
+
+-author('steve@zeank.in-berlin.de').
+
+%%-define(ejabberd_debug, true).
+
+-behaviour(gen_mod).
+
+-export([start_link/0]).
+-export([start/2, stop/1, reload/3, process/2, open_session/2,
+ close_session/1, find_session/1, clean_cache/1]).
+
+-export([depends/2, mod_opt_type/1, mod_options/1]).
+
+-include("logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("xmpp.hrl").
+-include("ejabberd_http.hrl").
+-include("bosh.hrl").
+
+-callback init() -> any().
+-callback open_session(binary(), pid()) -> ok | {error, any()}.
+-callback close_session(binary()) -> ok | {error, any()}.
+-callback find_session(binary()) -> {ok, pid()} | {error, any()}.
+-callback use_cache() -> boolean().
+-callback cache_nodes() -> [node()].
+
+-optional_callbacks([use_cache/0, cache_nodes/0]).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+process([], #request{method = 'POST', data = <<>>}) ->
+ ?DEBUG("Bad Request: no data", []),
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}};
+process([],
+ #request{method = 'POST', data = Data, ip = IP, headers = Hdrs}) ->
+ ?DEBUG("Incoming data: ~p", [Data]),
+ Type = get_type(Hdrs),
+ ejabberd_bosh:process_request(Data, IP, Type);
+process([], #request{method = 'GET', data = <<>>}) ->
+ {200, ?HEADER(?CT_XML), get_human_html_xmlel()};
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
+ {200, ?OPTIONS_HEADER, []};
+process(_Path, _Request) ->
+ ?DEBUG("Bad Request: ~p", [_Request]),
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
+
+-spec open_session(binary(), pid()) -> ok | {error, any()}.
+open_session(SID, Pid) ->
+ Mod = gen_mod:ram_db_mod(global, ?MODULE),
+ case Mod:open_session(SID, Pid) of
+ ok ->
+ delete_cache(Mod, SID);
+ {error, _} = Err ->
+ Err
+ end.
+
+-spec close_session(binary()) -> ok.
+close_session(SID) ->
+ Mod = gen_mod:ram_db_mod(global, ?MODULE),
+ Mod:close_session(SID),
+ delete_cache(Mod, SID).
+
+-spec find_session(binary()) -> {ok, pid()} | error.
+find_session(SID) ->
+ Mod = gen_mod:ram_db_mod(global, ?MODULE),
+ case use_cache(Mod) of
+ true ->
+ ets_cache:lookup(
+ ?BOSH_CACHE, SID,
+ fun() ->
+ case Mod:find_session(SID) of
+ {ok, Pid} -> {ok, Pid};
+ {error, _} -> error
+ end
+ end);
+ false ->
+ case Mod:find_session(SID) of
+ {ok, Pid} -> {ok, Pid};
+ {error, _} -> error
+ end
+ end.
+
+start(Host, _Opts) ->
+ Mod = gen_mod:ram_db_mod(Host, ?MODULE),
+ init_cache(Host, Mod),
+ Mod:init(),
+ clean_cache(),
+ TmpSup = gen_mod:get_module_proc(Host, ?MODULE),
+ TmpSupSpec = {TmpSup,
+ {ejabberd_tmp_sup, start_link, [TmpSup, ejabberd_bosh]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
+ supervisor:start_child(ejabberd_gen_mod_sup, TmpSupSpec).
+
+stop(Host) ->
+ TmpSup = gen_mod:get_module_proc(Host, ?MODULE),
+ supervisor:terminate_child(ejabberd_gen_mod_sup, TmpSup),
+ supervisor:delete_child(ejabberd_gen_mod_sup, TmpSup).
+
+reload(Host, _NewOpts, _OldOpts) ->
+ Mod = gen_mod:ram_db_mod(global, ?MODULE),
+ init_cache(Host, Mod),
+ Mod:init(),
+ ok.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+get_type(Hdrs) ->
+ try
+ {_, S} = lists:keyfind('Content-Type', 1, Hdrs),
+ [T|_] = str:tokens(S, <<";">>),
+ [_, <<"json">>] = str:tokens(T, <<"/">>),
+ json
+ catch _:_ ->
+ xml
+ end.
+
+depends(_Host, _Opts) ->
+ [].
+
+mod_opt_type(json) ->
+ econf:and_then(
+ econf:bool(),
+ fun(false) -> false;
+ (true) ->
+ ejabberd:start_app(jiffy),
+ true
+ end);
+mod_opt_type(max_concat) ->
+ econf:pos_int(unlimited);
+mod_opt_type(max_inactivity) ->
+ econf:timeout(second);
+mod_opt_type(max_pause) ->
+ econf:timeout(second);
+mod_opt_type(prebind) ->
+ econf:bool();
+mod_opt_type(queue_type) ->
+ econf:queue_type();
+mod_opt_type(ram_db_type) ->
+ econf:db_type(?MODULE);
+mod_opt_type(use_cache) ->
+ econf:bool();
+mod_opt_type(cache_size) ->
+ econf:pos_int(infinity);
+mod_opt_type(cache_missed) ->
+ econf:bool();
+mod_opt_type(cache_life_time) ->
+ econf:timeout(second, infinity).
+
+-spec mod_options(binary()) -> [{json, boolean()} |
+ {atom(), term()}].
+mod_options(Host) ->
+ [{json, false},
+ {max_concat, unlimited},
+ {max_inactivity, timer:seconds(30)},
+ {max_pause, timer:seconds(120)},
+ {prebind, false},
+ {ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)},
+ {queue_type, ejabberd_option:queue_type(Host)},
+ {use_cache, ejabberd_option:use_cache(Host)},
+ {cache_size, ejabberd_option:cache_size(Host)},
+ {cache_missed, ejabberd_option:cache_missed(Host)},
+ {cache_life_time, ejabberd_option:cache_life_time(Host)}].
+
+%%%----------------------------------------------------------------------
+%%% Cache stuff
+%%%----------------------------------------------------------------------
+-spec init_cache(binary(), module()) -> ok.
+init_cache(Host, Mod) ->
+ case use_cache(Mod, Host) of
+ true ->
+ ets_cache:new(?BOSH_CACHE, cache_opts(Host));
+ false ->
+ ets_cache:delete(?BOSH_CACHE)
+ end.
+
+-spec use_cache(module()) -> boolean().
+use_cache(Mod) ->
+ use_cache(Mod, global).
+
+-spec use_cache(module(), global | binary()) -> boolean().
+use_cache(Mod, Host) ->
+ case erlang:function_exported(Mod, use_cache, 0) of
+ true -> Mod:use_cache();
+ false -> mod_bosh_opt:use_cache(Host)
+ end.
+
+-spec cache_nodes(module()) -> [node()].
+cache_nodes(Mod) ->
+ case erlang:function_exported(Mod, cache_nodes, 0) of
+ true -> Mod:cache_nodes();
+ false -> ejabberd_cluster:get_nodes()
+ end.
+
+-spec delete_cache(module(), binary()) -> ok.
+delete_cache(Mod, SID) ->
+ case use_cache(Mod) of
+ true ->
+ ets_cache:delete(?BOSH_CACHE, SID, cache_nodes(Mod));
+ false ->
+ ok
+ end.
+
+-spec cache_opts(binary()) -> [proplists:property()].
+cache_opts(Host) ->
+ MaxSize = mod_bosh_opt:cache_size(Host),
+ CacheMissed = mod_bosh_opt:cache_missed(Host),
+ LifeTime = mod_bosh_opt:cache_life_time(Host),
+ [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
+
+-spec clean_cache(node()) -> non_neg_integer().
+clean_cache(Node) ->
+ ets_cache:filter(
+ ?BOSH_CACHE,
+ fun(_, error) ->
+ false;
+ (_, {ok, Pid}) ->
+ node(Pid) /= Node
+ end).
+
+-spec clean_cache() -> ok.
+clean_cache() ->
+ ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
+
+%%%----------------------------------------------------------------------
+%%% Help Web Page
+%%%----------------------------------------------------------------------
+
+get_human_html_xmlel() ->
+ Heading = <<"ejabberd ",
+ (iolist_to_binary(atom_to_list(?MODULE)))/binary>>,
+ #xmlel{name = <<"html">>,
+ attrs =
+ [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}],
+ children =
+ [#xmlel{name = <<"head">>,
+ children =
+ [#xmlel{name = <<"title">>,
+ children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"style">>,
+ children = [{xmlcdata, get_style_cdata()}]}]},
+ #xmlel{name = <<"body">>,
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"container">>}],
+ children = get_container_children(Heading)}]}]}.
+
+get_container_children(Heading) ->
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.ejabberd.im">>}],
+ children =
+ [#xmlel{name = <<"img">>,
+ attrs = [{<<"height">>, <<"32">>},
+ {<<"src">>, get_image_src()}]}]}]}]},
+ #xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"white section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"h1">>, children = [{xmlcdata, Heading}]},
+ #xmlel{name = <<"p">>, children =
+ [{xmlcdata, <<"An implementation of ">>},
+ #xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"http://xmpp.org/extensions/xep-0206.html">>}],
+ children = [{xmlcdata, <<"XMPP over BOSH (XEP-0206)">>}]}]},
+ #xmlel{name = <<"p">>, children =
+ [{xmlcdata, <<"This web page is only informative. To "
+ "use HTTP-Bind you need a Jabber/XMPP "
+ "client that supports it.">>}]}]}]},
+ #xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"section">>}],
+ children =
+ [#xmlel{name = <<"div">>,
+ attrs = [{<<"class">>, <<"block">>}],
+ children =
+ [#xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.ejabberd.im">>},
+ {<<"title">>, <<"ejabberd XMPP server">>}],
+ children = [{xmlcdata, <<"ejabberd">>}]},
+ {xmlcdata, <<" is maintained by ">>},
+ #xmlel{name = <<"a">>,
+ attrs = [{<<"href">>, <<"https://www.process-one.net">>},
+ {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
+ children = [{xmlcdata, <<"ProcessOne">>}]} ]}]}
+ ].
+
+get_style_cdata() ->
+ case misc:read_css("bosh.css") of
+ {ok, Data} -> Data;
+ {error, _} -> <<>>
+ end.
+
+get_image_src() ->
+ case misc:read_img("bosh-logo.png") of
+ {ok, Img} ->
+ B64Img = base64:encode(Img),
+ <<"data:image/png;base64,", B64Img/binary>>;
+ {error, _} ->
+ <<>>
+ end.