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%>
================================================
FILE: portals/post.portal
================================================
<%=title%>
<%=title%>
<%=user.name%>
<%=user.email%>
<%=content%>
================================================
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%>