What is Megaphone?

What is Megaphone?
The Megaphone project is about enhancing open source chat software. Specifically, the goal is to allow ejabberd to support 1,000,000 simultaneous users. See The Plan page for more details on how I plan to solve this problem. See the About this Blog page for more details on why I created this blog.

Friday, March 30, 2012

This Really Should be Easier

Previously...
  • I got a simple pass-through to work.
  • I made progress with a simple BOSH pass-through.
  • I got Pidgin to work with the pass-through.

I decided to try and extend the pass-through to be more complex, rather than trying to make the multi-client version work more like the single client version.  This was partially because the multi-client version currently uses raw TCP instead of making use of the node HTTP server, but also because I like the idea of having something that works that I can come back to at each step.

At this point, I'm trying to modify the ejabberd module and the nodejs program to use connection IDs and content lengths.  One additional change that I made was to do away with fixed fixed headers in favor of a variable length header.  

The new header looks like this:

    <connection ID>|<content length>|<content>

The basic differences from the old style headers is that 1) the connection ID and length fields are not zero-padded, and 2) the length field is now the content length field: it contains the character count of the content field instead of the length of the entire message.

Next time: connection IDs and content length fields.

Wednesday, March 28, 2012

Simple ejabberd Module

Previously...
  • I decided to try a simple pass-through.
  • I got a simple pass-through to work.
  • I made progress with a simple BOSH pass-through.

After a bit more work, I got the BOSH pass-through to work.  Here it is:

-module(mod_simple).
-export([start/2, stop/1, start_module/2, data_loop/1]).
-behavior(gen_mod).


-include("ejabberd.hrl").


start(Host, [Port]) ->
    spawn(?MODULE, start_module, [Host, Port]).


stop(_Host) -> ok.


start_module(Host, Port) ->
    {ok, Socket} = gen_tcp:listen(Port, [binary, {active, false}]),
    ?DEBUG("~p is now listening on ~p:~p", [?MODULE, Host, Port]),
    module_loop(Socket).


module_loop(Socket) ->
    {ok, Client} = gen_tcp:accept(Socket),
    ?DEBUG("Got connection", []),
    PID = spawn(?MODULE, data_loop, [Client]),
    gen_tcp:controlling_process(Client, PID),
    module_loop(Socket).


data_loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        { error, closed } ->
            ?DEBUG("connection closed", []);
            
        { ok, Data } ->
            StrData = binary_to_list(Data),
            ?DEBUG("received ~p", [StrData]),
            { _ResponseCode, _Headers, Response} =  
                ejabberd_http_bind:process_request(Data, {{127, 0, 0, 1}, 1234}),
            ?DEBUG("response ~p", [Response]),
            gen_tcp:send(Socket, Response),
            data_loop(Socket)
    end.

The module receives control from ejabberd via the start function.  It immediately calls start_module to fire up something that will hang around and listen for connections.  start_module starts up a gen_tcp listener then goes into module_loop to accept individual connections.


The module is intended to work with a nodejs part that connects to it.  In this, the single-connection version, the nodejs part serves no useful purpose but I am including it because the multi-connection part will need it.

Getting back to the erlang code, when a new connection comes in, the module_loop function starts up a new process (data_loop) and then goes back to waiting for more connections.

data_loop expects to receive the body of a BOSH message which it passes on to ejabberd_http_bind process_request.  For whatever reason, process_request wants an IP address along with the data, so I give it 127.0.0.1:1234  process_request responds with a response code, a set of HTTP headers and the body of the response.  data_loop ignores everything but the body of the response, which it forwards on to the nodejs program.

Not sure whether or not the response code needs to be returned or if the response headers are needed.  For now the values are discarded, but in the future I may modify the megaphone "protocol" to include them.

Here is the nodejs program:


var util = require('util'),
    net = require('net'),
    config = require('./config.js').data,
    http = require('http');


var clientPort = 8280;
var ejabberdPort = 7280;
var response = null;


var serverSocket = net.createConnection(ejabberdPort, "localhost");
console.log("connected to server");


