Tuesday, June 8, 2010

Fab.js and String Objects

‹prev | My Chain | next›

While messing about with my (fab) game today, I notice that the chat system is completely broken. Sigh.

By completely broken, when I POST a chat message, I get an "empty" response from the fab.js backend:
cstrom@whitefall:~/repos/my_fab_game$ curl http://localhost:4011/chat -i -d '{"id":"foo", "say":"hi"}'
curl: (52) Empty reply from server
Looking at the backend, I find that it has crashed completely:
...
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
/home/cstrom/repos/my_fab_game/game.js:34
broadcast(comet_player_say(obj.body.substr(0,100)));
^
TypeError: Object {"id":"foo", "say":"hi"} has no method 'substr'
at listener (/home/cstrom/repos/my_fab_game/game.js:34:49)
at IncomingMessage.<anonymous> (/home/cstrom/.node_libraries/fab/apps/fab.nodejs.js:29:36)
at IncomingMessage.emit (events:25:26)
at HTTPParser.onBody (http:98:23)
at Stream.ondata (http:745:22)
at IOWatcher.callback (net:373:31)
at node.js:204:9
Like I said...

Sigh.

I added the substr to limit the chat message to 100 characters, but didn't notice that that the body of the message was a JSON string rather than the chat message itself. Since it is a JSON string, I need to parse the JSON into a Javascript object, limit the chat message in that object to 100 characters, then re-stringify it to be broadcast to the game:
  ( /chat/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
var msg = JSON.parse(obj.body);
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));

}
return listener;
};
} )
Sadly, when I restart and POST to the chat resource, I still get an empty resource:
cstrom@whitefall:~/repos/my_fab_game$ curl http://localhost:4011/chat -i -d '{"id":"foo", "say":"hi"}'
curl: (52) Empty reply from server
The backend is still crashing, this time with a different message:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
/home/cstrom/repos/my_fab_game/game.js:34
var msg = JSON.parse(obj.body);
^
Hunh? I am not even sure what that means there is not even an error message. Let's have a look at the obj.body just before it crashes:
//...
else if ( obj.body ) {
for (var prop in obj.body) {
puts(prop + ": " + obj.body[prop]);
}
var msg = JSON.parse(obj.body);
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
}
//...
After I crash the server, I find this in the output:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
0: 123
1: 34
2: 105
3: 100
4: 34
...
22: 34
23: 125
length: 24
binarySlice: function binarySlice() { [native code] }
asciiSlice: function asciiSlice() { [native code] }
...
toString: function (encoding, start, stop) {
encoding = (encoding || 'utf8').toLowerCase();
...
Oh man! That is a Javascript String object, not a string literal. Happily, there is a toString() method in that list of properties. I can use that to resolve the problem:
  ( /chat/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
var msg = JSON.parse(obj.body.toString());
msg.body = msg.say.substr(0,100);
broadcast(comet_player_say(JSON.stringify(msg)));
}
return listener;
};
} )
Indeed that does resolve the problem. I now have my chat system working again.

That Javascript String object has me a bit worried, though. I know that the fab.js source uses typeof obj == "string" checks in places. Those will fail for String objects (typeof would return "object").

To test this out, I convert the fab app upstream of the chat resource into a binary app. Then, I can delegate the responsibility of converting the supplied JSON string into a JSON object to the built-in fab.parse app:
  ( /chat/ )
(
function(app) {
return function() {
var out = this;
return app.call( function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
var msg = {
id: obj.body.id,
say: obj.body.say.substr(0,100)
};
broadcast(comet_player_say(JSON.stringify(msg)));
}
return listener;
});
};
} )
(fab.parse)
(fab.echo)
When I access the chat resource now, I get an error similar to the unary version of the chat fab app:
cstrom@whitefall:~/repos/my_fab_game$ ./game.js
/home/cstrom/repos/my_fab_game/game.js:38
say: obj.body.say.substr(0,100)
^
TypeError: Cannot call method 'substr' of undefined
at listener (/home/cstrom/repos/my_fab_game/game.js:38:35)
at listener (/home/cstrom/.node_libraries/fab/apps/fab.map.js:11:19)
at IncomingMessage.<anonymous> (/home/cstrom/.node_libraries/fab/apps/fab.nodejs.js:29:36)
at IncomingMessage.emit (events:25:26)
at HTTPParser.onBody (http:98:23)
at Stream.ondata (http:745:22)
at IOWatcher.callback (net:373:31)
at node.js:204:9
If I alter the fab.parse.js app from:
exports.app = fab.map( function( obj ) {
var body = obj.body;
if ( typeof body == "string" ) obj.body = JSON.parse( body );
return obj;
})
To make use of toString() (which works on string literals and String objects):
exports.app = fab.map( function( obj ) {
var body = obj.body;
if ( body && body.toString ) obj.body = JSON.parse( body.toString() );
return obj;
})
Then my chat server works again.

I suspect that recent node.js changes have started sending String objects and string literals interchangeably. I will investigate a bit more tomorrow then address the issue in the remaining built-in fab apps.

Day #128

No comments:

Post a Comment