Friday, April 23, 2010

Fab.js: Simple Map App

‹prev | My Chain | next›

My master plan for furthering my understanding of fab.js tonight centers around getting a functional fab.map in place somewhere.

I try various things that may or may not have an resemblance to how fab.map expects to be used, but nothing works. I always end up with this from curl:
cstrom@whitefall:~/repos/fab$ curl localhost:4011/all_dbs
curl: (52) Empty reply from server
...and something like this from the fab.js server:
cstrom@whitefall:~/repos/fab$ node ./play/all_dbs.js
Error: process.mixin() has been removed.
at EventEmitter.mixin (node.js:11:11)
at listener (/home/cstrom/repos/fab/apps/fab.nodejs.js:39:34)
at listener (/home/cstrom/repos/fab/apps/fab.map.js:9:19)
at IncomingMessage.<anonymous> (/home/cstrom/repos/fab/apps/fab.nodejs.http.js:29:15)
at HTTPParser.onBody (http:95:23)
at Client.ondata (http:601:30)
at IOWatcher.callback (net:307:31)
at node.js:748:9
Backing up a bit, I try some other fab.js apps (contentLength/stringify), but they have similar problems. What have I done here? My script is extremely simple—it contains a call to my CouchDB server and one of the hello-world examples bundled with fab.js:
fab = require( "../" );

require( "http" ).createServer( fab

( fab.nodejs )

// These don't work either
// ( fab.contentLenth )
// ( fab.stringify )

( /^\/all_dbs/ )

// I try various maps here

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

( /^\/hello/ )

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

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

( 404 )

).listen( 0xFAB );
At least the hello world still works right? Without a pathname argument and with:
cstrom@whitefall:~/repos/fab$ curl localhost:4011/hello
Hello, world!
cstrom@whitefall:~/repos/fab$ curl localhost:4011/hello/foo
curl: (52) Empty reply from server
Gah! What on earth have I done?!

OK. Let's take a step back and try the example code itself:
cstrom@whitefall:~/repos/fab$ node ./examples/index.js 
...and access it with curl (it is the seventh hello-world example included in the fab.js source):
cstrom@whitefall:~/repos/fab$ curl -i localhost:4011/hello/7/foo
curl: (52) Empty reply from server
Aha! It wasn't me. This is what I get for playing around with something that is undergoing major refactoring. No worries, it would not be fun if the code base were stagnant. Speaking of which, I have been doing all of this without pulling the most recent changes (idiot!).

After stashing my changes from last night, I pull, then retry the included example:
cstrom@whitefall:~/repos/fab$ curl -i localhost:4011/hello/7/foo
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked

Hello, foo!
Nice! I also find that the example in my play code is working. Sadly, the fab.nodejs.http call still fails on the third try:
cstrom@whitefall:~/repos/fab$ curl localhost:4011/all_dbs
["eee","test","seed"]
cstrom@whitefall:~/repos/fab$ curl localhost:4011/all_dbs
["eee","test","seed"]
cstrom@whitefall:~/repos/fab$ curl localhost:4011/all_dbs
curl: (52) Empty reply from server
cstrom@whitefall:~/repos/fab$ curl localhost:4011/all_dbs
curl: (7) couldn't connect to host
No matter, let's see about fab.map...

The simplest map of which I can think is one that appends text to the body of an upstream app (downstream is toward the requesting client). From the fab.js documentation, fab.map converts an ordinary function into a binary app. Binary, in fab.js terms means it sits between downstream and upstream apps. To do so, it needs access to the upstream app, which fab.js will supply.

This is the simple append text map that I come up with:
fab = require( "../" );

require( "http" ).createServer( fab

( fab.nodejs )

( fab.map( function (app) {
app.body += " FOO!!!";
return app;
}))



( /^\/all_dbs/ )

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

( /^\/hello/ )

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

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

( 404 )

).listen( 0xFAB );
The fab.map app supplies my function with a copy of the upstream app. I use that to append to the body of the upstream response and...
strom@whitefall:~/repos/fab$ curl localhost:4011/hello/foo -i
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked

Hello, foo! FOO!!!
Yay! I actually mapped the upstream app into something else. I may be just beginning to understand this stuff.

Day #82

2 comments:

  1. Nice! The structure for testing apps is now in place, and I'm going to start documenting it Sunday.

    A few things: you should use 'obj' instead of 'app' as the name of the argument in your map function, because what you're getting is a partial HTTP response. Also, you'll need to check if 'obj.body' exists, because the upstream app may pass a response that has no body (only a status, or headers, for example).

    In other news, I added a fab.nodejs.listen app that can replace the standard node createServer boilerplate if you'd like. Combined with a safe use of 'with', you can make pretty terse definitions:

    http://github.com/jed/fab/blob/master/README.md

    Jed

    ReplyDelete
  2. Oooh... shiny :)

    I had noticed the with-fab stuff in some of the latest code. Very clean looking indeed :)

    I think I based the map on contentLength which has the obj.body check. I wondered what that was about, but ignored it for tonight's exercise--I knew I had a body and just wanted a proof of concept. Good to know what's up with that.

    Thanks again for all the help (and the awesome framework)!

    -Chris

    ReplyDelete