Repository: JacksonTian/ping Branch: master Commit: 01850fcb934e Files: 32 Total size: 46.6 KB Directory structure: gitextract_4ffgiy3b/ ├── README.md ├── app.js ├── asset.js ├── assets/ │ ├── eventproxy.js │ ├── index.html │ ├── portal_client.js │ └── post/ │ └── index.js ├── certs/ │ ├── ping-csr.pem │ └── ping-key.pem ├── config.js ├── context.js ├── controllers/ │ └── index.js ├── cookie.js ├── framework.js ├── mime.js ├── model.js ├── models/ │ ├── index.js │ └── post.js ├── package.json ├── partials/ │ ├── comments.view │ ├── friends.view │ └── recentPosts.view ├── ping.js ├── portal.js ├── portals/ │ ├── index.portal │ └── post.portal ├── portalview.js ├── proxy.js ├── server.js ├── session.js ├── utils.js └── views/ └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # Hello, Ping. ## 静态文件服务器部分 [用Node.js打造你的静态文件服务器](http://cnodejs.org/blog/?p=3904) ## 动态文件服务器部分 [用Node.js构建动态服务器基础](http://cnodejs.org/blog/?p=4520) ================================================ FILE: app.js ================================================ var PORT = process.argv[2] || 8000; var http = require("http"); var url = require("url"); var fs = require("fs"); var path = require("path"); var mime = require("./mime").types; var config = require("./config"); var utils = require("./utils"); var zlib = require("zlib"); var server = http.createServer(function(request, response) { response.setHeader("Server", "Node/V5"); response.setHeader('Accept-Ranges', 'bytes'); var pathname = url.parse(request.url).pathname; if (pathname.slice(-1) === "/") { pathname = pathname + config.Welcome.file; } var realPath = path.join("assets", path.normalize(pathname.replace(/\.\./g, ""))); var pathHandle = function (realPath) { fs.stat(realPath, function (err, stats) { if (err) { response.writeHead(404, "Not Found", {'Content-Type': 'text/plain'}); response.write("This request URL " + pathname + " was not found on this server."); response.end(); } else { if (stats.isDirectory()) { realPath = path.join(realPath, "/", config.Welcome.file); pathHandle(realPath); } else { var ext = path.extname(realPath); ext = ext ? ext.slice(1) : 'unknown'; var contentType = mime[ext] || "text/plain"; response.setHeader("Content-Type", contentType); var lastModified = stats.mtime.toUTCString(); var ifModifiedSince = "If-Modified-Since".toLowerCase(); response.setHeader("Last-Modified", lastModified); if (ext.match(config.Expires.fileMatch)) { var expires = new Date(); expires.setTime(expires.getTime() + config.Expires.maxAge * 1000); response.setHeader("Expires", expires.toUTCString()); response.setHeader("Cache-Control", "max-age=" + config.Expires.maxAge); } if (request.headers[ifModifiedSince] && lastModified == request.headers[ifModifiedSince]) { response.writeHead(304, "Not Modified"); response.end(); } else { var compressHandle = function (raw, statusCode, reasonPhrase,contentLength) { var stream = raw; var acceptEncoding = request.headers['accept-encoding'] || ""; var matched = ext.match(config.Compress.match); if (matched && acceptEncoding.match(/\bgzip\b/)) { response.setHeader("Content-Encoding", "gzip"); stream = raw.pipe(zlib.createGzip()); } else if (matched && acceptEncoding.match(/\bdeflate\b/)) { response.setHeader("Content-Encoding", "deflate"); stream = raw.pipe(zlib.createDeflate()); } else{ response.setHeader('Content-Length',contentLength); } response.writeHead(statusCode, reasonPhrase); stream.pipe(response); }; if (request.headers["range"]) { var range = utils.parseRange(request.headers["range"], stats.size); if (range) { response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + stats.size); var raw = fs.createReadStream(realPath, {"start": range.start, "end": range.end}); compressHandle(raw, 206, "Partial Content",(range.end - range.start + 1)); } else { response.writeHead(416, "Request Range Not Satisfiable"); response.end(); } } else { var raw = fs.createReadStream(realPath); compressHandle(raw, 200, "Ok",stats.size); } } } } }); }; pathHandle(realPath); }); server.listen(PORT); console.log("Server running at port: " + PORT + "."); ================================================ FILE: asset.js ================================================ var url = require("url"); var fs = require("fs"); var path = require("path"); var mime = require("./mime").types; var config = require("./config"); var utils = require("./utils"); var zlib = require("zlib"); var Asset = function () {}; Asset.prototype.dispatch = function (request, response) { response.setHeader("Server", "Node/V5"); response.setHeader('Accept-Ranges', 'bytes'); var pathname = url.parse(request.url).pathname; if (pathname.slice(-1) === "/") { pathname = pathname + config.Welcome.file; } var realPath = path.join("assets", path.normalize(pathname.replace(/\.\./g, ""))); var pathHandle = function (realPath) { fs.stat(realPath, function (err, stats) { if (err) { response.writeHead(404, "Not Found", {'Content-Type': 'text/plain'}); response.write("This request URL " + pathname + " was not found on this server."); response.end(); } else { if (stats.isDirectory()) { realPath = path.join(realPath, "/", config.Welcome.file); pathHandle(realPath); } else { var ext = path.extname(realPath); ext = ext ? ext.slice(1) : 'unknown'; var contentType = mime[ext] || "text/plain"; response.setHeader("Content-Type", contentType); response.setHeader('Content-Length', stats.size); var lastModified = stats.mtime.toUTCString(); var ifModifiedSince = "If-Modified-Since".toLowerCase(); response.setHeader("Last-Modified", lastModified); if (ext.match(config.Expires.fileMatch)) { var expires = new Date(); expires.setTime(expires.getTime() + config.Expires.maxAge * 1000); response.setHeader("Expires", expires.toUTCString()); response.setHeader("Cache-Control", "max-age=" + config.Expires.maxAge); } if (request.headers[ifModifiedSince] && lastModified == request.headers[ifModifiedSince]) { response.writeHead(304, "Not Modified"); response.end(); } else { var compressHandle = function (raw, statusCode, reasonPhrase) { var stream = raw; var acceptEncoding = request.headers['accept-encoding'] || ""; var matched = ext.match(config.Compress.match); if (matched && acceptEncoding.match(/\bgzip\b/)) { response.setHeader("Content-Encoding", "gzip"); stream = raw.pipe(zlib.createGzip()); } else if (matched && acceptEncoding.match(/\bdeflate\b/)) { response.setHeader("Content-Encoding", "deflate"); stream = raw.pipe(zlib.createDeflate()); } response.writeHead(statusCode, reasonPhrase); stream.pipe(response); }; if (request.headers["range"]) { var range = utils.parseRange(request.headers["range"], stats.size); if (range) { response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + stats.size); response.setHeader("Content-Length", (range.end - range.start + 1)); var raw = fs.createReadStream(realPath, {"start": range.start, "end": range.end}); compressHandle(raw, 206); } else { response.removeHeader("Content-Length"); response.writeHead(416); response.end(); } } else { var raw = fs.createReadStream(realPath); compressHandle(raw, 200); } } } } }); }; pathHandle(realPath); }; exports.Asset = Asset; ================================================ FILE: assets/eventproxy.js ================================================ /*global exports */ /** * @fileoverview This file is used for define the EventProxy library. * @author Jackson Tian * @version 0.1.0 */ (function () { /** * @description EventProxy. A module that can be mixed in to *any object* in order to provide it with * custom events. You may `bind` or `unbind` a callback function to an event; * `trigger`-ing an event fires all callbacks in succession. * @constructor * @name EventProxy * @class EventProxy. An implementation of task/event based asynchronous pattern. * @example * var render = function (template, resources) {}; * var proxy = new EventProxy(); * proxy.assign("template", "l10n", render); * proxy.trigger("template", template); * proxy.trigger("l10n", resources); */ var EventProxy = function () { if (!(this instanceof EventProxy)) { return new EventProxy(); } this._callbacks = {}; this._fired = {}; }; /** * @description Bind an event, specified by a string name, `ev`, to a `callback` function. * Passing `"all"` will bind the callback to all events fired. * @memberOf EventProxy# * @param {string} eventName Event name. * @param {function} callback Callback. */ EventProxy.prototype.addListener = function (ev, callback) { this._callbacks = this._callbacks || {}; this._callbacks[ev] = this._callbacks[ev] || []; this._callbacks[ev].push(callback); return this; }; EventProxy.prototype.bind = EventProxy.prototype.addListener; EventProxy.prototype.on = EventProxy.prototype.addListener; EventProxy.prototype.await = EventProxy.prototype.addListener; /** * @description Remove one or many callbacks. If `callback` is null, removes all * callbacks for the event. If `ev` is null, removes all bound callbacks * for all events. * @memberOf EventProxy# * @param {string} eventName Event name. * @param {function} callback Callback. */ EventProxy.prototype.removeListener = function (ev, callback) { var calls = this._callbacks, i, l; if (!ev) { this._callbacks = {}; } else if (calls) { if (!callback) { calls[ev] = []; } else { var list = calls[ev]; if (!list) { return this; } l = list.length; for (i = 0; i < l; i++) { if (callback === list[i]) { list[i] = null; break; } } } } return this; }; EventProxy.prototype.unbind = EventProxy.prototype.removeListener; /** * @description Remove all listeners. * It equals unbind(); Just add this API for as same as Event.Emitter. * @memberOf EventProxy# * @param {string} event Event name. */ EventProxy.prototype.removeAllListeners = function (event) { return this.unbind(event); }; /** * @description Trigger an event, firing all bound callbacks. Callbacks are passed the * same arguments as `trigger` is, apart from the event name. * Listening for `"all"` passes the true event name as the first argument. * @param {string} eventName Event name. * @param {mix} data Pass in data. */ EventProxy.prototype.trigger = function (eventName, data) { var list, calls, ev, callback, args, i, l; var both = 2; if (!(calls = this._callbacks)) { return this; } while (both--) { ev = both ? eventName : 'all'; list = calls[ev]; if (list) { for (i = 0, l = list.length; i < l; i++) { if (!(callback = list[i])) { list.splice(i, 1); i--; l--; } else { args = both ? Array.prototype.slice.call(arguments, 1) : arguments; callback.apply(this, args); } } } } return this; }; EventProxy.prototype.emit = EventProxy.prototype.trigger; EventProxy.prototype.fire = EventProxy.prototype.trigger; /** * @description Bind an event like the bind method, but will remove the listener after it was fired. * @param {string} ev Event name. * @param {function} callback Callback. */ EventProxy.prototype.once = function (ev, callback) { var self = this, wrapper = function () { callback.apply(self, arguments); self.unbind(ev, wrapper); }; this.bind(ev, wrapper); return this; }; /** * @description Bind an event, and trigger it immediately. * @param {string} ev Event name. * @param {function} callback Callback. * @param {mix} data The data that will be passed to calback as arguments. */ EventProxy.prototype.immediate = function (ev, callback, data) { this.bind(ev, callback); this.trigger(ev, data); return this; }; var _assign = function (eventname1, eventname2, cb, once) { var proxy = this, length, index = 0, argsLength = arguments.length, bind, _all, callback, events, isOnce, times = 0, flag = {}; // Check the arguments length. if (argsLength < 3) { return this; } events = Array.prototype.slice.apply(arguments, [0, argsLength - 2]); callback = arguments[argsLength - 2]; isOnce = arguments[argsLength - 1]; // Check the callback type. if (typeof callback !== "function") { return this; } length = events.length; bind = function (key) { var method = isOnce ? "once" : "bind"; proxy[method](key, function (data) { proxy._fired[key] = proxy._fired[key] || {}; proxy._fired[key].data = data; if (!flag[key]) { flag[key] = true; times++; } }); }; for (index = 0; index < length; index++) { bind(events[index]); } _all = function () { if (times < length) { return; } var data = []; for (index = 0; index < length; index++) { data.push(proxy._fired[events[index]].data); } if (isOnce) { proxy.unbind("all", _all); } callback.apply(null, data); }; proxy.bind("all", _all); }; /** * @description Assign some events, after all events were fired, the callback will be executed once. * @example * proxy.all(ev1, ev2, callback); * proxy.all([ev1, ev2], callback); * proxy.all(ev1, [ev2, ev3], callback); * @param {string} eventName1 First event name. * @param {string} eventName2 Second event name. * @param {function} callback Callback, that will be called after predefined events were fired. */ EventProxy.prototype.all = function (eventname1, eventname2, cb) { var args = Array.prototype.concat.apply([], arguments); args.push(true); _assign.apply(this, args); return this; }; EventProxy.prototype.assign = EventProxy.prototype.all; /** * @description Assign some events, after all events were fired, the callback will be executed first time. * then any event that predefined be fired again, the callback will executed with the newest data. * @example * proxy.tail(ev1, ev2, callback); * proxy.tail([ev1, ev2], callback); * proxy.tail(ev1, [ev2, ev3], callback); * @memberOf EventProxy# * @param {string} eventName1 First event name. * @param {string} eventName2 Second event name. * @param {function} callback Callback, that will be called after predefined events were fired. */ EventProxy.prototype.tail = function () { var args = Array.prototype.concat.apply([], arguments); args.push(false); _assign.apply(this, args); return this; }; EventProxy.prototype.assignAll = EventProxy.prototype.tail; EventProxy.prototype.assignAlways = EventProxy.prototype.tail; /** * @description The callback will be executed after the event be fired N times. * @memberOf EventProxy# * @param {string} eventName Event name. * @param {number} times N times. * @param {function} callback Callback, that will be called after event was fired N times. */ EventProxy.prototype.after = function (eventName, times, callback) { var proxy = this, firedData = [], all; all = function (name, data) { if (name === eventName) { times--; firedData.push(data); if (times < 1) { proxy.unbind("all", all); callback.apply(null, [firedData]); } } }; proxy.bind("all", all); return this; }; /** * @description The callback will be executed after any registered event was fired. It only executed once. * @memberOf EventProxy# * @param {string} eventName1 Event name. * @param {string} eventName2 Event name. * @param {function} callback The callback will get a map that has data and eventName attributes. */ EventProxy.prototype.any = function () { var proxy = this, index, _bind, len = arguments.length, callback = arguments[len - 1], events = Array.prototype.slice.apply(arguments, [0, len - 1]), count = events.length, _eventName = events.join("_"); proxy.once(_eventName, callback); _bind = function (key) { proxy.bind(key, function (data) { proxy.trigger(_eventName, {"data": data, eventName: key}); }); }; for (index = 0; index < count; index++) { _bind(events[index]); } }; /** * @description The callback will be executed when the evnet name not equals with assigned evnet. * @memberOf EventProxy# * @param {string} eventName Event name. * @param {function} callback Callback. */ EventProxy.prototype.not = function (eventName, callback) { var proxy = this; proxy.bind("all", function (name, data) { if (name !== eventName) { callback(data); } }); }; /** * Create a new EventProxy * @example * var ep = EventProxy.create(); * ep.assign('user', 'articles', function(user, articles) { * // do something... * }); * * // or one line ways: Create EventProxy and Assign * * var ep = EventProxy.create('user', 'articles', function(user, articles) { * // do something... * }); * * @returns {EventProxy} */ EventProxy.create = function () { var ep = new EventProxy(); if (arguments.length) { ep.assign.apply(ep, Array.prototype.slice.call(arguments)); } return ep; }; // Event proxy can be used in browser and Nodejs both. if (typeof exports !== "undefined") { exports.EventProxy = EventProxy; } else { this.EventProxy = EventProxy; } }()); ================================================ FILE: assets/index.html ================================================

