The following is how I created a custom ejabberd module to POST offline messages to a web application. I should mention this is not production ready I’ve only just started working with Erlang and the XMPP server ejabberd. The main reason I’m experimenting with XMPP is to provide apple push notifications once a user closes an iPhone application. My day job is programming in C# so this is going to be from a Windows .Net developer perspective.
Why use ejabberd which is written in a wacky language called Erlang? Well… you will have to decide for yourself. Erlang is a functional programming language which is known for concurrency and high reliability. Also, it’s the 24th most popular language on GitHub as of writting this so it can’t be that bad. :)
If you don’t have ejabberd setup yet see my previous post on getting strophejs, ejabberd, and IIS all working together.
Step one, take a deep breath and get up to speed on XMPP, ejabberd, and Erlang. Easy right?
Don’t worry, it will all come together by the end of this post. Well, at least you will write some Erlang by the end of the post. Here are some resource to have handy.
Books:
Let’s start with getting the Erlang environment up and running. You need Erlang to compile the new module. The Erlang version is important because it should match the one used in the installed ejabberd server. What version of Erlang was used with your installed ejabberd? You can find that information here using your ejabberd version number (if you don’t know the version number skip ahead to the next section before following this link): http://www.ejabberd.im/erlang.
If you don’t know your ejabberd version here is how to get it.
Go to start, run, and type cmd to open a command prompt. CD to your ejabberd\bin folder.
Mine in “D:\Program Files (x86)\ejabberd-2.1.9\bin”. Then run the following command:
ejabberdctl status
You should see something like this “The node ejabberd@localhost is started with status: started
ejabberd 2.1.9 is running in that node”
Note on ejabberd start error and erlang cookie
If you you get an error like the following “Failed RPC connection to the node ejabberd@localhost: nodedown”
Try to run the start command:
ejabberdctl start
If you get the same error when trying to start you might have a “.erlang.cookie” mismatch. On my box ejabberd runs using the
erlang cookie from “C:\Windows\.erlang.cookie”. This needs to match the one in your user folder. My Erlang cookie is located here:
“C:\Users\rowej\.erlang.cookie”. If you are getting the error mentioned, copy your erlang cookie from the windows folder
into your user folder.
Now that you have the ejabberd version go to ejabberd official site and see which version of erlang was used. This is important so you are using the same
version when you compile your module.
Now open your favorite text editor. I recommend Sublime Text 2 with Erlang syntax highlighting turned on. Also, there are several choices of plugins for NetBeans or Eclipse. The officially recommended IDE is Erlang mode for Emacs.
The next step is to get a basic module installed and make sure it is working. Lets start with a very simple module that does nothing but logs out information. Finally, time to start coding!
Create a file named mod_http_offline.erl.
Lets start with a very simple module that just does logging. This should give us a good idea of how ejabberd handles modules. Fill in the file you created “mod_http_offline.erl” with the following code:
%% name of module must match file name -module(mod_http_offline). -author("Earl The Squirrel"). %% Every ejabberd module implements the gen_mod behavior %% The gen_mod behavior requires two functions: start/2 and stop/1 -behaviour(gen_mod). %% public methods for this module -export([start/2, stop/1, create_message/3]). %% included for writing to ejabberd log file -include("ejabberd.hrl"). %% ejabberd functions for JID manipulation called jlib. -include("jlib.hrl"). start(_Host, _Opt) -> post_offline_message("testFrom", "testTo", "testBody"), ?INFO_MSG("mod_http_offline loading", []), ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 50). stop (_Host) -> ?INFO_MSG("stopping mod_http_offline", []), ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 50). create_message(_From, _To, Packet) -> Type = xml:get_tag_attr_s("type", Packet), FromS = xml:get_tag_attr_s("from", Packet), ToS = xml:get_tag_attr_s("to", Packet), Body = xml:get_path_s(Packet, [{elem, "body"}, cdata]), if (Type == "chat") -> post_offline_message(FromS, ToS, Body) end. post_offline_message(From, To, Body) -> ?INFO_MSG("Posting From ~p To ~p Body ~p~n",[From, To, Body]), ?INFO_MSG("post request sent (not really yet)", []).
Now lets get the necessary include files to compile the code. You will have to go to the ejabberd site and download the src.
select “source code” from the drop down list. Then use a tool like TarTool to unzip it. Copy jlib.hrl and ejabberd.hrl into the same folder where where you created the module source mod_http_offline.erl
Now open the erlang shell (programs > Erlang), type in:
Remember you must use a slash (forward slash). If you cut and paste from windows explorer you will get back slashes and you won’t be able to change directories.
cd("path/to/where.you/saved/the-module").
Then compile by doing the following:
c(mod_http_offline).
You should see this result and a .beam file now created in the directory:
./mod_http_offline.erl:8: Warning: behaviour gen_mod undefined
Shut down ejabberd server and copy the resulting .beam file to the directory where all the other ejabberd .beam files are.
\ejabberd-2.1.9\lib\ejabberd-2.1.9\ebin
Configure ejabberd to enable this module via the ejabberd.cfg. Add it to the list of modules in the Module section.
{mod_http_offline, []}
Start up eJabberd.
After it starts lets examine the log files to see if our module loaded.
example\path\ejabberd-2.1.9\logs\ejabberd.log
You should see the following log entries:
=INFO REPORT==== 2011-12-30 14:55:27 ===
I(<0.36.0>:mod_http_offline:44) : Posting From "testFrom" To "testTo" Body "testBody"
=INFO REPORT==== 2011-12-30 14:55:27 ===
I(<0.36.0>:mod_http_offline:45) : post request sent (not really yet)
=INFO REPORT==== 2011-12-30 14:55:27 ===
I(<0.36.0>:mod_http_offline:21) : mod_http_offline loading
Now we can have fun and send some offline messages. Get two XMPP clients running logged in with two different users. I used my admin account created during the ejabberd setup and a new test account created via the ejabberd Web Admin. I used the agxXMPP client to send messages back and forth but you can use which ever XMPP client is easier for you. You can find a list of them on the official XMPP website client list. Send a few message to a user who is not connected (offline) and check your ejabberd.log file.(example\path\ejabberd-2.1.9\logs\ejabberd.log). Here is what mine looks like after sending a Hello world message.
=INFO REPORT==== 2011-12-30 13:31:25 ===
I(<0.493.0>:mod_http_offline:44) : Posting From [] To "[email protected]" Body "hello world"
Shut down ejabberd and setup a page to handle HTTP POST’s via Erlang. use your favorite web framework for the page it really doesn’t matter. If you are using MVC .Net it might look somethign like this.
[HttpPost] public ActionResult Process() { var formValues = Request.Form; string from; string to; string body; if (!string.IsNullOrEmpty(formValues["From"])) { from = formValues["From"]; } if (!string.IsNullOrEmpty(formValues["To"])) { to = formValues["To"]; } if (!string.IsNullOrEmpty(formValues["Body"])) { body = formValues["Body"]; }
Then from the Erlang console send a post to make sure everything is working with this command:
http:request(post, {“http://localhost/OfflineDemoWebhost/Message/Process”,[], “application/x-www-form-urlencoded”,”From=earl the squirrel&To=you&Body=Hello World”}, [], [])
If you see a response starting with this you are good to go.
{ok,{{"HTTP/1.1",200,"OK"},
Now lets change the module to send the post adding in a few more lines of code.
%% name of module must match file name -module(mod_http_offline). -author("Earl The Squirrel"). %% Every ejabberd module implements the gen_mod behavior %% The gen_mod behavior requires two functions: start/2 and stop/1 -behaviour(gen_mod). %% public methods for this module -export([start/2, stop/1, create_message/3]). %% included for writing to ejabberd log file -include("ejabberd.hrl"). %% ejabberd functions for JID manipulation called jlib. -include("jlib.hrl"). start(_Host, _Opt) -> ?INFO_MSG("mod_http_offline loading", []), inets:start(), ?INFO_MSG("HTTP client started", []), post_offline_message("testFrom", "testTo", "testBody"), ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 50). stop (_Host) -> ?INFO_MSG("stopping mod_http_offline", []), ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 50). create_message(_From, _To, Packet) -> Type = xml:get_tag_attr_s("type", Packet), FromS = xml:get_tag_attr_s("from", Packet), ToS = xml:get_tag_attr_s("to", Packet), Body = xml:get_path_s(Packet, [{elem, "body"}, cdata]), if (Type == "chat") -> post_offline_message(FromS, ToS, Body) end. post_offline_message(From, To, Body) -> ?INFO_MSG("Posting From ~p To ~p Body ~p~n",[From, To, Body]), http:request(post, {"http://localhost/OfflineDemoWebhost/Message/Process",[], "application/x-www-form-urlencoded", lists:concat(["From=", From,"&To=", To,"&Body=", Body])}, [], []), ?INFO_MSG("post request sent", []).
Shut down ejabberd again and recompile. Copy the beam file overitting the old version. Now start up the server and you should have offline messages posting to your web application.
Leave a Reply