Thursday, March 12, 2015

Codeschool: Real Time Web, Part I

The only thing I have done before watching the codeschool course was to download and run the installation file from https://nodejs.org. It installs node and npm (the package manager for javascript). You don't actually need to, but it's always nice to see something actually working.

Main points:

  • non-blocking
  • event loop - checking for events continuously (e.g. request, connection, close)

Here is a non-blocking code example:
  fs.readFile('/etc/hosts', function(err,contents) {
    console.log(contents);
  });
  console.log('Doing something else');
or equivalently
  var callback = function(err,contents) {
    console.log(contents);
  });
  fs.readFile('/etc/hosts',callback);
The Hello World, which you can run with node hello.js
  var http = require('http')

  http.createServer(function(request,response) {
    response.writeHead(200, {
      'Content-Type': 'text/html'
    });
    response.write("Hello!");
    setTimeout(function(){
      response.write("I am done.");
      response.end()
    }, 5000);
  }).listen(8080);

Events

Many objects in node emit events, for example, fs.readStream emits a data event, and obviously we can listen for these events. The objects that can emit events inherit from the EventEmitter class.

We can create custom EventEmitter:

  var EventEmitter = require('events').EventEmitter;
  var logger = new EventEmitter();
If you want to listen for error events:
  logger.on('error',function(message){
    console.log('Error: ' + message);
  });
And to trigger the event:
  logger.emit('error','An error');

In fact, earlier, when we called http.createServer(function(request,response) {...}), this returns a http.Server object, which is an EventEmitter, set to listen for request events (it is all in the documentation for node.js). It is possible to write it this way:

  var server = http.createServer();
  server.on('request', function(request,response}{
    response.writeHead(200);
    response.write("Hello, this is dog");
    response.end();
  });
which is how you can explicitly bind several listeners on the same object. Actually, you can listen to an event more than once:
var http = require('http');
var server = http.createServer();

server.on('request', function(request, response) {
  response.writeHead(200);
  response.write("Hello, this is dog");
  response.end();
});

server.on('request', function(request, response) {
  console.log("New request coming in...");
});

server.on('close', function(){
  console.log("Closing down the server...");
});

server.listen(8080);
In the code above, whenever a request event happens, two things happen: send a response and write to console.

Streams

Streams is about how data is transferred back and forth. Streams can be readable, writable, or both. For example, in the arguments for http.createServer, request is a readable stream, response is a writable stream. These streams are kept open until we close the connection.

How do you read from a stream (e.g. how do you read request?). The request object is an EventEmitter, and it emits two events: 'readable', when the server can start reading the data, and 'end', when the client finishes uploading the data. Here is an example code:

http.createServer(function(request,response){
  response.writeHead(200)
  request.on('readable',function(){
    var chunk = null;
    while (null !== (chunk = request.read())) {
      response.write(chunk);
    }
  });
  request.on('end'), function() {
    response.end();
  });
}).listen(8080);
(the .write function implicitly does toString()). However, there is an even faster method, using pipe:
http.createServer(function(request,response) {
  response.writeHead(200);
  request.pipe(response);
}).listen(8080);

How is that useful? Well, the second main example is about reading and writing file:

var fs = require('fs'); // require the filesystem module

var file = fs.createReadStream("readme.md");
var newfile = fs.createWriteStream("readme_copy.md");

file.pipe(newFile);
You can for example do request.pipe(newfile), and when you for example do
$ curl --upload-file readme.md localhost:8080
it will upload readme.md to newfile. Here is a complete implementation of file upload with progress update:
http.createServer(function(request,response){
  var newFile = fs.createWriteStream("readme_copy.md");
  var fileBytes = request.headers['content-length'];
  var uploadedBytes = 0;

  request.on('readable', function() {
    var chunk = null;
    while(null !== (chunk = request.read())) {
      uploadedBytes += chunk.length;
      var progress = (uploadedBytes / fileBytes)*100;
      response.write("progress: " + parseInt(progress,10) + "%\n");
    }
  });
  request.pipe(newFile);
}).listen(8080);
To do list: play around with http://gulpjs.com.

No comments:

Post a Comment