SlideShare a Scribd company logo
1 of 29
Download to read offline
REAL TIME WEB
                          WITH NODE
                           By Tim Caswell




Saturday, June 5, 2010
Live Interaction
            The web is about doing things, not just tourism anymore.

Saturday, June 5, 2010
Uses of Live Interaction
                         Chat Widget
                         Twitter Feed
                         Stock Ticker
                         Real-Time Game
                         Collaborative
                         Documents
                         TXJS Demos


Saturday, June 5, 2010
Why we need non-blocking
                  Polling is too slow and inefficient.
                  Live interaction requires the server to push data.
                  In order to push data, the connections need to be
                  persistent.
                  The server needs to handle thousands of persistent
                  connections
                  Threads aren’t going to scale.


Saturday, June 5, 2010
This is your server.
                         (with blocking I/O)
Saturday, June 5, 2010
This is your server
                  with low concurrency.




Saturday, June 5, 2010
This is your server
               with high concurrency.

Saturday, June 5, 2010
Connect
              We’ll use a new node
                framework that
            “connects” the web users
                 to each other.




Saturday, June 5, 2010
It’s like Japanese Legos
Saturday, June 5, 2010
Pre-Built Blocks
         Connect.createServer([
             {filter: "log"},
             {filter: "body-decoder", route: "/stream"},
             {provider: "pubsub", route: "/stream"},
             {filter: "conditional-get"},
             {filter: "cache"},
             {filter: "gzip"},
             {provider: "cache-manifest", root: root},
             {provider: "static", root: root}
         ]);



Saturday, June 5, 2010
And easy too!
Saturday, June 5, 2010
method-override.js
           var key;
           // Initialize any state (on server startup)
           exports.setup = function (env) {
               key = this.key || "_method";
           };
           // Modify the request stream (on request)
           exports.handle = function(err, req, res, next){
               if (key in req.body) {
                    req.method = req.body[key].toUpperCase();
               }
               next();
           };

Saturday, June 5, 2010
response-time.js
        exports.handle = function(err, req, res, next){
            var start = new Date,
                writeHead = res.writeHead;

                     res.writeHead = function(code, headers){
                         res.writeHead = writeHead;
                         headers['X-Response-Time'] =
                                        (new Date - start) + "ms";
                         res.writeHead(code, headers);
                     };

                     next();
        };

Saturday, June 5, 2010
Well, actually, it’s
             not always easy.
Saturday, June 5, 2010
static.js




