Thursday, March 15, 2012

CRUD Over Hispter MVC WebSockets

‹prev | My Chain | next›

There are two things that I still do not have working in my Dart WebSocket implementation: sub-protocols and a scheme for different CRUD operations. The first is (hopefully) just an implementation detail of websockets. The latter is more a matter of me choosing something.

First up sub-protocols, which is an optional means to allow the server to specialize how to handle certain kinds of traffic. In the backend, I might specify the "comics-protocol" to handle changes to my comic book data store:
wsServer.on('request', function(request) {
  var connection = request.accept("comics-protocol", request.origin);
  console.log((new Date()) + ' Connection accepted.');

  connection.on('message', function(message) { /* ... */ }
}
(this is still using WebSocket-Node in my node.js backend)

Back in Dart I try variations of:
WebSocket ws;

main() {
  HipsterSync.sync = wsSync;
  ws = new WebSocket("ws://localhost:3000/", protocols: ["comics-protocol"]);
  // ...
}
But I only end up with variations of:
Internal error: 'http://localhost:3000/scripts/main.dart': Error: line 14 pos 21: invalid arguments passed to constructor 'WebSocket' for interface 'WebSocket'
  ws = new WebSocket("ws://localhost:3000/", protocols: ["comics-protocol"]);
Digging through the source code a bit, it really seems that the Dart constructor only accepts a single argument. In other words, this seems like a TODO for Dart. No matter, I can live with that in my one-protocol app and can always make a notation that this is not baked when I discuss it in Dart for Hipsters.

After reverting, I change my attention to operations other than "read". In the client, I update my websocket-based Hipster MVC sync layer to send specialized messages depending on the CRUD method being used:
wsSync(method, model) {
  final completer = new Completer();

  String message = "$method: ${model.url}";
  if (method == 'delete') message = "$method: ${model.id}";
  if (method == 'create') message = "$method: ${JSON.stringify(model.attributes)}";

  print("sending: $message");
  ws.send(message);
  // ...
}
On the back end, I then parse these messages with simple regular expressions:
connection.on('message', function(message) {
    if (message.type === 'utf8') {
      console.log('Received Message: ' + message.utf8Data);

      if (/^read/.test(message.utf8Data)) {
        connection.sendUTF(jsonComics());
      }
      else if (/^create: (.+)/.test(message.utf8Data)) {
        var comic = createComic(JSON.parse(RegExp.$1));
        connection.sendUTF(comic);
      }
    }
  });
And amazingly, that just works. When I create a new comic book, the JSON representation of that new object comes back over the websocket, which is already sending the message to the collection via a Future:
wsSync(method, model) {
  final completer = new Completer();

  String message = "$method: ${model.url}";
  if (method == 'delete') message = "$method: ${model.id}";
  if (method == 'create') message = "$method: ${JSON.stringify(model.attributes)}";

  print("sending: $message");
  ws.send(message);

  // Handle messages from the server, completing the completer
  ws.
    on.
    message.
    add((event) {
      print("The data in the event is: " + event.data);

      completer.complete(JSON.parse(event.data));
    });

  return completer.future;
}
Hitting one out of two goals ain't bad for tonight. I have never used websocket sub-protocols directly, so I have no real idea how important they are. They do seem like a nice-to-have. Regardless, it is good to see my Hipster MVC library holding up so far.

Day #326

No comments:

Post a Comment