try to develop a sample service using ejabberd router

Today free, try to develop a sample service using ejabberd router.
+ Requirements:

  • Erlang/OTP, XMPP/Jabber protocol and Module development for Ejabberd skills (of course).
  • Jabber clients for testing this service : PSI, Pidgin, Tkabber … I use all :D
  • Mastery of two modules: gen_mod and gen_server behaviour
  • Emacs with Erlang mode (optional, but recommended). :D

+ Using Emacs to creat a file with file name is : mod_sample.erl
+ OK, now we are using emacs/mode to code generation gen_server’s skeleton :D -> Erlang/Skeletons/gen_server

+ Service name : Sample (sample.example.com) with example.com is virtual host.
+ Scope : I do not think but it could be an ejabberd service pattern :D

Erlang
%%%-------------------------------------------------------------------
%%% File    : mod_sample.erl
%%% Author  : Cuong Le <>
%%% Description : ejabberd service using ejabberd_router
%%%
%%% Created : 24 Nov 2009 by Cuong Le <>
%%%-------------------------------------------------------------------
-module(mod_sample).
 
-behaviour(gen_server).
-behaviour(gen_mod).
 
%% API
-export([start_link/2]).
 
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
   terminate/2, code_change/3]).
 
%% gen_mod callbacks
-export([start/2,stop/1]).
 
-define(PROCNAME, ejabberd_mod_sample).
 
-record(state, {
host
}).
 
-include("ejabberd.hrl").
-include("jlib.hrl").
 
%%====================================================================
%% API
%%====================================================================
 
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
 
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
 
%%====================================================================
%% gen_mod callbacks
%%====================================================================
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, permanent, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
 
stop(Host)->
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
    supervisor:terminate_child(ejabberd_sup, Proc),
    supervisor:delete_child(ejabberd_sup, Proc).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
 
init([Host, Opts]) ->
 
MyHost = gen_mod:get_opt_host(Host, Opts, "sample.@HOST@"),
 
ejabberd_router:register_route(MyHost),
 
{ok, #state{
host = MyHost
}}.
 
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
 
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
 
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
 
handle_cast(_Msg, State) ->
{noreply, State}.
 
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
 
handle_info({route, From, To, Packet}, #state{host = Host} = State) ->
    case catch do_route(From, To, Packet, Host) of
  {'EXIT', Reason}->
      ?ERROR_MSG("~p",[Reason]);
  _ ->
      ok
    end,
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
 
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
 
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
ok.
 
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
 
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
 
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
 
do_route(From, To, Packet, Host)->
    {xmlelement, Name, Attrs, _Els} = Packet,
    case To of 
  #jid{luser = "", lresource = ""} ->
      case Name of
    "iq" ->        
        case jlib:iq_query_info(Packet) of
      #iq{type=get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ ->
          {xmlelement, _, QAttrs, _} = SubEl,
          Node = xml:get_attr_s("node", QAttrs),
          Res = case iq_disco_info(Host, Node, From, Lang) of
              {result, IQRes} ->
            jlib:iq_to_xml(
              IQ#iq{type = result,
              sub_el = [{xmlelement, "query",
                   QAttrs, IQRes}]});
              {error, Error} ->
            jlib:make_error_reply(Packet, Error)
          end,
          ejabberd_router:route(To, From, Res);            
      #iq{} ->
          Err = jlib:make_error_reply(
            Packet,
            ?ERR_FEATURE_NOT_IMPLEMENTED),
          ejabberd_router:route(To, From, Err);          
      _ ->            
          ok
        end;       
    _ ->
        ok
      end;
  _ ->
      ok
    end.
 
string_to_node(SNode) ->
    string:tokens(SNode, "/").
 
iq_disco_info(Host, SNode, From, _Lang) ->
    Node = string_to_node(SNode),
    case Node of
  [] ->
      {result,
       [{xmlelement, "identity",
         [{"category", "test"},
    {"type", "service"},
    {"name", "sample"}], []}]
      };
  _ ->
      ok  
    end.

To be continue … :D

blog comments powered by Disqus