serverSocket.on("data", function(data) {
    if (response != null)
    {
        var headers = {
            'content-type' : 'text/xml; charset=utf-8',
            'content-length' : data.length
        };


        response.writeHead(200, headers);
        response.end(data);
    }
}); 


var server = http.createServer(function (req, resp) {
    req.on("data", function (data) {
        serverSocket.write(data);
    });
    
    response = resp;
});


server.listen(clientPort);


console.log("listening on port " + clientPort);

The program is much simpler than the multi-connection version because, well, it only has to deal with a single connection.  I like not having to deal with headers and CRLF sequences, so I may end up using the httpserver approach with the multi-connection version; especially since the multi-connection version does not work at this point.

Next time: trying to apply these results to the multi-connection version.



Tuesday, March 27, 2012

Another Step Forward

Previously...
  • I noted that the megaphone replies were missing the content-length header.
  • I decided to try a simple pass-through.
  • I got a simple pass-through to work.

I worked on getting a reasonably simple, single-connection program to work.  It's not quite there yet, but it's a lot further along than the multi-connection program.  


One thing that I did differently was that the ejabberd side was much simpler.  All that is really needed for an ejabberd module is to create a start function and a stop function.  To use the module create a line in the ejabberd.cfg file in the section for modules.  For example:


{modules,
    {mod_adhoc, []},
    ...
    {mod_simple, [7280]}
}


In this example, a module called mod_simple gets passed the parameter 7280.  The function would look something like this:


-module(mod_simple).
-export([start/2, stop/1, start_module/1]).
-behavior(gen_mod).
-include("ejabberd.hrl").


start(_Host, [Port]) ->
    spawn(?MODULE, start_module, [Port]),
    ok.


stop(_Ignored) ->
    ok.


start_module(Port) ->
    <do whatever>.


In the example above, the value passed to start for Port would be 7280.  


Next time: more ejabberd module details.

Monday, March 26, 2012

A Modest Success

Previously...
  • I noted that ECM is a piece of junk.
  • I noted that the megaphone replies were missing the content-length header.
  • I decided to try a simple pass-through.

I tried the simple pass through and it worked!  This, however, is not quite the victory one might hope for.  The real purpose of doing something like that was to ensure that BOSH through node was working at all --- the fact that it did work means that there is at least a chance of megaphone working.

The next step is to set up an extremely simple pass through that goes into ejabberd instead of using the regular BOSH interface via port 5280.  If I can get that to work, it will show not only that my code really stinks, but also that my goal is very achieveable...even in the real world!

Next time: the results of this revised, evil master plan.

Saturday, March 24, 2012

Another Perfectly Good Theory

Previously...
  • I solved the endless loop problem.
  • I noted that ECM is a piece of junk.
  • I noted that the megaphone replies were missing the content-length header.

After last time I had resolved to add a content-length header to the messages in the (as it now turns out) vain hope that it would allow pidgin to get a bit farther.  This hope was vain.  It did not come to pass.  Hence it was a vain hope.  

This is different from a vein hope in that it had nothing to do with blood flow.  If it were vein, then perhaps I could start a blog about vampires and angst; which, truth to be told, would not make it much different from this blog.  But I digress.  Also, I am trying to fill out a modest blog posting.

At this point I need inspiration.  As in other times when I am at this point, I turn to beer.  

One of the things that beer has given me is the idea of trying to create a very simple pass-thru that, very simply, just passes the data from the pidgin side to the ejabberd side.  I figure that if I can make it there I can make it anywhere.  It's up to you, New York, New York!

But I digress again.

Next time: the results of the pass-thru.

Friday, March 23, 2012

Content Length Required

Previously...
  • I solved the undelivered data problem and added heartbeats.
  • I solved the endless loop problem.
  • I noted that ECM is a piece of junk.

In an attempt to get Pidgin and megaphone to talk to each other I tried looking at a conversation via wireshark.  Pidgin was using port 5280 (BOSH) in this example, thereby bypassing megaphone.  Here is some of the conversation:


POST /http-bind/ HTTP/1.1
Host: ubuntu2
User-Agent: Pidgin 2.6.6 (libpurple 2.6.6)
Content-Encoding: text/xml; charset=utf-8
Content-Length: 225

