Read Assignment

Video

Overview

The purpose of this assignment is to learn how to read documents from a CouchDB database from inside a Node.js program. This will also give you experience accessing remote services using HTTP. Such services are sometimes referred to as Web services and are very common in software systems these days.

This assignment builds on the ajax and couch assignments. In the ajax assignment, we created a Web page that uses ajax to request a message string from the server, which it displays to the user. However, the message string was hard-coded into the program. In this assignment, we will use what we learned in the couch assignment to retrieve the message string from a CouchDB database.

Assignment Folder

Create a directory named read for the work you do for this assignment. At the end of the assignment, this folder will contain the following files.

Reading

Instructions

Step One: Copy Existing Code

Copy your app server code from the cache assignment to the read folder as a starting point.

Start your CouchDB server if not already started.

Test that the application is working correctly by visiting http://localhost:5000/ in a browser.

Step Two: Create Stubs

In this step we create a partially implemented module named db that provides a layer of abstraction between the request handlers and the database. The db module exports a single function named getMessage. We also modify message, the module that handles requests for the message. Actual interaction with the database will be done in a later step.

Create db.js with the following contents.

exports.getMessage = function(cb) {
  cb(null, Math.random() + '');
};

The getMessage function is implemented as a stub for this intermediate step of development. The purpose of this function is to retrieve a message string from the database. If for whatever reason there is a problem getting this string, the callback function cb is called with an instance of Error as its sole argument. If there is no problem, then cb is called with null as its first argument and the message string as its second argument.

We use the random number generator to simulate changing message data. The random function returns a number, which we convert to a string by concatinating with the empty string.

There is one issue with the stub code for the getMessage function: it invokes the callback immediately. When interacting with the database, we will need to invoke asynchronous functions, which means we will return from getMessage before the callback cb is called. But our stub code returns from getMessage after the callback cb is called. As a result, the stub code might permit logic errors that could be caught at this point. The following code is an improved version of the stub code.

exports.getMessage = function(cb) {
  setTimeout(function() {
    cb(null, Math.random() + '');
  }, 4);
};

The above version of getMessage has the function returning before the callback is invoked, which is the normal sequence of events with asynchronous functions.

Another improvement we can make is to alternate between returning an error and returning a message. This will test our error hanlding code. The following version of the stub code accomplishes this.

var error = false;

exports.getMessage = function(cb) {
  setTimeout(function() {
    if (error) cb(new Error('database server error'));
    else cb(null, Math.random() + '');
    error = !error;
  }, 4);
};

With our database code stubbed out, we can now make changes to the message request handing code, which is in the module message. The following code is a rewrite of this module so that the getMessage function of the db module is used to get the message to return to the client.

var http = require('http');
var db = require('./db');

exports.handle = function(req, res) {
  db.getMessage(function(err, message) {
    if (err) {
      console.log(err.message);
      res.writeHead(500, 'server error');
      res.end();
      return;
    }
    var responseObject = { msg: message };
    var responseString = JSON.stringify(responseObject);
    var responseBody = new Buffer(responseString, 'utf-8');
    res.writeHead(200, {
      'Content-Type': 'application/json',
      'Content-Length': responseBody.length,
      'Pragma': 'no-cache',
      'Cache-Control': 'no-cache, no-store'
    });
    res.end(responseBody);
  });
};

Note that we require the db module using an absolute pathname './db'. We do this because Nodejs does not search the current directory when resolving references to modules.

Also note that we need to create a Buffer object for each request. This is required because the message data may change between requests.

Test your implementation by running the server and testing with a browser.

Step Three: Write Test Database Creation Script

Use what you learned from the CouchDB assignment to modify the file createdb.sh to create a database called read. Insert a single document into this database with a property named text. This property should be set to the string "the read assignment". Also, specify the value message for the _id field so that we can identify this document in a straightforward way.

Modify test_update.sh so that it changes the message in the database to "the new message".

Step Four: Complete the Database Module

Replace the stub code in db.js with the following code.

db.js
var assert      = require('assert');
var querystring = require('querystring');
var http        = require('http');

exports.getMessage = function(cb) {
  var options = {
      hostname: 'localhost',
      auth: 'admin:1234',
      port: 5984,
      path: '/read/message',
      method: 'GET'
  };
  send(options, function(err, data) {
    if (err) {
      console.log(err.message);
      return cb(err);
    }
    if (data.text === undefined) {
      throw new Error(
        'text property missing from msg document.\n' + JSON.stringify(data)
      );
    }
    cb(null, data.text);
  });
};

// Send request and receive data (a Javascript object).
// The options argument is the same as for http.request.
// cb = function(err, data)
function send(options, cb) {
  var req;

  // create request
  req = http.request(options, function(res) {
    var dataString;  // to be converted to Javascript object

    // tell node how to convert received bytes to a Javascript string
    res.setEncoding('utf8');

    // accumulate data
    res.on('data', function (chunk) {
      if (dataString === undefined) dataString = chunk; else dataString += chunk;
    });

    // parse received data
    res.on('end', function() {
      var data;
      try {
        data = JSON.parse(dataString);
      } catch (err) {
        return cb(err);
      }
      cb(null, data);
    });
  });

  // register error listener
  // pass error directly to callback
  req.on('error', cb);

  // send request
  req.end();
};

The db.getMessage function calls the send function, which calls the Node.js request function from the http module. The request function returns an instance of ClientRequest. This is used to send an HTTP message to the database server. So, in this part of our code, our Web server plays the role of a Web client.

First, test that the browser gets the current message from our server and the server code runs without error.

Second, while our server is running, run test_update.sh to set a different message. Then click on the make a request link to see that the new message is displayed to the user. Do this test without restarting the Web application server.