It works!

================================================ FILE: assets/portal_client.js ================================================ (function (global) { var Portal = new EventProxy(); Portal.bigPipe = function (id, html) { document.getElementById(id).innerHTML = html; }; global.Portal = Portal; }(this)); ================================================ FILE: assets/post/index.js ================================================ Portal.on("onlineUsers", function (data) { setTimeout(function () { document.getElementById("onlineUsers").innerHTML = "888人"; }, 100); }); ================================================ FILE: certs/ping-csr.pem ================================================ -----BEGIN CERTIFICATE REQUEST----- MIIB1TCCAT4CAQAwfjELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFNoYW5naGFpMREw DwYDVQQHDAhTaGFuZ2hhaTEOMAwGA1UECgwFQ05vZGUxFTATBgNVBAMMDEphY2tz b24gVGlhbjEiMCAGCSqGSIb3DQEJARYTc2h5dm8xOTg3QGdtYWlsLmNvbTCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0mGIYebaRufKjh5sPbeAMi2Y8kuDUfsk ue7KWdnjQd7ZPsUXEhDaN8uljXuHqMCE9cotjGoMK1uwlIsCiFrzv4zj03VABrkc YHBIBW9LW7kssNfzM3Qs4smvqBwJ2+zVUN8itFf1PFGwEpyLV+JB6rO25q7Wbkq6 JEmHWAb1Un0CAwEAAaAXMBUGCSqGSIb3DQEJBzEIDAYxMjM0NTYwDQYJKoZIhvcN AQEFBQADgYEAnkabG4W2k0F0TPAvJtezI/n9qt+2krBG8Zl91l30Ov7wKtv2dimF K1ET00SxJCpj2ED6wXr2THw6GNHjfc8JcTbLxJtcsiUBbmtUhPt02J1SeRqJfYWm ssmWlNGhP/JE/+bNvncEX2D0rwuB0wijxbllN8ULQFBJCxhNo6BpaMM= -----END CERTIFICATE REQUEST----- ================================================ FILE: certs/ping-key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQDSYYhh5tpG58qOHmw9t4AyLZjyS4NR+yS57spZ2eNB3tk+xRcS ENo3y6WNe4eowIT1yi2MagwrW7CUiwKIWvO/jOPTdUAGuRxgcEgFb0tbuSyw1/Mz dCziya+oHAnb7NVQ3yK0V/U8UbASnItX4kHqs7bmrtZuSrokSYdYBvVSfQIDAQAB AoGBAJo7oq6Tfcapu9fA+f2s/7DJuO046wc5JU2igHqqwG7UH1RjTWyTKkfpZm94 9pWCvncrR4U/YbiPub7MwoiQlUytDdAwEu5i+ME0Pb0LCiSGA6XT+XMOaHXQu8Pp o320FUUQ19jD4sIC4ZAYw9JIQqnrRRyL5Ldzz7QWWFEpaN5BAkEA/sKC0oCr0KOc N4M7PmT54p67lCrjp2Q9D8XwGwnD5+dUeEPfa4vIGsoYN5VSkwH8QbbVBK4C7kKW GbaApZKoZQJBANNnty7y0Ata2lbYy7itm18YQiR3jTa+VKq4FpVHBtUphzDei8zh zD/UqrQjj5Od0xG5cGJxofkDy348Bs2XRDkCQErvUeWzHVawWUnm2u0+bFYhVJF/ kBjznhZepYJ+e9Zhr/H0HOqYYhKnMTpgPLqrEdUOf1fyC0Cj61zC1tJc8hUCQFQC BAZRESFiAh++2P3TZ0mbvzT0mRYm/kg1DSxW5D0y2nkuBontNJgs74TUGMsFTYne ke1c0Iu+2U+ZlO5/7OkCQDmCHHoZy2b2DUaweOdqNIkyqcNLAadJDlcZvN35tvj0 bPRvezY2G7LTcg9j/9jhEMGi9ELEQDzYvwIsNKXO2qk= -----END RSA PRIVATE KEY----- ================================================ FILE: config.js ================================================ exports.Expires = { fileMatch: /^(gif|png|jpg|js|css)$/ig, maxAge: 60*60*24*365 }; exports.Compress = { match: /css|html/ig }; exports.Welcome = { file: "index.html" }; exports.Timeout = 20 * 60 * 1000; exports.Secure = null; ================================================ FILE: context.js ================================================ var path = require("path"); var fs = require("fs"); var footprint = require("footprint"); var Context = function (request, response, session, framework) { this.request = request; this.response = response; this.session = session; this.framework = framework; }; Context.prototype.none = function () { this.response.writeHead(204); this.response.end(); }; Context.prototype.renderJSON = function (jsonObj) { this.response.setHeader("Content-Type", "application/json"); this.response.writeHead(200); this.response.end(JSON.stringify(jsonObj)); }; Context.prototype.redirect = function (url) { this.response.setHeader("Location", url); this.response.writeHead(301); this.response.end(); }; Context.prototype._renderView = function (viewEngine, template, data) { var framework = this.framework, request = this.request, response = this.response; try { response.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); response.write(viewEngine.template(template, data)); response.end(); } catch (ex) { console.log(ex.message); console.log(ex.stack); framework.handler500(request, response, "Parse template error."); } }; Context.prototype.renderView = function (view, data) { var context = this, framework = context.framework, request = context.request, response = context.response; // Get engine. var viewEngine = footprint; // Check cache. viewEngine._cache = viewEngine._cache || {}; var template = viewEngine._cache[view]; if (template) { context._renderView(viewEngine, template, data); } else { var filePath = path.join(__dirname, "views/", view); path.exists(filePath, function (exists) { if(!exists) { framework.handler500(request, response, "This template file doesn't exist."); } else { fs.readFile(filePath, "utf8", function(err, file) { if (err) { framework.handler500(request, response, err); } else { viewEngine._cache[view] = file; context._renderView(viewEngine, file, data); } }); } }); } }; Context.prototype.renderPartial = Context.prototype.renderView; exports.Context = Context; ================================================ FILE: controllers/index.js ================================================ var get = exports.get = {}; get.index = function () { var response = this.response; response.setHeader("Content-Type", "text/html"); response.writeHead("200"); response.end("

Hello NodeV5.

\n"); }; get.none = function () { this.none(); }; get.json = function () { var obj = {"Hello": "world!"}; this.renderJSON(obj); }; get.redirect = function () { this.redirect("https://github.com/JacksonTian/nodev5"); }; get.render = function () { var obj = {"title": "NodeV5"}; this.renderView("index.html", obj); }; ================================================ FILE: cookie.js ================================================ exports.parse = function (cookies) { var map = {}; var pairs = cookies.split(";"); pairs.forEach(function (pair) { var kv = pair.split("="); map[kv[0].trim()] = kv[1] || ""; }); return map; }; exports.stringify = function (cookie) { var buffer = [cookie.key, "=", cookie.value]; if (cookie.expires) { buffer.push("; expires=", (new Date(cookie.expires)).toUTCString()); } if (cookie.path) { buffer.push("; path=", cookie.path); } if (cookie.domain) { buffer.push("; domain=", cookie.domain); } if (cookie.secure) { buffer.push("; secure"); } if (cookie.httpOnly) { buffer.push("; httponly"); } return buffer.join(""); }; ================================================ FILE: framework.js ================================================ var http = require("http"); var url = require("url"); var cookie = require("./cookie"); var session = require("./session"); var config = require("./config"); var path = require("path"); var Context = require("./context").Context; var Framework = function () { this.sessionManager = new session.SessionManager(config.Timeout); }; Framework.prototype.dispatch = function (request, response) { if (request.url == "/favicon.ico") { response.writeHead(404, "Not Found"); response.end(); return; } var routeInfo = this.route(request.url); var controller; try { controller = require('./controllers/' + routeInfo.controller); var method = request.method.toLowerCase() || 'get'; var action = controller[method] ? controller[method][routeInfo.action] : null; if (action) { this.enableGet(request, response); this.enableCookie(request, response); this.enablePost(request, response); var curSession = this.enableSession(request, response); // Pass request response session and framework into context object. var context = new Context(request, response, curSession, this); request.on("end", function () { action.apply(context, routeInfo.args); }); } else { this.handler500(request, response, 'Error: Controller "' + routeInfo.controller + '" without action "' + routeInfo.action + '" for "' + request.method + '" request.'); } } catch (ex) { console.log(ex.message); console.log(ex.stack); this.handler500(request, response, 'Error: Controller "' + routeInfo.controller + '" dosen\'t exsit.'); } }; // Add get parse supports Framework.prototype.enableGet = function () { http.IncomingMessage.prototype.get = function (key) { if (!this._urlMap) { this._urlMap = url.parse(this.url, true); } return this._urlMap.query[key]; }; }; // Add cookie parse and set supports Framework.prototype.enableCookie = function (request, response) { http.IncomingMessage.prototype.cookie = function () { this.cookie = function (key) { if (!this._cookieMap) { this._cookieMap = cookie.parse(this.headers.cookie || ""); } return this._cookieMap[key]; }; }; http.ServerResponse.prototype.setCookie = function (cookieObj) { if (!this._setCookieMap) { this._setCookieMap = {}; } this._setCookieMap[cookieObj.key] = cookie.stringify(cookieObj); var returnVal = []; for(var key in this._setCookieMap) { returnVal.push(this._setCookieMap[key]); } this.setHeader("Set-Cookie", returnVal.join(", ")); }; }; // Add post parse supports Framework.prototype.enablePost = function () { http.IncomingMessage.prototype.post = function () { if (!this._postMap) { this._postMap = qs.parse(this.postData); } return this._postMap[key]; }; }; // Recept post body. Framework.prototype.recept = function (request) { if (request.method === "POST") { var _postData = ""; this.on('data', function (chunk) { _postData += chunk; }) .on("end", function () { request.postData = _postData; }); } }; // Add session supports Framework.prototype.enableSession = function (request, response) { var sessionManager = this.sessionManager; var sessionId = request.cookie(session.SESSIONID_KEY); var curSession; if (sessionId && (curSession = sessionManager.get(sessionId))) { if (sessionManager.isTimeout(curSession)) { sessionManager.remove(sessionId); curSession = sessionManager.renew(response); } else { curSession.updateTime(); } } else { curSession = sessionManager.renew(response); } return curSession; }; Framework.prototype.handler500 = function (request, response, err) { response.writeHead(500, {'Content-Type': 'text/plain'}); response.end(err); }; Framework.prototype.route = function (requestUrl) { // /controller/action/parameter1/parameter2 var pathname = url.parse(requestUrl).pathname; var path = pathname.split("/"); path.shift(); // Remove the first "/" return { controller: path[0] || "index", action: path[1] || "index", args: path.slice(2) || [], }; }; exports.Framework = Framework; ================================================ FILE: mime.js ================================================ exports.types = { "css": "text/css", "gif": "image/gif", "html": "text/html", "ico": "image/x-icon", "jpeg": "image/jpeg", "jpg": "image/jpeg", "js": "text/javascript", "json": "application/json", "pdf": "application/pdf", "png": "image/png", "svg": "image/svg+xml", "swf": "application/x-shockwave-flash", "tiff": "image/tiff", "txt": "text/plain", "wav": "audio/x-wav", "wma": "audio/x-ms-wma", "wmv": "video/x-ms-wmv", "xml": "text/xml" }; ================================================ FILE: model.js ================================================ var Model = function () { this.keys = {}; }; Model.prototype.get = function (key, callback) { var val = this.keys[key]; if (val) { } else { if (this.state[key] === "pending") { this.on(key, callback); } else { this.state[key] = "pending"; // TODO get it. this.state[key] = "gotit"; } } }; Model.prototype.register = function (key, callback) { this.keys[key] = callback; }; ================================================ FILE: models/index.js ================================================ module.exports = model = function () {}; model.before = function (callback) { setTimeout(function () { var data = { "title": "用Nodejs打造你的静态文件服务器", "content": 'The concept is come from otakustay. You can find it here.', "author": "Jackson Tian", "time": "2011-12-12" }; callback(data); }, 100); }; ================================================ FILE: models/post.js ================================================ module.exports = model = function () {}; model.before = function (callback) { setTimeout(function () { var data = { "title": "用Nodejs打造你的静态文件服务器", "content": '

在《The Node Beginner Book》的中文版(http://nodebeginner.org/index-zh-cn.html)发布之后,获得国内的好评。也有同学觉得这本书略薄,没有包含进阶式的例子。@otakustay同学说:“确实,我的想法是在这之上补一个简单的MVC框架和一个StaticFile+Mimetype+CacheControl机制,可以成为一个更全面的教程”。正巧的是目前我手里的V5项目有一些特殊性:

', "author": "Jackson", "time": '2011 年 11 月 11 日', "who": "Jackson Tian", "user": {"name": "Jackson Tian", "email": "shyvo1987@gmail.com"} }; callback(data); }, 100); }; model.pipe = function (data, callback) { setTimeout(function () { callback(data.viewName + "'s Pipe Content " + Math.random()); }, Math.random() * 1000); }; ================================================ FILE: package.json ================================================ { "author": "Jackson Tian (http://weibo.com/shyvo)", "name": "node-ping", "description": "Web Framework based on Nodejs.", "version": "0.1.0", "homepage": "https://github.com/JacksonTian/ping", "repository": { "type": "git", "url": "git://github.com/JacksonTian/ping.git" }, "main": "server.js", "scripts": { "test": "expresso test/test.js" }, "engines": { "node": "0.6.6" }, "dependencies": {}, "devDependencies": {} } ================================================ FILE: partials/comments.view ================================================ Comments View <%=who%> ================================================ FILE: partials/friends.view ================================================ Friends view. ================================================ FILE: partials/recentPosts.view ================================================ Recent Posts view. ================================================ FILE: ping.js ================================================ var http = require("http"); var https = require("https"); var config = require("./config"); exports.createServer = function (framework, options) { options = options || config.secure; var server = options ? https.createServer(options) : http.createServer(); server.on("request", function (request, response) { framework.dispatch(request, response); }); return server; }; ================================================ FILE: portal.js ================================================ var url = require("url"); var path = require("path"); var fs = require("fs"); var vm = require("vm"); var PortalView = require("./portalview").PortalView; var Portal = function () { }; Portal.extension = ".portal"; Portal.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g }; Portal.preprocess = function (str, sandbox, settings) { var c = settings || Portal.templateSettings; try { var temp = str.replace(c.interpolate, function (match, code) { var script = "view.viewData." + code; return vm.runInNewContext(script, sandbox); //return sandbox.view.viewData[code] ? sandbox.view.viewData[code] : "The key " + code + " is undefined."; }).replace(c.evaluate, function (match, code) { console.log(code); var result = vm.runInNewContext(code, sandbox); //console.log(result); return result; }); } catch (ex) { return ex.stack; } return temp; }; Portal.postprocess = function (str, sandbox, settings) { var c = settings || Portal.templateSettings; var temp = str.replace(c.interpolate, function (match, code) { return sandbox[code] ? sandbox[code] : "The key " + code + " is undefined."; }); return temp; }; Portal.prototype.route = function (requestUrl) { var portal = requestUrl.split("/")[1]; var pathname = portal + Portal.extension; var realPath = path.join("portals", path.normalize(pathname.replace(/\.\./g, ""))); return { "portal": portal, "path": realPath }; }; Portal.prototype.dispatch = function (request, response) { if (request.url == "/favicon.ico") { response.writeHead(404); response.end(); return; } var routeInfo = this.route(request.url); var portalView = new PortalView(); portalView.all("before", "file", function (viewData, file) { console.log("Portal ready."); portalView.viewData = viewData; var sandbox = { "view": portalView }; var preprocessed = Portal.preprocess(file, sandbox); portalView.after("partial_end", portalView.partialSeq.length, function () { response.writeHead(200); response.write(Portal.postprocess(preprocessed, portalView.partialViews)); console.log("Postprocess done."); portalView.fire("postprocess_done"); }); portalView.getPartials(); portalView.getPipes(); console.log("Preprocess done."); }); portalView.on("postprocess_done", function () { portalView.processAjax(response); portalView.processPipes(response); }); try { var model = new require("./models/" + routeInfo.portal); portalView.model = model; model.before(function (viewData) { portalView.fire("before", viewData); }); } catch (ex) { response.writeHead(500); response.write(ex.stack); response.end("\n"); } fs.readFile(routeInfo.path, function (err, file) { if (err) { response.writeHead(404, {'Content-Type': 'text/plain'}); response.write("This request URL " + pathname + " was not found on this server.\n"); response.end(); } else { portalView.fire("file", file.toString("utf-8")); } }); }; exports.Portal = Portal; ================================================ FILE: portals/index.portal ================================================ <%=title%>

<%=title%>

<%=content%>
<%view.partial("comments");%>
================================================ FILE: portals/post.portal ================================================ <%=title%>

<%=title%>

<%=user.name%> <%=user.email%> <%=content%>
<%view.partial("comments");%>
================================================ FILE: portalview.js ================================================ var util = require("util"); var path = require("path"); var fs = require("fs"); var EventProxy = require("eventproxy").EventProxy; var footprint = require("footprint"); var PortalView = function () { EventProxy.call(this); this.ajaxSeq = []; this.partialViews = {}; this.partialSeq = []; this.pipeSeq = []; this.pipes = []; }; util.inherits(PortalView, EventProxy); /** * Put partial view into sequence. Replace call with view name in template. * After preprocess phase, portal will get all partial views through this sequence. * The view name will be replaced in postprocess phase. */ PortalView.prototype.partial = function (viewName, data) { data = data || this.viewData; this.partialSeq.push({"viewName": viewName, "viewData": data}); return "<%=" + viewName + "%>"; }; /** * Put ajax call into sequence. These scripts will were outputed after all partial views outputed. */ PortalView.prototype.ajax = function (viewName, data) { var script = ''; this.ajaxSeq.push(script); return ""; }; /** * Put pipe call into sequence. Trigger these calls after preprocess phase. */ PortalView.prototype.bigPipe = function (viewName, data) { this.pipeSeq.push({"viewName": viewName, "viewData": data}); return ""; }; /** * */ PortalView.prototype.getPartial = function (data) { var that = this; var viewPath = path.join("partials", data.viewName + ".view"); fs.readFile(viewPath, function (err, view) { if (err) { throw err; } else { console.log(data.viewData); var html = footprint.template(view.toString("utf-8"), data.viewData); that.partialViews[data.viewName] = html; that.fire("partial_end", html); } }); }; PortalView.prototype.getPartials = function () { var that = this; this.partialSeq.forEach(function (val) { that.getPartial(val); }); }; PortalView.prototype.getPipe = function (data) { var that = this; this.model.pipe(data, function (content) { var script = ''; that.pipes.push(script); that.fire("pipe_end", script); }); }; PortalView.prototype.getPipes = function () { var that = this; this.pipeSeq.forEach(function (val) { that.getPipe(val); }); }; PortalView.prototype.processAjax = function (response) { this.ajaxSeq.forEach(function (script) { response.write(script); }); }; /** * @description */ PortalView.prototype.processPipes = function (response) { // Process sequnences this.pipes.forEach(function (script) { response.write(script); }); // Process coming pipes. var times = this.pipeSeq.length - this.pipes.length; this.on("pipe_end", function (script) { response.write(script); }); this.after("pipe_end", times, function () { console.log("Complete."); response.end(); }); }; exports.PortalView = PortalView; ================================================ FILE: proxy.js ================================================ var http = require("http"); var cp = require("child_process"); var map = { "www.nodev5.com": "www", "assets.nodev5.com": "assets" }; var fork = function () { var worker = cp.fork.apply(cp, arguments); worker.on('message', function (message) { }); return worker; }; var workers = { www: cp.fork(__dirname + "/asset_app.js", [8001]), assets: cp.fork(__dirname + "/v5_app.js", [8002]), }; var server = http.createServer(function(request, response) { // TODO switch() var worker = workers[map[request.headers.host]] || workers["assets"]; //worker.send("", server._handle); // console.log(server); // response.writeHead(200); // response.end(); }); server.on("connection", function (socket) { console.log(socket); }); server.listen(8080); console.log("Server runing at port: 80."); ================================================ FILE: server.js ================================================ var http = require("http"); var ping = require("./ping"); var Framework = require("./framework").Framework; var Asset = require("./asset").Asset; var Portal = require("./portal").Portal; var portal = new Portal(); ping.createServer(portal).listen(8000); console.log("Portal server is running at 8000 port."); // Static file server. var asset = new Asset(); ping.createServer(asset).listen(8001); console.log("Static file server is running at 8001 port."); // Dynamic handle. var framework = new Framework(); ping.createServer(framework).listen(8002); console.log("Dynamic server is running at 8002 port."); ================================================ FILE: session.js ================================================ var Session = function (sessionId) { this.sessionId = sessionId; this._map = {}; }; Session.prototype.set = function (name, value) { this._map[name] = value; }; Session.prototype.get = function (name) { return this._map[name]; }; Session.prototype.remove = function (key) { delete this._map[key]; }; Session.prototype.removeAll = function () { delete this._map; this._map = {}; }; Session.prototype.updateTime = function () { this._updateTime = new Date().getTime(); }; var SESSIONID_KEY = exports.SESSIONID_KEY = "session_id"; var SessionManager = function (timeout) { this.timeout = timeout; this._sessions = {}; }; SessionManager.prototype.renew = function (response) { var that = this; var sessionId = [new Date().getTime(), Math.round(Math.random() * 1000)].join(""); var session = new Session(sessionId); session.updateTime(); this._sessions[sessionId] = session; var clientTimeout = 30 * 24 * 60 * 60 * 1000; var cookie = {key: SESSIONID_KEY, value: sessionId, path: "/", expires: new Date().getTime() + clientTimeout}; response.setCookie(cookie); return session; }; SessionManager.prototype.get = function (sessionId) { return this._sessions[sessionId]; }; SessionManager.prototype.remove = function (sessionId) { delete this._sessions[sessionId]; }; SessionManager.prototype.isTimeout = function (session) { return (session._updateTime + this.timeout) < new Date().getTime(); }; exports.Session = Session; exports.SessionManager = SessionManager; ================================================ FILE: utils.js ================================================ exports.parseRange = function (str, size) { if (str.indexOf(",") != -1) { return; } var range = str.split("-"), start = parseInt(range[0].match(/\d+/)[0], 10), end = parseInt(range[1], 10); // Case: 100- if (isNaN(end)) { end = size - 1; } // Invalid if (isNaN(start) || isNaN(end) || start > end || end > size) { return; } return {start: start, end: end}; }; ================================================ FILE: views/index.html ================================================ <%=title%>

<%=title%>