tag:blogger.com,1999:blog-91115429035480562182024-03-05T15:18:34.311-05:00Erlang and other cool thingsXMPP and IM, Erlang and other functional languages, AWS and cloud; sharing thoughts and experience with like-minded professionals.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comBlogger20125tag:blogger.com,1999:blog-9111542903548056218.post-7122989320930999832010-11-21T01:01:00.001-05:002010-11-21T01:02:30.650-05:00gen_client: running BOSH and new configuration optionsAs a result of efforts to make <a href="http://github.com/bokner/gen_client">gen_client </a>run over BOSH, there are now 2 new groups of options:<br />
<br />
<ul><li><i>connection</i> - allows to specify type of connection and its parameters. Available types are <b>tcp</b>, <b>ssl</b> and <b>bosh. </b></li>
<li><i>auth - </i>allows to specify type of authentication and its parameters. There are currently 2 types reflecting exmpp authentication: <b>basic</b> (or legacy auth) and <b>sasl.</b> .</li>
</ul><ul></ul>Note: this version of gen_client will not work with exmpp versions lower than 0.9.5.<br />
<br />
Example: to login with <b>my_acct@my_domain.com</b> through BOSH served at <b>http://my_server.com:5280/http-bind</b> using SASL DIGEST-MD5:<br />
<br />
<pre class="erlang" name="code">gen_client:start("my_acct@my_domain.com", [{connection, {bosh, "http://my_server.com:5280/http-bind"}}, {auth, [{password, "my_pwd"}, {sasl, "DIGEST-MD5"}]}]. </pre><br />
Yes, that's that simple. The default connection type is tcp, and default auth type is SASL PLAIN, so the code that uses previous versions of gen_client shouldn't break.<br />
<br />
Note: as of now (v. 0.9.5) exmpp supports only SASL PLAIN, SASL ANONYMOUS and SASL DIGEST-MD5. Also, BOSH will only work with SASL, but not with basic authentication.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-40928100783616093882010-08-11T12:47:00.000-04:002010-08-13T15:02:36.165-04:00The gen_client API overviewI've been using <a href="http://github.com/bokner/gen_client">gen_client</a> in few real projects, which helped to weed out many bugs, shape the design and evaluate features. I feel that it's now ready for public usage, so I would like to briefly explain some concepts used by gen_client and show how they might help to make XMPP client programming easier.<br />
<br />
<b><span class="Apple-style-span" style="font-family: inherit;">Starting the client.</span></b><br />
<br />
<b><i>start(Jid, Host, Port, Password) </i></b><br />
<b><i>start(Jid, Host, Port, Password, Options)</i></b><br />
<b><i>start(Account, Domain, Host, Port, Password, Options)</i></b><br />
<b><i>start(Account, Domain, Resourse, Host, Port, Password, Options)</i></b><br />
<br />
Above calls create a client session and return {ok, ClientRef} tuple in case everything went well. ClientRef (which is a Pid of gen_server process associated with the client session) then can be used to make gen_client API calls. For example:<br />
<span class="Apple-style-span" style="white-space: pre;"> </span><br />
<pre class="erlang" name="code">{ok, Client} = gen_client:start("gen_client@jabber.ru", "jabber.ru", 5222, "test",
[{debug, true}, {presence, {true, "I'm online."}}, {reconnect, 15000}]),
gen_client:add_plugin(Client, disco_plugin, [test_disco, []]).</pre><br />
Options describe different aspect of the client, such as (default values go first):<br />
<br />
<ul><li>{debug, false | true} - printing out debug info;</li>
<li>{presence, {true, Msg} | false} - should the client send a presence, and if yes, specify the presence message;</li>
<li>{reconnect, Timeout} - should the client reconnect after losing the connection, and the timeout for reconnection;</li>
<li>{log_in, true | false} - should the client log in automatically; useful when you want to choose between logging in and registration;</li>
<li>more to come... </li>
</ul><div style="text-align: left;"><b><br />
</b><br />
<b>Handlers</b></div><b><br />
</b><br />
<b><i>add_handler(Client, Handler)</i></b><br />
<b><i><span class="Apple-style-span" style="font-style: normal; font-weight: normal;"><b><i>add_handler(Client, Handler, Priority)</i></b></span></i></b><br />
<b><i>remove_handler(Client, HandlerKey)</i></b><br />
<b><br />
</b><br />
Handler is a callback function that handles incoming stanzas. Handlers can be added to (or removed from) the client session at will. a<i>dd_handler/2,3</i> calls return the key value which could be used to remove the handler later, if needed. Each handler could be assigned a priority at the time it's added to the client. When stanza is received, it will be applied to the chain of handlers according to handlers' priority and/or output. For example, a handler can interrupt the chain of subsequent handler calls by returning stop.<br />
<br />
<div style="text-align: left;"><b>Plugins</b></div><b><br />
</b><br />
<b><span class="Apple-style-span" style="font-weight: normal;"><b><i>add_plugin(Client, Plugin, Args)</i></b></span></b><br />
<b><span class="Apple-style-span" style="font-weight: normal;"><b><i><span class="Apple-style-span" style="font-style: normal; font-weight: normal;"><b><span class="Apple-style-span" style="font-weight: normal;"><b><i>add_plugin(Client, Plugin, Args, Priority)</i></b></span></b></span></i></b></span></b><br />
<b><i>remove_plugin(Client, PluginKey) </i></b><br />
<b><i><br />
</i></b><br />
Theoretically handlers should be sufficient for processing of any incoming stanzas.<br />
However, in many cases handling of stanzas involves fair amount of repetitive code. For example, responses to discovery requests (disco_info and disco_items) have to have certain headers, workflow sequences that utilize ad-hoc commands need to keep state etc. Plugins are meant to encapsulate such common processing blocks, letting developers to focus on specifics.<br />
To be a plugin, the module has to implement gen_client_plugin behavior, namely init/1, terminate/1 and handle/3 functions. The gen_client:add_plugin/3 makes The idea is that handle/3 will contain the bulk of boilerplate code, at the same time letting the plugin users to customize it by passing arguments to either init/1 or handle/3 functions.<br />
If above explanation sounds somewhat obscure, hopefully looking at the code of disco_plugin and test example in test_gen_client:test/0 will make things a bit clearer. The other available plugin is adhoc_plugin, which I am planning to talk about in more details in following posts. <br />
<br />
<b>Blocking and non-blocking requests</b><br />
<br />
XMPP messaging is asynchronous, and that's great. However, quite often your code needs to get an immediate response before the flow can continue. The examples are discovery, ping and command requests, pubsub retrival requests and many others<iq><iq>. Of course, it's possible to allocate a callback for the expected response, but this does make the code harder to write and understand.</iq></iq><br />
gen_client supports both asynchronous and synchronous requests. The former is simply a wrapper of exmpp:send_packet/2:<br />
<br />
<b><i>send_packet(ClientRef, Packet)</i></b><br />
<br />
where ClientRef is a client session reference created by one of gen_client:start functions (see above).<br />
<br />
The synchronous request is a bit more interesting. Here are definitions:<br />
<br />
<b><i>send_sync_packet(Client, Packet, Timeout)</i></b><br />
<b><i>send_sync_packet(Client, Packet, Trigger, Timeout)</i></b><br />
<br />
Here's how it works:<br />
<br />
The calling process sends the packet and timeout value to the client session process. The client session process creates a temporary handler that would "look" for "matching" incoming message, and then sends the packet to the server and waits for the response to arrive within specified timeout.<br />
How do we describe "matching"? By defining a "trigger" function that tests incoming message against some condition. For <iq> requests, "matching" means that response message will have the same id attribute as the request. So send_sync_packet/3 does just that: creates a function that looks at id attribute of incoming message and if it happens to be equal to request id, signals the client session that response has arrived. </iq><br />
So as we can see, send_sync_packet/3 could be treated as "send iq and wait for response", which makes it somewhat close to sendIQ function in <a href="http://code.stanziq.com/strophe/strophejs/doc/1.0.1/files/core-js.html">Strophe.js</a><br />
In case your "matching criteria" is different from simple id matching, you can use send_sync_packet/4 that allows to define arbitrary "trigger" function.<br />
<br />
Important: even though the calling process will be blocked, the active handlers will still work, because they will be called in separate (exmpp controlling) process. In other words, while your main process waits for response to a particular <iq> request, another kinds of incoming messages will still be handled in parallel.</iq><br />
<br />
That's all for now. Make sure to <a href="http://github.com/bokner/gen_client">check out the code</a> and please let me know what do you think.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-89354322181059600062010-07-24T12:16:00.000-04:002010-08-13T15:31:18.363-04:00The gen_client v0.9: new design and features<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">The </span></span><a href="http://github.com/bokner/gen_client"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">gen_client</span></span></a><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;"> underwent a lot of changes over the past few months. The most significant are:</span></span><br />
<div><ul><li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">Plugin framework added;</span></span></li>
<li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">Startup options introduced. The ones that work now are:</span></span></li>
</ul><ol><li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">"presence" - allows to specify if presence should be send after logging in;</span></span></li>
<li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">"reconnect" - auto-reconnect,</span></span></li>
<li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;"> "script" - arbitrary function to call right after the connection was established; the default is exmpp:login/1</span></span></li>
</ol><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">Other options coming soon: mechanisms (i.e BASIC, PLAIN etc), connection types (i.e. TCP, BOSH, SSL).</span></span><br />
<ul><li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">Dynamic handlers implementation was switched to gen_event;</span></span></li>
<li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">The code can now be built with faxien, in addition to Emakefile.</span></span></li>
</ul><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">I have to mention that API was changed almost entirely, just in case you did use previous versions. </span></span><br />
<div><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">Check out the example of using API in test_gen_client:test/0. There are 2 Jabber account that I opened at jabber.ru in order to make it easier to try it live. That's what is happening:</span></div></div><div></div><div><ol><li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">The first account signs in, announces the presence and adds "disco" plugin with user-defined content (implemented in test_disco module).</span></span></li>
<li><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">The second account signs in, announces presence and sends two synchronous requests to the first account to retrieve "disco_info" and "disco_items".</span></span></li>
</ol></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiYY7fSuljGBpp5gSMIGpgPogCfG2iShOY5uZeVKa0yhJAz3WZh2K8MdQq2uj4gLtnZsDhXP7JxqALejcCSGpx1_TEQNXLZpKqRTPQMO391tv_Nc6x7wnNDiAGQHB1oLlTnObMf6B_kRJT/s1600/Picture+9.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;"><img border="0" height="181" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiYY7fSuljGBpp5gSMIGpgPogCfG2iShOY5uZeVKa0yhJAz3WZh2K8MdQq2uj4gLtnZsDhXP7JxqALejcCSGpx1_TEQNXLZpKqRTPQMO391tv_Nc6x7wnNDiAGQHB1oLlTnObMf6B_kRJT/s320/Picture+9.png" width="320" /></span></span></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">test_gen_client:test() output</span></span></td></tr>
</tbody></table><div><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">Below is a code for test/0:</span></span><br />
<br />
<pre class="erlang" name="code">test() ->
{ok, Client1} = gen_client:start("gen_client@jabber.ru", "jabber.ru", 5222, "test",
[{debug, true}, {presence, {true, "I'm online."}}, {reconnect, 15000}]),
gen_client:add_plugin(Client1, disco_plugin, [test_disco, []]),
%% Log in with 2nd client and send discovery request to 1st client
{ok, Client2} = gen_client:start("gen_client2@jabber.ru", "jabber.ru", 5222, "test",
[{debug, true}, {presence, {true, "I'm online."}}, {reconnect, 15000}]),
%% We want to know what resource the first client was assigned, as disco requests should be sent to a particular resource
Jid1 = gen_client:get_client_jid(Client1),
%% We need to convert it to string to comply with exmpp_client_disco calls
Jid1Str = exmpp_jid:to_list(Jid1),
{ok, Info} = gen_client:send_sync_packet(Client2, exmpp_client_disco:info(Jid1Str), 10000),
io:format("Disco info from gen_client:~p~n", [gen_client:get_xml(Info)]),
{ok, Items} = gen_client:send_sync_packet(Client2, exmpp_client_disco:items(Jid1Str), 10000),
io:format("Disco items from gen_client:~p~n", [gen_client:get_xml(Items)]),
ok.</pre><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">The gen_client code can be obtained </span></span><a href="http://github.com/bokner/gen_client"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">here</span></span></a><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">.</span></span><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;"><br />
</span></span> </div><div><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><span class="Apple-style-span" style="font-size: small;">Coming soon: adhoc commands plugin with examples. </span></span><br />
<span class="Apple-style-span" style="font-family: 'Lucida Grande';"><span class="Apple-style-span" style="font-size: small;"><br />
</span> </span><br />
<span class="Apple-style-span" style="font-family: 'Lucida Grande';"><span class="Apple-style-span" style="font-size: small;"><br />
</span> </span></div><div><span class="Apple-style-span" style="font-family: 'Lucida Grande';"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Lucida Grande';"><br />
</span></div><div><span class="Apple-style-span" style="font-family: 'Lucida Grande';"><br />
</span></div>Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-26279128737852246612010-04-15T14:53:00.000-04:002010-04-17T11:55:59.808-04:00The end of cross-domain hassle for BOSH Exciting news to anyone who is using <a href="http://xmpp.org/extensions/xep-0124.html">BOSH</a> with ejabberd - since release 2.1.3 you don't have to proxy your http-bind link anymore. Imagine no nginx configuration, no <a href="http://metajack.im/2009/04/07/tape-your-stuff-to-the-web/">Tape</a>, just point exactly to where your http-bind link is. This, I believe, albeit looking rather insignificant news, will bring XMPP development to the new level of acceptance.<br />
I learned first about it from <a href="http://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy/">Jack Moffitt's site</a>. When I had a chance, I installed ejabberd 2.1.3 on AWS and fired a test web page that logs in using <a href="http://code.stanziq.com/strophe/">Strophe</a> right from my local box. The habit of crafting nginx configuration first has developed over time so strong that I didn't actually expect it to work. Fortunately, ejabberd proved me wrong - it works, and much better and faster without proxying (which is logical to expect).<br />
So if you read tutorial on how to set up BOSH with ejabberd, you can now skip the whole topic talking about nginx, cross-domain limitations and proxy. In your Strophe code, instead of doing, for example:<br />
var BOSH_SERVICE = "/http-bind";<br />
you do:<br />
var BOSH_SERVICE = "http://your_ejabberd_server:5280/http-bind";<br />
<br />
The consequences are many, the most important I think is how easy it becomes to embed BOSH anywhere.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-85720567056077090382010-04-10T13:02:00.000-04:002010-04-10T20:15:47.781-04:00Quick fix for digest authenticationAt rare times when I can't find Erlang code for things that have long been available in other languages, my pride of being Erlang programmer takes it very personally. One of these times came recently, when I had to call a web service protected by <a href="http://en.wikipedia.org/wiki/Digest_access_authentication">digest authentication</a> from my code. Couldn't find it anywhere, but I probably spent more time trying to google the solution than I did writing the code, which is available <a href="http://gist.github.com/362131">here</a>. Be forewarned that it was tested only with one particular web service, and by no means it tries to implement full spec. It was basically written by reading Wikipedia. hex/1 function was ripped from ejabberd code base that implements digest check on server side. Enjoy!Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-63766206638958745302010-02-11T16:15:00.000-05:002010-02-17T09:48:12.458-05:00gen_client example walk-throughIn this post I will show and comment the code for the XMPP bot that solves the problem of temporary subscriptions. Please see previous post for explanation of the problem.<br />
<br />
The code is using <a href="http://rfid-ale.blogspot.com/2010/01/genclient-behaviour-for-building-xmpp.html">gen_client behaviour</a>, extension of <a href="https://support.process-one.net/doc/display/EXMPP/exmpp+home">exmpp</a> library. You can find source code for gen_client <a href="http://github.com/bokner/gen_client">here. </a><br />
<br />
First, we start client process, using one of variations of <b>gen_client:start</b> (line 19). The last two parameters are module and its parameters. Module must implement gen_client behavior, and that's how you create your own client logic. <br />
This example is using dummy_client module, which we could consider as "next to minimal" implementation of gen_client - all it does is sending "available" presence upon start (<b>run/2 callback</b>), and "unavailable" presence upon termination (<b>stop/1 callback</b>). How useful is this? The answer: we can add functionality to the callback module at any time during run-time, using either <b>addHandler/2 </b>or variations of <b>send_sync_packet</b> with attached triggers.<br />
<br />
Let's look at the code for some examples:<br />
<br />
Lines 20 through 34: we are dynamically adding a handler for monitoring JIDs going offline. Once the JID sends "unavailable" presence, the handler cancels all JID's subscriptions.<br />
As long as tidy_bot is on duty, there will be no mess anymore!<br />
<br />
Lines 37 through 52: we also want to clean the mess that may have been created while tidy_bot wasn't online. So we obtain a list of subscriptions, using synchronized request (line 118), and for every such subscription the presence probe to its owner is being sent (line 73). Subscriptions, whose owners had not responded to the probe, would be canceled. Probe request is a synchronized request with trigger function.<br />
Note how are we getting response to both synchronized responses in the same process. <br />
Because sync calls is a distinctive feature of gen_client, let me explain with some more details:<br />
<br />
The variation <b>send_sync_packet/4</b> uses "trigger function" (specified by 3rd parameter), which takes each incoming stanza and returns true if this stanza carries a relevant response. This triggers the end of processing for synchronized request, and the calling process receives <i>{ok, IncomingStanza}</i>. If, on the other hand, there was no "triggering" stanza during timeout period (specified by 4st parameter of <b>send_sync_packet/4</b>), the calling process will receive a <i>timeout </i>atom.<br />
In our case, the trigger function expects to see "available" presence for the JID we've sent the probe to (line 57). If this happens within the timeout interval (4 seconds, as defined by this <b>send_sync_packet</b> call), the calling process receives {ok, PresenceMessage}, otherwise it receives a <i>timeout</i>.<br />
Note that because of the way the trigger function in this particular case was constructed, the calling process doesn't even need to analyze the message itself.<br />
<br />
The simpler variation, <b>send_sync_packet/3</b>, is not defining trigger function. In this case, the default "trigger function" will be used. It will just try to match identifiers of incoming and outgoing stanzas. So <b>send_sync_packet/3</b> is most appropriate for sending IQ stanzas, where the request almost always matches the response by identifier.<br />
<br />
There is also a case of asynchronous request (line 88). In this particular case, it's "fire and forget" approach, i.e. we don't want to analyze the response. If we did, we could do it by assigning a handler function, as you've already seen before.<br />
<br />
It's important to note that synchronous requests only block the calling process, but not the handling of incoming stanzas. So, taking our example, the handler we have set up for monitoring "unavailable" presence will still be operational while the probe requests get sent. Moreover, every handler call spawns a separate process, so incoming stanzas don't wait for prior ones to be processed.<br />
<br />
A little more about handlers and triggers. <b>gen_client</b> internally applies each of added handlers to incoming stanzas. Triggers implicitly add specialized handlers that "wrap" trigger function into appropriate call. After the handler was applied to a stanza, the gen_client decides either to keep the handler for subsequent processing, or to dispose of it. Trigger-based handlers always get disposed, by their purpose of serving a single synchronous request. As for other handlers, the rule is that the handler will be applied to all incoming stanzas until it returns <i>stop</i> atom, at which point it will be disposed of. There is also a possibility to remove handler (gen_client:remove_handler/2), if you cared to save a handler reference returned by add_handler/2. <br />
<br />
I want to take the opportunity to thank <a href="http://jldupont.blogspot.com/" target="_blank">Jean-Lou Dupont</a> for his excellent <a href="http://jldupont.blogspot.com/2009/06/erlang-syntax-highlighter.html">erlang syntax highlighter</a>, which I'm using below. <br />
<br />
<pre class="erlang" name="code">%% Author: bokner
%% Created: Feb 3, 2010
%% Description: Monitors and clears temporary pubsub subscriptions.
-module(tidy_bot).
%%
%% Include files
%%
-include_lib("exmpp/include/exmpp_client.hrl").
-include("gen_client.hrl").
%%
%% Exported Functions
%%
-export([tidy_subscriptions/5]).
%%
%% API Functions
%%
tidy_subscriptions(Jid, Password, Host, Port, PubSub) ->
{ok, Session, _C} = gen_client:start(Jid, Host, Port, Password, dummy_client, ["On tidy duty"]),
JidOfflineHandler =
fun(#received_packet{packet_type = presence, type_attr = "unavailable", from = PeerJid}, #client_state{jid = BotJid} = _State) when BotJid /= PeerJid ->
{Node, Domain, _Resource} = PeerJid,
case exmpp_jid:bare_compare(BotJid, exmpp_jid:make(Node, Domain)) of
false ->
io:format("~p gone offline~n", [PeerJid]),
unsubscribe_from_all_nodes(Session, PeerJid, PubSub);
_Other ->
void
end,
ok;
(_Other, _Session) ->
ok
end,
gen_client:add_handler(Session, JidOfflineHandler),
%% Get subscriptions
process_subscriptions(
Session, PubSub,
fun(SubscriptionList) ->
lists:foreach(fun(S) ->
spawn(
fun() ->
unsubscribe_temporary(Session, PubSub,
exmpp_xml:get_attribute(S, "jid", undefined),
exmpp_xml:get_attribute(S, "node", undefined),
exmpp_xml:get_attribute(S, "subid", undefined)
)
end
)
end,
SubscriptionList
) end),
ok.
unsubscribe_temporary(Session, PubSub, Jid, Node, _Subid) ->
%% Prepare handler for presence
ProbeSuccessfull = fun(#received_packefrom = FullJid, packet_type = presence, type_attr = "available"}, _State) ->
{Acc, Domain, Resource} = FullJid,
case exmpp_jid:parse(Jid) of
{jid, Jid, Acc, Domain, Resource} ->
io:format("probe matches for ~p~n", [FullJid]),
true;
_NoMatch ->
io:format("probe doesn't match for ~p, ~p~n", [Jid, FullJid]),
false
end;
(_NonPresence, _State) ->
false
end,
%% Send presence probe
io:format("Sending probe to ~p:~n", [Jid]),
ProbeResult = gen_client:send_sync_packet(Session, exmpp_stanza:set_recipient(
exmpp_presence:probe(), Jid), ProbeSuccessfull, 4000),
io:format("result of probe for ~p:~n~p~n", [Jid, ProbeResult]),
case ProbeResult of
timeout ->
unsubscribe_from_node(Session, Jid, Node, PubSub),
timeout;
{ok, #received_packet{type_attr = Type}} ->
io:format("Probe:~p:~p~n", [Jid, Type]);
Other ->
io:format("Unexpected:~p~n", [Other])
end.
unsubscribe_from_node(Session, Jid, Node, PubSub) ->
io:format("Unsubscribing ~p from ~p...", [Jid, Node]),
gen_client:send_packet(Session, exmpp_client_pubsub:unsubscribe(Jid, PubSub, Node)),
io:format("Done.~n").
unsubscribe_from_all_nodes(Session, {Acc, Domain, Resource} = Jid, PubSub) ->
io:format("Unsubscribing ~p~n", [Jid]),
process_subscriptions(
Session, PubSub,
fun(SList) ->
lists:foreach(fun(S) ->
spawn(
fun() ->
JidAttr = exmpp_xml:get_attribute(S, "jid", undefined),
case JidAttr == exmpp_jid:to_binary(Acc, Domain, Resource) of
true ->
unsubscribe_from_node(Session, JidAttr, exmpp_xml:get_attribute(S, "node", undefined), PubSub);
false ->
void
end
end
)
end,
SList
) end
),
ok.
process_subscriptions(Session, PubSub, Fun) ->
{ok, SubscriptionPacket} = gen_client:send_sync_packet(Session, exmpp_client_pubsub:get_subscriptions(PubSub), 5000),
%%io:format("Subscriptions:~p~n", [SubscriptionPacket]),
Payload = exmpp_iq:get_payload(exmpp_iq:xmlel_to_iq(SubscriptionPacket#received_packet.raw_packet)),
Fun(
exmpp_xml:get_elements(
exmpp_xml:get_element(Payload, "subscriptions"),
"subscription")
).</pre>Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-91670929802672345752010-02-10T01:26:00.000-05:002010-02-10T01:36:57.467-05:00gen_client in action: fixing temporary subscriptionsIn this post I'd like to discuss one particular pubsub challenge, namely temporary subscriptions. I will also use this discussion as an opportunity to show some gen_client code that was written in order to deal with this challenge.<br />
<br />
Let's assume we have a web-based XMPP weather service available for free public access, so everyone can come to our site, choose places and watch weather data updated in real-time. This is what pubsub is designed for: data streams being publishers, the clients being subscribers; each data stream will publish to its respective node, and the clients will subscribe to nodes they are interested in. Now comes interesting part: we expect significant number of clients to come use our service, so we don't want to create accounts for each client. Instead clients will be automatically signed in with some shared account and assigned random resource name. XEP-0060 allows subscriptions based on full JIDs, so each client will still have its own subscriptions. However, using random resource names impose having temporary subscriptions, because the moment client signs out, the resource name he was using becomes unusable, and so do subscriptions. <br />
<br />
So how do we deal wth temporary subscriptions? XEP-0060 (1.13rc13, p. 12.4) describes how it should be: once subscriber goes offline, the temporary subscription gets canceled. Unfortunately, it looks like ejabberd (v. 2.1.2 at the time of writing) doesn't yet have it working, at least I was unable to configure temporary subscriptions the way XEP-0060 suggests. This means that once resource is gone, its subscriptions are still hanging around. Bad (very bad) thing about it, not to mention excessive memory consumption, is that ejabberd will push data meant for these orphaned subscriptions to the resources of the same account that are still online. This will be an absolute mess - even if your client code is smart enough to filter foreign subscriptions (possibly by matching subscription identifiers), the enormous traffic will be generated pretty soon. Remember, we have a single shared account for all our clients. Conclusion: we absolutely have to find the way to get rid of orphaned subscriptions.<br />
<br />
<a href="http://github.com/bokner/gen_client/blob/master/src/examples/tidy_bot.erl">Here</a> is a source code of the bot (using gen_client) that supports temporary subscriptions by monitoring resources and getting rid of subscriptions at the moment resource they belong to goes offline. Additionally, it cleans up such subscriptions on a startup. I'm planning to do code walk-through and explain gen_client capabilities shown in the code in one of posts following shortly.<br />
<br />
This is, of course, a temporary solution and should only be used if your XMPP server doesn't support temporary subscriptions. <br />
<br />
I still have a feeling that there might be an easier solution, but I haven't found any practical cases of using temporary subscriptions, so if anyone has related experience, please come forward and share it here.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-45825580753528956142010-01-28T02:23:00.000-05:002010-01-29T16:53:50.906-05:00gen_client behaviour for building XMPP clients in Erlang.<a href="http://github.com/bokner/gen_client"> The gen_client project</a> aims to provide a structured way to write XMPP client code in Erlang. The framework heavily relies on exmpp, but also borrows some ideas from my favorite Strophe javascript library. The objective of the project is to create a set of generic behaviours and let a client developer to "fill in the blanks", i.e. to implement callback methods pretty much in the same fashion the code based on OTP/Erlang behaviours is written.<br />
Why not to just use exmpp, one might ask? Sure you can. However, going from basic examples <br />
to decently capable client code is not so easy in exmpp. Motivation behind <i>gen_client</i> is to make coding XMPP in Erlang as effortless as Strophe does for Javascript.<br />
One example of what I mean by "efortless" is how exmpp controls handling of incoming stream. By default, sending and receiving stanzas happens in a single process. Clearly, this is not very useful unless your XMPP client is happy with "question-answer" flow (as in echo_client.erl example from exmpp distribution), as opposed to asynchronous flow.<br />
Of course, exmpp has means to assign a separate process for handling incoming stream (exmpp_session:set_controlling_process/2). However, it would be nice to have this as default, which is what <i>gen_client</i> does.<br />
Continuing with this, sometimes you may need a synchronous handling. For instance, you'd have to search through the whole tree of pubsub nodes, do some calculations and send results elsewhere. While this kind of task can be coded using callbacks, it does make coding much harder to deal with compared to sequential style. With <i>gen_client</i>, you can choose between asynchronous (gen_client:send_packet) and synchronous (gen_client:send_sync_packet) requests.<br />
<br />
And of course, each incoming stanza will be handled by gen_client in a spawned process, so your client can do many things at once - we are using Erlang for the reason, right?<br />
<br />
To start with gen_client, write your module that implements <i>gen_client</i> behaviour. And off you go:<i> </i><br />
<i>gen_client:start(Username, Domain, Host, Port, Password, Module, [ModuleArgs]).</i><br />
<br />
Summary of features that are already there:<br />
<br />
<ul><li>Simultaneous handling of multiple incoming stanzas;</li>
<li>Synchronous and asynchronous requests;</li>
<li>Attaching IQ/presence/message handlers at runtime (somewhat similar to Strophe's addHandler style);</li>
<li>Support for ad-hoc commands (XEP-0050) and service discovery (XEP-0030);</li>
<li>Compatibility with exmpp and hence ability to reuse its codebase.<br />
</li>
</ul><br />
Documentation and examples will follow time permitting. This is the work in progress, mostly experimental, so please use with caution. Usual disclaimers are in place. Please share your thoughts and ask questions, if any. This code is being used in real projects, so I appreciate any feedback from you as means of moving <i>gen_client</i> to a production quality.<br />
<br />
Links:<br />
<ul><li><a href="http://github.com/bokner/gen_client">gen_client on github</a> </li>
<li><a href="http://www.blogger.com/goog_1264636038915"><span id="goog_1264636038912"></span><span id="goog_1264636038913"></span></a><a href="http://download.process-one.net/exmpp/exmpp-0.9.2-r894.tar.gz">exmpp version used by gen_client</a> </li>
</ul>Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-51369287862073812872009-10-12T01:01:00.001-04:002013-03-06T00:28:01.678-05:00How to make ejabberd cluster setup a bit easier<div style="font-family: inherit;">
Even though there is a decent official documentation and many excellent posts on the topic of setting ejabberd cluster, it still could be confusing, as it was for me. It is possible to avoid some guessing work though and make things more "automated". Following explanation uses the directory structure of installation created by standard ejabberd binary installer:</div>
<div style="font-family: inherit;">
<br /></div>
<div style="font-family: inherit;">
1. Install ejabberd on a single node</div>
<div style="font-family: inherit;">
2. Locate ejabberdctl.cfg in <b>con</b>f directory of your installation and adjust Erlang node name on the last line:</div>
<br />
<pre class="terminal"><code>ERLANG_NODE=ejabberd@`hostname -f` </code></pre>
<pre class="terminal"><code> </code></pre>
<pre class="terminal" style="font-family: inherit;"><code>3. Run ejabberd:</code></pre>
<pre class="terminal" style="font-family: inherit;"><code> <ejabberd-dir><ejabberd dir="" installation=""></ejabberd></ejabberd-dir></code><code><ejabberd-dir></ejabberd-dir></code><code><ejabberd-dir><ejabberd dir="" installation=""></ejabberd></ejabberd-dir></code><code><ejabberd-dir>your_ejabberd_dir</ejabberd-dir></code><code><ejabberd-dir><ejabberd dir="" installation="">/bin/ejabberdctl start</ejabberd></ejabberd-dir></code></pre>
<pre class="terminal" style="font-family: inherit;"><code>4. Make sure it runs:</code></pre>
<pre class="terminal" style="font-family: inherit;"><code> <ejabberd-dir></ejabberd-dir></code><code><ejabberd-dir><ejabberd dir="" installation=""></ejabberd></ejabberd-dir></code><code><ejabberd-dir></ejabberd-dir></code><code><ejabberd-dir><ejabberd dir="" installation=""></ejabberd></ejabberd-dir></code><code><ejabberd-dir>your_ejabberd_dir</ejabberd-dir></code><code><ejabberd-dir>/bin/ejabberdctl status</ejabberd-dir>
</code></pre>
<pre class="terminal" style="font-family: inherit;"><code> By now you're done with first node.
</code></pre>
<pre class="terminal" style="font-family: inherit;"><code>For the second and consequent nodes:</code></pre>
<pre class="terminal" style="font-family: inherit;"><code>5. Synchronize node's Erlang cookie with one at 1st node (check out "Clustering setup" in ejabberd documentation on how it's done);
</code></pre>
<pre class="terminal" style="font-family: inherit;"><code>6. repeat steps 1 to 4;
</code></pre>
<pre class="terminal" style="font-family: inherit;"><code>7. Run:</code></pre>
<pre class="terminal" style="font-family: inherit;"><code> <ejabberd-dir></ejabberd-dir></code><code><ejabberd-dir><ejabberd dir="" installation=""></ejabberd></ejabberd-dir></code><code><ejabberd-dir>your_ejabberd_dir/bin/ejabberdctl debug</ejabberd-dir>
</code></pre>
<pre class="terminal" style="color: black; font-family: inherit;"><code>At Erlang shell prompt (press Enter when asked "press any key"), </code><i><code></code></i><code>type:
<pre class="erlang" name="code">FirstNode = 'ejabberd@first', %% use the name of your first node (ejabberd@<fqdn>, see p.2 above)
mnesia:stop(),
mnesia:delete_schema([node()]),
mnesia:start(),
mnesia:change_config(extra_db_nodes, [FirstNode]),
mnesia:change_table_copy_type(schema, node(), disc_copies).
</fqdn></pre>
</code></pre>
<pre class="terminal" style="color: black; font-family: inherit;"><span class="Apple-style-span" style="font-family: monospace;">
</span></pre>
<div style="font-family: inherit;">
<code>The above script is a replacement of p.2,3 of official ejabberd clustering setup doc. It takes advantage of not having to manually figure out Mnesia location and proper syntax of the command suggested in there. </code></div>
<div style="font-family: inherit;">
<code>8. End debug session by pressing Ctrl-c, Ctrl-c; </code></div>
<pre class="terminal" style="font-family: inherit;"><code>9. Continue with p.4 of official ejabberd clustering setup document. </code></pre>
<pre class="terminal" style="font-family: inherit;"><code> </code></pre>
<div style="text-align: justify;">
The piece of code above could probably be useful elsewhere, for example as part of ejabberd admin interface. Imagine having "Join cluster" and "Leave cluster" buttons somewhere on a Nodes page. Also, it's probably possible to save some manual work in situations where you want a bunch of ejabberd nodes to join existing cluster. In this case you could wrap something similar to above code into a single function and do rpc call on each of these nodes. All such things would obviously require a bit more work, such as checking if the running node is already part of the cluster etc.</div>
Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-18354958511499364362009-09-18T02:29:00.000-04:002009-09-19T00:19:03.610-04:00Erlang for Java programmer - Java can be good at times, tooPeace and let's say no to flame wars, comrades! Don't get me wrong, Java is an awesome language, but if you have already started serious coding in Erlang, you will probably never look back. Or maybe you will occasionally, like I did few days back, when I was charged with the task of cloning ejabberd module by replacing <b>mnesia</b> with <b>odbc</b> calls. <br />
It might sound like fairly routine task, and it was for the most part, except sometimes it's hard to reason about Erlang function output just by looking at the source code. Java methods, on the other hand, always have statically typed output, which obviously eliminates any guessing work. So, after few attempts trying to mentally trace some of <b>mnesia</b> transactions, I suddenly felt my brain overloaded and had to resort to <a href="http://www.ejabberd.im/interconnect-erl-nodes">debugging ejabberd from remote node</a> :-) I had to recompile the module with <b>-compile(export_all)</b> in order to be able to try functions that contained <b>mnesia</b> code in Erlang shell and figure out their exact output. This, of course, is an intrusive action and assumes the source code is available. <br />
It could have been much easier, though, if the code was accompanied by <a href="http://erlang.org/documentation/doc-5.4.2.1/lib/edoc-0.1/doc/html/index.html">documentation markup</a>. Having function specifications will hugely help anyone who will ever have to work with the code, including the owner. I'm now ready to make resolution to have specifications for every single function in my code, including non-API functions. By the way, <a href="https://support.process-one.net/doc/display/EXMPP/exmpp+home">exmpp</a> team really impressed me by thoroughly following this path throughout entire code base.<br />
To me, having to have function specs in the code, while being a good thing on its own, is a very small price for the feeling of liberation and increased performance that dynamic typing in Erlang gives you compared to a static one in Java.<br />
In conclusion, check out <a href="http://www.it.uu.se/research/group/hipe/dialyzer/.../wrangler.pdf">much more comprehensive discussion of typing problem in Erlang</a>.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-68360261741647891092009-09-07T23:14:00.000-04:002010-05-16T12:41:24.505-04:00XMPP web demo: setting it upI will describe the <a href="http://rfid-ale.blogspot.com/2009/08/xmpp-web-project-walkthrough.html">XMPP demo</a> setup on AWS EC2, starting with Alestic Ubuntu karmic image (<i>ami-19a34270</i>). Hopefully, you should be able to adjust the process to your environment of choice.<br />
<br />
<div style="text-align: center;"><span style="font-size: large;">Setting up</span><br />
<span style="font-size: large;"> <br />
</span></div><div style="text-align: left;">Brace yourself, that's going to be a long one. Major components to install and configure are:</div><ul><li>Erlang environment</li>
<li>exmpp</li>
<li>ejabberd</li>
<li>nginx</li>
<li>and, of course, weazard itself<br />
</li>
</ul>So, provided you have running instance of <i>ami-19a34270</i>, let's ssh to it and start by:<br />
<br />
<b>sudo apt-get update && sudo apt-get upgrade</b><br />
<br />
We'll need svn to checkout some stuff later:<br />
<b> <br />
</b><br />
<b>apt-get install subversion<br />
</b><br />
<br />
Then install Erlang (version R12B5 or newer; karmic has R13B01):<br />
<b> <br />
</b><br />
<b>apt-get install erlang</b><br />
<br />
Now, let's move on to exmpp. First, we need to install some libraries that exmpp is using for XML support:<br />
<br />
<b>apt-get install libxml2-dev</b><br />
<br />
or, alternatively:<br />
<br />
<b> apt-get install libexpat1-dev<br />
</b><br />
<br />
<b>Update:</b> the demo will work fine with <b>libxml2, </b>however, at the time of writing (Nov 07, 2009), if you're planning to use exmpp for other purposes, particularly, exmpp_session module, you're advised to install libexpat1-dev instead. See https://support.process-one.net/si/jira.issueviews:issue-html/EXMPP-16/EXMPP-16.html for details.<br />
<br />
<b>apt-get install libssl-dev</b><br />
<b>apt-get install pkg-config</b><br />
<b></b> <b><br />
</b><br />
Now, download the exmpp distribution and build:<br />
<br />
<b>wget http://download.process-one.net/exmpp/exmpp-0.9.1-r857.tar.gz</b><br />
<b>gunzip </b><b>exmpp-0.9.1-r857.tar.gz</b><br />
<b>tar -xvf </b><b>exmpp-0.9.1-r857.tar </b><b></b><br />
<b>./configure</b><br />
<b>make</b><br />
<b>sudo make install<br />
</b><br />
<br />
At this point you may want to check if exmpp is in working condition. Start <b>erl </b>and at the prompt type<b>: </b><br />
<br />
<b>1>exmpp:start().</b><br />
<b>ok</b><br />
<b>2>exmpp_xml:start_parser().</b><br />
<br />
If response is similar to<br />
<i>{xml_parser,[{max_size,infinity},<br />
{root_depth,0},<br />
{names_as_atom,true},<br />
{emit_endtag,false}],<br />
#Port<0.747>},</i><br />
<br />
then everything went fine and you can type <b>q().</b> at the prompt to close Erlang shell.<br />
<br />
Next, installing ejabberd. We need to add a <i>Jabber domain name</i> to <i>/etc/hosts</i> first.<br />
<br />
The first line in <i>/etc/hosts</i> after editing will look something like (substitute <b>zephyr</b> to anything you like):<br />
<br />
<i>127.0.0.1 localhost.localdomain localhost <b>zephyr.local</b></i><br />
<br />
Note: You will be using this domain name in many places, so please make note of it; I will be using <b>zephyr.local</b><b> </b>in this post going forward.<br />
<br />
Download, unpack and launch <b>ejabberd</b> installer:<br />
<br />
<span style="font-size: x-small;"><b>wget http://www.process-one.net/downloads/ejabberd/2.0.5/ejabberd-2.0.5-linux-x86-installer.bin.gz</b></span><br />
<span style="font-size: x-small;"><b>gunzip ejabberd-2.0.5-linux-x86-installer.bin.gz</b></span><br />
<span style="font-size: x-small;"><b>chmod 755 ejabberd-2.0.5-linux-x86-installer.bin</b></span><br />
<span style="font-size: x-small;"><b>./ejabberd-2.0.5-linux-x86-installer.bin</b></span><br />
<br />
Make default choices during installation; when asked about ejabberd server domain, enter host name you previously added to /etc/hosts (i.e. zephyr.local). Note default installation directory ( /opt/ejabberd-2.0.5 by default), you will need it shortly.<br />
<br />
Once installation is completed, edit ejabberdctl.cfg by changing the very last line from:<br />
<br />
ERLANG_NODE=ejabberd@localhost<br />
to<br />
ERLANG_NODE=ejabberd@<b>zephyr.local</b><br />
<br />
This is an important change, especially if you're planning to reuse your EC2 instance; I will omit the explanation for now.<br />
<br />
Now, let's make sure ejabberd is good:<br />
<br />
<b>cd /opt/ejabberd-2.0.5/bin</b><br />
<b>./ejabberdctl start</b><br />
<b>./ejabberdctl status</b><br />
<br />
You should see: <b><br />
Node 'ejabberd@zephyr.local' is started. Status: started<br />
ejabberd is running</b><br />
<b> <br />
</b><br />
Next step - configure external XMPP component on ejabberd side.<br />
Open up ejabberd.cfg in /opt/ejabberd-2.0.5/conf with your editor (you may want to make a backup copy first), locate the line starting with <b>{5269, ejabberd_s2s_in </b><br />
and add service description after the closing curly bracket:<br />
<br />
<i> {5269, ejabberd_s2s_in, [<br />
{shaper, s2s_shaper},<br />
{max_stanza_size, 131072}<br />
]},<br />
<br />
<span style="color: red;"> {7047, ejabberd_service,</span><br style="color: red;" /><span style="color: red;"> [{hosts, ["test1.zephyr.local"],</span><br style="color: red;" /><span style="color: red;"> [{password, "secret"}]}]},</span></i><br />
<br />
<i> %%<br />
%% ejabberd_service: Interact with external components (transports...)<br />
%%<br />
</i><br />
<br />
Save ejabberd.cfg and restart ejabberd:<br />
<br />
<b>/opt/ejabberd-2.0.5/bin/ejabberdctl restart</b><br />
<b>/opt/ejabbberd-2.0.5/bin/ejabberd status</b><br />
<br />
Provided ejabberd has restarted successfully, let's create a bot account (it's curently being used to help with weazard registration):<br />
<br />
<b>/opt/ejabberd-2.0.5/bin/ejabberd register _weazard_bot zephyr.local weazard</b><br />
<br />
We're done with ejabberd for now. Time to checkout our demo code:<br />
<br />
<b>mkdir weazard</b><br />
<b>cd weazard<br />
</b><br />
<b>svn co https://tagsahead.svn.beanstalkapp.com/erltwit/trunk/web</b><br />
<b>svn co https://tagsahead.svn.beanstalkapp.com/xmpp_component/trunk </b><br />
<br />
Review config.js in web/js and adjust it, if needed.<br />
<br />
Review weazard.app in xmpp_component/trunk/ebin and adjust it.<br />
<br />
We will use nginx as our web server:<br />
<br />
<b>apt-get install nginx</b><br />
<br />
Below is configuration fragment that has to be included (directly to /etc/nginx/nginx.conf or through <b>include</b> directive):<br />
<br />
server {<br />
listen 8000;<br />
root /home/nginx/web/;<br />
<br />
<b><span style="color: #cc0000;"> location /http-bind {</span><br style="color: #cc0000;" /><span style="color: #cc0000;"> proxy_pass http://localhost:5280/http-bind/;</span><br style="color: #cc0000;" /><span style="color: #cc0000;"> }</span></b><br />
}<br />
<br />
We'll need to move our web application to the place where nginx can access it, i.e. <b>/home/nginx/web</b><br />
The highlighted piece is necessary because due to Javascript security Strophe can't directly reach ejabberd's HTTP binding service.<br />
<br />
<b>Update: </b>Starting ejabberd v 2.1.3 configuring proxy is not necessary, so you won't have to include the highlighted text above. You might still want to configure the proxy if your web clients are behind firewall with the BOSH port (default 5280) disabled. See <a href="http://rfid-ale.blogspot.com/2010/04/end-of-cross-domain-hassle-for-bosh.html">http://rfid-ale.blogspot.com/2010/04/end-of-cross-domain-hassle-for-bosh.html</a> for details. <br />
<br />
<br />
We're done!<br />
<br />
<div style="text-align: center;"><span style="font-size: large;">Running demo</span></div><div style="text-align: left;"></div><div style="text-align: left;">Start ejabberd:</div><div style="text-align: left;"></div><div style="text-align: left;"><b>/opt/ejabberd-2.0.5/ejabberdctl start </b>(or <b>restart</b>, if it's already running);</div><div style="text-align: left;"></div><div style="text-align: left;">Start nginx:</div><div style="text-align: left;"></div><div style="text-align: left;"><b>sudo /etc/init.d/nginx start </b>(or <b>restart</b>, if it's already running);</div><div style="text-align: left;"></div><div style="text-align: left;"><br />
Start weazard XMPP component:</div><div style="text-align: left;"></div><div style="text-align: left;"><b>cd ~/</b><b>weazard/xmpp_component/ebin</b> </div><div style="text-align: left;"><b>erl -sname main -setcookie weazard</b></div><div style="text-align: left;"></div><div style="text-align: left;"><br />
At the prompt, type:</div><div style="text-align: left;"></div><div style="text-align: left;"><b>1>application:start(weazard).</b></div><div style="text-align: left;"></div><div style="text-align: left;"><br />
If you see the line:</div><div style="text-align: left;"></div><div style="text-align: left;"><i>Component : "test1.zephyr.local" started.</i></div><div style="text-align: left;"></div><div style="text-align: left;"><br />
in the output, then weazard has managed to connect to ejabberd and is ready to push data to subscribers.</div><div style="text-align: left;"></div><div style="text-align: left;"><br />
You're now ready to launch your browser and try:</div><div style="text-align: left;"></div><div style="text-align: left;"><br />
<b>http://yourhostname<your host="" name=""><your host="">:8000/weazard.html</your></your></b></div><div style="text-align: left;"></div><div style="text-align: left;"></div><div style="text-align: left;"><br />
Can I congratulate you with running demo? I hope so, but if you are not there yet, fear not! As you can see, a lot of things can potentially go wrong. There's plenty of resources on configuring ejabberd/BOSH/nginx, and I'm here to help to the best of my abilities.</div>Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.comtag:blogger.com,1999:blog-9111542903548056218.post-70053444805746040702009-08-27T18:58:00.000-04:002009-08-28T05:42:49.089-04:00XMPP web demo updateYesterday was a big day, I got over 100 visitors on my blog, and many of you have registered and tried the demo. Was it because of me posting about demo on identi.ca and metajack.im? Anyway, thank you all for taking time and trying, I have gotten strong motivation to keep up with XMPP topic because of your interest.<br /><br />Couple of things that I wanted to mention: first, some of newly registered users seemed to drop out really quick. I'm guessing that was because they weren't able to see much action from the demo. I should have explained that if you have small number of subscriptions, chances are you'd have to wait for few minutes before seeing any data update. That's because the update generator chooses one station at a time every 15 seconds, so if you have only couple stations chosen, it'd take in average about 2 mins for updates to show up, given there are currently 17 stations total. So the hint is, if you want a real entertainment, subscribe at least to 8-9 stations, then you'll see it all rolling full speed.<br />There are also few cases where people dropped out not even finishing registration (initial greeting from Jabber still sits there for such accounts). I would be curious to know, what had happened there? Was it a slow script, browser crash or something else? I'd appreciate any feedback you could give.<br />And speaking about feedback, you can now easily send a message to a friendly support team (that would be me) from within demo! Just click the button on Support panel and submit the 2-field form, I'll be sure to read and respond. Note that all this done with Javascript one-liner, I didn't have to touch any of server code, all that goodness given for free by ejabberd and Strophe.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com2tag:blogger.com,1999:blog-9111542903548056218.post-5513381769943918752009-08-26T11:42:00.000-04:002009-09-23T22:44:33.214-04:00XMPP web project walk-throughNow that the <a href="http://rfid-ale.blogspot.com/2009/08/real-time-web-applications-with-xmpp.html">XMPP demo is online</a> and pretty stable, I will try to explain what is it actually doing. Should have done it at the time of putting it online, but it's never too late to do a right thing.<br />
<br />
<div style="text-align: center;"><span style="font-weight: bold;">The objective</span>.<br />
<br />
</div>The objective of <a href="http://rfid-ale.blogspot.com/2009/08/real-time-web-applications-with-xmpp.html">demo</a> is to show how information can be delivered to browser clients in real time using XMPP/BOSH technique.<br />
<br />
<div style="text-align: center;"><span style="font-weight: bold;">The functionality.</span><br />
<br />
</div>The <a href="http://rfid-ale.blogspot.com/2009/08/real-time-web-applications-with-xmpp.html">demo application</a> gives prospective clients ability to subscribe to one or more (imaginary) weather stations and receive (made-up) weather alerts in real-time. The client can subscribe/unsubscribe to/from any of available weather stations. The subscription will be associated with client's account and used for the future sessions, so the client is required to register account first time she's using the demo.<br />
NOTE: You may use existing shared account (username: <span style="font-style: italic;">shared</span>, password: <span style="font-style: italic;">shared</span>) in case you do not wish to register. Registering is safe, though, as it creates a Jabber account on local domain, which is not accessible from outside. If you decide to use <span style="font-style: italic;">shared</span> account, expect other people to alter your subscriptions. You may actually watch someone turning your subscriptions on and off, which represents a nice case of real-time collaboration :-)<br />
<br />
<div style="text-align: center;"><span style="font-weight: bold;">Visual representation and controls.</span><br />
<br />
<div style="text-align: left;">Right-hand panel contains weather station locations organized as <span style="font-style: italic;">country -> region -> city</span> hierarchy. The icon next to station location denotes its subscription status, as follows:<br />
<br />
<br />
<div style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwPbca3PgziA6RAmkZ1JCmy_2yAwd7NR5_pILfrY29NKkJ76F76uQxpoegiwnfFGicFNnysXVAEURk2lhLwE5KdpjbKlHoBWf59l0dwbhZs1R11KHi-1kH1U2agdMdl_tQ9uiXct_C02sQ/s1600-h/transmit_delete.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5374477529641769330" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwPbca3PgziA6RAmkZ1JCmy_2yAwd7NR5_pILfrY29NKkJ76F76uQxpoegiwnfFGicFNnysXVAEURk2lhLwE5KdpjbKlHoBWf59l0dwbhZs1R11KHi-1kH1U2agdMdl_tQ9uiXct_C02sQ/s200/transmit_delete.png" style="cursor: pointer; float: left; height: 16px; margin: 0pt 10px 10px 0pt; width: 16px;" /></a><br />
</div><div style="text-align: left;">- client is not subscribed to the station;<br />
</div><div style="text-align: left;"><br />
</div><div style="text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGokueWxCh7J8LoPmz_Pjc2fowZHcWgbFQ0QzvnpHkN3zqFk6RjwkHeDKkKGgdBJ7gDOYrf9CU9y5mTS3Pk31634PX6gtSR436-avS7L_dWy4ZmStxEm4H7oJyk_88fblQDMYwr168D1bb/s1600-h/transmit_go.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5374478011455583986" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGokueWxCh7J8LoPmz_Pjc2fowZHcWgbFQ0QzvnpHkN3zqFk6RjwkHeDKkKGgdBJ7gDOYrf9CU9y5mTS3Pk31634PX6gtSR436-avS7L_dWy4ZmStxEm4H7oJyk_88fblQDMYwr168D1bb/s400/transmit_go.png" style="cursor: pointer; float: left; height: 16px; margin: 0pt 10px 10px 0pt; width: 16px;" /></a>- client is subscribed to the station;<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLvnukSU06S0tPz3Q9kzHO49Ah1p5B7pMG1UIzAf6MUI1gM-N0UsNuXdNB4SXVtgmjsMf-R6JeSj70TSBgb5vTVG_6WMfo5-B1u0H_7woC8Xi6hZTnCqMmV9c-nxkbAsLW25Mzj_pTCV5r/s1600-h/transmit_blue.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5374816828991311490" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLvnukSU06S0tPz3Q9kzHO49Ah1p5B7pMG1UIzAf6MUI1gM-N0UsNuXdNB4SXVtgmjsMf-R6JeSj70TSBgb5vTVG_6WMfo5-B1u0H_7woC8Xi6hZTnCqMmV9c-nxkbAsLW25Mzj_pTCV5r/s400/transmit_blue.png" style="cursor: pointer; float: left; height: 16px; margin: 0pt 10px 10px 0pt; width: 16px;" /></a>- the request to turn subscription on/off has been sent to the server.<br />
</div><div style="text-align: left;"><br />
</div><br />
Clicking on station name will send request to turn subscription on/off for the logged in client.<br />
<br />
Middle panel contains weather updates, one per subscribed station. Once update is received from the station, corresponding section will show up. Only last update is shown. The client can click on any section to unfold detailed information about last update received from the station.<br />
<br />
</div></div><br />
<div style="text-align: center;"><span style="font-weight: bold;">The moving parts</span> (architecture, that is :-).<br />
<br />
<div style="text-align: left;">On the client side we have HTML page that employs <a href="http://people.chesspark.com/%7Ejack/strophe-preview/doc/files/strophe-js.html"><span style="font-weight: bold;">Strophe.js</span></a> library. Strophe provides communication with XMPP server using BOSH protocol.<br />
<br />
On the server side:<br />
<ul><li><a href="http://www.process-one.net/en/ejabberd/"><span style="font-weight: bold;">ejabberd</span></a> - XMPP server;</li>
</ul><ul><li><span style="font-weight: bold;">weazard</span> - Erlang application that acts as XMPP component, using <a href="http://rfid-ale.blogspot.com/2009/07/xmpp-gencomponent-update-design-and.html">gen_component</a> implementation; it exposes weather stations for the service discovery and manages subscription requests by routing weather updates to subscribers;<br />
</li>
</ul><span style="font-weight: bold;"></span><br />
<ul><li><span style="font-weight: bold;">weather update generator </span>- generates weather alerts every 15 seconds by randomly choosing weather station, temperature and condition. </li>
</ul><br />
<div style="text-align: center;"><span style="font-weight: bold;">The flow.</span><br />
</div></div></div><div style="text-align: left;"><br />
The client signs in, triggering <span style="font-weight: bold;">Strophe</span> to start <a href="http://xmpp.org/extensions/xep-0030.html">service discovery</a> procedure by sending "disco" requests to <span style="font-weight: bold;">weazard</span> in order to find out what weather stations are available. <span style="font-weight: bold;">Strophe</span> then receives "disco" responses through its callbacks and builds the treeview that reflects <span style="font-weight: bold;">weazard</span>'s service structure (i.e. country->region->city hierarchy that resides on the right-hand panel). After the treeview has been finished, <span style="font-weight: bold;">Strophe</span> advertises client's presence, which in turn triggers XMPP server to route presence to all client's subscribed stations. As stations' addressing includes <span style="font-weight: bold;">weazard</span>'s JID as host, <span style="font-weight: bold;">weazard</span> takes over and sends presence from each of subscribed stations back to <span style="font-weight: bold;">Strophe</span>. <span style="font-weight: bold;">Strophe</span> then uses presence callbacks to show subscription state (all those red and green "transmission" icons in right-hand panel and message sections in middle panel) for all discovered stations in the client's view.<br />
From this moment on, Strophe listens to weather alert messages and updates corresponding sections in the middle panel as they go.<br />
On the server side, <span style="font-weight: bold;">weazard</span> receives alerts from <span style="font-weight: bold;">weather update generator, </span>turns them into XMPP messages and routes them to subscribers via XMPP server.<span style="font-weight: bold;"><br />
<br />
</span><br />
<div style="text-align: center;"><span style="font-weight: bold;">Advantages of using XMPP/BOSH web vs. traditional web frameworks.</span><br />
<br />
<div style="text-align: left;">The most obvious advantage is a real-time interaction. This doesn't directly have to do with XMPP, but rather is provided by BOSH protocol. Very similar techniques have been around for quite some time, for instance, <a href="http://en.wikipedia.org/wiki/Comet_%28programming%29">Comet</a> or <a href="http://en.wikipedia.org/wiki/Pushlets">pushlets</a>. BOSH is currently supported by most well-known XMPP server, which makes it natural to use with XMPP.<br />
Another important thing is ability to reuse XMPP server features. For instance, there is no need to have separate database of registered users and their subscriptions, as this is already maintained by XMPP server. All changes in user's presence (i.e. logged in/out, disconnected) and component presence (restart, disconnection etc.) are also taken care of. It is possible to easily implement IM features, such as live support or chat between users.<br />
XMPP/BOSH also enables asynchronous messaging style. This means that the code that submits the request will never block on waiting for the response; instead the response will be handled by a callback function. In practical terms, this makes web application more responsive and user friendly. Technically, the same behavior can be implemented without XMPP, it's just that asynchronous messaging is a natural way of life for XMPP, as opposed to a web server that is more accustomed to sequential "request-response" interactions.<br />
<br />
<br />
<div style="text-align: center;"><span style="font-weight: bold;">Word of caution.</span><br />
<br />
<div style="text-align: left;">Although the demo has some things done right, in case you decide to reuse the code, you should be aware that the approach being used for serving subscriptions is not suitable for every situation. Let me explain: if 500 clients will subscribe to a single weather station, the component will send 500 nearly identical XML messages to the Jabber server every time the update happens. This is not good, and should rather be implemented with PubSub or MUC. If, on the other hand, you have one-to-one subscription (say, client wants to get updates from her Twitter account), then you're fine.<br />
</div></div></div><div style="text-align: left;"><br />
</div><div style="text-align: left;">I will keep fixing bugs and adding new features to the demo. Your feedback is most welcomed.<br />
</div><div style="text-align: left;"><br />
</div><div style="text-align: left;"><br />
<br />
</div><span style="font-weight: bold;"></span><br />
</div></div>Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com5tag:blogger.com,1999:blog-9111542903548056218.post-91672534485931950722009-08-23T00:36:00.000-04:002009-08-24T11:27:29.978-04:00Intermission: Erlang production nirvanaWell, almost. <a href="http://rfid-ale.blogspot.com/2009/08/real-time-web-applications-with-xmpp.html">The XMPP Web demo project</a>, despite using some of the best tools available today, is still a typical software project with bugs popping up here and there. Since it went on air couple of days back, one of the server-side components kept going down after few hours of up-time. The Erlang node it was running on would suddenly cease to exist without a sound or even a crash dump left anywhere. The cause is not yet completely clear, and it's going to take some time to figure out and fix.<br />This would probably be a show-stopper for any production system I had ever encountered. Not for the system made with Erlang! While I'm working on the bug, I'd like to keep my demo up and running without having to monitor that failing component, so here's how:<br /><ul><li>The node that runs component in question will be started from master node using slave:start;</li><li>Once the node is started, the component application will be launched using rpc:call</li><li>The node will be monitored with erlang:monitor_node; in case monitor process detects node going down, we'll repeat it all again.</li></ul><a href="https://tagsahead.svn.beanstalkapp.com/xmpp_component/trunk/node_utils.erl">The code</a> is only a few lines long. It works with packaged application, but can be easily adapted to Module:Function form. Note that there is a stop_node/2 function, in case you do want to stop the application without exiting your Erlang shell.<br />"Normal" crashes (the ones that don't bring node down) have been already taken care of with <a href="http://www.erlang.org/doc/man/supervisor.html">Erlang supervisor</a>. So nothing can now stop you from trying <a href="http://rfid-ale.blogspot.com/2009/08/real-time-web-applications-with-xmpp.html">the demo </a>any time of day :-)<br /><br />Update: I should have mentioned that another option to keep your node up is to use <span style="font-weight: bold;">-heart </span>switch of <span style="font-weight: bold;">erl </span>command. Coding solution allows more control though, for instance you may want to be able to run some checks before restarting node, whereas <span style="font-weight: bold;">-heart</span> will restart unconditionally.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com2tag:blogger.com,1999:blog-9111542903548056218.post-57603590590337298112009-08-21T01:09:00.001-04:002011-07-02T11:59:29.354-04:00Real-time Web applications with XMPP: working demoYes, this is a mid-size demo web app with XMPP, more specifically, combination of Strophe, exmpp, ejabberd on Amazon EC2. The purpose of this exercise was to create a "proof of concept" and prepare myself for more serious projects. The demo is now <a href="http://weazard.chirpber.com:8000/weazard.html">available online </a>, go ahead and check it out. You could either use predefined account (username: acc1, password : acc1), or register your own. Choose weather stations from right-hand panel and watch your browser being updated in a real time with data coming from (fake) weather service. Clicking on station's icon will toggle subscription, green arrow icon meaning the account is currently subscribed to that station. You will also see (normally quickly passing) blue icon meaning station is waiting for updated subscription status from the server. The demo is in early beta (if I can say this about demo at all), some features, such as live network status and live support are coming soon.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisBYWZtgiLIZkhTPkR3jN3kIR2pMX5WMWW82pwsWrAl_3SQkPQ-piBMhYfbxCWFeGj5KLNgUpdvWxATPajphn3Q6IsAWQE03U-nqn2aH7_Kjsg-4u1mv-xPyDo81NdhW3ZQpt_hpKDLVN2/s1600-h/Picture+2.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5372302980657518018" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisBYWZtgiLIZkhTPkR3jN3kIR2pMX5WMWW82pwsWrAl_3SQkPQ-piBMhYfbxCWFeGj5KLNgUpdvWxATPajphn3Q6IsAWQE03U-nqn2aH7_Kjsg-4u1mv-xPyDo81NdhW3ZQpt_hpKDLVN2/s400/Picture+2.png" style="cursor: pointer; display: block; height: 253px; margin: 0px auto 10px; text-align: center; width: 400px;" /></a><br />
<br />
The app is very dynamic. Try this: open 2-3 windows with the same account and watch subscription changes you're making in one window instantly showing up in others. This is a nice feature that could be used for real-time collaboration. It's possible to imagine a number of applications that need real-time updates and collaborations of that sort: trader floors, stock tickers, asset tracking, order fulfillment, arrivals/departures, ticket reservations, chats, tweets, you name it.<br />
It's not surprising that Google have chosen XMPP as core protocol for the Google Wave.<br />
What's the big deal, you might ask? We have been using dynamic Ajax-based apps for a while now. This is a better Ajax, though, as it uses XMPP over HTTP (BOSH) protocol. You can read about it in details <a href="http://xmpp.org/extensions/xep-0124.html">here</a> and <a href="http://metajack.wordpress.com/2008/07/02/xmpp-is-better-with-bosh/">here</a>. Briefly, most Ajax applications are using polling, which is poorly scalable and resource-hungry technique. Javascript libraries that employ BOSH, on the other hand, are using Ajax with "semi-permanent" connections, keeping them alive until server really has data to send to the client.<br />
And, with Javascript libraries supporting BOSH, such as Strophe or jsJac, plus JQuery, "<a href="http://metajack.im/2009/01/25/we-dont-need-no-stinkin-web-frameworks/">We Don't Need No Stinkin' Web Frameworks</a>" :-)<br />
<br />
The XMPP-specific features having been implemented/used in the demo:<br />
<ul><li>in-band registration;</li>
<li>service discovery;</li>
<li>roster push handlng;</li>
<li>presence notifications and subscription handling.</li>
</ul>In the next few days, I'll go over setting all things up on EC2, which is no small feat, at least it wasn't for me :-) I'm also planning to add some more features, such as self-restoring after disconnections and crashes, both on server and browser sides. As usual, your input is appreciated and welcomed.<br />
<br />
The source (HTML/JS/Erlang) is available <a href="https://github.com/bokner/weazard">here</a>.<br />
<br />
P.S. Please remember this is a fake data, so don't use it for planning your weekends, although I have taken care not to show "Heavy Snow" icon during summer. I might be not that far off from official weather feeds, though :-) One day I might just take a real feed and turn that demo into something useful :-)Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com7tag:blogger.com,1999:blog-9111542903548056218.post-72223833854193583652009-07-11T22:53:00.000-04:002009-07-12T02:51:04.578-04:00XMPP gen_component update: Design and featuresFor the last few weeks I've been trying to put together a framework for creating XMPP components in Erlang. While <a href="https://tagsahead.svn.beanstalkapp.com/xmpp_component">the code</a> I have now is not by any means covering all possible features the XMPP developer might need, I think it's a good starting point. I'm going to go over the interface and features of gen_component behavior that represents framework's API. Your input is very much appreciated.<br /><br /><span style="font-style: italic;">gen_component</span> implementation is backed by <span style="font-style: italic;">gen_fsm</span>, so just like for <span style="font-style: italic;">gen_fsm</span>, there is a certain set of callback functions that must be provided by a callback module.<br />XMPP (external) component could be seen as intermediary between XMPP server that routes messages, and some backend service that needs to be exposed to XMPP server's clients. The service could be anything: a database, a game, a stock ticker, Twitter feeds etc. Hence there are functions handling XMPP server conversations, such as <span style="font-style: italic;">handle_iq/4, handle_presence/4</span> and <span style="font-style: italic;">handle_message/4,</span> and the functions handling interactions with backend service, such as <span style="font-style: italic;">feed_component/2</span> and <span style="font-style: italic;">handle_feed/2</span>. XMPP service discovery support for the component is backed by functions <span style="font-style: italic;">disco_info/2 </span>and <span style="font-style: italic;">disco_items/2</span>. There are also functions that deal with life cycle of <span style="font-style: italic;">gen_component,</span> such as <span style="font-style: italic;">start_component/6, stop_component/1</span> and their respective callbacks <span style="font-style: italic;">init/2 </span>and <span style="font-style: italic;">terminate/1</span>. Interface includs some helper functions such as<span style="font-style: italic;"> send_packet/2</span> and <span style="font-style: italic;">print_packet/1.<br /><br /></span>As a proof of concept I built a <span style="font-style: italic;">test_component</span> module<span style="font-style: italic;"><span style="font-style: italic;"> </span></span>that implements<span style="font-style: italic;"><span style="font-style: italic;"> gen_component </span></span>callbacks<span style="font-style: italic;"><span style="font-style: italic;">. test_component </span></span>represents mocked weather alert service Jabber clients could subscribe to. The client browses available weather alerts and registers with ones he wishes to get updated with. That's how it looks like in action:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSIYeguV6l8G5zeDOGoqGjln86nb455-GvwNQ9amVsOSbCZfDr0ty5jhU1dovjzdL3lF-ik2LNHVVMmZhF0YsuZF7Dofft29Z8hyphenhyphen_7H9pxla0phiAdaZ0-nOo0ZgUeZMeh7W0gYKekMxTG/s1600-h/Picture+2.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 380px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSIYeguV6l8G5zeDOGoqGjln86nb455-GvwNQ9amVsOSbCZfDr0ty5jhU1dovjzdL3lF-ik2LNHVVMmZhF0YsuZF7Dofft29Z8hyphenhyphen_7H9pxla0phiAdaZ0-nOo0ZgUeZMeh7W0gYKekMxTG/s400/Picture+2.png" alt="" id="BLOGGER_PHOTO_ID_5357456515396484978" border="0" /></a><a href="https://support.process-one.net/doc/display/EXMPP/">exmpp</a> library has been used extensively in gen_component, and I found it pretty stable, although there were some gotchas that I attribute to my lack of experience. I'm planning to do a detailed code walkthrough with comments on exmpp usage.<br /><br />Source code for gen_component is <a href="https://tagsahead.svn.beanstalkapp.com/xmpp_component">here</a>.<br /><br /><span style="font-style: italic;"><br /></span>Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com1tag:blogger.com,1999:blog-9111542903548056218.post-89639610143347552982009-07-03T16:24:00.000-04:002009-07-07T08:30:43.088-04:00gen_component project: service discovery with exmppThe <a href="http://xmpp-erlang.blogspot.com/2009/06/building-xmpp-components-with-epeios.html"><span style="font-style: italic;">gen_component </span></a>behavior is growing up and is making more sense to me as the <a href="http://xmpp-erlang.blogspot.com/2009/06/building-xmpp-components-with-epeios.html">XMPP component project</a> is moving along. I can even see <span style="font-style: italic;">gen_component</span> rivaling <a href="http://www.erlang.org/doc/man/gen_server.html"><span style="font-style: italic;">gen_server</span></a> in the nearest future :-). Jokes aside, it will have <span style="font-style: italic;">handle_iq</span>, <span style="font-style: italic;">handle_message</span>, <span style="font-style: italic;">handle_presence</span> and possibly more callbacks with helpers glueing it all together, in a fashion similar to OTP behaviors.<br />By the way, the component doesn't even have to be a process, depending on what exactly your component does. So far I managed to avoid maintaining conversational state, but will have to introduce it going forward. For instance, to tell the registered user from a new one, having state seems to be a must, doesn't it? Still, should I use Mnesia for it, rather than process state?<br /><br />The project is now having service discovery written using <a href="https://support.process-one.net/doc/display/EXMPP/exmpp+home"><span style="font-weight: bold;">exmpp</span></a>. The imaginary weather alert service exposes itself through disco_info/1 and disco_items/1 (which are now part of gen_component).<br /><br />That's how it looks like in Psi:<br /><br /><div style="text-align: left;"><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5rLWRkBM6u_aUz_jUnm1Byn_opzswFEQMqTSgc4sNwqLG9P03i4gI1Dg2gh6utJenW-wHEAo9Uo2vrUMalNERScI-0KDl8Xvqae8SyVS1PH_9Sd_4O8JcMzhtT_NZdIl5cBOsbz3037M/s1600-h/Picture+1.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 441px; height: 273px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5rLWRkBM6u_aUz_jUnm1Byn_opzswFEQMqTSgc4sNwqLG9P03i4gI1Dg2gh6utJenW-wHEAo9Uo2vrUMalNERScI-0KDl8Xvqae8SyVS1PH_9Sd_4O8JcMzhtT_NZdIl5cBOsbz3037M/s320/Picture+1.png" alt="" id="BLOGGER_PHOTO_ID_5354335208436470194" border="0" /></a><br />As you can see, there are two weather alerts running, each one having few nodes for specific weather alerts. Are you thinking of stock tickers, twits etc. ? Yes, everything is possible with gen_component and XMPP :-)<br /><br /></div>Check out <a href="https://tagsahead.svn.beanstalkapp.com/xmpp_component">the source code and instructions</a>.<br />Next in line is in-band registration.<br />I'll be doing web interface with <a href="http://people.chesspark.com/%7Ejack/strophe-preview/doc/files/strophe-js.html">Strophe</a> or <a href="http://blog.jwchat.org/jsjac/">jsJac </a>as well once I have a chance.<br />I would be interested in your thoughts and ideas on <span style="font-style: italic;">gen_component</span>.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com0tag:blogger.com,1999:blog-9111542903548056218.post-83409187965048184462009-07-03T16:20:00.000-04:002009-07-05T01:29:15.512-04:00Writing XMPP components in Erlang with exmppI'm going to show how to create an external <span class="blsp-spelling-error" id="SPELLING_ERROR_0">XMPP</span> component in <span class="blsp-spelling-error" id="SPELLING_ERROR_1">Erlang</span>. If you're in a hurry and familiar with <span class="blsp-spelling-error" id="SPELLING_ERROR_2">ejabberd</span>, you may want to go straight to the <a href="https://tagsahead.svn.beanstalkapp.com/xmpp_component">source code</a>.<br />This mini-project started when I decided to build <span class="blsp-spelling-error" id="SPELLING_ERROR_3">XMPP</span> interface for the system that had already been completed. I chose external component over <a href="http://www.process-one.net/en/wiki/ejabberd_module_development/"><span class="blsp-spelling-error" id="SPELLING_ERROR_4">ejabberd</span> module</a>, because I wanted to avoid <span class="blsp-spelling-error" id="SPELLING_ERROR_5">ejabberd</span> dependencies and be able to use it with any Jabber server. As the system was written in <span class="blsp-spelling-error" id="SPELLING_ERROR_6">Erlang</span>, and I had already written gen_mods, <a href="http://www.process-one.net/en/blogs/article/epeios_write_a_module_for_an_xmpp_server_once_run_it_everywhere"><span class="blsp-spelling-error" id="SPELLING_ERROR_7">Epeios</span></a> seemed to be an obvious way to go, as it promotes turning standard <span class="blsp-spelling-error" id="SPELLING_ERROR_8">ejabberd</span> modules into <span class="blsp-spelling-error" id="SPELLING_ERROR_9">XMPP</span> components. Life turned out to be not so rosy though, after I had realized that module development in <span class="blsp-spelling-error" id="SPELLING_ERROR_10">Epeios</span> is deprived of features that makes writing gen_mods so convenient, namely hooks and handlers. <span class="blsp-spelling-error" id="SPELLING_ERROR_11">Epeios</span> site states that they are working on it, but for now you're totally on your own when it comes to handling all traffic routed to the component. <span class="blsp-spelling-error" id="SPELLING_ERROR_12">Epeios</span> still handles <span class="blsp-spelling-error" id="SPELLING_ERROR_13">XEP</span>-0114 connection protocol, but this alone wasn't enough to justify its usage.<br /><br />So I decided to create a module with <span class="blsp-spelling-error" id="SPELLING_ERROR_14">XEP</span>-0114 implementation by borrowing some code from <span style="font-weight: bold;"><span class="blsp-spelling-error" id="SPELLING_ERROR_15">xmpp</span>_component.<span class="blsp-spelling-error" id="SPELLING_ERROR_16">erl</span></span> in <span class="blsp-spelling-error" id="SPELLING_ERROR_17">Epeios</span> distribution.<br />The <span class="blsp-spelling-error" id="SPELLING_ERROR_18">Epeios</span> code (version 1.0.0) implies that:<br /><ul><li>the component has <span style="font-weight: bold;">start/2</span> and <span style="font-weight: bold;">stop/1</span>, which is what <span style="font-weight: bold;">gen_mod</span> behavior requires;</li><li><span style="font-weight: bold;">start/2</span> creates a process and returns {<span class="blsp-spelling-error" id="SPELLING_ERROR_19">ok</span>, <span class="blsp-spelling-error" id="SPELLING_ERROR_20">Pid</span>}, which is a deviation from <a href="http://www.process-one.net/en/wiki/ejabberd_module_development/">official <span class="blsp-spelling-error" id="SPELLING_ERROR_21">ejabberd</span> module development doc</a>;<br /></li><li>the process created by <span style="font-weight: bold;">start/2</span> has to be able to handle messages of form<span style="font-weight: bold;"> {route, <span class="blsp-spelling-error" id="SPELLING_ERROR_22">JID</span>_From, <span class="blsp-spelling-error" id="SPELLING_ERROR_23">JID</span>_To, Packet}</span>;</li><li>to send a stanza on behalf of component, the module calls <span style="font-weight: bold;"><span class="blsp-spelling-error" id="SPELLING_ERROR_24">xmpp</span>_component:send_to_server/1</span>;</li></ul>So it appears that in order to use standard <span class="blsp-spelling-error" id="SPELLING_ERROR_25">ejabberd</span> modules in <span class="blsp-spelling-error" id="SPELLING_ERROR_26">Epeios</span>, one should wrap them in a way that satisfies above requirements. As my goal was just ripping out some code pieces rather than reusing the whole thing, I felt that following <span style="font-weight: bold;">gen_mod</span> wouldn't bring much value, instead I wanted a behavior that would aid component writing in a more structured way.<br />Hence the <span style="font-weight: bold;">gen_component </span>behavior was created...<br /><pre class="source-code"><code>-module(gen_component).<br /><br />-export([behaviour_info/1]).<br />-export([send_packet/1, component_name/0]).<br />% Return a list of required functions and their <span class="blsp-spelling-error" id="SPELLING_ERROR_27">arity</span><br />behaviour_info(callbacks) -><br />[{receive_packet, 3}, {send_packet, 1},<br />{component_name, 0}];<br />behaviour_info(_Other) -><br />undefined.<br /><br />% Helpers<br />send_packet(<span class="blsp-spelling-error" id="SPELLING_ERROR_28">XmlString</span>) -><br /><span class="blsp-spelling-error" id="SPELLING_ERROR_29">xep</span>0114:send_to_server(<span class="blsp-spelling-error" id="SPELLING_ERROR_30">XmlString</span>).<br /><br />component_name() -><br /><span class="blsp-spelling-error" id="SPELLING_ERROR_31">xep</span>0114:component_name().<br /><br /></code></pre>...and the code in<span style="font-weight: bold;"> <span class="blsp-spelling-error" id="SPELLING_ERROR_32">xmpp</span>_component.<span class="blsp-spelling-error" id="SPELLING_ERROR_33">erl</span></span> was altered accordingly in order to replace <span style="font-weight: bold;">gen_mod</span> by <span style="font-weight: bold;">gen_component</span>.<br /><br />This left me with only one dependency - <span style="font-weight: bold;"><span class="blsp-spelling-error" id="SPELLING_ERROR_34">xmpp</span>_component:start_<span class="blsp-spelling-error" id="SPELLING_ERROR_35">xml</span>_parser/1 </span>directly loads platform-dependent <span style="font-weight: bold;">expat_<span class="blsp-spelling-error" id="SPELLING_ERROR_36">erl</span>.so</span> driver, location of which has to be defined in <span class="blsp-spelling-error" id="SPELLING_ERROR_37">Epeios</span> configuration file. I didn't want to tweak configuration and remember to choose suitable driver every time the component is being deployed.<br />Recently released <a href="https://support.process-one.net/doc/display/EXMPP/exmpp+home"><span class="blsp-spelling-error" id="SPELLING_ERROR_38">exmpp</span></a> library came to rescue. The <span class="blsp-spelling-error" id="SPELLING_ERROR_39">exmpp</span> build script automatically resolves the dependency on linked drivers that are used by XML parser. However, the interface of <span class="blsp-spelling-error" id="SPELLING_ERROR_40">exmpp</span> XML parser is significantly different from the <span class="blsp-spelling-error" id="SPELLING_ERROR_41">ejabberd</span> one that <span class="blsp-spelling-error" id="SPELLING_ERROR_42">Epeios</span> is using. While I was preparing myself for a next round of tweaking, I found that <span class="blsp-spelling-error" id="SPELLING_ERROR_43">exmpp</span> team had already taken care of this problem by providing adapters (in <span class="blsp-spelling-error" id="SPELLING_ERROR_44">src</span>/<span class="blsp-spelling-error" id="SPELLING_ERROR_45">compat</span> directory) that allow client code to use <span class="blsp-spelling-error" id="SPELLING_ERROR_46">ejabberd</span> XML parser interface with <span class="blsp-spelling-error" id="SPELLING_ERROR_47">exmpp</span> behind it. After some more coding I had a minimal component that was visible to external clients upon connection to <span class="blsp-spelling-error" id="SPELLING_ERROR_48">ejabberd</span> and could send and receive <span class="blsp-spelling-error" id="SPELLING_ERROR_49">XMPP</span> packets routed by <span class="blsp-spelling-error" id="SPELLING_ERROR_50">ejabberd</span>.<br /><br />References:<br /><br /><ul><li><a href="https://tagsahead.svn.beanstalkapp.com/xmpp_component">The source code<br /></a></li><li><a href="http://www.process-one.net/en/blogs/article/epeios_write_a_module_for_an_xmpp_server_once_run_it_everywhere"><span class="blsp-spelling-error" id="SPELLING_ERROR_51">Epeios</span></a></li><li><a href="https://support.process-one.net/doc/display/EXMPP/exmpp+home"><span class="blsp-spelling-error" id="SPELLING_ERROR_52">exmpp</span></a></li><li><a href="http://www.process-one.net/en/ejabberd/docs"><span class="blsp-spelling-error" id="SPELLING_ERROR_53">ejabberd</span> documentation</a></li></ul>Your comments and questions are welcome.<br /><br /><br /><span style="font-style: italic;">Update</span>, July 2, 2009: Revamped the code to add more <span style="font-weight: bold;">exmpp</span> stuff; the framework now supports multiple components running on the same node (previously it was only possible to run a single component). Next will add service discovery support. <span style="font-weight: bold;">exmpp</span> appears to be quite handy, many helpers etc. Need to spend more time with it, though - lack of docs and comprehensive examples slows me down considerably.Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com0tag:blogger.com,1999:blog-9111542903548056218.post-47308926598737006952009-06-23T16:11:00.000-04:002009-07-05T01:29:15.530-04:00Erlang for Java programmer - Design patterns, p.1The Gamma et al. "Design patterns" book formulates common desing patterns that could be applied to any object-oriented language. When we move to functional language domain though, many OO design patterns simply don't apply. This is hardly surprising, because functional programmers by definition don't do OO design. Some of the problems that OO design patterns are supposed to solve still have their counterparts in functional programming.<br /><br />We are going to walk through the book's pattern catalog and try to show a matching Erlang approach for the OO patterns, where possible.<br /><br />Going forward, I will be referring to "Design patterns" book as <span style="font-style: italic; font-weight: bold;">DP</span>. In (unlikely) case you'll want to follow, it'll be helpful to have this book handy.<br /><br /><br /><br /><div style="text-align: center;"><br /> <span style="font-size:130%;"><strong>Adapter (Wrapper)</strong></span><br /><br /></div><br /><br />Intention(<span style="font-weight: bold; font-style: italic;">DP</span>): <em>Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.</em><br /><br /><br /><br />Let's just substitute "class" to "function", and we'll have it for Erlang:<br /><br /><br /><br /><em>Convert the interface of a function into another interface clients expect. Adapter lets functions work together that couldn't otherwise because of incompatible interfaces.</em><br /><br /><br /><br />Now, function's interface obviously being its arguments, in most cases we can easily create adapter for that function, which is another function having desired set of arguments.<br /><br /><br /><br />Example:<br /><br /><br /><br />Trying to stick closely to <span style="font-style: italic; font-weight: bold;">DP</span> example, let's assume that we are working on drawing editor and want to implement text drawing capabilities in module <strong>textshape</strong>. The <a href="http://www.trapexit.org/Defining_Your_Own_Behaviour">behavior</a> that we have decided to use for all shape kinds contains bounding_box/1 function that returns bottom left and top right coordinates for any shape. So we are now concerned with task of writing bounding_box() flavour for text shapes. We have 3rd party module <strong>textview</strong> at our disposal, and we want to reuse it for our purpose. It happens that <strong>textview</strong> exports (in Java terms, has public) functions:<br /><br /><br /><br />get_origin(TextView) that returns origin point of the text view in form of tuple {X, Y}, and<br /><br />get_extent(TextView) that returns width and height of the text view in form of tuple {Width, Height}.<br /><br /><br /><br />Our bounding_box/1 function then might look as follows:<br /><br /><br /><br /><pre style="padding: 5px; overflow: auto; color: #000000; background-color: rgb(0, 0, 0); line-height: 14px; width: 100%;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;font-size:12px;"><br /><code><span style="color: #FFFF00; background-color: rgb(0, 0, 0);"># Calculates bounding box for text drawing;</span><br /><span style="color: #FFFF00; background-color: rgb(0, 0, 0);"># returns tuple {TopLeftX, TopLeftY, BottomRightX, BottomRightY}</span><br /></code><br /><br /></pre><br /><pre style="padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: #33CC00; background-color: rgb(0, 0, 0); font-size: 12px; line-height: 14px; width: 100%;"><br /><code>bounding_box(TextShape) -><br /> TextView = convert_to_textview(TextShape),<br /> {X, Y} = textview:get_origin(TextView),<br /> {Width, Height} = textview:get_extent(TextView),<br /> {X, Y, X + Width, Y + Height).<br /></code><br /></pre><br /><pre><br /><br /><br /></pre><br /><br /><br /><br />What about convert_to_textview/1 function? Not to worry, it should be easy. Remember, TextView and TextShape are not objects, but rather data structures, so conversion is normally just a matter of knowing what these structures are and matching parts of them. Below is a sample implementation, just to give an idea:<br /><br /><br /><br /><pre style="padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: #000000; background-color: rgb(0, 0, 0); font-size: 12px; line-height: 14px; width: 100%;"><br /><code style="color: #FFFF00;"># In this case, we assume that TextShape is a list of properties, such as:<br /># [{text, "Desing patterns"}, {font, "Arial"}, {color, "red"}, .....];<br /># TextView is a dictionary with keys viewtext, viewfont, viewcolor, viewstyle etc.<br /></code><br /><br /></pre><br /><pre style="padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: #33CC00; background-color: rgb(0, 0, 0); font-size: 12px; line-height: 14px; width: 100%;"><br /><code>convert_to_textview(TextShape) -><br /> lists:foldl(fun(Prop, Acc) -><br /> {Key, Value} = convert_prop(Prop),<br /> case Key of<br /> not_present -><br /> Acc;<br /> _Other -><br /> dict:store(Key, Value, Acc)<br /> end<br /> end, dict:new(), TextShape).<br /><br /><br />convert_prop({"text", Value}) -><br />{"viewtext", Value};<br />convert_prop({"font", Value}) -><br /> {"viewfont", Value};<br />%% Conversions for other properties<br />...................................<br />%% Properties that don't have counterparts in TextView structure<br />convert_prop({_, _}) -> not_present.<br /><br /></code><br /></pre><br /><pre><br /><br /><br /></pre><br /><br /><br /><br />That's it, we have Adapter implementation blueprint for Erlang. Looks kinda trivial, don't you think? That's what you're gonna like once you start Erlang coding - many things are easier and definitely less verbose compared to Java.<br /><br />Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com0tag:blogger.com,1999:blog-9111542903548056218.post-19759073965772257552009-06-18T17:14:00.000-04:002009-07-05T01:29:15.521-04:00Erlang for Java programmer - power of functions<p>I have been using Java for more than 10 years now, and at the time I had come across Erlang, while I was excited about what Erlang has to offer, I was a bit worried on how to survive in absence of beloved OO features, such as abstract classes, polymorphism, inheritance etc. It turned out that these questions were somewhat irrelevant, instead I should have asked:<br /></p><br /><div style="text-align: left; font-style: italic;"><br /> How the problems that OOP (Java, in particular) solves using feature "X" can be solved by functional language (Erlang)?<br /><br /></div><br /><br /><div style="text-align: center;"><br /> <span style="font-size:130%;"><strong>Polymorhysm vs. Higher-order functions</strong></span><br /><br /></div><br /><br />I'd like to show a practical example where Java programmer would use polymorphism, and Erlang programmer would use higher-order functions.<br /><br /><br /><br />One of my Jabber/XMPP projects required message filtering based on some set of conditions. The customer wanted to implement some kind of censorship for their chat rooms. If incoming message fails any of those conditions, it has to be dropped. Let's assume that for this task we will maintain a list of pairs {condition, text}, where condition is a boolean string function that applies to the message text, for example the list could look like:<br /><br />{"contains", "@$#*%!"}, {"equals", "This chatroom sucks"}, {"contains", "morons"}<br /><br />etc.<br /><br /><br /><br />So to pass the "appropriateness" test, the incoming message should fail each condition in a condition list.<br /><br />Now, let's limit ourseleves to 2 functions (<strong>contains</strong> and <strong>equals</strong>) and see how we go about coding this in Java:<br /><br /><br /><br /><div style="background-color: #000000; color: rgb(255, 255, 255);"><br /> <span style="margin: 20px; color: #33CC00;">package com.teamhand.messaging.utils;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">import java.util.ArrayList;</span><br /><br /> <span style="margin: 20px; color: #33CC00;">import java.util.List;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public class Censorship {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">List <Condition> conditions = new ArrayList<Condition<();</span><br /><br /> <span style="margin: 20px; color: #33CC00;">public Censorship() {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">super();</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public void addCondition(Condition condition) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">conditions.add(condition);</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public boolean checkMessage(String message) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">for (Condition condition : conditions) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">if (condition.check(message)) return false; // message has been censored</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <span style="margin: 20px; color: #33CC00;">return true; // message has passed the censorship</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public static void main(String[] args) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">ContainsCondition c1 = new ContainsCondition("@$#*%!");</span><br /><br /> <span style="margin: 20px; color: #33CC00;">EqualsCondition c2 = new EqualsCondition("This chatroom sucks");</span><br /><br /> <span style="margin: 20px; color: #33CC00;">ContainsCondition c3 = new ContainsCondition("morons");</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">Censorship c = new Censorship();</span><br /><br /> <span style="margin: 20px; color: #33CC00;">c.addCondition(c1);</span><br /><br /> <span style="margin: 20px; color: #33CC00;">c.addCondition(c2);</span><br /><br /> <span style="margin: 20px; color: #33CC00;">c.addCondition(c3);</span><br /><br /> <span style="margin: 20px; color: #33CC00;">String message1 = "I'm a good message";</span><br /><br /> <span style="margin: 20px; color: #33CC00;">String message2 = "Are you gonna listen, you morons?";</span><br /><br /> <span style="margin: 20px; color: #33CC00;">System.out.println(c.checkMessage(message1));</span><br /><br /> <span style="margin: 20px; color: #33CC00;">System.out.println(c.checkMessage(message2));</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">// Condition class</span><br /><br /> <span style="margin: 20px; color: #33CC00;">package com.teamhand.messaging.utils;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public abstract class Condition {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">public abstract boolean check(String message);</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">//Implementation classes</span><br /><br /> <span style="margin: 20px; color: #33CC00;">//</span><br /><br /> <span style="margin: 20px; color: #33CC00;">//</span><br /><br /> <span style="margin: 20px; color: #33CC00;">package com.teamhand.messaging.utils;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">// ContainsCondition class</span><br /><br /> <span style="margin: 20px; color: #33CC00;">public class ContainsCondition extends Condition {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">private String template;</span><br /><br /> <span style="margin: 20px; color: #33CC00;">public ContainsCondition(String template) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">this.template = template;</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">@Override</span><br /><br /> <span style="margin: 20px; color: #33CC00;">public boolean check(String message) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">return message.contains(template);</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">// EqualsCondition class</span><br /><br /> <span style="margin: 20px; color: #33CC00;">package com.teamhand.messaging.utils;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public class EqualsCondition extends Condition{</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">private String template;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public EqualsCondition(String template) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">this.template = template;</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">public boolean check(String message) {</span><br /><br /> <span style="margin: 20px; color: #33CC00;">return message.equals(template);</span><br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">}</span><br /><br /> <br /><br /></div><br /><br /><br /><br /><br /><br />The implementation is typical - we have a polymorphic list of Condition instances, whose classes implement condition-specific check() method.<br /><br /><br /><br />Now, it's Erlang's turn:<br /><br /><br /><br /><div style="background-color: #000000; color: rgb(255, 255, 255);"><br /> <span style="margin: 20px; color: #33CC00;">-module(censorship).</span><br /><br /> <span style="margin: 20px; color: #33CC00;">-export([check_message/2, test/0]).</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">condition({"contains", Phrase}) -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">fun(MsgBody) -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">string:str(MsgBody, Phrase) > 0</span><br /><br /> <span style="margin: 20px; color: #33CC00;">end;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">condition({"equals", Phrase}) -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">fun(MsgBody) -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">(MsgBody == Phrase)</span><br /><br /> <span style="margin: 20px; color: #33CC00;">end;</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">condition(Other) -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">throw({error, {not_supported, Other}}).</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">%% Returns false is message should be censored, true otherwise</span><br /><br /> <span style="margin: 20px; color: #33CC00;">check_message(Message, Conditions) -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">not lists:any(fun(Condition) -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">Condition(Message) end, Conditions).</span><br /><br /> <br /><br /> <span style="margin: 20px; color: #33CC00;">test() -></span><br /><br /> <span style="margin: 20px; color: #33CC00;">C1 = condition({"contains", "@$#*%!"}),</span><br /><br /> <span style="margin: 20px; color: #33CC00;">C2 = condition({"equals", "This chatroom sucks"}),</span><br /><br /> <span style="margin: 20px; color: #33CC00;">C3 = condition({"contains", "morons"}),</span><br /><br /> <span style="margin: 20px; color: #33CC00;">CondList = [C1, C2, C3],</span><br /><br /> <span style="margin: 20px; color: #33CC00;">{check_message("I'm a good message", CondList), check_message("Are you gonna listen, you morons?", CondList)}.</span><br /><br /></div><br /><br /><br /><br />Note how condition/1 function returns another function, so in check_message/2 we have a list of functions, each one representing a condition. As you may have expected, the solutions are very similar, except the Erlang one bears significantly less code.<br /><br /><br /><br />To conclude, this post is not to start another flame war on merits of languages, but rather for those OOP programmers who have decided to try Erlang and may need some help in finding common ground while exploring functional style.<br /><br />Anonymoushttp://www.blogger.com/profile/05753708423104305537noreply@blogger.com0