jetty: Shit happens!!
April 9th, 2011 — 01:11 pmDon’t set Jetty’s <stopPort> to the same <port> where Jetty is running or you might run into some nasty-hard-to-debug problem.
Don’t set Jetty’s <stopPort> to the same <port> where Jetty is running or you might run into some nasty-hard-to-debug problem.
I’ve been playing with mochiweb for a while and I found it a nice way to improve my Erlang knowledge. For making things more interesting I want to use mochiweb to implement a BOSH manager. If you are not tempted to read the spec documentation about BOSH I can give you this excerpt:
BOSH, the technology defined in this specification, essentially provides a “drop-in” alternative to a long-lived, bidirectional TCP connection …
For applications that require both “push” and “pull” semantics, BOSH is significantly more bandwidth-efficient and responsive than most other bidirectional HTTP-based transport protocols and the techniques now commonly known as “Ajax”…
That is, BOSH is quite similar to WebSockets
So, at this point is where mochiweb comes in. It will provide the framework for handling the HTTP requests where the BOSH requests themselves are wrapped. But before going any further lets explain how mochiweb works, with a picture:
What do we have here?
Easy, huh?
And what is all that about a BOSH manager? The BOSH manager handles the connection between the resource-client (for example a browser) and the resource-owner (for example a server). It has to keep HTTP connections opened to fake this bidirectional comunication. That is, the client ask for something about the resource and so it sends a request to the BOSH manager, the BOSH manager sends this same request to the server and waits for a reply. If a reply arrives, then it’s forwarded to the client. But aside from this, which is no more than plain-old AJAX, BOSH offers PUSH technologies. If any event happens at the server and the client has to be notified, the BOSH manager forwards the event to the client. That’s why it has to keep HTTP connections opened, it must have a valid path.
In Erlang, creating and destroying processes is extremely cheap, so what I wanted was to spawn a new process for each single BOSH session (between one client and one or multiple servers). Lets give another picture:
On the picture above, there are two separate BOSH managers (each of them is a Erlang process) BM1 and BM2 which handler connections from one client to one server (BM1 ) and from another different client to two servers (BM2)
To complete the setup I used, I have to talk about StropheJS which is a library for writing xmpp clients (BOSH is an xmpp extension).
Well, once I have explained more or less what I’m doing with mochiweb and BOSH, lets got to the details.
First, lets talk about my callback module for mochiweb. It’s not a ver big one nor a complicated one, not because I don’t know how to move on but was the error I’ll talk later which didn’t allow me to progress. So ladys and gentleman here is my litle callback module:
-module(mochibosh).
-export([start/0, start/1, stop/0]).
-export([handler/1]).
-export([init/0]).
-record(mochibosh_state,{procs, xml_models}).
handler(Req) ->
io:format("CALLBACK. Petition on port ~p ~n", [inet:port(Req:get(socket))]),
mochibosh ! Req,
ok.
start() ->
erlang:register(mochibosh, spawn_link(?MODULE, init, [])),
start([{ip,"192.168.1.110"}, {port, 8888}]).
start(Options) ->
Options2 = [{loop, {?MODULE, handler}} | Options],
mochiweb_http:start(Options2).
stop() ->
mochibosh ! exit,
mochiweb_http:stop().
% Internal API
init() ->
Models = init_xml_models(),
loop(#mochibosh_state{procs = dict:new(), xml_models = Models}).
loop(State = #mochibosh_state{procs = Procs, xml_models = Models}) ->
receive
Request ->
io:format("SPAWNED PROCESS. Request received on port ~p ~n", [inet:port(Request:get(socket))]),
loop(State)
end.
init_xml_models()->
[{bosh_init, init_xml_model("bosh_init.xsd")}].
init_xml_model(Xsdfile) ->
{ok, Model} = erlsom:compile_xsd_file(Xsdfile),
Model.
It very easy. If want to use it, you cant fire a Erlang terminal and type mochibosh:start() and it will be up and running. The loop
option in the tuple that is passed to mochiweb_http:start is used to provide the callback module. In this case is the mochibosh module itself (?MODULE)
So what would happen if we try it….
In one terminal we fire the Erlang shell (erl) and start mochibosh:
20:38:25 mochibosh $ erl
Erlang R14A (erts-5.8) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.8 (abort with ^G)
1> mochibosh:start().
{ok,<0.34.0>}
In another terminal we issue a simple request with curl…
20:40:19 mochibosh $ curl -v http://192.168.1.110:8888 * About to connect() to 192.168.1.110 port 8888 (#0) * Trying 192.168.1.110... connected * Connected to 192.168.1.110 (192.168.1.110) port 8888 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.21.0 (i386-apple-darwin10.2.0) libcurl/7.21.0 OpenSSL/1.0.0a zlib/1.2.5 libidn/1.19 > Host: 192.168.1.110:8888 > Accept: */* > ^C
And meanwhile in the Erlang shell..
{ok,<0.34.0>}
CALLBACK. Petition on port {ok,8888}
SPAWNED PROCESS. Request received on port {ok,8888}
2>
GREAT!! Everything seems to work. So now, I decided to move on and begin to develop my BOSH manager.
As I sayed, I’ll be using StropheJS for the browser/client side. For testing purpouses I began using one of the examples shipped with the library: echobot.js .This example will issue a request for starting a BOSH session (among other things) and that is perfect for my testing pourposes. So lets, try.
(Imagin that the request is sent from echobot to the BOSH manager)
And I expecto to get the same reply from my callback module as when I tryed it with curl. Lets see:
CALLBACK. Petition on port {ok,8888}
SPAWNED PROCESS. Request received on port {error,einval}
WTF!!!!! What’s that of {error, einval}. Well, I can see that’s an error, but why? As this post is getting really big, I’ll leave the explanation for another one.
Just two things:
Enjoy!!
¿Harto de que en alguna ocasión IRB sea demasiado verboso? Yo si.
Por defecto, IRB siempre “escupirá” el valor resultante de evaluar la expresión introducida por el usuario. Aunque la mayoría de las veces eso está bien, hay otras en las que sacarias el corazón con una cucharilla de Té a quien tuvieras delante por la crispación que produce esta “verbosidad”.
Me refiero a situaciones del tipo “quiero recorrer un array y listar el nombre de sus elementos con puts”, ejemplo:
ArrayEnorme.each do |element| puts "#{element.name}\n" end
Con el código anterior vamos a mostrar el nombre de cada elemento del Array. Hasta aquí todo normal. El problema de evaluar esto en IRB es que, si ArrayEnorme tiene bastantes elementos y cada elemento tiene bastantes atributos, las lineas que imprimos con el nombre de cada elemento quedaran “sepultadas” por un montón de datos inutiles. Es decir, si el buffer de nuestra consola no se ha llenado con estos datos inutiles (perdiendo así las lineas con el nombre de cada elemento) tendremos que hacer scroll ad-infinitum, que para el caso es casí como si se hubierán perdido.
Para evitar esto, os presento mi solución de andar por casa:
def toggle_output context.return_format == "Output canceled\n" ? IRB.conf[:PROMPT][context.prompt_mode][:RETURN] : context.return_format = "Output canceled\n" end alias tgo toggle_output
Después de copiar las lineas anteriores en nuestro .irbrc podemos hacer pruebas:
madtrick::madtrick::$irb irb(main):001:0> 2 + 1 => 3 irb(main):002:0> toggle_output Output canceled irb(main):003:0> 2 + 1 Output canceled irb(main):004:0> toggle_output => "=> %s\n" irb(main):005:0> 2 + 1 => 3
Y voila, ya podemos callar y dar voz a IRB cuando nos plazca.