Node.js

Event-driven & asynchrous IO & Event Emitter

  1. NOTE :

    • Node.js is an application [a process] in OS; but has multiple threads
    • Only one JS thread, all others are worker threads in thread pool used by OS
    • they share data via message queue
  2. Asynchrous I/O

    • Each I/O operation is assigned a worker thread in Node.js run time env;
    • But only one master thread (js engine) deal with computation
  3. Even Loop

    • Node.jsV8libuvOS

    • libuv [cross platform Asynchronous I/O]: handles utilize other threads to complete asynchronous I/O

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
         ┌───────────────────────────┐
      ┌─>│ timers │
      │ └─────────────┬─────────────┘
      │ ┌─────────────┴─────────────┐
      │ │ pending callbacks │
      │ └─────────────┬─────────────┘
      │ ┌─────────────┴─────────────┐
      │ │ idle, prepare │
      │ └─────────────┬─────────────┘ ┌───────────────┐
      │ ┌─────────────┴─────────────┐ │ incoming: │
      │ │ poll │<─────┤ connections, │
      │ └─────────────┬─────────────┘ │ data, etc. │
      │ ┌─────────────┴─────────────┐ └───────────────┘
      │ │ check │
      │ └─────────────┬─────────────┘
      │ ┌─────────────┴─────────────┐
      └──┤ close callbacks │
      └───────────────────────────┘

Node.js use cases

  1. I/O-bound application
    • Except the only one JS processing thread, all other threads deal with I/O operation, so that I/O processing can happend simultaneously => save time
    • JS processing thread has no CPU-bound computation, only stay in event loop, and keep staring at the top of message queue; When some event done, handle its callbacks

process.nextTick() vs. setImmediate()

  1. tick: each iteration of an Event Loop is called a tick.
  2. process.nextTick() : will execute all callbacks set by it previousely in next tick
  3. setImmediate(): will execute only one callback in next tick, even set multiple callbacks

CommonJS: require & exports

  1. Sad History: Before CommonJS, JavaScript doesn’t have module importing feature, so it is limited to small script, cannot be used to develop large project

  2. Current state : Many modules like buffer File Http Socket can be imported using CommonJS

  3. Implementation:

    • module is is glolal object in Node.js, have property exports

      1
      module = { exports: {} }
    • Before we step into the details, let’s see before CommonJS, how to use import function. The public functions are exposed while the private properties and methods are encapsulated

      1
      2
      3
      4
      5
      6
      7
      8
      var revealingModule = (function () {
      var privateVar = "Ben Thomas";
      function setNameFn( strName ) {
      privateVar = strName;
      }
      return { setName: setNameFn, };
      })();
      revealingModule.setName( "Paul Adams" );
    • When a module is exported, inside Node.js it will be wrapped as IIFE (Immediately Invoked Function Expression)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 1. original code
      var greet = function () { console.log('Hello World'); };
      module.exports = greet;

      // 2. Parsed code by Node.js
      (function (exports, require, module, __filename, __dirname) { //add by node.js
      var greet = function () { console.log('Hello World'); };
      module.exports = greet;
      }).apply(); //add by node.js

      return module.exports;
  4. How to use:

    • define module: module.exports or exports [module.exports === exports]

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /* --- circle.js --- */

      // 1st way
      exports.area = (radius) => Math.pow(radius, 2) * 3.14;
      exports.circunference = (radius) => 2 * radius * 3.14;

      // 2nd way
      const area = (radius) => Math.pow(radius, 2) * 3.14;
      const circunference = (radius) => 2 * radius * 3.14;
      module.exports = {area: area, circumference: circunference};

      // 3rd way
      module.exports = {
      area: (radius) => Math.pow(radius, 2) * 3.14;
      circunference: (radius) => 2 * radius * 3.14;
      }
    • use module everywhere:

      1
      2
      3
      const circle = require('./circle');
      circle.area(3)
      circle.circunference(3)

AMD: (Asynchronous Module Definition)

  1. Why need AMD?**

    • Since Node.js are always running locally inside server, the require are instantly completed, i.e. synchronously
    • Browser need to download js files from server asynchorously. While one file want require another, the other one might still not downloaded => ERROR!
    • Thus, we need another mechnism able to import modules asynchronously : AMD comes in!
  2. How to use it in browser?