<body content='text/xml; charset=utf-8' secure='true' to='ubuntu2' xml:lang='en' xmpp:version='1.0' ver='1.6' xmlns:xmpp='urn:xmpp:xbosh' rid='2652978132620311' wait='60' hold='1' xmlns='http://jabber.org/protocol/httpbind'/>HTTP/1.1 200 OK
Content-Length: 764
Content-Type: text/xml; charset=utf-8
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type

<body xmlns='http://jabber.org/protocol/httpbind' sid='860957cf51a1a6420b82b482853285a914afba43' wait='60' requests='2' inactivity='30' maxpause='120' polling='2' ver='1.8' from='ubuntu2' secure='true' authid='219217664' xmlns:xmpp='urn:xmpp:xbosh' xmlns:stream='http://etherx.jabber.org/streams' xmpp:version='1.0'><stream:features xmlns:stream='http://etherx.jabber.org/streams'><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism><mechanism>DIGEST-MD5</mechanism><mechanism>SCRAM-SHA-1</mechanism></mechanisms><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://www.process-one.net/en/ejabberd/' ver='yy7di5kE0syuCXOQTXNBTclpNTo='/><register xmlns='http://jabber.org/features/iq-register'/></stream:features></body>

Consider now the conversation that I see going on between Pidgin and ECM:


HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type


<body xmlns='http://jabber.org/protocol/httpbind' sid='48f09e6c35ad89c27f0d82ae348110f58137d251' wait='60' requests='2' inactivity='30' maxpause='120' polling='2' ver='1.8' from='ubuntu2' secure='true' authid='539841753' xmlns:xmpp='urn:xmpp:xbosh' xmlns:stream='http://etherx.jabber.org/streams' xmpp:version='1.0'><stream:features xmlns:stream='http://etherx.jabber.org/streams'><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism><mechanism>DIGEST-MD5</mechanism><mechanism>SCRAM-SHA-1</mechanism></mechanisms><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://www.process-one.net/en/ejabberd/' ver='yy7di5kE0syuCXOQTXNBTclpNTo='/><register xmlns='http://jabber.org/features/iq-register'/></stream:features></body>

In addition to being gibberish, it also shows that the responses from megaphone are not including content-length headers in their responses.  This is likely (one of) the problem(s) that is causing the two of them to fail to communicate.  

So solving this problem will cause everything to just magically work.

I'm serious this time.

Next time: a solution to this problem.

Thursday, March 22, 2012

ECM Stinks

Previously...
  • I noted that data was not getting delivered to ECM.
  • I solved the undelivered data problem and added heartbeats.
  • I solved the endless loop problem.

For some time now I have been getting the following error when I try to close a connection via pidgin:

/home/cnh/megaphone/ecm/Exchange.js:84
me.ecm.unregisterExchange(me);
         ^
TypeError: Object [object Object] has no method 'unregisterExchange'
    at Socket.<anonymous> (/home/cnh/megaphone/ecm/Exchange.js:84:10)
    at Socket.emit (events.js:64:17)
    at TCP.onread (net.js:348:51)

I finally resolved to fix this problem only to discover a host of other problems.  This has led me to the inescapable conclusion that ECM really, really stinks.  

This is not, perhaps, so much of a surprise as I threw the thing together with minimal understanding of how nodejs works, but I had hoped that the basics were there.  Unfortunately I was wrong.

The good news is that, while it basically does not work, it is fairly easy to understand.  The next order of business is to basically go back to the drawing board with the thing and try to cobble together something that has a chance of working.

Wednesday, March 21, 2012

End of the Endless Loop

Previously...
  • I modified megaphone to send packets.
  • I noted that data was not getting delivered to ECM.
  • I solved the undelivered data problem and added heartbeats.

It turned out the endless loop on the ECM side was a relatively simple problem.  Here is the method that was receiving the packets:


    me.processDataFromServer = function (data)
    {
console.log("processDataFromServer: " + data);
        //
        // check for a complete packet before adding additional data.  This is not 
        // supposed to happen, but check anyway
        //
        if (me.packet && me.packet.complete)
        {
            me.processPacket();
        }

        if (null == me.packet)
        {
            me.packet = new Packet(data);
        }
        else
        {
            me.packet.add(data);
        }

        while (me.packet && me.packet.complete)
        {
            var leftovers = me.packet.leftovers;

            me.processPacket();

            if (leftovers)
            {
                me.packet = new Packet(leftovers);
            }
        }
    };

