Skip to content
This repository
Browse code

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.
  • Loading branch information...
commit 51aa577fbebf63d1a707ce3de652decdd1f12bba 1 parent ab10c71
Reid Burke reid authored

Showing 3 changed files with 71 additions and 63 deletions. Show diff stats Hide diff stats

  1. +6 44 lib/blizzard/index.js
  2. +58 0 lib/hijack.js
  3. +7 19 lib/hub/listener.js
50 lib/blizzard/index.js
@@ -4,6 +4,8 @@ var http = require("http");
4 4 var util = require("util");
5 5 var urlParser = require("url");
6 6
  7 +var hijack = require("../hijack");
  8 +
7 9 var BlizzardSession = require("./session");
8 10
9 11 var EventEmitter2 = require("eventemitter2").EventEmitter2;
@@ -177,50 +179,10 @@ Blizzard.prototype.connect = function (url, cb) {
177 179 * @param {HTTPServer} httpServer
178 180 */
179 181 Blizzard.prototype.listen = function (httpServer) {
180   - var self = this,
181   - listeners = httpServer.listeners("upgrade");
182   -
183   - httpServer.removeAllListeners("upgrade");
184   -
185   - httpServer.on("upgrade", function (req, socket, head) {
186   -
187   - // First, attempt to upgrade to Blizzard.
188   - var success = self.serverUpgrade(req, socket, head),
189   - written = false,
190   - originalWrite;
191   -
192   - if (!success) {
193   - // Blizzard was not the protocol asked for.
194   -
195   - // If data is written to the socket,
196   - // the connection was upgraded.
197   - originalWrite = socket.write;
198   - socket.write = function (string, encoding, fd) {
199   - written = true;
200   - originalWrite.call(this, string, encoding, fd);
201   - };
202   -
203   - // Try other upgrade listeners, e.g. Socket.io.
204   - listeners.forEach(function (fn) {
205   - fn(req, socket, head);
206   - });
207   -
208   - // Restore original write.
209   - socket.write = originalWrite;
210   -
211   - // No listener wrote to the socket.
212   - // Destroy the connection.
213   - if (!written && socket.writable) {
214   - socket.write([
215   - "HTTP/1.1 400 Bad Request",
216   - "X-Reason: Protocol not supported",
217   - "Connection: close",
218   - "Content-Length: 0",
219   - "", ""
220   - ].join("\r\n"));
221   - socket.end();
222   - }
223   - }
  182 + var self = this;
  183 +
  184 + hijack(httpServer, "upgrade", function (req, socket, head) {
  185 + return self.serverUpgrade(req, socket, head);
224 186 });
225 187
226 188 return this;
58 lib/hijack.js
... ... @@ -0,0 +1,58 @@
  1 +"use strict";
  2 +
  3 +/**
  4 + * @module hijack
  5 + */
  6 +
  7 +/**
  8 + * Attach the provided function as the first
  9 + * listener of the given EventEmitter event.
  10 + *
  11 + * All listeners of the event will be removed
  12 + * and replaced with a listener that will run
  13 + * the provided function first, followed by
  14 + * the original listeners if the function
  15 + * returned false.
  16 + *
  17 + * Works with Node.js v0.7+ where the array
  18 + * returned by `ee.listeners()` is a reference.
  19 + *
  20 + * @method hijack
  21 + * @param {EventEmitter} ee Emitter.
  22 + * @param {String} event Event name.
  23 + * @param {Function} firstFn Function to run first.
  24 + */
  25 +module.exports = function hijack(ee, event, firstFn) {
  26 + var listeners = ee.listeners(event),
  27 + i = 0,
  28 + length = listeners.length,
  29 + originalListeners = [];
  30 +
  31 + // Note: listeners is a reference in Node v0.7.
  32 + // Calling `removeAllListeners` no longer destroys
  33 + // the listener array, which causes it survive
  34 + // as a reference. See joyent/node commits:
  35 + // - 78dc13fbf97e2e3003e6f3baacdd5ff60e8de3f7
  36 + // - 928ea564d16da47e615ddac627e0b4d4a40d8196
  37 + //
  38 + // Make a copy first.
  39 + for (; i < length; i += 1) {
  40 + originalListeners[i] = listeners[i];
  41 + }
  42 +
  43 + ee.removeAllListeners(event);
  44 +
  45 + ee.on(event, function () {
  46 + var args = Array.prototype.slice.call(arguments),
  47 + stack = [firstFn].concat(originalListeners),
  48 + handled;
  49 +
  50 + handled = firstFn.apply(ee, args);
  51 +
  52 + if (!handled) {
  53 + originalListeners.forEach(function (fn) {
  54 + fn.apply(ee, args);
  55 + });
  56 + }
  57 + });
  58 +};
26 lib/hub/listener.js
@@ -5,7 +5,8 @@
5 5 */
6 6
7 7 var util = require("util"),
8   - url = require("url");
  8 + url = require("url"),
  9 + hijack = require("../hijack");
9 10
10 11 /**
11 12 * A **HubListener** augments an existing `http.Server`,
@@ -39,14 +40,9 @@ var proto = HubListener.prototype;
39 40 * @private
40 41 */
41 42 proto._setupServer = function () {
42   - var self = this,
43   - upgradeListeners = this.server.listeners("upgrade"),
44   - requestListeners = this.server.listeners("request");
  43 + var self = this;
45 44
46   - this.server.removeAllListeners("upgrade");
47   - this.server.removeAllListeners("request");
48   -
49   - this.server.on("upgrade", function (req, socket, head) {
  45 + hijack(this.server, "upgrade", function (req, socket, head) {
50 46 var server = this,
51 47 parsedUrl = url.parse(req.url),
52 48 resolvedUrl,
@@ -70,22 +66,14 @@ proto._setupServer = function () {
70 66
71 67 if (yetiUpgrade) {
72 68 self.hub.server.emit("upgrade", req, socket, head);
73   - return;
  69 + return true;
74 70 }
75   -
76   - upgradeListeners.forEach(function (listener) {
77   - listener.call(server, req, socket, head);
78   - });
79 71 });
80 72
81   - this.server.on("request", function (req, res) {
  73 + hijack(this.server, "request", function faxd (req, res) {
82 74 if (self.match(req, res)) {
83   - return;
  75 + return true;
84 76 }
85   - var server = this;
86   - requestListeners.forEach(function (listener) {
87   - listener.call(server, req, res);
88   - });
89 77 });
90 78 };
91 79

0 comments on commit 51aa577

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