Tuesday, September 7, 2010

Upstream Apps in Fab.js v.0.5

‹prev | My Chain | next›

I crashed and burned last night trying to write my own v0.5 fab.js app for my (fab) game. Tonight I hope to do a bit better.

I am trying to provide a /player_status resource that will return a plain text representation of all the players currently in a room:
  (route, /^\/player_status/)
( player_status )
()
The players are all stored in CouchDB. To get all of them, I have an all callback method on the players object that can be used something like:
      players.all(function(list) {
list.forEach(function(player) {
console.log(player);
});
});
Although I kinda/sorta got things working last night, I am going to toss that work. I ended up with very weird stacktraces that leads me to believe that I am on the wrong track.

The normal skeleton of a (fab) app looks something like this:
    fab.myApp = function( write ) {
return function read( body ) {
var done;

// app logic here

return done ? write : read;
}
}
I cannot use that in this case because there are no upstream (further from the browser) apps to be read.

After much fiddling, I end up with this:
function player_status(write) {
return write(function(write) {
return fab.stream(function(stream) {
stream(write(undefined, { headers: { "Content-Type": "text/plain"} }));
players.all(function(list) {
list.forEach(function(player) {
stream(write(player._id + "\n"));
});
stream(write("\n"));
stream(write());
});
});
});
Amazingly, this works:
cstrom@whitefall:~$ curl http://localhost:4011/player_status -i
HTTP/1.1 200 OK
Content-Type: text/plain
Connection: keep-alive
Transfer-Encoding: chunked

bob
I must confess that I am a little puzzled why it works. My best guess, the first line of the app body:
  return write(function(write) {
Behaves identically to (fab) middleware with an upstream app:
  (route, /^\/player_status/)
( middleware )
( upstream )
()
My player_status is writing back to the browser, reading from the supplied anonymous function.

The next line, I find bizarre:
    return fab.stream(function(stream) {
The fab.stream app queues up upstream messages to be sent back to the browser. The fab.stream app knows to send the entire queue when the upstream app signals the last of its output with an empty argument list. In this case, the anonymous function supplied as the argument to fab.stream serves as the upstream app (the signal to terminate is the last line:
      function(list) {
list.forEach(function(player) {
stream(write(player._id + "\n"));
});
stream(write("\n"));
stream(write());
});
The reason that I find this bizarre is that fab.stream takes a single argument—the downstream / write stream:
fab.stream = function stream( write, queue ) {
queue = queue || [];

//...

return function read() {
if ( !arguments.length ) return write ? write( drain ) : drain;

queue[ length++ ] = arguments;
return read;
}
}
In my working-through-a-small-miracle code, however, I am not supplying a write stream to the browser. I am supplying the opposite—an upstream app from which data will be read.

Somehow the read() function inside fab.stream, the function that is responsible for writing back downstream to the browser, is being supplied as the argument to the anonymous function that is supplied to fab.stream.

I believe that this is due to if-function-then-apply-with-arguments logic in the main fab app itself. fab.stream returns the read() function, which is then applied with the arguments to fab.stream—my fake downstream app in this case.

Ugh. Not sure about that explanation. I kinda/sorta think I understand, but that is not true understanding.

I think I'll sleep on in at this point and pick back up with more investigation tomorrow.

Day #219

1 comment:

  1. it may help to realize there's no such thing as a single upstream app anymore... the entire stream of calls after the current one constitute everything upstream.

    think of fab.stream as a pause button that takes everything upstream and returns a function that replays it. the main use of this when you have async/non-blocking code that you need to handle before continuing.

    if you can distill this into a gist i'd like to help you troubleshoot, just lemme know, okay?

    ReplyDelete