forked from TooTallNate/node-stream-stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstream-stack.js
More file actions
209 lines (193 loc) · 6.89 KB
/
Copy pathstream-stack.js
File metadata and controls
209 lines (193 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
var Stream = require('stream').Stream;
/**
* StreamStack
* -----------
* Turns low-level `Stream` objects into stackable stream, meant
* to fill out your desired protocol stack. But also allow for
* the protocol to be implemented independent of the underlying
* transport.
* An example overall stack could look like:
* - net.Stream <- TCP Layer
* - HttpRequestStack <- HTTP Layer
* - `write()`s an HTTP request upstream
* - Response comes back with 'gzip' transfer-encoding
* - GzipDecoderStack <- Decoding Layer
* - `.pipe()` into a 'fs.WriteStream' <- Save to a File
*/
function StreamStack(stream, events) {
if (!(stream instanceof Stream)) {
throw new Error("StreamStack expects an instance of 'Stream' as an argument!");
}
if (!(this instanceof StreamStack)) {
return new StreamStack(stream, events);
}
Stream.call(this);
// If this is the first time the parent stream has been used in a
// StreamStack, then a StackEmitter will need to be injected into the stream.
if (!stream._stackEmitter) {
StackEmitter.call(stream);
}
stream._stacks.push(this);
// A reference to the parent stream for event handlers, etc.
this.stream = stream;
// TODO: Remove, if I can find a good reason to.
events = events || {};
if (!('data' in events))
events.data = proxyEvent('data');
if (!('end' in events))
events.end = proxyEvent('end');
if (!('error' in events))
events.error = proxyEvent('error');
if (!('close' in events))
events.close = proxyEvent('close');
if (!('fd' in events))
events.fd = proxyEvent('fd');
if (!('drain' in events))
events.drain = proxyEvent('drain');
// If the StreamStack instance intends on intercepting events emitted from
// the parent stream, then the handlers need to be passed as a second 'events'
// object in the constructor. It takes care of attaching them to the parent
// stream. Handlers are invoked in 'this' StreamStack instance.
if (events) {
this._stackEvents = {};
for (var ev in events) {
this._stackEvents[ev] = events[ev].bind(this);
stream.on(ev, this._stackEvents[ev]);
}
}
}
require('util').inherits(StreamStack, Stream);
exports.StreamStack = StreamStack;
// By default, just proxy all the standard ReadStream and WriteStream
// functions upstream. If the StreamStack implementation needs to overwrite
// or augment any of the behavior, then simply overwrite that function.
// The most common is to augment the 'write()' function, such that the
// passed data goes through some kind of filter before being passed to
// the parent stream.
StreamStack.prototype.write = function(buf, type) {
return this.stream.write(buf, type);
}
StreamStack.prototype.end = function(buf, type) {
if (buf) {
this.write(buf, type);
}
return this.stream.end();
}
StreamStack.prototype.pause = function() {
if (this.stream.pause) {
return this.stream.pause();
} else {
return this.stream.emit('pause');
}
}
StreamStack.prototype.resume = function() {
if (this.stream.resume) {
return this.stream.resume();
} else {
return this.stream.emit('resume');
}
}
StreamStack.prototype.destroy = function(error) {
return this.stream.destory(error);
}
// The 'cleanup()' function should be called after a StreamStack instance is
// finished doing it's "thing", to cleanly allow another new StreamStack
// instance to be attached to the parent Stream.
StreamStack.prototype.cleanup = function() {
// Remove 'this' from the parent Stream's '_stacks' Array
var index = this.stream._stacks.indexOf(this);
this.stream._stacks.splice(index, 1);
// Set 'this.stream' to null.
// If any events were binded through the constructor, they get unbinded here
if (this._stackEvents) {
for (var ev in this._stackEvents) {
this.stream.removeListener(ev, this._stackEvents[ev]);
}
this._stackEvents = null;
}
// TODO: Maybe 'delete' instead? Is there any benefit?
this.stream = null;
}
// By default, the 'readable' and 'writable' property lookups get proxied
// to the parent stream. You can set the variables if needed, and to relinquish
// control of the variable back upstream, set it to `undefined`.
Object.defineProperty(StreamStack.prototype, "readable", {
get: function() {
if (this._readable != undefined) {
return this._readable;
}
return this.stream.readable;
},
set: function(value) {
this._readable = value;
},
enumerable: true
});
Object.defineProperty(StreamStack.prototype, "writable", {
get: function() {
if (this._writable != undefined) {
return this._writable;
}
return this.stream.writable;
},
set: function(value) {
this._writable = value;
},
enumerable: true
});
// Walks up the 'stream' properties until it finds and returns the top-most
// stream. i.e. it gets the low-level stream this stack is based on.
Object.defineProperty(StreamStack.prototype, "topStream", {
get: function() {
var rtn = this.stream;
while (rtn.stream) {
rtn = rtn.stream;
}
return rtn;
},
enumerable: true
});
// Stupid workaround. Attach a listener for the given 'eventName'.
// The callback simply re-emits the events and all arguments on 'this'.
function proxyEvent(eventName) {
return function() {
var args = [eventName];
args = args.concat(args.slice.call(arguments));
this.emit.apply(this, args);
}
}
// Parent streams need to have StackEmitter called on them the first time a
// StreamStack instance is attempting to use it. The __proto__ of the parent
// stream will be injected with StackEmitter's prototype, to benefit from
// the overwritten 'emit()' function.
function StackEmitter() {
// The Array that holds the active StreamStack instances on a parent Stream.
this._stacks = [];
// Get a reference to the original 'emit' function, since we're about to
// monkey-patch with our own 'emit' function.
this._origEmit = this.emit;
// Mix-in the rest of the StackEmitter properties.
for (var prop in StackEmitter.prototype) {
this[prop] = StackEmitter.prototype[prop];
}
}
// A flag to indicate that the parent stream has already been injected.
StackEmitter.prototype._stackEmitter = true;
// The custom 'emit()' function is responsible for iterating through the list
// of active StreamStack instances, and IFF the StreamStack instance didn't
// pass a handler to the current event, it should re-emit on the child as well.
StackEmitter.prototype.emit = function(eventName) {
var stack;
// Emit on the parent Stream first
var rtn = this._origEmit.apply(this, arguments);
// Next re-emit on all the active StreamStack instances (if any)
for (var i=0, l=this._stacks.length; i<l; i++) {
stack = this._stacks[i];
if (!stack._stackEvents || !(eventName in stack._stackEvents)) {
if (!stack.emit.apply(stack, arguments)) {
rtn = false;
}
}
}
return rtn;
}