Before moving on with fab.js, I need to take a break to investigate binary apps. Specifically, I need to ensure that I understand listeners.
I start with my usual fab.js skeleton:
var puts = require( "sys" ).puts;I replace the default 404 response with two binaries and a very simple unary that responds with a body containing only "foo":
with ( require( ".." ) )
( fab )
( listen, 0xFAB )
( 404 );
var puts = require( "sys" ).puts;I start up the fab.js server:
with ( require( ".." ) )
( fab )
( listen, 0xFAB )
// binary app
(
function (app) {
return function () {
return app.call(this);
};
}
)
// binary app
(
function (app) {
return function () {
return app.call(this);
};
}
)
// unary app
(
function () {
this({body: "foo"})();
}
);
cstrom@whitefall:~/repos/fab$ node ./play/binary_binary.jsNow, when I access any resource on my fab.js server, I get this response:
cstrom@whitefall:~/repos/fab$ curl http://localhost:4011/barEasy enough.
foo
But I was accessing the "/bar" resource on my server, so I really want the server to reply with "bar". For that to happen, the (fab) unary app needs to return a listener to the downstream app requesting the headers:
(Now, when I access the same resource, I get an empty response from the server because it crashed:
function () {
var out = this;
return function (head) {
out({body: head.url})();
};
}
);
cstrom@whitefall:~/repos/fab$ node ./play/binary_binary.jsThis turns out to be a problem with my use of
TypeError: Cannot call method 'toString' of undefined
at ServerResponse.write (http:345:31)
at listener (/home/cstrom/repos/fab/apps/fab.nodejs.js:55:20)
at /home/cstrom/repos/fab/play/binary_binary.js:30:22
at Server.<anonymous> (/home/cstrom/repos/fab/apps/fab.nodejs.js:18:17)
at HTTPParser.onIncoming (http:562:10)
at HTTPParser.onHeadersComplete (http:84:14)
at Stream.ondata (http:524:30)
at IOWatcher.callback (net:307:31)
at node.js:748:9
head.url
rather than head.url.pathname
. The backtrace pointed to the correct line (18), but the error was less-than-helpful. Anyhow, my new unary function looks like:(Now when I access the "/bar" resource I get my desired response:
function () {
var out = this;
return function (head) {
out({body: head.url.pathname.substring(1)})();
};
}
);
cstrom@whitefall:~/repos/fab$ curl http://localhost:4011/barNice!
bar
That is especially nice because I did not have to set up anything special in the downstream apps to call that listener—fab.js just took care of it for me. But what if I want to access the response in my binary middleware? For that, I do need a listener:
(Here, I give the upstream listener, the
function (app) {
return function () {
var out = this;
return app.call(
function listener(obj) {
if (obj) arguments[0] = {body: obj.body + '+foo'};
out.apply(this, arguments);
return listener;
}
);
};
}
)
out()
call in my unary app something to call directly in my binary/middleware app. I take the object with the path body and append '+foo' to the body, I then send the response on its merry way back downstream with another apply. Now, if I access the '/bar' resource, I should get a response of 'bar+foo':cstrom@whitefall:~/repos/fab$ curl http://localhost:4011/barPerfect!
bar+foo
So far all of this jibes with my understanding of binary apps in fab.js. So why did I bother with all of this? Well, it is because
fab.nodejs.http
does not seem to behave like a binary app. Specifically, if my downstream app replies with a resource that fab.nodejs.http
should proxy, say a CouchDB resource:( fab.nodejs.http )...then everything is hunky-dory:
(
function () {
var out = this({body:'http://localhost:5984/seed/test'});
if (out) out();
}
)
cstrom@whitefall:~/repos/fab$ curl http://localhost:4011/testThe problem occurs if I try to infer the CouchDB resource from the header information:
{"_id":"test","_rev":"2-4b4f7289fd8b290dfcffe7bdfc23cf8d","change":"bar"}
( fab.nodejs.http )My unary is identical to my previous unary (I'm just pre-pending the CouchDB database URL), so I know that it is a valid (fab) unary app. Sadly however, when I access any resource, it just hangs.
(
function () {
var out = this;
return function (head) {
out = out({body: 'http://localhost:5984/seed' + head.url.pathname});
if (out) out();
};
}
)
Armed with my knowledge of how a binary / middleware app ought to respond to an upstream listener, I root around in
fab.nodejs.http
and find that is does not, in fact, return an upstream listener. It works just fine if the upstream app returns without inspecting headers, but, if it needs to listen for those headers... nothing.So I add a listener:
exports.app = function( app ) {Nothing special there—I am just repeating what I already verified was working in my simplistic binary app. Now, when I access the "test" resource on my fab.js server, the CouchDB "test" document is returned:
var url = require( "url" )
, http = require( "http" );
return function() {
var out = this;
return app.call( function listener( obj ) {
if (obj) {
// proxy the request as before with node.js
}
return listener;
});
}
cstrom@whitefall:~/repos/fab$ curl http://localhost:4011/testMore importantly, when I access the "foo" resource on my fab.js server, the "foo" document is retrieved from CouchDB:
{"_id":"test","_rev":"2-4b4f7289fd8b290dfcffe7bdfc23cf8d","change":"bar"}
cstrom@whitefall:~/repos/fab$ curl http://localhost:4011/fooI will submit a pull request tomorrow. For now, I am thrilled to have finally gotten to the bottom of this pesky little mystery. In retrospect, maybe I should have suspected
{"_id":"foo","_rev":"1-967a00dff5e02add41819138abb3284d"}
fab.nodejs.http
since it is undergoing major refactoring, but my inexperience with fab.js led me astray. As with anything new, breaking the problem down into small, well understood chunks until I could recreate the problem served me well in solving this little problem.Day #88
No comments:
Post a Comment