The problem was that processPacket was supposed to set "me.packet" to null, but in the case of a heartbeat packet, it was not doing this.  Once that was resolved, the endless loop well...ended.  

At this point, it seems like data is being received by ECM, but Pidgin never sends a reply to the reply from ejabberd.

For next time: is ECM really receiving a reply?

Tuesday, March 20, 2012

node and erlang Heartbeats

Previously...
  • I got megaphone to receive packets.
  • I modified megaphone to send packets.
  • I noted that data was not getting delivered to ECM.

Last time, I resolved to create a "heartbeat" style message between megaphone and ECM.  In the course of doing this I noted that, if I tried to connect ECM to megaphone after a crash of ECM, data would not get delivered.  That is, if I start up ejabberd/megaphone and then connect ECM to it, data gets delivered to ECM.  If I try reconnecting ECM to megaphone, things don't work.

The nodejs side of the heartbeat does most of the work.  When ECM first connects to megaphone, it sets up an interval timer.  Each time the interval timer fires, ECM sends another heartbeat.  The systems distinguish heartbeats from regular messages by using connection ID 0 for heartbeat messages.  

Here is the (modified) code for ECM that starts up the heartbeat process:

        me.socket.connect(config.server.port, config.server.host, function () {
            console.log ("now connected to " + config.server.host + ":" + config.server.port);
            setInterval(me.sendHeartBeat, 5000);
        });

This fragment only includes the code for the connect.  The function to send the heartbeat is also pretty simple:

    me.sendHeartBeat = function()
    {
        var hbmsg =
            "0000000041|"
            + "00000000000000000000|"
            + "heartbeat";

        me.socket.write(hbmsg);
    };

The code for receiving a message had to be modified to check for heartbeat messages:

   me.processPacket = function ()
    {
        console.log("packet: " + me.packet);

        if (me.packet && me.packet.complete)
        {
            if (me.packet.connectionId == 0)
            {
                console.log("heartbeat");
            }
            else
            {
                var connectionId = me.packet.connectionId;
                var intCID = parseInt(connectionId, 10);
                var exchange = me.sockets.get(intCID);
console.log("got exchange: " + exchange + " about to send reply");
                exchange.sendReply(me.packet.content);
                me.packet = null;
            }
        }
    }

The megaphone side is simpler than the ECM side.  It just looks for messages with a connection ID of 0 and immediately sends a response for them:


process_request(ConnectionID, Data) ->
    if
        ConnectionID == 0 ->
            Response = "heartbeat",
            ?MODULE:send(ConnectionID, Response);

        true ->
            NewData = parse_packet(Data),
            ResponseData = ejabberd_http_bind:process_request(NewData, {{127,0,0,1}, 1234}),
            Response = response_data_to_response(ResponseData),
            ?MODULE:send(ConnectionID, Response)
    end.

So now everything is peachy.  Except that the ECM side simply prints out "packet: [object]" and "heartbeat" over and over again.  Next time I will try to figure out why this is.

Monday, March 19, 2012

Totally Confused

Previously...
  • I changed the system to stop sending "raw" HTTP.
  • I got megaphone to receive packets.
  • I modified megaphone to send packets.

I thought there was a relatively straight-forward problem with ECM - specifically, it looked liked it was getting the equivalent of a null pointer exception.  So I looked into the problem, fixed what seemed to be wrong and tried again.

This, of course, did not work.

So far as I can tell, megaphone is formatting the data correctly, it's sending the data, it just doesn't seem to be getting to ECM.  At least all the time.

Sometimes, the packet arrives at ECM, I get a message and whatnot, but most of the time ECM just sort of sits there with an expectant look.

It's times like these that make me glad I've been giving shout outs to various countries, because this means I can forget my troubles by going out and drinking the celebrated alcoholic beverage of said country.  I have to admit, however, that I'm partial to wine and beer --- I tend to avoid distilled stuff.

