Wednesday, April 21, 2010

Fab.js: Getting Started

‹prev | My Chain | next›

There seemed to be some fascination with fab.js emanating from JSConf last weekend. So before moving on, I think I'll give it a try.

First up, I clone the repository:
cstrom@whitefall:~/repos$ git clone git://github.com/jed/fab.git
Initialized empty Git repository in /home/cstrom/repos/fab/.git/
remote: Counting objects: 639, done.
remote: Compressing objects: 100% (448/448), done.
remote: Total 639 (delta 237), reused 304 (delta 122)
Receiving objects: 100% (639/639), 123.69 KiB | 143 KiB/s, done.
Resolving deltas: 100% (237/237), done.
Next, hmmm... I don't honestly know what is next. Sadly, I did not attend JSConf, so I do not actually know how to install/compile/run it. The README and examples only give sample code, not instructions for running them.

After flailing for a bit, I am able to get the example running... with node.js:
cstrom@whitefall:~/repos/fab$ node ./examples/index.js
So far, that is pretty unexciting.

To actually do anything with fab.js, I need to interact with it on port 4011. The example/index.js file pulls in several other example including a series of seven different "hello worlds". I think I'll start there. All seven are different ways of sending the text "Hello, world" to a web client. Number 4 seems intersting:
    ( /4/ ) // asynchronous
( function() {
var
out = this,
res = "Hello, world!".split(""),
interval = setInterval( function() {
out = out({ body: res.shift() });

if ( !res.length ) {
if ( out ) out();
clearInterval( interval );
}
}, 500 );
})
This splits the "Hello, world!" text into individual characters and then sends them out one every half second. Browsers and curl buffer the output until the fab.js server completes. To see this work, I run curl with no buffering and hit enter a couple of times:
cstrom@whitefall:~/repos/fab$ curl -N localhost:4011/hello/4
Hello,
w
o
r
ld
!
Cool.

To actually do something real with fab.js, I will try to hook it up to my favorite HTTP noSQL DB (CouchDB). For tonight, I would be satisfied to pull back the list of all DBs on my local server.

Since I am learning, I start with a copy of the README's example script (adding a call to fab.nodejs which seems to be required):
fab = require( "../" );

require( "http" ).createServer( fab

( fab.nodejs )

( /^\/hello/ )

( fab.tmpl, "Hello, <%= this[ 0 ] %>!" )

( /^\/(\w+)$/ )
( fab.capture )
( [ "world" ] )

( 404 )

).listen( 0xFAB );
I start the script, then access the /hello resource to find:
cstrom@whitefall:~/repos/fab$ curl localhost:4011/hello
Hello, world!
So far so good. Now I'd like to pull the output of http://localhost:5984/_all_dbs if a request goes into the /all_dbs resource. The fab.nodejs.http method looks like the ticket:
fab = require( "../" );

require( "http" ).createServer( fab

( fab.nodejs )

( /^\/all_dbs/ )

( fab.nodejs.http("http://localhost:5984/_all_dbs") )


( /^\/hello/ )

( fab.tmpl, "Hello, <%= this[ 0 ] %>!" )

( /^\/(\w+)$/ )
( fab.capture )
( [ "world" ] )

( 404 )

).listen( 0xFAB );
Unfortunately, when I access the /all_dbs resource, I get:
cstrom@whitefall:~/repos/fab$ node ./play/all_dbs.js
Warning: ClientRequest.prototype.close has been renamed to end()
ReferenceError: back is not defined
at ClientRequest.<anonymous> (/home/cstrom/repos/fab/index.js:316:32)
at HTTPParser.onIncoming (http:650:20)
at HTTPParser.onHeadersComplete (http:84:14)
at Client.ondata (http:601:30)
at IOWatcher.callback (net:307:31)
at node.js:748:9
Ew.

I am using an up-to-date node.js:
cstrom@whitefall:~/repos/fab$ node --version
0.1.91
Not knowing what else to do, I remove the back() listener from the index.js file of fab.js:
          .addListener( "response", function( response ) {
back({
status: response.statusCode,
headers: response.headers
});


response
.addListener( "data", function( chunk ) {
out({ body: chunk });
})
.addListener( "end", out )
.setBodyEncoding( "utf8" );
})
.close();
With that gone, after a restart, I obtain the expected results from the /all_dbs resource:
cstrom@whitefall:~/repos/fab$ curl localhost:4011/all_dbs
["eee","test","seed"]
Nice. There is certainly a lot going on here that I do not understand and I know that I am only scratching the surface of fab.js. Still, I feel like I am off to a reasonable start.

Day #80

4 comments:

  1. You really need to read through Jed's jsconf presentation slides here: http://www.flickr.com/photos/tr4nslator/sets/72157623883700702/

    I'm just blown away by what he has come up with.

    ReplyDelete
  2. Awesome!

    Man that really would have helped last night, but something to read today before I try again :)

    ReplyDelete
  3. Hey Chris,

    It looks like fab.nodejs.http had a misnamed downstream app name. 'back' was supposed to be 'out'. I fixed this in HEAD, so feel free to pull.

    The API for fab.nodejs.http will probably change (to more match fab.nodejs.fs), from unary to binary, where all the information about the HTTP call to make is provided by the upstream app.

    Let me know if you have any questions, okay?

    ReplyDelete
  4. Jed,

    Great thanks! I had the feeling that there was something wrong with the 'back', but I'm still pretty raw with node.js, let alone fab.js.

    Good to know about the fab.nodejs.http change. I still need to get a grasp on upstream/downstream and unary/binary/ternary, so I doubt I'll be basing anything significant on it just yet. Still, you never know :)

    Thanks for the very interesting framework and for taking time to lend a few pointers!

    -Chris

    ReplyDelete