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

Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
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
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
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
 
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
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGSujit Pal
 
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
 
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
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 

Recently uploaded (20)

Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
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
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
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
 
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
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAG
 
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
 
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
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 

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