Web

  1. Request Method

    • request.method: GET, POST, PUT, DELETE

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        function (req, res) { 
        switch (req.method)
        case 'POST':
        update(req, res);
        break;
        case 'DELETE':
        remove(req, res);
        break;
        case 'PUT':
        create(req, res);
        break;
        case 'GET':
        default:
        get(req, res);
        }
        }
  2. Request URL

    • complete URL like the following: (Hash part will be discarded)

      1
      http://user:pass@host.com:8080/p/a/t/h?query=string#hash
    • pathname = url.parse(req.url).pathname

    • routers in Node.js:

      • url: /[controller]/[action]/a/b/c

      • handlers for different controllers & actions

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        // routes different urls to their related handlers
        var url = require('url')
        function (req, res) {
        var pathname = url.parse(req.url).pathname;
        var paths = pathname.split('/');
        var controller = paths[1] || 'index';
        var action = paths[2] || 'index';
        var args = paths.slice(3);
        if (handles[controller] && handles[controller][action]) {
        handles[controller][action].apply(null, [req, res].concat(args));
        } else {
        res.writeHead(500);
        res.end('找不到响应控制器'); }
        }

        // handlers object
        handles.index = {};
        handles.index.index = function (req, res, foo, bar) {
        res.writeHead(200);
        res.end(foo);
        };
  3. Request Query String

    • query = url.parse(req.url).query

    • url: /domainname/path?foo=bar&baz=val

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        //eg1.  '/domainname/path?foo=bar&baz=val' 
        {
        foo: 'bar',
        baz: 'val'
        }

        //eg2. '/domainname/path?foo=bar&foo=val'
        {
        foo: ['bar', 'val'],
        }
  4. Cookie

    • workflow: [ key point: browser sends cookie for each request]

      • server sends cookie to browser
      • browser stores it in localstorage
      • browser sends cookie every time making request
    • [For browser:] string: Cookie: foo=bar; baz=val => req.header.cookie

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        // browser parse cookie
        var parseCookie = function (cookie) {
        var cookies = {};
        if (!cookie) { return cookies; }
        var list = cookie.split(';');
        for (var i = 0; i < list.length; i++) {
        var pair = list[i].split('=');
        cookies[pair[0].trim()] = pair[1];
        }
        return cookies;
        };

        // browser adds cookie to `req` object
        function (req, res) {
        req.cookies = parseCookie(req.headers.cookie);
        hande(req, res);
        }
    • [For server:]

      • Format:

        • 1
          Set-Cookie: name=value; Path=/ ; Expires = Sun, 01/21/1987 09:01:01 GMT; Domain = .domain.com;
      • arguments:

        • name&value: the useful stuff
        • domain: cookie only sent to the domain
        • path: cookie only sent when this domain/path
        • expire / maxAge: when or how long expires
        • HttpOnly: will make this cookie cannot be access by document.cookie
        • secure: only sent when HTTPS; not sent when HTTP
      • Serialize Cookie [Server] : convert cookie to string

        • 1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          var serialize = function (name, val, opt) { 
          var pairs = [name + '=' + encode(val)]; opt = opt || {};
          if (opt.maxAge) pairs.push('Max-Age=' + opt.maxAge);
          if (opt.domain) pairs.push('Domain=' + opt.domain);
          if (opt.path) pairs.push('Path=' + opt.path);
          if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString());
          if (opt.httpOnly) pairs.push('HttpOnly');
          if (opt.secure) pairs.push('Secure');
          return pairs.join('; ');
          };
  5. Session

    • why need it: cookie with too much data will make delay in network transmission

    • Solution:

      • store data on server side, give a key (sessionID) to cookie to refer its data
      • set expire to its corresponding cookie
  6. Cache response file [ has nothing to do with cookie ]

    • Client: If-Modified-Since, Server: Last-Modified
    • Client: If-None-Match, Server: ETag
      • solve problem: even if file modified, but content didn’t modified
      • compare the digest of target file
    • Server: Expires / Cache-Control
      • the first 2 ways still need to send request, and wait for response => waste time!
      • This approach doesn’t need to send request if doesn’t expires
      • But there is a problem: the time are not the same on two sides, max-age of cache-controlcomes in
    • how to update cache when server file updated within expired period?
      • Problem: browser will use stale file, since it not expire
      • We also update the URL, since Cache pairs with URL
  7. Authentication / Authorization

    • basic authentication
      • request with : useranme & password
    • OAuth2.0
      • request with access_token
    • Bearer token: JWT
      • request with Json Web Token
  8. Form Data

    • content-type: application/x-www-form-urlencoded (“foo=bar&baz=val”)
    • content-type: application/json;
    • content-type: application/xml;
  9. Simulate a Client(browser) & a Server

    1. server implementation [send response as a server]

      1. 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        let http = require('http')
        let fs = require('fs')
        let url = require('url')

        http.creatServer( function(req, res){
        let pathname = url.parse(req.url).pathname
        console.log("request for " + pathname + "received.")

        fs.readFile(pathname.substr(1), function(err, data){
        if(err){
        console.log(err)
        res.writeHead(404, {'Content-Type': 'text/html'})
        }else{
        res.writeHead(200, {'Content-Type': 'text/html'})
        res.write(data.toString())
        }
        res.end()
        })
        }).listen(8080)

        console.log("Server running at http://127.0.0.1:8080/")
    2. client implementation [send request as a browser]

      1. 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        let http = require('http')

        let options ={
        host: 'localhost',
        port: '8080',
        path: '/index.html'
        }

        let callback = function(res){
        let body = ''
        res.on( 'data', function(data){
        body += data
        })

        res.on( 'end', function(){
        console.log(body)
        })
        }

        let req = http.request(options, callback)
        req.end()

XSS / CSRF

  1. XSS: cross site script
    • inject script from unsafe input, and runs it to get cookie, ie sessionID
    • [Solution]: convert to HTML entities: & will be &amp;
  2. CSRF:
    • don’t bother to get sessionID
    • lure customer to another website, while he is in middle of session of some weak-protection Bank website
    • the other website will submit a form to Bank website indicating transfer money
    • Bank website server cannot tell from whom that request is. It still believe that is from its customer since he is in the middle of the session
    • [Solution]:
      • the Bank form add another random hidden input field
      • each time receive request, check if the random value equal to sent one
      • This way the form cannot be duplicate

Global variables

  1. console
    • console.log('11')
  2. process
    • process.exit(0)
    • process.nextTick(callback)
  3. __filename: absolute path + name for the current script being executed
  4. __dirname: absolute path for the current script being executed

Util Objects

  1. util: make one object inherit from the other one
    1. util.inherits(constructor, superConstructor)

Versions

  1. semantic versioning
    • consists of three numbers, separated by periods, such as 2.3.0
    • the middle number has to be incremented, when new functionality is added
    • the first number has to be incremented, when compatibility is broken, so that existing code that uses the package might not work with the new version,
  2. caret character (^)
    • any version compatible with the given number may be installed
    • ^2.3.0 would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed