Skip to content
Browse files

Filled out the push server quite a bit

Almost ready for integration with the chat server
Have a test version of the jsonp client
  • Loading branch information...
1 parent d6d53b3 commit 3928d5d2565f929031da751a622e6007e50432ae @jcwilk committed Feb 21, 2011
Showing with 1,005 additions and 9 deletions.
  1. +558 −0 lib/node-router.js
  2. +303 −0 lib/paperboy.js
  3. +61 −0 public/push.js
  4. +83 −9 server.js
View
558 lib/node-router.js
@@ -0,0 +1,558 @@
+/*
+Copyright (c) 2010 Tim Caswell <tim@creationix.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var sys = require('sys');
+var fs = require('fs');
+var path = require('path');
+var http = require('http');
+var url_parse = require("url").parse;
+
+// Used as a simple, convient 404 handler.
+function notFound(req, res, message) {
+ message = (message || "Not Found\n") + "";
+ res.writeHead(404, {
+ "Content-Type": "text/plain",
+ "Content-Length": message.length
+ });
+ if (req.method !== "HEAD")
+ res.write(message);
+ res.end();
+}
+
+// Modifies req and res to call logger with a log line on each res.end
+// Think of it as "middleware"
+function logify(req, res, logger) {
+ var end = res.end;
+ res.end = function () {
+ // Common Log Format (mostly)
+ logger((req.socket && req.socket.remoteAddress) + " - - [" + (new Date()).toUTCString() + "]"
+ + " \"" + req.method + " " + req.url
+ + " HTTP/" + req.httpVersionMajor + "." + req.httpVersionMinor + "\" "
+ + res.statusCode + " - \""
+ + (req.headers['referer'] || "") + "\" \"" + (req.headers["user-agent"] ? req.headers["user-agent"].split(' ')[0] : '') + "\"");
+ return end.apply(this, arguments);
+ }
+ var writeHead = res.writeHead;
+ res.writeHead = function (code) {
+ res.statusCode = code;
+ return writeHead.apply(this, arguments);
+ }
+}
+
+exports.getServer = function getServer(logger) {
+
+ logger = logger || sys.puts;
+
+ var routes = [];
+
+ // Adds a route the the current server
+ function addRoute(method, pattern, handler, format) {
+ if (typeof pattern === 'string') {
+ pattern = new RegExp("^" + pattern + "$");
+ }
+ var route = {
+ method: method,
+ pattern: pattern,
+ handler: handler
+ };
+ if (format !== undefined) {
+ route.format = format;
+ }
+ routes.push(route);
+ }
+
+ // The four verbs are wrappers around addRoute
+ function get(pattern, handler) {
+ return addRoute("GET", pattern, handler);
+ }
+ function post(pattern, handler, format) {
+ return addRoute("POST", pattern, handler, format);
+ }
+ function put(pattern, handler, format) {
+ return addRoute("PUT", pattern, handler, format);
+ }
+ function del(pattern, handler) {
+ return addRoute("DELETE", pattern, handler);
+ }
+ function head(pattern, handler) {
+ return addRoute("HEAD", pattern, handler);
+ }
+
+ // This is a meta pattern that expands to a common RESTful mapping
+ function resource(name, controller, format) {
+ get(new RegExp('^/' + name + '$'), controller.index);
+ get(new RegExp('^/' + name + '/([^/]+)$'), controller.show);
+ post(new RegExp('^/' + name + '$'), controller.create, format);
+ put(new RegExp('^/' + name + '/([^/]+)$'), controller.update, format);
+ del(new RegExp('^/' + name + '/([^/]+)$'), controller.destroy);
+ };
+
+ function resourceController(name, data, on_change) {
+ data = data || [];
+ on_change = on_change || function () {};
+ return {
+ index: function (req, res) {
+ res.simpleJson(200, {content: data, self: '/' + name});
+ },
+ show: function (req, res, id) {
+ var item = data[id];
+ if (item) {
+ res.simpleJson(200, {content: item, self: '/' + name + '/' + id});
+ } else {
+ res.notFound();
+ }
+ },
+ create: function (req, res) {
+ req.jsonBody(function (json) {
+ var item, id, url;
+ item = json && json.content;
+ if (!item) {
+ res.notFound();
+ } else {
+ data.push(item);
+ id = data.length - 1;
+ on_change(id);
+ url = "/" + name + "/" + id;
+ res.simpleJson(201, {content: item, self: url}, [["Location", url]]);
+ }
+ });
+ },
+ update: function (req, res, id) {
+ req.jsonBody(function (json) {
+ var item = json && json.content;
+ if (!item) {
+ res.notFound();
+ } else {
+ data[id] = item;
+ on_change(id);
+ res.simpleJson(200, {content: item, self: "/" + name + "/" + id});
+ }
+ });
+ },
+ destroy: function (req, res, id) {
+ delete data[id];
+ on_change(id);
+ res.simpleJson(200, "200 Destroyed");
+ }
+ };
+ };
+
+ // Create the http server object
+ var server = http.createServer(function (req, res) {
+
+ // Enable logging on all requests using common-logger style
+ logify(req, res, logger);
+
+ var uri, path;
+
+ // Performs an HTTP 302 redirect
+ res.redirect = function redirect(location) {
+ res.writeHead(302, {"Location": location});
+ res.end();
+ }
+
+ // Performs an internal redirect
+ res.innerRedirect = function innerRedirect(location) {
+ logger("Internal Redirect: " + req.url + " -> " + location);
+ req.url = location;
+ doRoute();
+ }
+
+ function simpleResponse(code, body, content_type, extra_headers) {
+ res.writeHead(code, (extra_headers || []).concat(
+ [ ["Content-Type", content_type],
+ ["Content-Length", Buffer.byteLength(body, 'utf8')]
+ ]));
+ if (req.method !== "HEAD")
+ res.write(body, 'utf8');
+ res.end();
+ }
+
+ res.simpleText = function (code, body, extra_headers) {
+ simpleResponse(code, body, "text/plain", extra_headers);
+ };
+
+ res.simpleHtml = function (code, body, extra_headers) {
+ simpleResponse(code, body, "text/html", extra_headers);
+ };
+
+ res.simpleJson = function (code, json, extra_headers) {
+ simpleResponse(code, JSON.stringify(json), "application/json", extra_headers);
+ };
+
+ res.notFound = function (message) {
+ notFound(req, res, message);
+ };
+
+ res.onlyHead = function (code, extra_headers) {
+ res.writeHead(code, (extra_headers || []).concat(
+ [["Content-Type", content_type]]));
+ res.end();
+ }
+
+ function doRoute() {
+ uri = url_parse(req.url);
+ path = uri.pathname;
+
+ for (var i = 0, l = routes.length; i < l; i += 1) {
+ var route = routes[i];
+ if (req.method === route.method) {
+ var match = path.match(route.pattern);
+ if (match && match[0].length > 0) {
+ match.shift();
+ match = match.map(function (part) {
+ return part ? unescape(part) : part;
+ });
+ match.unshift(res);
+ match.unshift(req);
+ if (route.format !== undefined) {
+ var body = "";
+ req.setEncoding('utf8');
+ req.addListener('data', function (chunk) {
+ body += chunk;
+ });
+ req.addListener('end', function () {
+ if (route.format === 'json') {
+ try {
+ body = JSON.parse(unescape(body));
+ } catch(e) {
+ body = null;
+ }
+ }
+ match.push(body);
+ route.handler.apply(null, match);
+ });
+ return;
+ }
+ var result = route.handler.apply(null, match);
+ switch (typeof result) {
+ case "string":
+ res.simpleHtml(200, result);
+ break;
+ case "object":
+ res.simpleJson(200, result);
+ break;
+ }
+
+ return;
+ }
+ }
+ }
+
+ notFound(req, res);
+ }
+ doRoute();
+
+ });
+
+
+ function listen(port, host, callback) {
+ port = port || 8080;
+
+ if (typeof host === 'undefined' || host == '*')
+ host = null;
+
+ server.listen(port, host, callback);
+
+ if (typeof port === 'number') {
+ logger("node-router server instance at http://" + (host || '*') + ":" + port + "/");
+ } else {
+ logger("node-router server instance at unix:" + port);
+ }
+ }
+
+ function end() {
+ return server.end();
+ }
+
+ // Return a handle to the public facing functions from this closure as the
+ // server object.
+ return {
+ get: get,
+ post: post,
+ put: put,
+ del: del,
+ resource: resource,
+ resourceController: resourceController,
+ listen: listen,
+ end: end
+ };
+}
+
+
+
+
+exports.staticHandler = function (filename) {
+ var body, headers;
+ var content_type = mime.getMime(filename)
+ var encoding = (content_type.slice(0,4) === "text" ? "utf8" : "binary");
+
+ function loadResponseData(req, res, callback) {
+ if (body && headers) {
+ callback();
+ return;
+ }
+
+ fs.readFile(filename, encoding, function (err, data) {
+ if (err) {
+ notFound(req, res, "Cannot find file: " + filename);
+ return;
+ }
+ body = data;
+ headers = [ [ "Content-Type" , content_type ],
+ [ "Content-Length" , body.length ]
+ ];
+ headers.push(["Cache-Control", "public"]);
+
+ callback();
+ });
+ }
+
+ return function (req, res) {
+ loadResponseData(req, res, function () {
+ res.writeHead(200, headers);
+ if (req.method !== "HEAD")
+ res.write(body, encoding);
+ res.end();
+ });
+ };
+};
+
+exports.staticDirHandler = function(root, prefix) {
+ function loadResponseData(req, res, filename, callback) {
+ var content_type = mime.getMime(filename);
+ var encoding = (content_type.slice(0,4) === "text" ? "utf8" : "binary");
+
+ fs.readFile(filename, encoding, function(err, data) {
+ if(err) {
+ notFound(req, res, "Cannot find file: " + filename);
+ return;
+ }
+ var headers = [ [ "Content-Type" , content_type ],
+ [ "Content-Length" , data.length ],
+ [ "Cache-Control" , "public" ]
+ ];
+ callback(headers, data, encoding);
+ });
+ }
+
+ return function (req, res) {
+ // trim off any query/anchor stuff
+ var filename = req.url.replace(/[\?|#].*$/, '');
+ if (prefix) filename = filename.replace(new RegExp('^'+prefix), '');
+ // make sure nobody can explore our local filesystem
+ filename = path.join(root, filename.replace(/\.\.+/g, '.'));
+ if (filename == root) filename = path.join(root, 'index.html');
+ loadResponseData(req, res, filename, function(headers, body, encoding) {
+ res.writeHead(200, headers);
+ if (req.method !== "HEAD")
+ res.write(body, encoding);
+ res.end();
+ });
+ };
+};
+
+
+// Mini mime module for static file serving
+var DEFAULT_MIME = 'application/octet-stream';
+var mime = exports.mime = {
+
+ getMime: function getMime(path) {
+ var index = path.lastIndexOf(".");
+ if (index < 0) {
+ return DEFAULT_MIME;
+ }
+ return mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME;
+ },
+
+ TYPES : { ".3gp" : "video/3gpp",
+ ".a" : "application/octet-stream",
+ ".ai" : "application/postscript",
+ ".aif" : "audio/x-aiff",
+ ".aiff" : "audio/x-aiff",
+ ".asc" : "application/pgp-signature",
+ ".asf" : "video/x-ms-asf",
+ ".asm" : "text/x-asm",
+ ".asx" : "video/x-ms-asf",
+ ".atom" : "application/atom+xml",
+ ".au" : "audio/basic",
+ ".avi" : "video/x-msvideo",
+ ".bat" : "application/x-msdownload",
+ ".bin" : "application/octet-stream",
+ ".bmp" : "image/bmp",
+ ".bz2" : "application/x-bzip2",
+ ".c" : "text/x-c",
+ ".cab" : "application/vnd.ms-cab-compressed",
+ ".cc" : "text/x-c",
+ ".chm" : "application/vnd.ms-htmlhelp",
+ ".class" : "application/octet-stream",
+ ".com" : "application/x-msdownload",
+ ".conf" : "text/plain",
+ ".cpp" : "text/x-c",
+ ".crt" : "application/x-x509-ca-cert",
+ ".css" : "text/css",
+ ".csv" : "text/csv",
+ ".cxx" : "text/x-c",
+ ".deb" : "application/x-debian-package",
+ ".der" : "application/x-x509-ca-cert",
+ ".diff" : "text/x-diff",
+ ".djv" : "image/vnd.djvu",
+ ".djvu" : "image/vnd.djvu",
+ ".dll" : "application/x-msdownload",
+ ".dmg" : "application/octet-stream",
+ ".doc" : "application/msword",
+ ".dot" : "application/msword",
+ ".dtd" : "application/xml-dtd",
+ ".dvi" : "application/x-dvi",
+ ".ear" : "application/java-archive",
+ ".eml" : "message/rfc822",
+ ".eps" : "application/postscript",
+ ".exe" : "application/x-msdownload",
+ ".f" : "text/x-fortran",
+ ".f77" : "text/x-fortran",
+ ".f90" : "text/x-fortran",
+ ".flv" : "video/x-flv",
+ ".for" : "text/x-fortran",
+ ".gem" : "application/octet-stream",
+ ".gemspec" : "text/x-script.ruby",
+ ".gif" : "image/gif",
+ ".gz" : "application/x-gzip",
+ ".h" : "text/x-c",
+ ".hh" : "text/x-c",
+ ".htm" : "text/html",
+ ".html" : "text/html",
+ ".ico" : "image/vnd.microsoft.icon",
+ ".ics" : "text/calendar",
+ ".ifb" : "text/calendar",
+ ".iso" : "application/octet-stream",
+ ".jar" : "application/java-archive",
+ ".java" : "text/x-java-source",
+ ".jnlp" : "application/x-java-jnlp-file",
+ ".jpeg" : "image/jpeg",
+ ".jpg" : "image/jpeg",
+ ".js" : "application/javascript",
+ ".json" : "application/json",
+ ".log" : "text/plain",
+ ".m3u" : "audio/x-mpegurl",
+ ".m4v" : "video/mp4",
+ ".man" : "text/troff",
+ ".mathml" : "application/mathml+xml",
+ ".mbox" : "application/mbox",
+ ".mdoc" : "text/troff",
+ ".me" : "text/troff",
+ ".mid" : "audio/midi",
+ ".midi" : "audio/midi",
+ ".mime" : "message/rfc822",
+ ".mml" : "application/mathml+xml",
+ ".mng" : "video/x-mng",
+ ".mov" : "video/quicktime",
+ ".mp3" : "audio/mpeg",
+ ".mp4" : "video/mp4",
+ ".mp4v" : "video/mp4",
+ ".mpeg" : "video/mpeg",
+ ".mpg" : "video/mpeg",
+ ".ms" : "text/troff",
+ ".msi" : "application/x-msdownload",
+ ".odp" : "application/vnd.oasis.opendocument.presentation",
+ ".ods" : "application/vnd.oasis.opendocument.spreadsheet",
+ ".odt" : "application/vnd.oasis.opendocument.text",
+ ".ogg" : "application/ogg",
+ ".p" : "text/x-pascal",
+ ".pas" : "text/x-pascal",
+ ".pbm" : "image/x-portable-bitmap",
+ ".pdf" : "application/pdf",
+ ".pem" : "application/x-x509-ca-cert",
+ ".pgm" : "image/x-portable-graymap",
+ ".pgp" : "application/pgp-encrypted",
+ ".pkg" : "application/octet-stream",
+ ".pl" : "text/x-script.perl",
+ ".pm" : "text/x-script.perl-module",
+ ".png" : "image/png",
+ ".pnm" : "image/x-portable-anymap",
+ ".ppm" : "image/x-portable-pixmap",
+ ".pps" : "application/vnd.ms-powerpoint",
+ ".ppt" : "application/vnd.ms-powerpoint",
+ ".ps" : "application/postscript",
+ ".psd" : "image/vnd.adobe.photoshop",
+ ".py" : "text/x-script.python",
+ ".qt" : "video/quicktime",
+ ".ra" : "audio/x-pn-realaudio",
+ ".rake" : "text/x-script.ruby",
+ ".ram" : "audio/x-pn-realaudio",
+ ".rar" : "application/x-rar-compressed",
+ ".rb" : "text/x-script.ruby",
+ ".rdf" : "application/rdf+xml",
+ ".roff" : "text/troff",
+ ".rpm" : "application/x-redhat-package-manager",
+ ".rss" : "application/rss+xml",
+ ".rtf" : "application/rtf",
+ ".ru" : "text/x-script.ruby",
+ ".s" : "text/x-asm",
+ ".sgm" : "text/sgml",
+ ".sgml" : "text/sgml",
+ ".sh" : "application/x-sh",
+ ".sig" : "application/pgp-signature",
+ ".snd" : "audio/basic",
+ ".so" : "application/octet-stream",
+ ".svg" : "image/svg+xml",
+ ".svgz" : "image/svg+xml",
+ ".swf" : "application/x-shockwave-flash",
+ ".t" : "text/troff",
+ ".tar" : "application/x-tar",
+ ".tbz" : "application/x-bzip-compressed-tar",
+ ".tci" : "application/x-topcloud",
+ ".tcl" : "application/x-tcl",
+ ".tex" : "application/x-tex",
+ ".texi" : "application/x-texinfo",
+ ".texinfo" : "application/x-texinfo",
+ ".text" : "text/plain",
+ ".tif" : "image/tiff",
+ ".tiff" : "image/tiff",
+ ".torrent" : "application/x-bittorrent",
+ ".tr" : "text/troff",
+ ".ttf" : "application/x-font-ttf",
+ ".txt" : "text/plain",
+ ".vcf" : "text/x-vcard",
+ ".vcs" : "text/x-vcalendar",
+ ".vrml" : "model/vrml",
+ ".war" : "application/java-archive",
+ ".wav" : "audio/x-wav",
+ ".wma" : "audio/x-ms-wma",
+ ".wmv" : "video/x-ms-wmv",
+ ".wmx" : "video/x-ms-wmx",
+ ".wrl" : "model/vrml",
+ ".wsdl" : "application/wsdl+xml",
+ ".xbm" : "image/x-xbitmap",
+ ".xhtml" : "application/xhtml+xml",
+ ".xls" : "application/vnd.ms-excel",
+ ".xml" : "application/xml",
+ ".xpm" : "image/x-xpixmap",
+ ".xsl" : "application/xml",
+ ".xslt" : "application/xslt+xml",
+ ".yaml" : "text/yaml",
+ ".yml" : "text/yaml",
+ ".zip" : "application/zip"
+ }
+};
View
303 lib/paperboy.js
@@ -0,0 +1,303 @@
+var
+ events = require('events'),
+ fs = require('fs'),
+ url = require('url'),
+ path = require('path');
+
+exports.filepath = function (webroot, url) {
+ // Unescape URL to prevent security holes
+ url = decodeURIComponent(url);
+ // Append index.html if path ends with '/'
+ fp = path.normalize(path.join(webroot, (url.match(/\/$/)=='/') ? url+'index.html' : url));
+ // Sanitize input, make sure people can't use .. to get above webroot
+ if (webroot[webroot.length - 1] !== '/') webroot += '/';
+ if (fp.substr(0, webroot.length) != webroot)
+ return(['Permission Denied', null]);
+ else
+ return([null, fp]);
+};
+
+exports.streamFile = function (filepath, headerFields, stat, res, req, emitter) {
+ var
+ emitter = new events.EventEmitter(),
+ extension = filepath.split('.').pop(),
+ contentType = exports.contentTypes[extension] || 'application/octet-stream',
+ charset = exports.charsets[contentType];
+
+ process.nextTick( function() {
+ if (charset)
+ contentType += '; charset=' + charset;
+ headerFields['Content-Type'] = contentType;
+
+ var etag = '"' + stat.ino + '-' + stat.size + '-' + Date.parse(stat.mtime) +'"';
+ headerFields['ETag'] = etag;
+
+ var statCode;
+ //Check to see if we can send a 304 and skip the send
+ if(req.headers['if-none-match'] == etag){
+ statCode = 304;
+ headerFields['Content-Length'] = 0;
+ }else {
+ headerFields['Content-Length'] = stat.size;
+ statCode = 200;
+ if (headerFields['Expires'] != undefined) {
+ var expires = new Date;
+ expires.setTime(expires.getTime() + headerFields['Expires']);
+ headerFields['Expires'] = expires.toUTCString();
+ }
+ }
+
+ res.writeHead(statCode, headerFields);
+
+ //If we sent a 304, skip sending a body
+ if (statCode == 304 || req.method === 'HEAD') {
+ res.end();
+ emitter.emit("success", statCode);
+ }
+ else {
+ fs.createReadStream(filepath,{'flags': 'r', 'encoding':
+ 'binary', 'mode': 0666, 'bufferSize': 4 * 1024})
+ .addListener("data", function(chunk){
+ res.write(chunk, 'binary');
+ })
+ .addListener("end", function(){
+ emitter.emit("success", statCode);
+ })
+ .addListener("close",function() {
+ res.end();
+ })
+ .addListener("error", function (e) {
+ emitter.emit("error", 500, e);
+ });
+ }
+ });
+ return emitter;
+};
+
+exports.deliver = function (webroot, req, res) {
+ var
+ stream,
+ fpRes = exports.filepath(webroot, url.parse(req.url).pathname),
+ fpErr = fpRes[0],
+ filepath = fpRes[1],
+ beforeCallback,
+ afterCallback,
+ otherwiseCallback,
+ errorCallback,
+ headerFields = {},
+ addHeaderCallback,
+ delegate = {
+ error: function (callback) {
+ errorCallback = callback;
+ return delegate;
+ },
+ before: function (callback) {
+ beforeCallback = callback;
+ return delegate;
+ },
+ after: function (callback) {
+ afterCallback = callback;
+ return delegate;
+ },
+ otherwise: function (callback) {
+ otherwiseCallback = callback;
+ return delegate;
+ },
+ addHeader: function (name, value) {
+ headerFields[name] = value;
+ return delegate;
+ }
+ };
+
+ process.nextTick(function() {
+ // Create default error and otherwise callbacks if none were given.
+ errorCallback = errorCallback || function(statCode) {
+ res.writeHead(statCode, {'Content-Type': 'text/html'});
+ res.end("<h1>HTTP " + statCode + "</h1>");
+ };
+ otherwiseCallback = otherwiseCallback || function() {
+ res.writeHead(404, {'Content-Type': 'text/html'});
+ res.end("<h1>HTTP 404 File not found</h1>");
+ };
+
+ //If file is in a directory outside of the webroot, deny the request
+ if (fpErr) {
+ statCode = 403;
+ if (beforeCallback)
+ beforeCallback();
+ errorCallback(403, 'Forbidden');
+ }
+ else {
+ fs.stat(filepath, function (err, stat) {
+ if( (err || !stat.isFile())) {
+ var exactErr = err || 'File not found';
+ if (beforeCallback)
+ beforeCallback();
+ if (otherwiseCallback)
+ otherwiseCallback(exactErr);
+ } else {
+ //The before callback can abort the transfer by returning false
+ var cancel = beforeCallback && (beforeCallback() === false);
+ if (cancel && otherwiseCallback) {
+ otherwiseCallback();
+ }
+ else {
+ stream = exports.streamFile(filepath, headerFields, stat, res, req)
+
+ if(afterCallback){
+ stream.addListener("success", afterCallback);
+ }
+ if(errorCallback){
+ stream.addListener("error", errorCallback);
+ }
+ }
+ }
+ });
+ }
+ });
+
+ return delegate;
+};
+
+exports.contentTypes = {
+ "aiff": "audio/x-aiff",
+ "arj": "application/x-arj-compressed",
+ "asf": "video/x-ms-asf",
+ "asx": "video/x-ms-asx",
+ "au": "audio/ulaw",
+ "avi": "video/x-msvideo",
+ "bcpio": "application/x-bcpio",
+ "ccad": "application/clariscad",
+ "cod": "application/vnd.rim.cod",
+ "com": "application/x-msdos-program",
+ "cpio": "application/x-cpio",
+ "cpt": "application/mac-compactpro",
+ "csh": "application/x-csh",
+ "css": "text/css",
+ "deb": "application/x-debian-package",
+ "dl": "video/dl",
+ "doc": "application/msword",
+ "drw": "application/drafting",
+ "dvi": "application/x-dvi",
+ "dwg": "application/acad",
+ "dxf": "application/dxf",
+ "dxr": "application/x-director",
+ "etx": "text/x-setext",
+ "ez": "application/andrew-inset",
+ "fli": "video/x-fli",
+ "flv": "video/x-flv",
+ "gif": "image/gif",
+ "gl": "video/gl",
+ "gtar": "application/x-gtar",
+ "gz": "application/x-gzip",
+ "hdf": "application/x-hdf",
+ "hqx": "application/mac-binhex40",
+ "html": "text/html",
+ "ice": "x-conference/x-cooltalk",
+ "ico": "image/x-icon",
+ "ief": "image/ief",
+ "igs": "model/iges",
+ "ips": "application/x-ipscript",
+ "ipx": "application/x-ipix",
+ "jad": "text/vnd.sun.j2me.app-descriptor",
+ "jar": "application/java-archive",
+ "jpeg": "image/jpeg",
+ "jpg": "image/jpeg",
+ "js": "text/javascript",
+ "json": "application/json",
+ "latex": "application/x-latex",
+ "lsp": "application/x-lisp",
+ "lzh": "application/octet-stream",
+ "m": "text/plain",
+ "m3u": "audio/x-mpegurl",
+ "man": "application/x-troff-man",
+ "me": "application/x-troff-me",
+ "midi": "audio/midi",
+ "mif": "application/x-mif",
+ "mime": "www/mime",
+ "movie": "video/x-sgi-movie",
+ "mp4": "video/mp4",
+ "mpg": "video/mpeg",
+ "mpga": "audio/mpeg",
+ "ms": "application/x-troff-ms",
+ "nc": "application/x-netcdf",
+ "oda": "application/oda",
+ "ogm": "application/ogg",
+ "pbm": "image/x-portable-bitmap",
+ "pdf": "application/pdf",
+ "pgm": "image/x-portable-graymap",
+ "pgn": "application/x-chess-pgn",
+ "pgp": "application/pgp",
+ "pm": "application/x-perl",
+ "png": "image/png",
+ "pnm": "image/x-portable-anymap",
+ "ppm": "image/x-portable-pixmap",
+ "ppz": "application/vnd.ms-powerpoint",
+ "pre": "application/x-freelance",
+ "prt": "application/pro_eng",
+ "ps": "application/postscript",
+ "qt": "video/quicktime",
+ "ra": "audio/x-realaudio",
+ "rar": "application/x-rar-compressed",
+ "ras": "image/x-cmu-raster",
+ "rgb": "image/x-rgb",
+ "rm": "audio/x-pn-realaudio",
+ "rpm": "audio/x-pn-realaudio-plugin",
+ "rtf": "text/rtf",
+ "rtx": "text/richtext",
+ "scm": "application/x-lotusscreencam",
+ "set": "application/set",
+ "sgml": "text/sgml",
+ "sh": "application/x-sh",
+ "shar": "application/x-shar",
+ "silo": "model/mesh",
+ "sit": "application/x-stuffit",
+ "skt": "application/x-koan",
+ "smil": "application/smil",
+ "snd": "audio/basic",
+ "sol": "application/solids",
+ "spl": "application/x-futuresplash",
+ "src": "application/x-wais-source",
+ "stl": "application/SLA",
+ "stp": "application/STEP",
+ "sv4cpio": "application/x-sv4cpio",
+ "sv4crc": "application/x-sv4crc",
+ "svg": "image/svg+xml",
+ "swf": "application/x-shockwave-flash",
+ "tar": "application/x-tar",
+ "tcl": "application/x-tcl",
+ "tex": "application/x-tex",
+ "texinfo": "application/x-texinfo",
+ "tgz": "application/x-tar-gz",
+ "tiff": "image/tiff",
+ "tr": "application/x-troff",
+ "tsi": "audio/TSP-audio",
+ "tsp": "application/dsptype",
+ "tsv": "text/tab-separated-values",
+ "txt": "text/plain",
+ "unv": "application/i-deas",
+ "ustar": "application/x-ustar",
+ "vcd": "application/x-cdlink",
+ "vda": "application/vda",
+ "vivo": "video/vnd.vivo",
+ "vrm": "x-world/x-vrml",
+ "wav": "audio/x-wav",
+ "wax": "audio/x-ms-wax",
+ "wma": "audio/x-ms-wma",
+ "wmv": "video/x-ms-wmv",
+ "wmx": "video/x-ms-wmx",
+ "wrl": "model/vrml",
+ "wvx": "video/x-ms-wvx",
+ "xbm": "image/x-xbitmap",
+ "xlw": "application/vnd.ms-excel",
+ "xml": "text/xml",
+ "xpm": "image/x-xpixmap",
+ "xwd": "image/x-xwindowdump",
+ "xyz": "chemical/x-pdb",
+ "zip": "application/zip"
+};
+
+exports.charsets = {
+ 'text/javascript': 'UTF-8',
+ 'text/html': 'UTF-8'
+};
View
61 public/push.js
@@ -0,0 +1,61 @@
+NodePush = function(){
+ //http://javascriptweblog.wordpress.com/2010/11/29/json-and-jsonp/
+ //USAGE:
+ //var obamaTweets = "http://www.twitter.com/status/user_timeline/BARACKOBAMA.json?count=5&callback=JSONPCallback";
+ //jsonp.fetch(obamaTweets, function(data) {console.log(data[0].text)});
+ var jsonp = {
+ callbackCounter: 0,
+
+ fetch: function(url, callback) {
+ var fn = 'JSONPCallback_' + this.callbackCounter++;
+ window[fn] = this.evalJSONP(callback);
+ url = url.replace('=JSONPCallback', '=' + fn);
+
+ var scriptTag = document.createElement('SCRIPT');
+ scriptTag.src = url;
+ document.getElementsByTagName('HEAD')[0].appendChild(scriptTag);
+ },
+
+ evalJSONP: function(callback) {
+ return function(data) {
+ var validJSON = false;
+ if (typeof data == "string") {
+ try {validJSON = JSON.parse(data);} catch (e) {
+ console.log('unable to parse: '+data)
+ }
+ } else {
+ validJSON = JSON.parse(JSON.stringify(data));
+ window.console && console.warn(
+ 'response data was not a JSON string');
+ }
+ if (validJSON) {
+ callback(validJSON);
+ } else {
+ throw("JSONP call returned invalid or empty JSON");
+ }
+ }
+ }
+ };
+
+ function pollerFactory(channel, callback){
+ var url = 'http://pushserver.duostack.com/m/'+channel+'.json?callback=JSONPCallback'
+ jsonp.fetch(url,callback)
+ }
+
+ function pollerManagerFactory(){
+ var pollers = [];
+
+ return {poll: function(channel,callback){
+ pollers.push(pollerFactory(channel, callback))
+ }}
+ }
+
+ function binderFactory(){
+ var pollerMan = pollerManagerFactory();
+ return function(channel,callback){
+ pollerMan.poll(channel,callback)
+ }
+ }
+
+ return {bind: binderFactory()}
+};
View
92 server.js
@@ -1,11 +1,85 @@
-var http = require('http');
+var util = require('util'),
+ inspect = util.inspect,
+ logInspect = function(item){console.log(util.inspect(item))},
+ url = require('url'),
+ querystring = require('querystring'),
+ fs = require('fs'),
+ paperboy = require('./lib/paperboy'),
+ path = require('path'),
+ publicRoot = path.join(path.dirname(__filename), 'public');
-http.createServer(function (req, res) {
- var respond = function(){
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.end('Hello World from Duostack!\n');
- };
- setTimeout(respond,5000);
-}).listen(3000, "127.0.0.1");
+function channelFactory(){
+ var callbacks = [];
+ var messages = [];
-console.log('Server running at http://127.0.0.1:3000/');
+ function textSince(start){
+ return messages.slice(start+1);
+ //.map(function(data){return data.text})
+ //.join('\n')
+ }
+
+ function processCallback(callback){
+ if(callback.lastMessage+1 >= messages.length){
+ callbacks.push(callback)
+ } else {
+ callback(textSince(callback.lastMessage))
+ }
+ }
+
+ function read(sequence, callback){
+ callback.lastMessage = sequence;
+ processCallback(callback);
+ }
+
+ function send(data){
+ messages.push(data);
+ var callbacksToProcess = callbacks;
+ callbacks = [];
+ while(callbacksToProcess.length > 0){
+ var callback = callbacksToProcess.shift();
+ processCallback(callback);
+ }
+ }
+
+ return {read: read, send: send}
+}
+
+function channelManagerFactory(){
+ var channels = {};
+
+ return function(channelName){
+ var c = channels[channelName];
+ if(c) return c;
+
+ return (channels[channelName] = channelFactory());
+ }
+}
+
+function main(){
+ var channelMan = channelManagerFactory();
+
+ var server = require('./lib/node-router').getServer();
+
+ server.get(new RegExp("^/m/([^?]*)$"),function(req,res,match){
+ var lastMessage = parseInt(url.parse(req.url,true).query.s || '-1');
+ logInspect(url.parse(req.url,true));
+ channelMan(match).read(lastMessage, function(data){
+ res.simpleText(200,JSON.stringify(data));
+ })
+ });
+
+ server.post(new RegExp("^/m/([^?]*)$"),function(req,res,match,data){
+ channelMan(match).send(data);
+ res.simpleJson(200,data);
+ },'json');
+
+ //var pushJs = fs.readFileSync('./public/push.js','utf8');
+ server.get('/push.js',function(req,res){
+ paperboy.deliver(publicRoot,req,res)
+ });
+
+ server.listen(3000);
+
+ //require('http').createServer(function (req, res) {logInspect(req); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('');}).listen(3000);
+}
+main();

0 comments on commit 3928d5d

Please sign in to comment.
Something went wrong with that request. Please try again.