Saturday, June 5, 2010
static.js
   var fs = require('fs'),
       Url = require('url'),
       Path = require('path');

   var lifetime = 1000 * 60 * 60; // 1 hour browser cache lifetime

   var DEFAULT_MIME = 'application/octet-stream';

   module.exports = {

         setup: function (env) {
             this.root = this.root || process.cwd();
         },

         handle: function (err, req, res, next) {
             // Skip on error
             if (err) {
                 next();
                 return;
             }
             var url = Url.parse(req.url);

               var pathname = url.pathname.replace(/..+/g, '.'),
                   filename = Path.join(this.root, pathname);

               if (filename[filename.length - 1] === "/") {
                   filename += "index.html";
               }


Saturday, June 5, 2010
static.js
                                           // Buffer any events that fire while waiting on the stat.
   var fs = require('fs'),                 var events = [];
       Url = require('url'),               function onData() {
       Path = require('path');                  events.push(["data"].concat(Array.prototype.slice.call(arguments)));
                                           }
   var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() {
                                             browser cache lifetime
                                                events.push(["end"].concat(Array.prototype.slice.call(arguments)));
                                           }
   var DEFAULT_MIME = 'application/octet-stream';
                                           req.addListener("data", onData);
   module.exports = {                      req.addListener("end", onEnd);


         setup: function (env) {             fs.stat(filename, function (err, stat) {
             this.root = this.root || process.cwd();
         },                                      // Stop buffering events
                                                 req.removeListener("data", onData);
         handle: function (err, req, res, next) {req.removeListener("end", onEnd);
               // Skip on error
               if (err) {                          // Fall through for missing files, thow error for other problems
                   next();                         if (err) {
                   return;                             if (err.errno === process.ENOENT) {
               }                                           next();
               var url = Url.parse(req.url);               // Refire the buffered events
                                                           events.forEach(function (args) {
                                                               req.emit.apply(req, args);
               var pathname = url.pathname.replace(/..+/g, '.'),
                                                           });
                   filename = Path.join(this.root, pathname);
                                                           return;
               if (filename[filename.length - 1] === "/") {
                   filename += "index.html";
               }


Saturday, June 5, 2010
static.js
                                            // Buffer any events that fire while waiting on the stat.
    var fs = require('fs'),                 var events = [];
        Url = require('url'),               function onData() {
        Path = require('path');                  events.push(["data"].concat(Array.prototype.slice.call(arguments)));
                                            }
    var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() {
                                              browser cache lifetime
                                                 events.push(["end"].concat(Array.prototype.slice.call(arguments)));
                                            }
    var DEFAULT_MIME = 'application/octet-stream';
                                            req.addListener("data", onData);
    module.exports = {                      req.addListener("end", onEnd);

(err);
        setup: function (env) {             fs.stat(filename, function (err, stat) {
rn;
            this.root = this.root || process.cwd();
        },                                      // Stop buffering events
                                                req.removeListener("data", onData);
                                                req.removeListener("end", onEnd);
 the file directly using (err, req, res, next) {
        handle: function buffers
ile(filename, function error data) {
            // Skip on (err,
            if (err) {                          // Fall through for missing files, thow error for other problems
err) {
                next();                         if (err) {
next(err);
                return;                             if (err.errno === process.ENOENT) {
return;
            }                                           next();
            var url = Url.parse(req.url);               // Refire the buffered events
writeHead(200, {
                                                        events.forEach(function (args) {
ontent-Type": Mime.type(filename),
                                                            req.emit.apply(req, args);
ontent-Length": pathname = url.pathname.replace(/..+/g, '.'),
            var data.length,
                                                        });
                filename = Path.join(this.root, pathname);
ast-Modified": stat.mtime.toUTCString(),
                                                        return;
/ Cache in browser for 1 year
ache-Control": (filename[filename.length - 1] === "/") {
            if "public max-age=" + 31536000
                filename += "index.html";
end(data); }

 Saturday, June 5, 2010
static.js
                                           // Buffer any events that fire while waiting on the stat.
   var fs = require('fs'),                 var events = [];
       Url = require('url'),               function onData() {
       Path = require('path');                  events.push(["data"].concat(Array.prototype.slice.call(arguments)));
                                           }
   var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() {
                                             browser cache lifetime
                                       };       events.push(["end"].concat(Array.prototype.slice.call(arguments)));
                                           }
   var DEFAULT_MIME = 'application/octet-stream';
                                       // Mini mime module for static file serving
                                           req.addListener("data", onData);
   module.exports = {                  var req.addListener("end", onEnd);
                                            Mime = {

(err);                                          type: function getMime(path) (err, stat) {
          setup: function (env) {                 fs.stat(filename, function {
rn;                                                var index = path.lastIndexOf(".");
                this.root = this.root || process.cwd();
          },                                       if (index < buffering events
                                                       // Stop 0) {
                                                     return DEFAULT_MIME;
                                                       req.removeListener("data", onData);
                                                   } req.removeListener("end", onEnd);
 the file directly using (err, req, res, next) { type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME;
          handle: function buffers
                                                   var
ile(filename,   // function error data) {
                     Skip on (err,
                if (err) {                         return Fall through for missing files, thow error "; charset=utf-8" : type;
                                                       // (/(text|javascript)/).test(type) ? type + for other problems
err) {                                          },
                      next();                          if (err) {
next(err);
                      return;                              if (err.errno === process.ENOENT) {
return;                                         TYPES : { ".3gp"
                }                                              next(); "video/3gpp",
                                                                     :
                var url = Url.parse(req.url);               ".a" Refire the buffered events
                                                               //    : "application/octet-stream",
writeHead(200, {                                            ".ai"    : "application/postscript",
                                                               events.forEach(function (args) {
ontent-Type": Mime.type(filename),                          ".aif" req.emit.apply(req, args);
                                                                     : "audio/x-aiff",
ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff",
                var data.length,
                                                            ".aiff" :
                                                               });
                      filename = Path.join(this.root, pathname);
ast-Modified": stat.mtime.toUTCString(),                    ".asc"
                                                               return; "application/pgp-signature",
                                                                     :
/ Cache in browser for 1 year                               ".asf"   : "video/x-ms-asf",
ache-Control": (filename[filename.length - 1] === "/") {
                if "public max-age=" + 31536000
                                                            ".asm"   : "text/x-asm",
                      filename += "index.html";
                                                            ".asx"   : "video/x-ms-asf",
end(data);      }
                                                            ".atom" : "application/atom+xml",
                                                            ".au"    : "audio/basic",
 Saturday, June 5, 2010                                     ".avi"   : "video/x-msvideo",
static.js
                                            // Buffer any events that fire while waiting on the stat.
   var fs = require('fs'), : "video/x-flv",var events = [];
                  ".flv"
                  ".for"   :                function onData() {
       Url = require('url'), "text/x-fortran",
       Path = require('path');
                  ".gem"                        events.push(["data"].concat(Array.prototype.slice.call(arguments)));
                           : "application/octet-stream",
                                            }
                  ".gemspec" : "text/x-script.ruby",
   var lifetime = ".gif" 60: *"image/gif", function onEnd() {
                  1000 *       60; // 1 hour browser cache lifetime
                                         };     events.push(["end"].concat(Array.prototype.slice.call(arguments)));
                  ".gz"    : "application/x-gzip",
                  ".h"     : "text/x-c",    }
   var DEFAULT_MIME = 'application/octet-stream';
                                         // Mini mime module for static file serving
                                            req.addListener("data", onData);
                  ".hh"    : "text/x-c",
   module.exports ".htm"
                  = {                    var Mime = {
                           : "text/html", req.addListener("end", onEnd);
(err);                   ".html" : "text/html",
rn;       setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) {
                         ".ico"   : {            type: function
                                                  fs.stat(filename, function {
                                                    var index = path.lastIndexOf(".");
                this.root = this.root || process.cwd();
                         ".ics"   : "text/calendar",
          },             ".ifb"   : "text/calendar", (index < buffering events
                                                    if // Stop 0) {
                                                      return DEFAULT_MIME;
                                                       req.removeListener("data", onData);
                         ".iso"   : "application/octet-stream",
                                                    }
                                  : req, res, next) {req.removeListener("end", onEnd);
 the file directly ".jar" (err,"application/java-archive",
          handle: function buffers
                          using                     var type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME;
ile(filename,   // function error: data) {
                     Skip".java"
                          on (err, "text/x-java-source",
                if (err) {                          return Fall through for missing files, thow error "; charset=utf-8" : type;
                                                       // (/(text|javascript)/).test(type) ? type + for other problems
                         ".jnlp" : "application/x-java-jnlp-file",
err) {                                           },
                      next();
                         ".jpeg" : "image/jpeg",       if (err) {
next(err);
                      return;
                         ".jpg"   : "image/jpeg",           if (err.errno === process.ENOENT) {
return;                                          TYPES : { ".3gp"     : "video/3gpp",
                }        ".js"    : "application/javascript", next();
                var url = Url.parse(req.url);                ".a" Refire the buffered events
                                                                //    : "application/octet-stream",
writeHead(200, { ".json" : "application/json",               ".ai"    : "application/postscript",
                         ".log"   : "text/plain",               events.forEach(function (args) {
ontent-Type": Mime.type(filename),                           ".aif" req.emit.apply(req, args);
                                                                      : "audio/x-aiff",
ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff",
                var data.length, "audio/x-mpegurl",
                         ".m3u"   :
                                                             ".aiff" :
                      filename = Path.join(this.root, pathname);
                         ".m4v"   : "video/mp4",                });
ast-Modified": stat.mtime.toUTCString(),                     ".asc"
                                                                return; "application/pgp-signature",
                                                                      :
/ Cache in browser ".man" year "text/troff",
                          for 1   :
                                                             ".asf"   : "video/x-ms-asf",
ache-Control": (filename[filename.length - 1] === "/") {
                if "public max-age=" + 31536000
                         ".mathml" : "application/mathml+xml",
                                                             ".asm"   : "text/x-asm",
                      filename += : "application/mbox",
                         ".mbox" "index.html";
                                                             ".asx"   : "video/x-ms-asf",
end(data);      }        ".mdoc" : "text/troff",
                         ".me"    : "text/troff",            ".atom" : "application/atom+xml",
                                                             ".au"    : "audio/basic",
                         ".mid"   : "audio/midi",
 Saturday, June 5, 2010                                      ".avi"   : "video/x-msvideo",
static.js
                                                   // Buffer any events that fire while waiting "text/x-c",
                                                                                       ".cc"    : on the stat.
    var fs = require('fs'), : "video/x-flv",var events = [];
                         ".flv"                                                        ".chm"   : "application/vnd.ms-htmlhelp",
          Url = require('url'), "text/x-fortran",
                         ".for"   :                function onData() {                 ".class"   : "application/octet-stream",
          Path = require('path');
                         ".gem"                         events.push(["data"].concat(Array.prototype.slice.call(arguments)));
                                  : "application/octet-stream",                        ".com"   : "application/x-msdownload",
                                                   }
                         ".gemspec" : "text/x-script.ruby",                            ".conf" : "text/plain",
    var lifetime = ".gif" 60: *"image/gif", function onEnd() {
                         1000 *       60; // 1 hour browser cache lifetime             ".cpp"   : "text/x-c",
                                               };       events.push(["end"].concat(Array.prototype.slice.call(arguments)));
                                                                                       ".crt"   : "application/x-x509-ca-cert",
                         ".gz"    : "application/x-gzip",
    var DEFAULT_MIME = 'application/octet-stream';
                         ".h"     : "text/x-c",    }                                   ".css"   : "text/css",
                                               // Mini mime module for static file serving
                                                   req.addListener("data", onData); ".csv"      : "text/csv",
                         ".hh"    : "text/x-c",
    module.exports ".htm"= {                   var Mime = {
                                  : "text/html", req.addListener("end", onEnd);        ".cxx"   : "text/x-c",
                         ".html" : "text/html",                                        ".deb"   : "application/x-debian-package",
(err);
rn;       setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) {
                         ".ico"   : {             type: function
                                                   fs.stat(filename, function {        ".der"   : "application/x-x509-ca-cert",
                                                     var index = path.lastIndexOf(".");".diff" : "text/x-diff",
                this.root = this.root || process.cwd();
                         ".ics"   : "text/calendar",
          },             ".ifb"   : "text/calendar", (index < buffering events
                                                     if // Stop 0) {                   ".djv"   : "image/vnd.djvu",
                                                       return DEFAULT_MIME;
                                                        req.removeListener("data", onData);
                                                                                       ".djvu" : "image/vnd.djvu",
                         ".iso"   : "application/octet-stream",
                                                     }
                                  : req, res, next) {req.removeListener("end", onEnd); ".dll"   : "application/x-msdownload",
 the file directly ".jar" (err,"application/java-archive",
          handle: function buffers
                          using                      var type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME;
                // function error: data) {
                     Skip".java"
                          on (err, "text/x-java-source",                               ".dmg"   : "application/octet-stream",
ile(filename,                                        return Fall through for missing files, thow error "; charset=utf-8" : type;
                if (err) {                              // (/(text|javascript)/).test(type) ? : "application/msword",
                         ".jnlp" : "application/x-java-jnlp-file",                     ".doc"   type + for other problems
err) {                                            },
                      next();
                         ".jpeg" : "image/jpeg",        if (err) {                     ".dot"   : "application/msword",
next(err);
                      return;
                         ".jpg"   : "image/jpeg",            if (err.errno === process.ENOENT) { "application/xml-dtd",
                                                                                       ".dtd"   :
return;                                           TYPES : { ".3gp"     : "video/3gpp", ".dvi"
                }        ".js"    : "application/javascript", next();                           : "application/x-dvi",
                var url = Url.parse(req.url);                 ".a" Refire the buffered events : "application/java-archive",
                                                                 //    : "application/octet-stream",
                                                                                       ".ear"
writeHead(200, { ".json" : "application/json",                ".ai"    : "application/postscript",
                         ".log"   : "text/plain",                events.forEach(function (args) : "message/rfc822",
                                                                                       ".eml"   {
ontent-Type": Mime.type(filename),                            ".aif" req.emit.apply(req, args); : "application/postscript",
                                                                       : "audio/x-aiff",
                                                                                       ".eps"
ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff",
                var data.length, "audio/x-mpegurl",
                         ".m3u"   :
                                                              ".aiff" :
                      filename = Path.join(this.root, pathname);
                         ".m4v"   : "video/mp4",                 });                   ".exe"   : "application/x-msdownload",
ast-Modified": stat.mtime.toUTCString(),                      ".asc"
                                                                 return; "application/pgp-signature",
                                                                       :               ".f"     : "text/x-fortran",
/ Cache in browser ".man" year "text/troff",
                          for 1   :
                                                              ".asf"   : "video/x-ms-asf",
                                                                                       ".f77"   : "text/x-fortran",
ache-Control": (filename[filename.length - 1] === "/") {
                if "public max-age=" + 31536000
                         ".mathml" : "application/mathml+xml",
                                                              ".asm"   : "text/x-asm", ".f90"
                      filename += : "application/mbox",
                         ".mbox" "index.html";                                                  : "text/x-fortran",
                                                              ".asx"   : "video/x-ms-asf",
end(data);      }        ".mdoc" : "text/troff",
                         ".me"    : "text/troff",             ".atom" : "application/atom+xml",
                                                              ".au"    : "audio/basic",
                         ".mid"   : "audio/midi",
 Saturday, June 5, 2010                                       ".avi"   : "video/x-msvideo",
static.js
                                                     // Buffer any events that fire while waiting "text/x-c",
                                                                                         ".cc"    : on the stat.
    var fs = require('fs'), : "video/x-flv",var events = [];
                         ".flv"                                                          ".chm"   : "application/vnd.ms-htmlhelp",
          Url = require('url'), "text/x-fortran",
                         ".for"    :                 function onData() {                 ".class"   : "application/octet-stream",
          Path = require('path');
                         ".gem"                            events.push(["data"].concat(Array.prototype.slice.call(arguments)));
                                   : "application/octet-stream",                         ".com"   : "application/x-msdownload",
                                                     }
                         ".gemspec" : "text/x-script.ruby",                              ".conf" : "text/plain",
    var lifetime = ".gif" 60: *"image/gif", function onEnd() {
                         1000 *        60; // 1 hour browser cache lifetime              ".cpp"   : "text/x-c",
                                                 };        events.push(["end"].concat(Array.prototype.slice.call(arguments)));
                                                                                         ".crt"   : "application/x-x509-ca-cert",
                         ".gz"     : "application/x-gzip",
    var DEFAULT_MIME = 'application/octet-stream';
                         ".h"      : "text/x-c",     }                                   ".css"   : "text/css",
                                                 // Mini mime module for static file serving
                                                     req.addListener("data", onData); ".csv"      : "text/csv",
                         ".hh"     : "text/x-c",
    module.exports ".htm"= {                     var Mime = {
                                   : "text/html", req.addListener("end", onEnd);         ".cxx"   : "text/x-c",
                         ".html" : "text/html",                                          ".deb"   : "application/x-debian-package",
(err);
rn;       setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) {
                         ".ico"    : {              type: function
                                                     fs.stat(filename, function {        ".der"   : "application/x-x509-ca-cert",
                                                       var index = path.lastIndexOf(".");".diff" : "text/x-diff",
                this.root = this.root || process.cwd();
                         ".ics"    : "text/calendar",
          },             ".ifb"    : "text/calendar", (index < buffering events
                                                       if // Stop 0) {                   ".djv"   : "image/vnd.djvu",
                                                         return DEFAULT_MIME;
                                                           req.removeListener("data", onData);
                                                                                         ".djvu" : "image/vnd.djvu",
                         ".iso"    : "application/octet-stream",
                                                       } req.removeListener("end", onEnd);
                                                                                         ".dll"   : "application/x-msdownload",
 the file directly ".jar"".bmp" req, "image/bmp", type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME;
          handle: function (err,"application/java-archive",
                          using buffers : res, next) {
                                   :
                                                       var
                // function error: data) {
                     Skip".java"
                          on (err, "text/x-java-source",
                                ".bz2"    : "application/x-bzip2",                       ".dmg"   : "application/octet-stream",
ile(filename,                                          return (/(text|javascript)/).test(type) ? : "application/msword", : type;
                                                                                                  type + "; charset=utf-8"
err) {          if (err) {                : "text/x-c", // Fall through for missing files, thow error for other problems
                         ".jnlp" : "application/x-java-jnlp-file",
                                ".c"                                                     ".doc"
                                                    },     if (err) {                    ".dot"   : "application/msword",
next(err);            next(); ".cab"
                         ".jpeg" : "image/jpeg",
                                          : "application/vnd.ms-cab-compressed",
                      return;
                         ".jpg"    : "image/jpeg",             if (err.errno === process.ENOENT) { "application/xml-dtd",
                                                                                         ".dtd"   :
return;                                             TYPES : { ".3gp"     : "video/3gpp", ".dvi"
                }        ".js"     : "application/javascript", next();                            : "application/x-dvi",
                var url = Url.parse(req.url);                   ".a" Refire the buffered events : "application/java-archive",
                                                                   //    : "application/octet-stream",
                                                                                         ".ear"
writeHead(200, { ".json" : "application/json",                  ".ai"    : "application/postscript",
                         ".log"    : "text/plain",                 events.forEach(function (args) : "message/rfc822",
                                                                                         ".eml"   {
ontent-Type": Mime.type(filename),                              ".aif" req.emit.apply(req, args); : "application/postscript",
                                                                         : "audio/x-aiff",
                                                                                         ".eps"
ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff",
                var data.length, "audio/x-mpegurl",
                         ".m3u"    :
                                                                ".aiff" :
                      filename = Path.join(this.root, pathname);
                         ".m4v"    : "video/mp4",                  });                   ".exe"   : "application/x-msdownload",
ast-Modified": stat.mtime.toUTCString(),                        ".asc"
                                                                   return; "application/pgp-signature",
                                                                         :               ".f"     : "text/x-fortran",
/ Cache in browser ".man" year "text/troff",
                          for 1    :
                                                                ".asf"   : "video/x-ms-asf",
                                                                                         ".f77"   : "text/x-fortran",
ache-Control": (filename[filename.length - 1] === "/") {
                if "public max-age=" + 31536000
                         ".mathml" : "application/mathml+xml",
                                                                ".asm"   : "text/x-asm", ".f90"
                      filename += : "application/mbox",
                         ".mbox" "index.html";                                                    : "text/x-fortran",
                                                                ".asx"   : "video/x-ms-asf",
end(data);      }        ".mdoc" : "text/troff",
                         ".me"     : "text/troff",              ".atom" : "application/atom+xml",
                                                                ".au"    : "audio/basic",
                         ".mid"    : "audio/midi",
 Saturday, June 5, 2010                                         ".avi"   : "video/x-msvideo",
Built-in Filter Modules
                   Authentication     Error Handler
                   Authorization      Gzip
                   Body Decoder       Log
                   Cache              Method Override
                   Conditional Get    Response Time
                   Debug              Session


Saturday, June 5, 2010
Built-in Data Providers

                         Static        Cache Manifest
                         Rest          Direct
                         Router        JSON-RPC
                         PubSub        More...


Saturday, June 5, 2010
Demo Time!


Saturday, June 5, 2010
app.js (stack)
                   require.paths.unshift("./lib");
                   var Connect = require('connect');
                   var root = __dirname + "/public";

                   module.exports = Connect.createServer([
                       {filter: "log"},
                       {filter: "body-decoder"},
                       {provider: "pubsub", route: "/stream",
                           logic: Backend},
                       {filter: "conditional-get"},
                       {filter: "cache"},
                       {filter: "gzip"},
                       {provider: "cache-manifest", root: root},
                       {provider: "static", root: root}
                   ]);

Saturday, June 5, 2010
app.js (Backend)
                         var Backend = {
                             subscribe: function (subscriber) {
                                 if (subscribers.indexOf(subscriber) < 0) {
                                     subscribers.push(subscriber);
                                 }
                             },
                             unsubscribe: function (subscriber) {
                                 var pos = subscribers.indexOf(subscriber);
                                 if (pos >= 0) {
                                     subscribers.slice(pos);
                                 }
                             },
                             publish: function (message, callback) {
                                 subscribers.forEach(function (subscriber) {
                                     subscriber.send(message);
                                 });
                                 callback();
                             }
                         };

Saturday, June 5, 2010
http://github.com/extjs/connect

             http://twitter.com/creationix

             http://raphaeljs.com

             http://nodejs.org


Saturday, June 5, 2010
Any
                         Questions
                            ?




Saturday, June 5, 2010

More Related Content

What's hot

Connectivity coding for java and mysql
Connectivity coding for java and mysqlConnectivity coding for java and mysql
Connectivity coding for java and mysqlFahad Ali Khan
 
Ejemplo radio
Ejemplo radioEjemplo radio
Ejemplo radiolupe ga
 
JCConf 2015 - 輕鬆學google的雲端開發 - Google App Engine入門(上)
JCConf 2015  - 輕鬆學google的雲端開發 - Google App Engine入門(上)JCConf 2015  - 輕鬆學google的雲端開發 - Google App Engine入門(上)
JCConf 2015 - 輕鬆學google的雲端開發 - Google App Engine入門(上)Simon Su
 
node.js Module Development
node.js Module Developmentnode.js Module Development
node.js Module DevelopmentJay Harris
 
Debugging: Rules & Tools
Debugging: Rules & ToolsDebugging: Rules & Tools
Debugging: Rules & ToolsIan Barber
 
The Ring programming language version 1.10 book - Part 92 of 212
The Ring programming language version 1.10 book - Part 92 of 212The Ring programming language version 1.10 book - Part 92 of 212
The Ring programming language version 1.10 book - Part 92 of 212Mahmoud Samir Fayed
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 SpringKiyotaka Oku
 
Java Concurrency in Practice
Java Concurrency in PracticeJava Concurrency in Practice
Java Concurrency in Practiceericbeyeler
 
Serial Killers - or Deserialization for fun and profit
Serial Killers - or Deserialization for fun and profitSerial Killers - or Deserialization for fun and profit
Serial Killers - or Deserialization for fun and profitblaufish
 
Pry, the good parts
Pry, the good partsPry, the good parts
Pry, the good partsConrad Irwin
 
Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v
Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v
Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v Arian Gutierrez
 
Monitoring MongoDB (MongoSV)
Monitoring MongoDB (MongoSV)Monitoring MongoDB (MongoSV)
Monitoring MongoDB (MongoSV)Boxed Ice
 
Introduce leo-redundant-manager
Introduce leo-redundant-managerIntroduce leo-redundant-manager
Introduce leo-redundant-managerParas Patel
 

What's hot (19)

Connectivity coding for java and mysql
Connectivity coding for java and mysqlConnectivity coding for java and mysql
Connectivity coding for java and mysql
 
Ejemplo radio
Ejemplo radioEjemplo radio
Ejemplo radio
 
JCConf 2015 - 輕鬆學google的雲端開發 - Google App Engine入門(上)
JCConf 2015  - 輕鬆學google的雲端開發 - Google App Engine入門(上)JCConf 2015  - 輕鬆學google的雲端開發 - Google App Engine入門(上)
JCConf 2015 - 輕鬆學google的雲端開發 - Google App Engine入門(上)
 
занятие8
занятие8занятие8
занятие8
 
Micro services workshop
Micro services workshopMicro services workshop
Micro services workshop
 
node.js Module Development
node.js Module Developmentnode.js Module Development
node.js Module Development
 
Intro to Redis
Intro to RedisIntro to Redis
Intro to Redis
 
Debugging: Rules & Tools
Debugging: Rules & ToolsDebugging: Rules & Tools
Debugging: Rules & Tools
 
Capistrano Rails
Capistrano RailsCapistrano Rails
Capistrano Rails
 
The Ring programming language version 1.10 book - Part 92 of 212
The Ring programming language version 1.10 book - Part 92 of 212The Ring programming language version 1.10 book - Part 92 of 212
The Ring programming language version 1.10 book - Part 92 of 212
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
 
zinno
zinnozinno
zinno
 
Java Concurrency in Practice
Java Concurrency in PracticeJava Concurrency in Practice
Java Concurrency in Practice
 
Swing database(mysql)
Swing database(mysql)Swing database(mysql)
Swing database(mysql)
 
Serial Killers - or Deserialization for fun and profit
Serial Killers - or Deserialization for fun and profitSerial Killers - or Deserialization for fun and profit
Serial Killers - or Deserialization for fun and profit
 
Pry, the good parts
Pry, the good partsPry, the good parts
Pry, the good parts
 
Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v
Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v
Tipo virus espia con esto aprenderan a espiar a personas etc jeropas de mrd :v
 
Monitoring MongoDB (MongoSV)
Monitoring MongoDB (MongoSV)Monitoring MongoDB (MongoSV)
Monitoring MongoDB (MongoSV)
 
Introduce leo-redundant-manager
Introduce leo-redundant-managerIntroduce leo-redundant-manager
Introduce leo-redundant-manager
 

Viewers also liked

Node Powered Mobile
Node Powered MobileNode Powered Mobile
Node Powered MobileTim Caswell
 
Open design at large scale
Open design at large scaleOpen design at large scale
Open design at large scaleshykes
 
Perspectives on Docker
Perspectives on DockerPerspectives on Docker
Perspectives on DockerRightScale
 
Open Design at large scale by Solomon Hykes
Open Design at large scale by Solomon HykesOpen Design at large scale by Solomon Hykes
Open Design at large scale by Solomon HykesDocker, Inc.
 
Docker Deployments
Docker DeploymentsDocker Deployments
Docker DeploymentsDocker, Inc.
 
Why Zsh is Cooler than Your Shell
Why Zsh is Cooler than Your ShellWhy Zsh is Cooler than Your Shell
Why Zsh is Cooler than Your Shelljaguardesignstudio
 

Viewers also liked (6)

Node Powered Mobile
Node Powered MobileNode Powered Mobile
Node Powered Mobile
 
Open design at large scale
Open design at large scaleOpen design at large scale
Open design at large scale
 
Perspectives on Docker
Perspectives on DockerPerspectives on Docker
Perspectives on Docker
 
Open Design at large scale by Solomon Hykes
Open Design at large scale by Solomon HykesOpen Design at large scale by Solomon Hykes
Open Design at large scale by Solomon Hykes
 
Docker Deployments
Docker DeploymentsDocker Deployments
Docker Deployments
 
Why Zsh is Cooler than Your Shell
Why Zsh is Cooler than Your ShellWhy Zsh is Cooler than Your Shell
Why Zsh is Cooler than Your Shell
 

Similar to Real Time Web with Node

NodeJs
NodeJsNodeJs
NodeJsdizabl
 
JS Fest 2019 Node.js Antipatterns
JS Fest 2019 Node.js AntipatternsJS Fest 2019 Node.js Antipatterns
JS Fest 2019 Node.js AntipatternsTimur Shemsedinov
 
JSConf: All You Can Leet
JSConf: All You Can LeetJSConf: All You Can Leet
JSConf: All You Can Leetjohndaviddalton
 
node.js practical guide to serverside javascript
node.js practical guide to serverside javascriptnode.js practical guide to serverside javascript
node.js practical guide to serverside javascriptEldar Djafarov
 
Node.js - A practical introduction (v2)
Node.js  - A practical introduction (v2)Node.js  - A practical introduction (v2)
Node.js - A practical introduction (v2)Felix Geisendörfer
 
Security Challenges in Node.js
Security Challenges in Node.jsSecurity Challenges in Node.js
Security Challenges in Node.jsWebsecurify
 
JSLab. Домников Виталий. "ES6 генераторы и Koa.js"
JSLab. Домников Виталий. "ES6 генераторы и Koa.js"JSLab. Домников Виталий. "ES6 генераторы и Koa.js"
JSLab. Домников Виталий. "ES6 генераторы и Koa.js"GeeksLab Odessa
 
JavaScript - Like a Box of Chocolates
JavaScript - Like a Box of ChocolatesJavaScript - Like a Box of Chocolates
JavaScript - Like a Box of ChocolatesRobert Nyman
 
Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Lukas Ruebbelke
 
Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Ismael Celis
 
Jersey framework
Jersey frameworkJersey framework
Jersey frameworkknight1128
 
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Domenic Denicola
 
soft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.jssoft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.jssoft-shake.ch
 
Build web application by express
Build web application by expressBuild web application by express
Build web application by expressShawn Meng
 

Similar to Real Time Web with Node (20)

NodeJs
NodeJsNodeJs
NodeJs
 
JS Fest 2019 Node.js Antipatterns
JS Fest 2019 Node.js AntipatternsJS Fest 2019 Node.js Antipatterns
JS Fest 2019 Node.js Antipatterns
 
Txjs
TxjsTxjs
Txjs
 
JSConf: All You Can Leet
JSConf: All You Can LeetJSConf: All You Can Leet
JSConf: All You Can Leet
 
Node.js - A Quick Tour
Node.js - A Quick TourNode.js - A Quick Tour
Node.js - A Quick Tour
 
node.js practical guide to serverside javascript
node.js practical guide to serverside javascriptnode.js practical guide to serverside javascript
node.js practical guide to serverside javascript
 
Node.js - Best practices
Node.js  - Best practicesNode.js  - Best practices
Node.js - Best practices
 
Node.js - A practical introduction (v2)
Node.js  - A practical introduction (v2)Node.js  - A practical introduction (v2)
Node.js - A practical introduction (v2)
 
Security Challenges in Node.js
Security Challenges in Node.jsSecurity Challenges in Node.js
Security Challenges in Node.js
 
JSLab. Домников Виталий. "ES6 генераторы и Koa.js"
JSLab. Домников Виталий. "ES6 генераторы и Koa.js"JSLab. Домников Виталий. "ES6 генераторы и Koa.js"
JSLab. Домников Виталий. "ES6 генераторы и Koa.js"
 
Node.js - A Quick Tour II
Node.js - A Quick Tour IINode.js - A Quick Tour II
Node.js - A Quick Tour II
 
JavaScript - Like a Box of Chocolates
JavaScript - Like a Box of ChocolatesJavaScript - Like a Box of Chocolates
JavaScript - Like a Box of Chocolates
 
Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015
 
Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010
 
JS everywhere 2011
JS everywhere 2011JS everywhere 2011
JS everywhere 2011
 
Jersey framework
Jersey frameworkJersey framework
Jersey framework
 
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
 
Node.js
Node.jsNode.js
Node.js
 
soft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.jssoft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.js
 
Build web application by express
Build web application by expressBuild web application by express
Build web application by express
 

Recently uploaded

Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxOnBoard
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 

Recently uploaded (20)

Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptx
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 

Real Time Web with Node

  • 1. REAL TIME WEB WITH NODE By Tim Caswell Saturday, June 5, 2010
  • 2. Live Interaction The web is about doing things, not just tourism anymore. Saturday, June 5, 2010
  • 3. Uses of Live Interaction Chat Widget Twitter Feed Stock Ticker Real-Time Game Collaborative Documents TXJS Demos Saturday, June 5, 2010
  • 4. Why we need non-blocking Polling is too slow and inefficient. Live interaction requires the server to push data. In order to push data, the connections need to be persistent. The server needs to handle thousands of persistent connections Threads aren’t going to scale. Saturday, June 5, 2010
  • 5. This is your server. (with blocking I/O) Saturday, June 5, 2010
  • 6. This is your server with low concurrency. Saturday, June 5, 2010
  • 7. This is your server with high concurrency. Saturday, June 5, 2010
  • 8. Connect We’ll use a new node framework that “connects” the web users to each other. Saturday, June 5, 2010
  • 9. It’s like Japanese Legos Saturday, June 5, 2010
  • 10. Pre-Built Blocks Connect.createServer([ {filter: "log"}, {filter: "body-decoder", route: "/stream"}, {provider: "pubsub", route: "/stream"}, {filter: "conditional-get"}, {filter: "cache"}, {filter: "gzip"}, {provider: "cache-manifest", root: root}, {provider: "static", root: root} ]); Saturday, June 5, 2010
  • 11. And easy too! Saturday, June 5, 2010
  • 12. method-override.js var key; // Initialize any state (on server startup) exports.setup = function (env) { key = this.key || "_method"; }; // Modify the request stream (on request) exports.handle = function(err, req, res, next){ if (key in req.body) { req.method = req.body[key].toUpperCase(); } next(); }; Saturday, June 5, 2010
  • 13. response-time.js exports.handle = function(err, req, res, next){ var start = new Date, writeHead = res.writeHead; res.writeHead = function(code, headers){ res.writeHead = writeHead; headers['X-Response-Time'] = (new Date - start) + "ms"; res.writeHead(code, headers); }; next(); }; Saturday, June 5, 2010
  • 14. Well, actually, it’s not always easy. Saturday, June 5, 2010
  • 16. static.js var fs = require('fs'), Url = require('url'), Path = require('path'); var lifetime = 1000 * 60 * 60; // 1 hour browser cache lifetime var DEFAULT_MIME = 'application/octet-stream'; module.exports = { setup: function (env) { this.root = this.root || process.cwd(); }, handle: function (err, req, res, next) { // Skip on error if (err) { next(); return; } var url = Url.parse(req.url); var pathname = url.pathname.replace(/..+/g, '.'), filename = Path.join(this.root, pathname); if (filename[filename.length - 1] === "/") { filename += "index.html"; } Saturday, June 5, 2010
  • 17. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), var events = []; Url = require('url'), function onData() { Path = require('path'); events.push(["data"].concat(Array.prototype.slice.call(arguments))); } var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() { browser cache lifetime events.push(["end"].concat(Array.prototype.slice.call(arguments))); } var DEFAULT_MIME = 'application/octet-stream'; req.addListener("data", onData); module.exports = { req.addListener("end", onEnd); setup: function (env) { fs.stat(filename, function (err, stat) { this.root = this.root || process.cwd(); }, // Stop buffering events req.removeListener("data", onData); handle: function (err, req, res, next) {req.removeListener("end", onEnd); // Skip on error if (err) { // Fall through for missing files, thow error for other problems next(); if (err) { return; if (err.errno === process.ENOENT) { } next(); var url = Url.parse(req.url); // Refire the buffered events events.forEach(function (args) { req.emit.apply(req, args); var pathname = url.pathname.replace(/..+/g, '.'), }); filename = Path.join(this.root, pathname); return; if (filename[filename.length - 1] === "/") { filename += "index.html"; } Saturday, June 5, 2010
  • 18. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), var events = []; Url = require('url'), function onData() { Path = require('path'); events.push(["data"].concat(Array.prototype.slice.call(arguments))); } var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() { browser cache lifetime events.push(["end"].concat(Array.prototype.slice.call(arguments))); } var DEFAULT_MIME = 'application/octet-stream'; req.addListener("data", onData); module.exports = { req.addListener("end", onEnd); (err); setup: function (env) { fs.stat(filename, function (err, stat) { rn; this.root = this.root || process.cwd(); }, // Stop buffering events req.removeListener("data", onData); req.removeListener("end", onEnd); the file directly using (err, req, res, next) { handle: function buffers ile(filename, function error data) { // Skip on (err, if (err) { // Fall through for missing files, thow error for other problems err) { next(); if (err) { next(err); return; if (err.errno === process.ENOENT) { return; } next(); var url = Url.parse(req.url); // Refire the buffered events writeHead(200, { events.forEach(function (args) { ontent-Type": Mime.type(filename), req.emit.apply(req, args); ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), var data.length, }); filename = Path.join(this.root, pathname); ast-Modified": stat.mtime.toUTCString(), return; / Cache in browser for 1 year ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 filename += "index.html"; end(data); } Saturday, June 5, 2010
  • 19. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), var events = []; Url = require('url'), function onData() { Path = require('path'); events.push(["data"].concat(Array.prototype.slice.call(arguments))); } var lifetime = 1000 * 60 * 60; // 1 hourfunction onEnd() { browser cache lifetime }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); } var DEFAULT_MIME = 'application/octet-stream'; // Mini mime module for static file serving req.addListener("data", onData); module.exports = { var req.addListener("end", onEnd); Mime = { (err); type: function getMime(path) (err, stat) { setup: function (env) { fs.stat(filename, function { rn; var index = path.lastIndexOf("."); this.root = this.root || process.cwd(); }, if (index < buffering events // Stop 0) { return DEFAULT_MIME; req.removeListener("data", onData); } req.removeListener("end", onEnd); the file directly using (err, req, res, next) { type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; handle: function buffers var ile(filename, // function error data) { Skip on (err, if (err) { return Fall through for missing files, thow error "; charset=utf-8" : type; // (/(text|javascript)/).test(type) ? type + for other problems err) { }, next(); if (err) { next(err); return; if (err.errno === process.ENOENT) { return; TYPES : { ".3gp" } next(); "video/3gpp", : var url = Url.parse(req.url); ".a" Refire the buffered events // : "application/octet-stream", writeHead(200, { ".ai" : "application/postscript", events.forEach(function (args) { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "audio/x-aiff", ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, ".aiff" : }); filename = Path.join(this.root, pathname); ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : / Cache in browser for 1 year ".asf" : "video/x-ms-asf", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".asm" : "text/x-asm", filename += "index.html"; ".asx" : "video/x-ms-asf", end(data); } ".atom" : "application/atom+xml", ".au" : "audio/basic", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 20. static.js // Buffer any events that fire while waiting on the stat. var fs = require('fs'), : "video/x-flv",var events = []; ".flv" ".for" : function onData() { Url = require('url'), "text/x-fortran", Path = require('path'); ".gem" events.push(["data"].concat(Array.prototype.slice.call(arguments))); : "application/octet-stream", } ".gemspec" : "text/x-script.ruby", var lifetime = ".gif" 60: *"image/gif", function onEnd() { 1000 * 60; // 1 hour browser cache lifetime }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); ".gz" : "application/x-gzip", ".h" : "text/x-c", } var DEFAULT_MIME = 'application/octet-stream'; // Mini mime module for static file serving req.addListener("data", onData); ".hh" : "text/x-c", module.exports ".htm" = { var Mime = { : "text/html", req.addListener("end", onEnd); (err); ".html" : "text/html", rn; setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) { ".ico" : { type: function fs.stat(filename, function { var index = path.lastIndexOf("."); this.root = this.root || process.cwd(); ".ics" : "text/calendar", }, ".ifb" : "text/calendar", (index < buffering events if // Stop 0) { return DEFAULT_MIME; req.removeListener("data", onData); ".iso" : "application/octet-stream", } : req, res, next) {req.removeListener("end", onEnd); the file directly ".jar" (err,"application/java-archive", handle: function buffers using var type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; ile(filename, // function error: data) { Skip".java" on (err, "text/x-java-source", if (err) { return Fall through for missing files, thow error "; charset=utf-8" : type; // (/(text|javascript)/).test(type) ? type + for other problems ".jnlp" : "application/x-java-jnlp-file", err) { }, next(); ".jpeg" : "image/jpeg", if (err) { next(err); return; ".jpg" : "image/jpeg", if (err.errno === process.ENOENT) { return; TYPES : { ".3gp" : "video/3gpp", } ".js" : "application/javascript", next(); var url = Url.parse(req.url); ".a" Refire the buffered events // : "application/octet-stream", writeHead(200, { ".json" : "application/json", ".ai" : "application/postscript", ".log" : "text/plain", events.forEach(function (args) { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "audio/x-aiff", ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, "audio/x-mpegurl", ".m3u" : ".aiff" : filename = Path.join(this.root, pathname); ".m4v" : "video/mp4", }); ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : / Cache in browser ".man" year "text/troff", for 1 : ".asf" : "video/x-ms-asf", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".mathml" : "application/mathml+xml", ".asm" : "text/x-asm", filename += : "application/mbox", ".mbox" "index.html"; ".asx" : "video/x-ms-asf", end(data); } ".mdoc" : "text/troff", ".me" : "text/troff", ".atom" : "application/atom+xml", ".au" : "audio/basic", ".mid" : "audio/midi", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 21. static.js // Buffer any events that fire while waiting "text/x-c", ".cc" : on the stat. var fs = require('fs'), : "video/x-flv",var events = []; ".flv" ".chm" : "application/vnd.ms-htmlhelp", Url = require('url'), "text/x-fortran", ".for" : function onData() { ".class" : "application/octet-stream", Path = require('path'); ".gem" events.push(["data"].concat(Array.prototype.slice.call(arguments))); : "application/octet-stream", ".com" : "application/x-msdownload", } ".gemspec" : "text/x-script.ruby", ".conf" : "text/plain", var lifetime = ".gif" 60: *"image/gif", function onEnd() { 1000 * 60; // 1 hour browser cache lifetime ".cpp" : "text/x-c", }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); ".crt" : "application/x-x509-ca-cert", ".gz" : "application/x-gzip", var DEFAULT_MIME = 'application/octet-stream'; ".h" : "text/x-c", } ".css" : "text/css", // Mini mime module for static file serving req.addListener("data", onData); ".csv" : "text/csv", ".hh" : "text/x-c", module.exports ".htm"= { var Mime = { : "text/html", req.addListener("end", onEnd); ".cxx" : "text/x-c", ".html" : "text/html", ".deb" : "application/x-debian-package", (err); rn; setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) { ".ico" : { type: function fs.stat(filename, function { ".der" : "application/x-x509-ca-cert", var index = path.lastIndexOf(".");".diff" : "text/x-diff", this.root = this.root || process.cwd(); ".ics" : "text/calendar", }, ".ifb" : "text/calendar", (index < buffering events if // Stop 0) { ".djv" : "image/vnd.djvu", return DEFAULT_MIME; req.removeListener("data", onData); ".djvu" : "image/vnd.djvu", ".iso" : "application/octet-stream", } : req, res, next) {req.removeListener("end", onEnd); ".dll" : "application/x-msdownload", the file directly ".jar" (err,"application/java-archive", handle: function buffers using var type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; // function error: data) { Skip".java" on (err, "text/x-java-source", ".dmg" : "application/octet-stream", ile(filename, return Fall through for missing files, thow error "; charset=utf-8" : type; if (err) { // (/(text|javascript)/).test(type) ? : "application/msword", ".jnlp" : "application/x-java-jnlp-file", ".doc" type + for other problems err) { }, next(); ".jpeg" : "image/jpeg", if (err) { ".dot" : "application/msword", next(err); return; ".jpg" : "image/jpeg", if (err.errno === process.ENOENT) { "application/xml-dtd", ".dtd" : return; TYPES : { ".3gp" : "video/3gpp", ".dvi" } ".js" : "application/javascript", next(); : "application/x-dvi", var url = Url.parse(req.url); ".a" Refire the buffered events : "application/java-archive", // : "application/octet-stream", ".ear" writeHead(200, { ".json" : "application/json", ".ai" : "application/postscript", ".log" : "text/plain", events.forEach(function (args) : "message/rfc822", ".eml" { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "application/postscript", : "audio/x-aiff", ".eps" ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, "audio/x-mpegurl", ".m3u" : ".aiff" : filename = Path.join(this.root, pathname); ".m4v" : "video/mp4", }); ".exe" : "application/x-msdownload", ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : ".f" : "text/x-fortran", / Cache in browser ".man" year "text/troff", for 1 : ".asf" : "video/x-ms-asf", ".f77" : "text/x-fortran", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".mathml" : "application/mathml+xml", ".asm" : "text/x-asm", ".f90" filename += : "application/mbox", ".mbox" "index.html"; : "text/x-fortran", ".asx" : "video/x-ms-asf", end(data); } ".mdoc" : "text/troff", ".me" : "text/troff", ".atom" : "application/atom+xml", ".au" : "audio/basic", ".mid" : "audio/midi", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 22. static.js // Buffer any events that fire while waiting "text/x-c", ".cc" : on the stat. var fs = require('fs'), : "video/x-flv",var events = []; ".flv" ".chm" : "application/vnd.ms-htmlhelp", Url = require('url'), "text/x-fortran", ".for" : function onData() { ".class" : "application/octet-stream", Path = require('path'); ".gem" events.push(["data"].concat(Array.prototype.slice.call(arguments))); : "application/octet-stream", ".com" : "application/x-msdownload", } ".gemspec" : "text/x-script.ruby", ".conf" : "text/plain", var lifetime = ".gif" 60: *"image/gif", function onEnd() { 1000 * 60; // 1 hour browser cache lifetime ".cpp" : "text/x-c", }; events.push(["end"].concat(Array.prototype.slice.call(arguments))); ".crt" : "application/x-x509-ca-cert", ".gz" : "application/x-gzip", var DEFAULT_MIME = 'application/octet-stream'; ".h" : "text/x-c", } ".css" : "text/css", // Mini mime module for static file serving req.addListener("data", onData); ".csv" : "text/csv", ".hh" : "text/x-c", module.exports ".htm"= { var Mime = { : "text/html", req.addListener("end", onEnd); ".cxx" : "text/x-c", ".html" : "text/html", ".deb" : "application/x-debian-package", (err); rn; setup: function (env) "image/vnd.microsoft.icon", getMime(path) (err, stat) { ".ico" : { type: function fs.stat(filename, function { ".der" : "application/x-x509-ca-cert", var index = path.lastIndexOf(".");".diff" : "text/x-diff", this.root = this.root || process.cwd(); ".ics" : "text/calendar", }, ".ifb" : "text/calendar", (index < buffering events if // Stop 0) { ".djv" : "image/vnd.djvu", return DEFAULT_MIME; req.removeListener("data", onData); ".djvu" : "image/vnd.djvu", ".iso" : "application/octet-stream", } req.removeListener("end", onEnd); ".dll" : "application/x-msdownload", the file directly ".jar"".bmp" req, "image/bmp", type = Mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME; handle: function (err,"application/java-archive", using buffers : res, next) { : var // function error: data) { Skip".java" on (err, "text/x-java-source", ".bz2" : "application/x-bzip2", ".dmg" : "application/octet-stream", ile(filename, return (/(text|javascript)/).test(type) ? : "application/msword", : type; type + "; charset=utf-8" err) { if (err) { : "text/x-c", // Fall through for missing files, thow error for other problems ".jnlp" : "application/x-java-jnlp-file", ".c" ".doc" }, if (err) { ".dot" : "application/msword", next(err); next(); ".cab" ".jpeg" : "image/jpeg", : "application/vnd.ms-cab-compressed", return; ".jpg" : "image/jpeg", if (err.errno === process.ENOENT) { "application/xml-dtd", ".dtd" : return; TYPES : { ".3gp" : "video/3gpp", ".dvi" } ".js" : "application/javascript", next(); : "application/x-dvi", var url = Url.parse(req.url); ".a" Refire the buffered events : "application/java-archive", // : "application/octet-stream", ".ear" writeHead(200, { ".json" : "application/json", ".ai" : "application/postscript", ".log" : "text/plain", events.forEach(function (args) : "message/rfc822", ".eml" { ontent-Type": Mime.type(filename), ".aif" req.emit.apply(req, args); : "application/postscript", : "audio/x-aiff", ".eps" ontent-Length": pathname = url.pathname.replace(/..+/g, '.'), "audio/x-aiff", var data.length, "audio/x-mpegurl", ".m3u" : ".aiff" : filename = Path.join(this.root, pathname); ".m4v" : "video/mp4", }); ".exe" : "application/x-msdownload", ast-Modified": stat.mtime.toUTCString(), ".asc" return; "application/pgp-signature", : ".f" : "text/x-fortran", / Cache in browser ".man" year "text/troff", for 1 : ".asf" : "video/x-ms-asf", ".f77" : "text/x-fortran", ache-Control": (filename[filename.length - 1] === "/") { if "public max-age=" + 31536000 ".mathml" : "application/mathml+xml", ".asm" : "text/x-asm", ".f90" filename += : "application/mbox", ".mbox" "index.html"; : "text/x-fortran", ".asx" : "video/x-ms-asf", end(data); } ".mdoc" : "text/troff", ".me" : "text/troff", ".atom" : "application/atom+xml", ".au" : "audio/basic", ".mid" : "audio/midi", Saturday, June 5, 2010 ".avi" : "video/x-msvideo",
  • 23. Built-in Filter Modules Authentication Error Handler Authorization Gzip Body Decoder Log Cache Method Override Conditional Get Response Time Debug Session Saturday, June 5, 2010
  • 24. Built-in Data Providers Static Cache Manifest Rest Direct Router JSON-RPC PubSub More... Saturday, June 5, 2010
  • 26. app.js (stack) require.paths.unshift("./lib"); var Connect = require('connect'); var root = __dirname + "/public"; module.exports = Connect.createServer([ {filter: "log"}, {filter: "body-decoder"}, {provider: "pubsub", route: "/stream", logic: Backend}, {filter: "conditional-get"}, {filter: "cache"}, {filter: "gzip"}, {provider: "cache-manifest", root: root}, {provider: "static", root: root} ]); Saturday, June 5, 2010
  • 27. app.js (Backend) var Backend = { subscribe: function (subscriber) { if (subscribers.indexOf(subscriber) < 0) { subscribers.push(subscriber); } }, unsubscribe: function (subscriber) { var pos = subscribers.indexOf(subscriber); if (pos >= 0) { subscribers.slice(pos); } }, publish: function (message, callback) { subscribers.forEach(function (subscriber) { subscriber.send(message); }); callback(); } }; Saturday, June 5, 2010
  • 28. http://github.com/extjs/connect http://twitter.com/creationix http://raphaeljs.com http://nodejs.org Saturday, June 5, 2010
  • 29. Any Questions ? Saturday, June 5, 2010