From 51aa577fbebf63d1a707ce3de652decdd1f12bba Mon Sep 17 00:00:00 2001 From: Reid Burke Date: Tue, 12 Jun 2012 15:26:00 -0700 Subject: [PATCH] Make a copy of `listeners` for hijacking events. Node.js v0.7+ `event.listeners()` returns a reference that continues to be used internally after calling `removeAllListeners()`. Make a copy before calling the original functions to prevent recursion on v0.7+. Since this pattern is common in Yeti, move this event hijacking into its own module. --- lib/blizzard/index.js | 50 +++++-------------------------------- lib/hijack.js | 58 +++++++++++++++++++++++++++++++++++++++++++ lib/hub/listener.js | 26 ++++++------------- 3 files changed, 71 insertions(+), 63 deletions(-) create mode 100644 lib/hijack.js diff --git a/lib/blizzard/index.js b/lib/blizzard/index.js index acc10f97..e44ecedb 100644 --- a/lib/blizzard/index.js +++ b/lib/blizzard/index.js @@ -4,6 +4,8 @@ var http = require("http"); var util = require("util"); var urlParser = require("url"); +var hijack = require("../hijack"); + var BlizzardSession = require("./session"); var EventEmitter2 = require("eventemitter2").EventEmitter2; @@ -177,50 +179,10 @@ Blizzard.prototype.connect = function (url, cb) { * @param {HTTPServer} httpServer */ Blizzard.prototype.listen = function (httpServer) { - var self = this, - listeners = httpServer.listeners("upgrade"); - - httpServer.removeAllListeners("upgrade"); - - httpServer.on("upgrade", function (req, socket, head) { - - // First, attempt to upgrade to Blizzard. - var success = self.serverUpgrade(req, socket, head), - written = false, - originalWrite; - - if (!success) { - // Blizzard was not the protocol asked for. - - // If data is written to the socket, - // the connection was upgraded. - originalWrite = socket.write; - socket.write = function (string, encoding, fd) { - written = true; - originalWrite.call(this, string, encoding, fd); - }; - - // Try other upgrade listeners, e.g. Socket.io. - listeners.forEach(function (fn) { - fn(req, socket, head); - }); - - // Restore original write. - socket.write = originalWrite; - - // No listener wrote to the socket. - // Destroy the connection. - if (!written && socket.writable) { - socket.write([ - "HTTP/1.1 400 Bad Request", - "X-Reason: Protocol not supported", - "Connection: close", - "Content-Length: 0", - "", "" - ].join("\r\n")); - socket.end(); - } - } + var self = this; + + hijack(httpServer, "upgrade", function (req, socket, head) { + return self.serverUpgrade(req, socket, head); }); return this; diff --git a/lib/hijack.js b/lib/hijack.js new file mode 100644 index 00000000..f6f166e8 --- /dev/null +++ b/lib/hijack.js @@ -0,0 +1,58 @@ +"use strict"; + +/** + * @module hijack + */ + +/** + * Attach the provided function as the first + * listener of the given EventEmitter event. + * + * All listeners of the event will be removed + * and replaced with a listener that will run + * the provided function first, followed by + * the original listeners if the function + * returned false. + * + * Works with Node.js v0.7+ where the array + * returned by `ee.listeners()` is a reference. + * + * @method hijack + * @param {EventEmitter} ee Emitter. + * @param {String} event Event name. + * @param {Function} firstFn Function to run first. + */ +module.exports = function hijack(ee, event, firstFn) { + var listeners = ee.listeners(event), + i = 0, + length = listeners.length, + originalListeners = []; + + // Note: listeners is a reference in Node v0.7. + // Calling `removeAllListeners` no longer destroys + // the listener array, which causes it survive + // as a reference. See joyent/node commits: + // - 78dc13fbf97e2e3003e6f3baacdd5ff60e8de3f7 + // - 928ea564d16da47e615ddac627e0b4d4a40d8196 + // + // Make a copy first. + for (; i < length; i += 1) { + originalListeners[i] = listeners[i]; + } + + ee.removeAllListeners(event); + + ee.on(event, function () { + var args = Array.prototype.slice.call(arguments), + stack = [firstFn].concat(originalListeners), + handled; + + handled = firstFn.apply(ee, args); + + if (!handled) { + originalListeners.forEach(function (fn) { + fn.apply(ee, args); + }); + } + }); +}; diff --git a/lib/hub/listener.js b/lib/hub/listener.js index 90ec7a8e..defb48fb 100644 --- a/lib/hub/listener.js +++ b/lib/hub/listener.js @@ -5,7 +5,8 @@ */ var util = require("util"), - url = require("url"); + url = require("url"), + hijack = require("../hijack"); /** * A **HubListener** augments an existing `http.Server`, @@ -39,14 +40,9 @@ var proto = HubListener.prototype; * @private */ proto._setupServer = function () { - var self = this, - upgradeListeners = this.server.listeners("upgrade"), - requestListeners = this.server.listeners("request"); + var self = this; - this.server.removeAllListeners("upgrade"); - this.server.removeAllListeners("request"); - - this.server.on("upgrade", function (req, socket, head) { + hijack(this.server, "upgrade", function (req, socket, head) { var server = this, parsedUrl = url.parse(req.url), resolvedUrl, @@ -70,22 +66,14 @@ proto._setupServer = function () { if (yetiUpgrade) { self.hub.server.emit("upgrade", req, socket, head); - return; + return true; } - - upgradeListeners.forEach(function (listener) { - listener.call(server, req, socket, head); - }); }); - this.server.on("request", function (req, res) { + hijack(this.server, "request", function faxd (req, res) { if (self.match(req, res)) { - return; + return true; } - var server = this; - requestListeners.forEach(function (listener) { - listener.call(server, req, res); - }); }); };