The current problem could make me change my ways in that vodka is looking awful good when I contemplate my troubles.  

One thing I'm thinking of doing to test out the whole connection between ECM and megaphone is to create a "background" process for megaphone that periodically sends data to ECM.  This would take the form of an "I'm alive!" style message that ECM would lovingly receive...and then promptly throw away.  Nevertheless, it would give me a warm fuzzy that something was actually happening in terms of the connection between megaphone and ECM.

Next time: implementing this marvelous plan.

Sunday, March 18, 2012

HTTP Responses

Previously...
  • I turned it on and...it didn't work.
  • I changed the system to stop sending "raw" HTTP.
  • I got megaphone to receive packets.

When we last visited our intrepid developer, I was looking at some sort of error where megaphone was complaining about the response it was getting from ejabberd_http_bind.  The response seems to be of the form:

{ <code>, <list of HTTP headers>, <data>} 

whereas megaphone wants to see something akin to just 

<data>

Judging by how ejabberd talks to pidgin over port 5280, it looks like the response should be something like this:

HTTP/1.1 <code> <description>
<header>: <value>
<header2>: <value2>
...
<header-n>: <value-n>
<blank line>
<data>

Where every line should be terminated with a carriage-return, linefeed combo.

I wrote some code to format the header list as a list that is closer to what is needed:

format_headers(Prefix, []) ->
    Prefix;
format_headers(Prefix, [{HeaderName, HeaderValue} | Rest]) ->
    NewPrefix = Prefix ++ io_lib:format("~s: ~s\r\n", [HeaderName, HeaderValue]),
    format_headers(NewPrefix, Rest).

And then something to format the response into a string:

response_data_to_response ({ResponseCode, Headers, Data}) ->
    HeaderList = format_headers([], Headers),
    HeaderStr = lists:flatten(HeaderList),
    ResponseCodeStr = response_code_to_string(ResponseCode),
    ResultList = io_lib:format("HTTP/1.1 ~w ~s\r\n~s\r\n~s", [ResponseCode, ResponseCodeStr, HeaderStr, Data]),
    lists:flatten(ResultList).

A very important note: you must use "lists:flatten" on the output of io_lib:format, otherwise you will get a listinstead of a string.  

At this point, I am bailing on the return code: I just print "OK" rather than interpreting the code correctly and printing out the correct response code.  Hence the definition for response_code_to_string:

response_code_to_string(_Code) ->
    "OK".

This appears to have a vague chance of working, except that, when the response is being sent back, ECM chooses that point to crap out:

/home/cnh/megaphone/ecm/ECM.js:149
var exchange = me.exchanges.get(intCID);
                               ^
TypeError: Cannot call method 'get' of undefined
    at [object Object].processPacket (/home/cnh/megaphone/ecm/ECM.js:149:32)
    at Socket.<anonymous> (/home/cnh/megaphone/ecm/ECM.js:187:7)
    at Socket.emit (events.js:67:17)
    at TCP.onread (net.js:327:14)

For next time: what is going on with ECM?

P.S. Another shout out to Russia, since that fine country's people have seen fit to actually look at this blog :-)

Thursday, March 15, 2012

Any Progress is Good Progress

Previously...
  • I wrote some new code to process requests.
  • I turned it on and...it didn't work.
  • I changed the system to stop sending "raw" HTTP.

I looked around at the way a connection using Pidgin to port 5280 worked and noticed that the IP address I was using was probably not in the expected format.  Whereas ejabberd was expecting something like this:

    {{127,0,0,1}, 1234} 

I was giving it something like this:

    <<127,0,0,1>>

I changed it over and ejabberd seemed much happier.  At least until it hit the next problem.

Now it is complaining about the following:

