Saturday, November 10, 2012

JSON and Stream in Dart

‹prev | My Chain | next›

Yesterday, I was able to code a Dart-based web server with surprising ease. Currently, it only replies with "Hello" to requests to the root URL and 404s for everything else, but it's a start. Today, I would like to serve up files from the filesystem and serve up JSON from alternate resource locations.

I think that I must have played with JSON in Dart at some point. If I am going to replace the node.js backend in my Dart Comics sample app, I need to refresh my memory how to do JSON in Dart. Without looking at the documentation, I try the following:
  app.addRequestHandler(
    (req) => req.method == 'GET' && req.path == '/json',
    (req, res) {
      var data = {
        'title': 'Watchmen',
        'author': 'Alan Moore'
      };

      res.outputStream.writeString(data.toJSON);
      res.outputStream.close();
    }
  );
I build a simple HashMap and try to call toJSON on it. Of course, I soon find out that there is no toJSON method/getter. And shortly after that, I rediscover the dart:json library:
#import('dart:io');
#import('dart:json');

main() {
  HttpServer app = new HttpServer();
  // ...

  app.addRequestHandler(
    (req) => req.method == 'GET' && req.path == '/json',
    (req, res) {
      var data = {
        'title': 'Watchmen',
        'author': 'Alan Moore'
      };

      res.outputStream.writeString(JSON.stringify(data));
      res.outputStream.close();
    }
  );

  app.listen('127.0.0.1', 8000);
}
That does the trick as I am now able to receive a JSON response:
➜  scripts git:(M1) ✗ curl -i http://localhost:8000/json
HTTP/1.1 200 OK
transfer-encoding: chunked

{"title":"Watchmen","author":"Alan Moore"}% 
Easy enough. I am a bit surprised that I forgot the dart:json library entirely like that. I will chalk it up to advancing age. Happily once remembered, I did not have to look up the stringify method—of course that is what it is named.

Before moving on, I would like to set a couple of header values. These I do look up, though perhaps I could have guessed them as well:
  app.addRequestHandler(
    (req) => req.method == 'GET' && req.path == '/json',
    (req, res) {
      var data = {
        'title': 'Watchmen',
        'author': 'Alan Moore'
      };

      res.contentLength = JSON.stringify(data).length;
      res.headers.contentType = 'application/json';
      res.outputStream.writeString(JSON.stringify(data));
      res.outputStream.close();
    }
  );
With that, I have my JSON resource responding as desired:
➜  scripts git:(M1) ✗ curl -i http://localhost:8000/json
HTTP/1.1 200 OK
content-length: 42
content-type: application/json

{"title":"Watchmen","author":"Alan Moore"}%
Now let's see how hard it is to read a file from the file system to be served up to the client. The express.js / node.js backend that I currently use serves up the file in public/index.html. In express.js, this just requires a configuration setting to describe the location of the “public” directory.

Were I coding it by hand in node.js, I would pipe a file reader stream to the response's output stream. Streams as a first-order concept in node is one of the many things that makes node so appealing. I wonder if I can to the same with Dart.

Indeed, there is a pipe() method on an input stream. That seems promising. So I change the responder for the root URL to be:
  app.addRequestHandler(
    (req) => req.method == 'GET' && req.path == '/',
    (req, res) {
      var file = new File.fromPath('public/index.html');
      var stream = file.openInputStream();
      stream.pipe(res.outputStream);
      res.outputStream.close();
    }
  );
I am not quite sure if that last close() is needed anymore. I suspect not, but there is one way to find out for certain. Only when I start the server and access the URL, I get a server crash.

D'oh! It seems the fromPath named constructor needs a Path object. What I really wanted there was the regular constructor:
      // ...
      var file = new File('public/index.html');
      // ...
That gets me further, but a request to the root URL now crashes the server on that close() line that worried me. So I remove it, leaving the root URL responder as:
  app.addRequestHandler(
    (req) => req.method == 'GET' && req.path == '/',
    (req, res) {
      var file = new File('public/index.html');
      var stream = file.openInputStream();
      stream.pipe(res.outputStream);
    }
  );
With that, a request to the root URL results in the homepage:
➜  scripts git:(M1) ✗ curl -i http://localhost:8000/
HTTP/1.1 200 OK
transfer-encoding: chunked

<!DOCTYPE html>
<html>
<head>
  <title>Dart Comics</title>
  <link rel="stylesheet" href="/stylesheets/style.css">

  <script src="/scripts/dart.js"></script>
  <script src="/scripts/web/main.dart" type="application/dart"></script>
</head>

<body>
  <h1>Dart Comics</h1>
  <p>Welcome to Dart Comics</p>

  <ul id="comics-list"></ul>

  <p id="add-comic">Add a sweet comic to the collection.</p>
</body>
</html>
That… is nice. I am excited to see stream support like that in the Dart IO library. I would not expect that that it is as extensive as node.js just yet, but it is promising to see it work so well in a simple case like this.

I call it night here. Up tomorrow, I will attempt to hack in my own public directory handler.


Day #566

No comments:

Post a Comment