=ERROR REPORT==== 2012-03-15 20:21:23 ===
Error in process <0.379.0> on node 'ejabberd@localhost' with exit value: {badarg,[{erlang,length,[{200,[{"Content-Type","text/xml; charset=utf-8"},{"Access-Control-Allow-Origin","*"},{"Access-Control-Allow-Headers","Content-Type"}],<<763 bytes>>}]},{megaphone_sender,send_packet,3},{megaphone_sender,main_loop... 

Which looks like megaphone is gagging on what ejabberd is returning --- raw HTTP instead of just an XML body.  The good news is that means that I don't have to worry about how to get a complete HTTP response from what ejabberd gives me.  All I have to do is just stream what I get back to ECM and let that in turn stream it back to the client.

So all in all, a good day working at the code.

Next time: modifying megaphone to pass back raw HTTP to ECM.

Wednesday, March 14, 2012

Consuming Headers with erlang:decode_packet

Previously...
  • I came up with a new plan.
  • I wrote some new code to process requests.
  • I turned it on and...it didn't work.

Last time I decided that the problem was that I was sending "raw" HTTP traffic to ejabberd_http_bind whereas it was expecting just the body of the request.  I therefore modified a small portion of the code to the following:

parse_packet(Data) ->
    {ok, Request, Remainder} = erlang:decode_packet(http, Data, []),
    consume_headers([Request], Remainder).

consume_headers(Headers, Data) ->
    case erlang:decode_packet(httph, Data, []) of
        { ok, http_eoh, Rest } ->
            Rest ;
        { ok, Header, Rest } ->
            consume_headers(Headers ++ [Header], Rest)
    end.

I actually got a little farther with this --- I didn't even get an error message --- instead the following was in the log:

=INFO REPORT==== 2012-03-14 20:21:28 ===
D(<0.390.0>:ejabberd_http_bind:1128) : --- incoming data --- 
<body content='text/xml; charset=utf-8' secure='true' to='ubuntu2' xml:lang='en' xmpp:version='1.0' ver='1.6' xmlns:xmpp='urn:xmpp:xbosh' rid='566658449718079' wait='60' hold='1' xmlns='http://jabber.org/protocol/httpbind'/>
 --- END --- 

=INFO REPORT==== 2012-03-14 20:21:28 ===
D(<0.390.0>:ejabberd_http_bind:124) : Starting session

=INFO REPORT==== 2012-03-14 20:21:28 ===
D(<0.391.0>:ejabberd_http_bind:310) : started: {"4d606f92e76e5b152eba41dbdb2caa35091eef1c",
                                                [],
                                                <<127,0,0,1>>}

=INFO REPORT==== 2012-03-14 20:21:33 ===
D(<0.391.0>:ejabberd_http_bind:547) : terminate: Deleting session 4d606f92e76e5b152eba41dbdb2caa35091eef1c

The system starts up the session just fine.  The only problem is that it deletes the session immediately.  Well, one thing at a time.

Next time: the next thing.

Tuesday, March 13, 2012

And It Didn't Work

Previously...
  • I decided on using ejabberd_http_bind:process_request.
  • I came up with a new plan.
  • I wrote some new code to process requests.

I wired it up, flipped the switch and...it didn't work.  Not exactly surprising, especially when you consider that I'm wading through my taxes right now.

The error that I got included the following:


=ERROR REPORT==== 2012-03-13 19:10:37 ===
Error in process <0.378.0> on node 'ejabberd@localhost' with exit value: {badarg,[{erlang,length,[{400,[{"Content-Type","text/xml; charset=utf-8"},{"Access-Control-Allow-Origin","*"},{"Access-Control-Allow-Headers","Content-Type"}],[]}]},{megaphone_sender,send_packet,3},{megaphone_sender,main_loop...

To a lesser mortal this might seem like gibberish.  To me it seems like gibberish.  It is gibberish.  But it also means that the receiver is passing on HTTP headers along with the rest of the packet to megaphone.  

That means that megaphone_receiver needs to discard the headers and pass along just the body of the request.  I find this ironic because I was previously trying to ensure that megaphone was passing the raw HTTP request to ejabberd_http_bind, but I digress.

Next time: removing the headers.

Monday, March 12, 2012

Processing Requests

Previously...
  • I analyzed ways of calling ejabberd_http_bind.
  • I decided on using ejabberd_http_bind:process_request.
  • I came up with a new plan.

I wrote a very small bit of code that takes the data from ECM and passes it on to ejabberd_http_bind:process_request:

put_data(State, ConnectionID, Data) ->
    spawn(?MODULE, process_request, [ConnectionID, Data]).

process_request(ConnectionID, Data) ->
    Response = ejabberd_http_bind:process_request(Data, <<127.0.0.1>>),
    ?MODULE:send(ConnectionID, Response).

One concern that I have with this is that the response may only include the XML and not the HTTP wrapping for the response.  If that is the case, then I will have to write some new stuff to create a response for the data.

One side effect of the new approach is that the whole waiter scheme is no longer needed.  Originally, I thought that some process would read the data and megaphone would block the requesting process until data was actually available.  With this scheme, ejabberd never gets called until data is available, so the whole waiter infrastructure is unnecessary.

Next time: turn it on and see if it works.

Sunday, March 11, 2012

Anything is Better than Nothing

Previously...
  • I discovered a correction for a previous correction.
  • I analyzed ways of calling ejabberd_http_bind.
  • I decided on using ejabberd_http_bind:process_request.

I have been stymied by the following question: how does data get back to the user through megaphone?

After trying to figure things out by using the precognitive power of beer I came to the conclusion that beer is very bad at helping me figure out code.  I then went on to try and use my old postings from this blog to guide my decision.  This worked a little better then beer, but when combined with perusing the source code I finally hit upon an answer: I was tired of sitting around and looking at code.

Well, it's not quite that bad, but I did decide that process_request probably only returns when it has something to "say" to the user.  This is the same approach that BOSH uses with HTTP POSTs: the user makes a POST to the BOSH URL and the server responds when it has something.

My new plan is therefore:
  • When megaphone gets a request from ECM, it starts up a new process.
  • The new process calls ejabberd_http_bind:process_request with the data.
  • When ejabberd responds to the call (via a return), it sends the data onto ECM.
  • The new process terminates.
  • Hope that this works.
While I'm not sure this will actually work, I have come to the conclusion that any action is better than no action.  Fools rush in and all that...sigh.

Next time: (hopefully) first bits of code to call process_request.


Friday, March 9, 2012

The Paralysis of Analysis

Previously...
  • I came to a conclusion regarding how to watch for new connections.
  • I discovered a correction for a previous correction.
  • I analyzed ways of calling ejabberd_http_bind.

Looking at mod_http_bind:process, I have to wonder if things are as bad as they look.  The answer is: if you have to ask the answer is probably yes.  

process([], #request{method = 'POST',
                     data = []}) ->
    ?DEBUG("Bad Request: no data", []),
    {400, ?HEADER, {xmlelement, "h1", [],
                    [{xmlcdata, "400 Bad Request"}]}};
process([], #request{method = 'POST',
                     data = Data,
                     ip = IP}) ->
    ?DEBUG("Incoming data: ~s", [Data]),
    ejabberd_http_bind:process_request(Data, IP);
process([], #request{method = 'GET',
                     data = []}) ->
    {200, ?HEADER, get_human_html_xmlel()};
process([], #request{method = 'OPTIONS',
                     data = []}) ->
    {200, ?OPTIONS_HEADER, []};
process(_Path, _Request) ->
    ?DEBUG("Bad Request: ~p", [_Request]),
    {400, ?HEADER, {xmlelement, "h1", [],
                    [{xmlcdata, "400 Bad Request"}]}}.

If I want to use this approach to starting up http_bind, I will need to create a request object.  The good news is that it doesn't appear that the function actually looks at anything other than the method, data, and ip fields.  This is based on the lines

process([], #request{method = 'POST',
                     data = Data,
                     ip = IP}) ->
    ?DEBUG("Incoming data: ~s", [Data]),
    ejabberd_http_bind:process_request(Data, IP);

The bad news is that the data is expected to be a list a la the results of erlang:decode_packet.

One thing that this option makes plain to me is that, if I'm going to have to parse the data into HTTP headers anyways, why not call ejabberd_http_bind:process_request directly, instead of calling it through mod_http_bind?  It ends up going to the same place, and if I call it directly, then I don't have to deal with creating a request object.

hmm....

Next time: starting on the code to parse out the data.