macgyver 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/Growl.h +5 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h +551 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlDefines.h +341 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Resources/Info.plist +40 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/_CodeSignature/CodeResources +34 -0
- data/assets/MacGap.app/Contents/Info.plist +48 -0
- data/assets/MacGap.app/Contents/MacOS/MacGap +0 -0
- data/assets/MacGap.app/Contents/PkgInfo +1 -0
- data/assets/MacGap.app/Contents/Resources/_debugger.js +1718 -0
- data/assets/MacGap.app/Contents/Resources/_http_agent.js +310 -0
- data/assets/MacGap.app/Contents/Resources/_http_client.js +533 -0
- data/assets/MacGap.app/Contents/Resources/_http_common.js +222 -0
- data/assets/MacGap.app/Contents/Resources/_http_incoming.js +194 -0
- data/assets/MacGap.app/Contents/Resources/_http_outgoing.js +597 -0
- data/assets/MacGap.app/Contents/Resources/_http_server.js +510 -0
- data/assets/MacGap.app/Contents/Resources/_linklist.js +76 -0
- data/assets/MacGap.app/Contents/Resources/_stream_duplex.js +69 -0
- data/assets/MacGap.app/Contents/Resources/_stream_passthrough.js +41 -0
- data/assets/MacGap.app/Contents/Resources/_stream_readable.js +900 -0
- data/assets/MacGap.app/Contents/Resources/_stream_transform.js +204 -0
- data/assets/MacGap.app/Contents/Resources/_stream_writable.js +456 -0
- data/assets/MacGap.app/Contents/Resources/_tls_legacy.js +887 -0
- data/assets/MacGap.app/Contents/Resources/_tls_wrap.js +831 -0
- data/assets/MacGap.app/Contents/Resources/application.icns +0 -0
- data/assets/MacGap.app/Contents/Resources/assert.js +326 -0
- data/assets/MacGap.app/Contents/Resources/buffer.js +724 -0
- data/assets/MacGap.app/Contents/Resources/child_process.js +1107 -0
- data/assets/MacGap.app/Contents/Resources/cluster.js +613 -0
- data/assets/MacGap.app/Contents/Resources/console.js +108 -0
- data/assets/MacGap.app/Contents/Resources/constants.js +22 -0
- data/assets/MacGap.app/Contents/Resources/crypto.js +691 -0
- data/assets/MacGap.app/Contents/Resources/dgram.js +459 -0
- data/assets/MacGap.app/Contents/Resources/dns.js +274 -0
- data/assets/MacGap.app/Contents/Resources/domain.js +292 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Window.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/events.js +312 -0
- data/assets/MacGap.app/Contents/Resources/freelist.js +43 -0
- data/assets/MacGap.app/Contents/Resources/fs.js +1732 -0
- data/assets/MacGap.app/Contents/Resources/http.js +119 -0
- data/assets/MacGap.app/Contents/Resources/https.js +134 -0
- data/assets/MacGap.app/Contents/Resources/module.js +529 -0
- data/assets/MacGap.app/Contents/Resources/net.js +1378 -0
- data/assets/MacGap.app/Contents/Resources/nodelike.js +195 -0
- data/assets/MacGap.app/Contents/Resources/os.js +64 -0
- data/assets/MacGap.app/Contents/Resources/path.js +517 -0
- data/assets/MacGap.app/Contents/Resources/public/index.html +38 -0
- data/assets/MacGap.app/Contents/Resources/punycode.js +507 -0
- data/assets/MacGap.app/Contents/Resources/querystring.js +206 -0
- data/assets/MacGap.app/Contents/Resources/readline.js +1311 -0
- data/assets/MacGap.app/Contents/Resources/repl.js +945 -0
- data/assets/MacGap.app/Contents/Resources/smalloc.js +90 -0
- data/assets/MacGap.app/Contents/Resources/stream.js +127 -0
- data/assets/MacGap.app/Contents/Resources/string_decoder.js +189 -0
- data/assets/MacGap.app/Contents/Resources/sys.js +24 -0
- data/assets/MacGap.app/Contents/Resources/timers.js +568 -0
- data/assets/MacGap.app/Contents/Resources/tls.js +220 -0
- data/assets/MacGap.app/Contents/Resources/tty.js +129 -0
- data/assets/MacGap.app/Contents/Resources/url.js +693 -0
- data/assets/MacGap.app/Contents/Resources/util.js +688 -0
- data/assets/MacGap.app/Contents/Resources/vm.js +73 -0
- data/assets/MacGap.app/Contents/Resources/zlib.js +524 -0
- data/assets/index.html +38 -0
- data/bin/macgyver +104 -0
- data/macgyver.gemspec +19 -0
- data/test/public/index.html +27 -0
- metadata +121 -0
@@ -0,0 +1,310 @@
|
|
1
|
+
// Copyright Joyent, Inc. and other Node contributors.
|
2
|
+
//
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
// copy of this software and associated documentation files (the
|
5
|
+
// "Software"), to deal in the Software without restriction, including
|
6
|
+
// without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
// persons to whom the Software is furnished to do so, subject to the
|
9
|
+
// following conditions:
|
10
|
+
//
|
11
|
+
// The above copyright notice and this permission notice shall be included
|
12
|
+
// in all copies or substantial portions of the Software.
|
13
|
+
//
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
17
|
+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
18
|
+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
20
|
+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
var net = require('net');
|
23
|
+
var url = require('url');
|
24
|
+
var util = require('util');
|
25
|
+
var EventEmitter = require('events').EventEmitter;
|
26
|
+
var ClientRequest = require('_http_client').ClientRequest;
|
27
|
+
var debug = util.debuglog('http');
|
28
|
+
|
29
|
+
// New Agent code.
|
30
|
+
|
31
|
+
// The largest departure from the previous implementation is that
|
32
|
+
// an Agent instance holds connections for a variable number of host:ports.
|
33
|
+
// Surprisingly, this is still API compatible as far as third parties are
|
34
|
+
// concerned. The only code that really notices the difference is the
|
35
|
+
// request object.
|
36
|
+
|
37
|
+
// Another departure is that all code related to HTTP parsing is in
|
38
|
+
// ClientRequest.onSocket(). The Agent is now *strictly*
|
39
|
+
// concerned with managing a connection pool.
|
40
|
+
|
41
|
+
function Agent(options) {
|
42
|
+
if (!(this instanceof Agent))
|
43
|
+
return new Agent(options);
|
44
|
+
|
45
|
+
EventEmitter.call(this);
|
46
|
+
|
47
|
+
var self = this;
|
48
|
+
|
49
|
+
self.defaultPort = 80;
|
50
|
+
self.protocol = 'http:';
|
51
|
+
|
52
|
+
self.options = util._extend({}, options);
|
53
|
+
|
54
|
+
// don't confuse net and make it think that we're connecting to a pipe
|
55
|
+
self.options.path = null;
|
56
|
+
self.requests = {};
|
57
|
+
self.sockets = {};
|
58
|
+
self.freeSockets = {};
|
59
|
+
self.keepAliveMsecs = self.options.keepAliveMsecs || 1000;
|
60
|
+
self.keepAlive = self.options.keepAlive || false;
|
61
|
+
self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets;
|
62
|
+
self.maxFreeSockets = self.options.maxFreeSockets || 256;
|
63
|
+
|
64
|
+
self.on('free', function(socket, options) {
|
65
|
+
var name = self.getName(options);
|
66
|
+
debug('agent.on(free)', name);
|
67
|
+
|
68
|
+
if (!socket.destroyed &&
|
69
|
+
self.requests[name] && self.requests[name].length) {
|
70
|
+
self.requests[name].shift().onSocket(socket);
|
71
|
+
if (self.requests[name].length === 0) {
|
72
|
+
// don't leak
|
73
|
+
delete self.requests[name];
|
74
|
+
}
|
75
|
+
} else {
|
76
|
+
// If there are no pending requests, then put it in
|
77
|
+
// the freeSockets pool, but only if we're allowed to do so.
|
78
|
+
var req = socket._httpMessage;
|
79
|
+
if (req &&
|
80
|
+
req.shouldKeepAlive &&
|
81
|
+
!socket.destroyed &&
|
82
|
+
self.options.keepAlive) {
|
83
|
+
var freeSockets = self.freeSockets[name];
|
84
|
+
var freeLen = freeSockets ? freeSockets.length : 0;
|
85
|
+
var count = freeLen;
|
86
|
+
if (self.sockets[name])
|
87
|
+
count += self.sockets[name].length;
|
88
|
+
|
89
|
+
if (count >= self.maxSockets || freeLen >= self.maxFreeSockets) {
|
90
|
+
self.removeSocket(socket, options);
|
91
|
+
socket.destroy();
|
92
|
+
} else {
|
93
|
+
freeSockets = freeSockets || [];
|
94
|
+
self.freeSockets[name] = freeSockets;
|
95
|
+
socket.setKeepAlive(true, self.keepAliveMsecs);
|
96
|
+
socket.unref();
|
97
|
+
socket._httpMessage = null;
|
98
|
+
self.removeSocket(socket, options);
|
99
|
+
freeSockets.push(socket);
|
100
|
+
}
|
101
|
+
} else {
|
102
|
+
self.removeSocket(socket, options);
|
103
|
+
socket.destroy();
|
104
|
+
}
|
105
|
+
}
|
106
|
+
});
|
107
|
+
}
|
108
|
+
|
109
|
+
util.inherits(Agent, EventEmitter);
|
110
|
+
exports.Agent = Agent;
|
111
|
+
|
112
|
+
Agent.defaultMaxSockets = Infinity;
|
113
|
+
|
114
|
+
Agent.prototype.createConnection = net.createConnection;
|
115
|
+
|
116
|
+
// Get the key for a given set of request options
|
117
|
+
Agent.prototype.getName = function(options) {
|
118
|
+
var name = '';
|
119
|
+
|
120
|
+
if (options.host)
|
121
|
+
name += options.host;
|
122
|
+
else
|
123
|
+
name += 'localhost';
|
124
|
+
|
125
|
+
name += ':';
|
126
|
+
if (options.port)
|
127
|
+
name += options.port;
|
128
|
+
name += ':';
|
129
|
+
if (options.localAddress)
|
130
|
+
name += options.localAddress;
|
131
|
+
name += ':';
|
132
|
+
return name;
|
133
|
+
};
|
134
|
+
|
135
|
+
Agent.prototype.addRequest = function(req, options) {
|
136
|
+
// Legacy API: addRequest(req, host, port, path)
|
137
|
+
if (typeof options === 'string') {
|
138
|
+
options = {
|
139
|
+
host: options,
|
140
|
+
port: arguments[2],
|
141
|
+
path: arguments[3]
|
142
|
+
};
|
143
|
+
}
|
144
|
+
|
145
|
+
var name = this.getName(options);
|
146
|
+
if (!this.sockets[name]) {
|
147
|
+
this.sockets[name] = [];
|
148
|
+
}
|
149
|
+
|
150
|
+
var freeLen = this.freeSockets[name] ? this.freeSockets[name].length : 0;
|
151
|
+
var sockLen = freeLen + this.sockets[name].length;
|
152
|
+
|
153
|
+
if (freeLen) {
|
154
|
+
// we have a free socket, so use that.
|
155
|
+
var socket = this.freeSockets[name].shift();
|
156
|
+
debug('have free socket');
|
157
|
+
|
158
|
+
// don't leak
|
159
|
+
if (!this.freeSockets[name].length)
|
160
|
+
delete this.freeSockets[name];
|
161
|
+
|
162
|
+
socket.ref();
|
163
|
+
req.onSocket(socket);
|
164
|
+
this.sockets[name].push(socket);
|
165
|
+
} else if (sockLen < this.maxSockets) {
|
166
|
+
debug('call onSocket', sockLen, freeLen);
|
167
|
+
// If we are under maxSockets create a new one.
|
168
|
+
req.onSocket(this.createSocket(req, options));
|
169
|
+
} else {
|
170
|
+
debug('wait for socket');
|
171
|
+
// We are over limit so we'll add it to the queue.
|
172
|
+
if (!this.requests[name]) {
|
173
|
+
this.requests[name] = [];
|
174
|
+
}
|
175
|
+
this.requests[name].push(req);
|
176
|
+
}
|
177
|
+
};
|
178
|
+
|
179
|
+
Agent.prototype.createSocket = function(req, options) {
|
180
|
+
var self = this;
|
181
|
+
options = util._extend({}, options);
|
182
|
+
options = util._extend(options, self.options);
|
183
|
+
|
184
|
+
options.servername = options.host;
|
185
|
+
if (req) {
|
186
|
+
var hostHeader = req.getHeader('host');
|
187
|
+
if (hostHeader) {
|
188
|
+
options.servername = hostHeader.replace(/:.*$/, '');
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
var name = self.getName(options);
|
193
|
+
|
194
|
+
debug('createConnection', name, options);
|
195
|
+
options.encoding = null;
|
196
|
+
var s = self.createConnection(options);
|
197
|
+
if (!self.sockets[name]) {
|
198
|
+
self.sockets[name] = [];
|
199
|
+
}
|
200
|
+
this.sockets[name].push(s);
|
201
|
+
debug('sockets', name, this.sockets[name].length);
|
202
|
+
|
203
|
+
function onFree() {
|
204
|
+
self.emit('free', s, options);
|
205
|
+
}
|
206
|
+
s.on('free', onFree);
|
207
|
+
|
208
|
+
function onClose(err) {
|
209
|
+
debug('CLIENT socket onClose');
|
210
|
+
// This is the only place where sockets get removed from the Agent.
|
211
|
+
// If you want to remove a socket from the pool, just close it.
|
212
|
+
// All socket errors end in a close event anyway.
|
213
|
+
self.removeSocket(s, options);
|
214
|
+
}
|
215
|
+
s.on('close', onClose);
|
216
|
+
|
217
|
+
function onRemove() {
|
218
|
+
// We need this function for cases like HTTP 'upgrade'
|
219
|
+
// (defined by WebSockets) where we need to remove a socket from the
|
220
|
+
// pool because it'll be locked up indefinitely
|
221
|
+
debug('CLIENT socket onRemove');
|
222
|
+
self.removeSocket(s, options);
|
223
|
+
s.removeListener('close', onClose);
|
224
|
+
s.removeListener('free', onFree);
|
225
|
+
s.removeListener('agentRemove', onRemove);
|
226
|
+
}
|
227
|
+
s.on('agentRemove', onRemove);
|
228
|
+
return s;
|
229
|
+
};
|
230
|
+
|
231
|
+
Agent.prototype.removeSocket = function(s, options) {
|
232
|
+
var name = this.getName(options);
|
233
|
+
debug('removeSocket', name, 'destroyed:', s.destroyed);
|
234
|
+
var sets = [this.sockets];
|
235
|
+
|
236
|
+
// If the socket was destroyed, remove it from the free buffers too.
|
237
|
+
if (s.destroyed)
|
238
|
+
sets.push(this.freeSockets);
|
239
|
+
|
240
|
+
sets.forEach(function(sockets) {
|
241
|
+
if (sockets[name]) {
|
242
|
+
var index = sockets[name].indexOf(s);
|
243
|
+
if (index !== -1) {
|
244
|
+
sockets[name].splice(index, 1);
|
245
|
+
// Don't leak
|
246
|
+
if (sockets[name].length === 0)
|
247
|
+
delete sockets[name];
|
248
|
+
}
|
249
|
+
}
|
250
|
+
});
|
251
|
+
if (this.requests[name] && this.requests[name].length) {
|
252
|
+
debug('removeSocket, have a request, make a socket');
|
253
|
+
var req = this.requests[name][0];
|
254
|
+
// If we have pending requests and a socket gets closed make a new one
|
255
|
+
this.createSocket(req, options).emit('free');
|
256
|
+
}
|
257
|
+
};
|
258
|
+
|
259
|
+
Agent.prototype.destroy = function() {
|
260
|
+
var sets = [this.freeSockets, this.sockets];
|
261
|
+
sets.forEach(function(set) {
|
262
|
+
Object.keys(set).forEach(function(name) {
|
263
|
+
set[name].forEach(function(socket) {
|
264
|
+
socket.destroy();
|
265
|
+
});
|
266
|
+
});
|
267
|
+
});
|
268
|
+
};
|
269
|
+
|
270
|
+
Agent.prototype.request = function(options, cb) {
|
271
|
+
if (util.isString(options)) {
|
272
|
+
options = url.parse(options);
|
273
|
+
}
|
274
|
+
// don't try to do dns lookups of foo.com:8080, just foo.com
|
275
|
+
if (options.hostname) {
|
276
|
+
options.host = options.hostname;
|
277
|
+
}
|
278
|
+
|
279
|
+
if (options && options.path && / /.test(options.path)) {
|
280
|
+
// The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
|
281
|
+
// with an additional rule for ignoring percentage-escaped characters
|
282
|
+
// but that's a) hard to capture in a regular expression that performs
|
283
|
+
// well, and b) possibly too restrictive for real-world usage. That's
|
284
|
+
// why it only scans for spaces because those are guaranteed to create
|
285
|
+
// an invalid request.
|
286
|
+
throw new TypeError('Request path contains unescaped characters.');
|
287
|
+
} else if (options.protocol && options.protocol !== this.protocol) {
|
288
|
+
throw new Error('Protocol:' + options.protocol + ' not supported.');
|
289
|
+
}
|
290
|
+
|
291
|
+
options = util._extend({
|
292
|
+
agent: this,
|
293
|
+
keepAlive: this.keepAlive
|
294
|
+
}, options);
|
295
|
+
|
296
|
+
// if it's false, then make a new one, just like this one.
|
297
|
+
if (options.agent === false)
|
298
|
+
options.agent = new this.constructor();
|
299
|
+
|
300
|
+
debug('agent.request', options);
|
301
|
+
return new ClientRequest(options, cb);
|
302
|
+
};
|
303
|
+
|
304
|
+
Agent.prototype.get = function(options, cb) {
|
305
|
+
var req = this.request(options, cb);
|
306
|
+
req.end();
|
307
|
+
return req;
|
308
|
+
};
|
309
|
+
|
310
|
+
exports.globalAgent = new Agent();
|
@@ -0,0 +1,533 @@
|
|
1
|
+
// Copyright Joyent, Inc. and other Node contributors.
|
2
|
+
//
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
// copy of this software and associated documentation files (the
|
5
|
+
// "Software"), to deal in the Software without restriction, including
|
6
|
+
// without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
// persons to whom the Software is furnished to do so, subject to the
|
9
|
+
// following conditions:
|
10
|
+
//
|
11
|
+
// The above copyright notice and this permission notice shall be included
|
12
|
+
// in all copies or substantial portions of the Software.
|
13
|
+
//
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
17
|
+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
18
|
+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
20
|
+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
var util = require('util');
|
23
|
+
var net = require('net');
|
24
|
+
var EventEmitter = require('events').EventEmitter;
|
25
|
+
var HTTPParser = process.binding('http_parser').HTTPParser;
|
26
|
+
var assert = require('assert').ok;
|
27
|
+
|
28
|
+
var common = require('_http_common');
|
29
|
+
|
30
|
+
var httpSocketSetup = common.httpSocketSetup;
|
31
|
+
var parsers = common.parsers;
|
32
|
+
var freeParser = common.freeParser;
|
33
|
+
var debug = common.debug;
|
34
|
+
|
35
|
+
var IncomingMessage = require('_http_incoming').IncomingMessage;
|
36
|
+
var OutgoingMessage = require('_http_outgoing').OutgoingMessage;
|
37
|
+
|
38
|
+
var agent = require('_http_agent');
|
39
|
+
var globalAgent = agent.globalAgent;
|
40
|
+
|
41
|
+
|
42
|
+
function ClientRequest(options, cb) {
|
43
|
+
var self = this;
|
44
|
+
OutgoingMessage.call(self);
|
45
|
+
|
46
|
+
options = util._extend({}, options);
|
47
|
+
|
48
|
+
self.agent = util.isUndefined(options.agent) ? globalAgent : options.agent;
|
49
|
+
|
50
|
+
var defaultPort = options.defaultPort || self.agent.defaultPort;
|
51
|
+
|
52
|
+
var port = options.port = options.port || defaultPort;
|
53
|
+
var host = options.host = options.hostname || options.host || 'localhost';
|
54
|
+
|
55
|
+
if (util.isUndefined(options.setHost)) {
|
56
|
+
var setHost = true;
|
57
|
+
}
|
58
|
+
|
59
|
+
self.socketPath = options.socketPath;
|
60
|
+
|
61
|
+
var method = self.method = (options.method || 'GET').toUpperCase();
|
62
|
+
self.path = options.path || '/';
|
63
|
+
if (cb) {
|
64
|
+
self.once('response', cb);
|
65
|
+
}
|
66
|
+
|
67
|
+
if (!util.isArray(options.headers)) {
|
68
|
+
if (options.headers) {
|
69
|
+
var keys = Object.keys(options.headers);
|
70
|
+
for (var i = 0, l = keys.length; i < l; i++) {
|
71
|
+
var key = keys[i];
|
72
|
+
self.setHeader(key, options.headers[key]);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
if (host && !this.getHeader('host') && setHost) {
|
76
|
+
var hostHeader = host;
|
77
|
+
if (port && +port !== defaultPort) {
|
78
|
+
hostHeader += ':' + port;
|
79
|
+
}
|
80
|
+
this.setHeader('Host', hostHeader);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
if (options.auth && !this.getHeader('Authorization')) {
|
85
|
+
//basic auth
|
86
|
+
this.setHeader('Authorization', 'Basic ' +
|
87
|
+
new Buffer(options.auth).toString('base64'));
|
88
|
+
}
|
89
|
+
|
90
|
+
if (method === 'GET' ||
|
91
|
+
method === 'HEAD' ||
|
92
|
+
method === 'DELETE' ||
|
93
|
+
method === 'CONNECT') {
|
94
|
+
self.useChunkedEncodingByDefault = false;
|
95
|
+
} else {
|
96
|
+
self.useChunkedEncodingByDefault = true;
|
97
|
+
}
|
98
|
+
|
99
|
+
if (util.isArray(options.headers)) {
|
100
|
+
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
|
101
|
+
options.headers);
|
102
|
+
} else if (self.getHeader('expect')) {
|
103
|
+
self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
|
104
|
+
self._renderHeaders());
|
105
|
+
}
|
106
|
+
|
107
|
+
if (self.socketPath) {
|
108
|
+
self._last = true;
|
109
|
+
self.shouldKeepAlive = false;
|
110
|
+
var conn = self.agent.createConnection({ path: self.socketPath });
|
111
|
+
self.onSocket(conn);
|
112
|
+
} else if (self.agent) {
|
113
|
+
// If there is an agent we should default to Connection:keep-alive,
|
114
|
+
// but only if the Agent will actually reuse the connection!
|
115
|
+
// If it's not a keepAlive agent, and the maxSockets==Infinity, then
|
116
|
+
// there's never a case where this socket will actually be reused
|
117
|
+
var agent = self.agent;
|
118
|
+
if (!agent.keepAlive && !Number.isFinite(agent.maxSockets)) {
|
119
|
+
self._last = true;
|
120
|
+
self.shouldKeepAlive = false;
|
121
|
+
} else {
|
122
|
+
self._last = false;
|
123
|
+
self.shouldKeepAlive = true;
|
124
|
+
}
|
125
|
+
self.agent.addRequest(self, options);
|
126
|
+
} else {
|
127
|
+
// No agent, default to Connection:close.
|
128
|
+
self._last = true;
|
129
|
+
self.shouldKeepAlive = false;
|
130
|
+
if (options.createConnection) {
|
131
|
+
var conn = options.createConnection(options);
|
132
|
+
} else {
|
133
|
+
debug('CLIENT use net.createConnection', options);
|
134
|
+
var conn = net.createConnection(options);
|
135
|
+
}
|
136
|
+
self.onSocket(conn);
|
137
|
+
}
|
138
|
+
|
139
|
+
self._deferToConnect(null, null, function() {
|
140
|
+
self._flush();
|
141
|
+
self = null;
|
142
|
+
});
|
143
|
+
}
|
144
|
+
|
145
|
+
util.inherits(ClientRequest, OutgoingMessage);
|
146
|
+
|
147
|
+
exports.ClientRequest = ClientRequest;
|
148
|
+
|
149
|
+
ClientRequest.prototype._finish = function() {
|
150
|
+
DTRACE_HTTP_CLIENT_REQUEST(this, this.connection);
|
151
|
+
COUNTER_HTTP_CLIENT_REQUEST();
|
152
|
+
OutgoingMessage.prototype._finish.call(this);
|
153
|
+
};
|
154
|
+
|
155
|
+
ClientRequest.prototype._implicitHeader = function() {
|
156
|
+
this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
|
157
|
+
this._renderHeaders());
|
158
|
+
};
|
159
|
+
|
160
|
+
ClientRequest.prototype.abort = function() {
|
161
|
+
// If we're aborting, we don't care about any more response data.
|
162
|
+
if (this.res)
|
163
|
+
this.res._dump();
|
164
|
+
else
|
165
|
+
this.once('response', function(res) {
|
166
|
+
res._dump();
|
167
|
+
});
|
168
|
+
|
169
|
+
if (this.socket) {
|
170
|
+
// in-progress
|
171
|
+
this.socket.destroy();
|
172
|
+
} else {
|
173
|
+
// haven't been assigned a socket yet.
|
174
|
+
// this could be more efficient, it could
|
175
|
+
// remove itself from the pending requests
|
176
|
+
this._deferToConnect('destroy', []);
|
177
|
+
}
|
178
|
+
};
|
179
|
+
|
180
|
+
|
181
|
+
function createHangUpError() {
|
182
|
+
var error = new Error('socket hang up');
|
183
|
+
error.code = 'ECONNRESET';
|
184
|
+
return error;
|
185
|
+
}
|
186
|
+
|
187
|
+
|
188
|
+
function socketCloseListener() {
|
189
|
+
var socket = this;
|
190
|
+
var req = socket._httpMessage;
|
191
|
+
debug('HTTP socket close');
|
192
|
+
|
193
|
+
// Pull through final chunk, if anything is buffered.
|
194
|
+
// the ondata function will handle it properly, and this
|
195
|
+
// is a no-op if no final chunk remains.
|
196
|
+
socket.read();
|
197
|
+
|
198
|
+
// NOTE: Its important to get parser here, because it could be freed by
|
199
|
+
// the `socketOnData`.
|
200
|
+
var parser = socket.parser;
|
201
|
+
req.emit('close');
|
202
|
+
if (req.res && req.res.readable) {
|
203
|
+
// Socket closed before we emitted 'end' below.
|
204
|
+
req.res.emit('aborted');
|
205
|
+
var res = req.res;
|
206
|
+
res.on('end', function() {
|
207
|
+
res.emit('close');
|
208
|
+
});
|
209
|
+
res.push(null);
|
210
|
+
} else if (!req.res && !req.socket._hadError) {
|
211
|
+
// This socket error fired before we started to
|
212
|
+
// receive a response. The error needs to
|
213
|
+
// fire on the request.
|
214
|
+
req.emit('error', createHangUpError());
|
215
|
+
req.socket._hadError = true;
|
216
|
+
}
|
217
|
+
|
218
|
+
// Too bad. That output wasn't getting written.
|
219
|
+
// This is pretty terrible that it doesn't raise an error.
|
220
|
+
// Fixed better in v0.10
|
221
|
+
if (req.output)
|
222
|
+
req.output.length = 0;
|
223
|
+
if (req.outputEncodings)
|
224
|
+
req.outputEncodings.length = 0;
|
225
|
+
|
226
|
+
if (parser) {
|
227
|
+
parser.finish();
|
228
|
+
freeParser(parser, req);
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
function socketErrorListener(err) {
|
233
|
+
var socket = this;
|
234
|
+
var parser = socket.parser;
|
235
|
+
var req = socket._httpMessage;
|
236
|
+
debug('SOCKET ERROR:', err.message, err.stack);
|
237
|
+
|
238
|
+
if (req) {
|
239
|
+
req.emit('error', err);
|
240
|
+
// For Safety. Some additional errors might fire later on
|
241
|
+
// and we need to make sure we don't double-fire the error event.
|
242
|
+
req.socket._hadError = true;
|
243
|
+
}
|
244
|
+
|
245
|
+
if (parser) {
|
246
|
+
parser.finish();
|
247
|
+
freeParser(parser, req);
|
248
|
+
}
|
249
|
+
socket.destroy();
|
250
|
+
}
|
251
|
+
|
252
|
+
function socketOnEnd() {
|
253
|
+
var socket = this;
|
254
|
+
var req = this._httpMessage;
|
255
|
+
var parser = this.parser;
|
256
|
+
|
257
|
+
if (!req.res && !req.socket._hadError) {
|
258
|
+
// If we don't have a response then we know that the socket
|
259
|
+
// ended prematurely and we need to emit an error on the request.
|
260
|
+
req.emit('error', createHangUpError());
|
261
|
+
req.socket._hadError = true;
|
262
|
+
}
|
263
|
+
if (parser) {
|
264
|
+
parser.finish();
|
265
|
+
freeParser(parser, req);
|
266
|
+
}
|
267
|
+
socket.destroy();
|
268
|
+
}
|
269
|
+
|
270
|
+
function socketOnData(d) {
|
271
|
+
var socket = this;
|
272
|
+
var req = this._httpMessage;
|
273
|
+
var parser = this.parser;
|
274
|
+
|
275
|
+
assert(parser && parser.socket === socket);
|
276
|
+
|
277
|
+
var ret = parser.execute(d);
|
278
|
+
if (ret instanceof Error) {
|
279
|
+
debug('parse error');
|
280
|
+
freeParser(parser, req);
|
281
|
+
socket.destroy();
|
282
|
+
req.emit('error', ret);
|
283
|
+
req.socket._hadError = true;
|
284
|
+
} else if (parser.incoming && parser.incoming.upgrade) {
|
285
|
+
// Upgrade or CONNECT
|
286
|
+
var bytesParsed = ret;
|
287
|
+
var res = parser.incoming;
|
288
|
+
req.res = res;
|
289
|
+
|
290
|
+
socket.removeListener('data', socketOnData);
|
291
|
+
socket.removeListener('end', socketOnEnd);
|
292
|
+
parser.finish();
|
293
|
+
|
294
|
+
var bodyHead = d.slice(bytesParsed, d.length);
|
295
|
+
|
296
|
+
var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
|
297
|
+
if (EventEmitter.listenerCount(req, eventName) > 0) {
|
298
|
+
req.upgradeOrConnect = true;
|
299
|
+
|
300
|
+
// detach the socket
|
301
|
+
socket.emit('agentRemove');
|
302
|
+
socket.removeListener('close', socketCloseListener);
|
303
|
+
socket.removeListener('error', socketErrorListener);
|
304
|
+
|
305
|
+
// TODO(isaacs): Need a way to reset a stream to fresh state
|
306
|
+
// IE, not flowing, and not explicitly paused.
|
307
|
+
socket._readableState.flowing = null;
|
308
|
+
|
309
|
+
req.emit(eventName, res, socket, bodyHead);
|
310
|
+
req.emit('close');
|
311
|
+
} else {
|
312
|
+
// Got Upgrade header or CONNECT method, but have no handler.
|
313
|
+
socket.destroy();
|
314
|
+
}
|
315
|
+
freeParser(parser, req);
|
316
|
+
} else if (parser.incoming && parser.incoming.complete &&
|
317
|
+
// When the status code is 100 (Continue), the server will
|
318
|
+
// send a final response after this client sends a request
|
319
|
+
// body. So, we must not free the parser.
|
320
|
+
parser.incoming.statusCode !== 100) {
|
321
|
+
socket.removeListener('data', socketOnData);
|
322
|
+
socket.removeListener('end', socketOnEnd);
|
323
|
+
freeParser(parser, req);
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
|
328
|
+
// client
|
329
|
+
function parserOnIncomingClient(res, shouldKeepAlive) {
|
330
|
+
var socket = this.socket;
|
331
|
+
var req = socket._httpMessage;
|
332
|
+
|
333
|
+
|
334
|
+
// propogate "domain" setting...
|
335
|
+
if (req.domain && !res.domain) {
|
336
|
+
debug('setting "res.domain"');
|
337
|
+
res.domain = req.domain;
|
338
|
+
}
|
339
|
+
|
340
|
+
debug('AGENT incoming response!');
|
341
|
+
|
342
|
+
if (req.res) {
|
343
|
+
// We already have a response object, this means the server
|
344
|
+
// sent a double response.
|
345
|
+
socket.destroy();
|
346
|
+
return;
|
347
|
+
}
|
348
|
+
req.res = res;
|
349
|
+
|
350
|
+
// Responses to CONNECT request is handled as Upgrade.
|
351
|
+
if (req.method === 'CONNECT') {
|
352
|
+
res.upgrade = true;
|
353
|
+
return true; // skip body
|
354
|
+
}
|
355
|
+
|
356
|
+
// Responses to HEAD requests are crazy.
|
357
|
+
// HEAD responses aren't allowed to have an entity-body
|
358
|
+
// but *can* have a content-length which actually corresponds
|
359
|
+
// to the content-length of the entity-body had the request
|
360
|
+
// been a GET.
|
361
|
+
var isHeadResponse = req.method == 'HEAD';
|
362
|
+
debug('AGENT isHeadResponse', isHeadResponse);
|
363
|
+
|
364
|
+
if (res.statusCode == 100) {
|
365
|
+
// restart the parser, as this is a continue message.
|
366
|
+
delete req.res; // Clear res so that we don't hit double-responses.
|
367
|
+
req.emit('continue');
|
368
|
+
return true;
|
369
|
+
}
|
370
|
+
|
371
|
+
if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
|
372
|
+
// Server MUST respond with Connection:keep-alive for us to enable it.
|
373
|
+
// If we've been upgraded (via WebSockets) we also shouldn't try to
|
374
|
+
// keep the connection open.
|
375
|
+
req.shouldKeepAlive = false;
|
376
|
+
}
|
377
|
+
|
378
|
+
|
379
|
+
DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
|
380
|
+
COUNTER_HTTP_CLIENT_RESPONSE();
|
381
|
+
req.res = res;
|
382
|
+
res.req = req;
|
383
|
+
|
384
|
+
// add our listener first, so that we guarantee socket cleanup
|
385
|
+
res.on('end', responseOnEnd);
|
386
|
+
var handled = req.emit('response', res);
|
387
|
+
|
388
|
+
// If the user did not listen for the 'response' event, then they
|
389
|
+
// can't possibly read the data, so we ._dump() it into the void
|
390
|
+
// so that the socket doesn't hang there in a paused state.
|
391
|
+
if (!handled)
|
392
|
+
res._dump();
|
393
|
+
|
394
|
+
return isHeadResponse;
|
395
|
+
}
|
396
|
+
|
397
|
+
// client
|
398
|
+
function responseOnEnd() {
|
399
|
+
var res = this;
|
400
|
+
var req = res.req;
|
401
|
+
var socket = req.socket;
|
402
|
+
|
403
|
+
if (!req.shouldKeepAlive) {
|
404
|
+
if (socket.writable) {
|
405
|
+
debug('AGENT socket.destroySoon()');
|
406
|
+
socket.destroySoon();
|
407
|
+
}
|
408
|
+
assert(!socket.writable);
|
409
|
+
} else {
|
410
|
+
debug('AGENT socket keep-alive');
|
411
|
+
if (req.timeoutCb) {
|
412
|
+
socket.setTimeout(0, req.timeoutCb);
|
413
|
+
req.timeoutCb = null;
|
414
|
+
}
|
415
|
+
socket.removeListener('close', socketCloseListener);
|
416
|
+
socket.removeListener('error', socketErrorListener);
|
417
|
+
// Mark this socket as available, AFTER user-added end
|
418
|
+
// handlers have a chance to run.
|
419
|
+
process.nextTick(function() {
|
420
|
+
socket.emit('free');
|
421
|
+
});
|
422
|
+
}
|
423
|
+
}
|
424
|
+
|
425
|
+
function tickOnSocket(req, socket) {
|
426
|
+
var parser = parsers.alloc();
|
427
|
+
req.socket = socket;
|
428
|
+
req.connection = socket;
|
429
|
+
parser.reinitialize(HTTPParser.RESPONSE);
|
430
|
+
parser.socket = socket;
|
431
|
+
parser.incoming = null;
|
432
|
+
req.parser = parser;
|
433
|
+
|
434
|
+
socket.parser = parser;
|
435
|
+
socket._httpMessage = req;
|
436
|
+
|
437
|
+
// Setup "drain" propogation.
|
438
|
+
httpSocketSetup(socket);
|
439
|
+
|
440
|
+
// Propagate headers limit from request object to parser
|
441
|
+
if (util.isNumber(req.maxHeadersCount)) {
|
442
|
+
parser.maxHeaderPairs = req.maxHeadersCount << 1;
|
443
|
+
} else {
|
444
|
+
// Set default value because parser may be reused from FreeList
|
445
|
+
parser.maxHeaderPairs = 2000;
|
446
|
+
}
|
447
|
+
|
448
|
+
parser.onIncoming = parserOnIncomingClient;
|
449
|
+
socket.on('error', socketErrorListener);
|
450
|
+
socket.on('data', socketOnData);
|
451
|
+
socket.on('end', socketOnEnd);
|
452
|
+
socket.on('close', socketCloseListener);
|
453
|
+
req.emit('socket', socket);
|
454
|
+
}
|
455
|
+
|
456
|
+
ClientRequest.prototype.onSocket = function(socket) {
|
457
|
+
var req = this;
|
458
|
+
|
459
|
+
process.nextTick(function() {
|
460
|
+
tickOnSocket(req, socket);
|
461
|
+
});
|
462
|
+
};
|
463
|
+
|
464
|
+
ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) {
|
465
|
+
// This function is for calls that need to happen once the socket is
|
466
|
+
// connected and writable. It's an important promisy thing for all the socket
|
467
|
+
// calls that happen either now (when a socket is assigned) or
|
468
|
+
// in the future (when a socket gets assigned out of the pool and is
|
469
|
+
// eventually writable).
|
470
|
+
var self = this;
|
471
|
+
var onSocket = function() {
|
472
|
+
if (self.socket.writable) {
|
473
|
+
if (method) {
|
474
|
+
self.socket[method].apply(self.socket, arguments_);
|
475
|
+
}
|
476
|
+
if (cb) { cb(); }
|
477
|
+
} else {
|
478
|
+
self.socket.once('connect', function() {
|
479
|
+
if (method) {
|
480
|
+
self.socket[method].apply(self.socket, arguments_);
|
481
|
+
}
|
482
|
+
if (cb) { cb(); }
|
483
|
+
});
|
484
|
+
}
|
485
|
+
}
|
486
|
+
if (!self.socket) {
|
487
|
+
self.once('socket', onSocket);
|
488
|
+
} else {
|
489
|
+
onSocket();
|
490
|
+
}
|
491
|
+
};
|
492
|
+
|
493
|
+
ClientRequest.prototype.setTimeout = function(msecs, callback) {
|
494
|
+
if (callback) this.once('timeout', callback);
|
495
|
+
|
496
|
+
var self = this;
|
497
|
+
function emitTimeout() {
|
498
|
+
self.emit('timeout');
|
499
|
+
}
|
500
|
+
|
501
|
+
if (this.socket && this.socket.writable) {
|
502
|
+
if (this.timeoutCb)
|
503
|
+
this.socket.setTimeout(0, this.timeoutCb);
|
504
|
+
this.timeoutCb = emitTimeout;
|
505
|
+
this.socket.setTimeout(msecs, emitTimeout);
|
506
|
+
return;
|
507
|
+
}
|
508
|
+
|
509
|
+
// Set timeoutCb so that it'll get cleaned up on request end
|
510
|
+
this.timeoutCb = emitTimeout;
|
511
|
+
if (this.socket) {
|
512
|
+
var sock = this.socket;
|
513
|
+
this.socket.once('connect', function() {
|
514
|
+
sock.setTimeout(msecs, emitTimeout);
|
515
|
+
});
|
516
|
+
return;
|
517
|
+
}
|
518
|
+
|
519
|
+
this.once('socket', function(sock) {
|
520
|
+
sock.setTimeout(msecs, emitTimeout);
|
521
|
+
});
|
522
|
+
};
|
523
|
+
|
524
|
+
ClientRequest.prototype.setNoDelay = function() {
|
525
|
+
this._deferToConnect('setNoDelay', arguments);
|
526
|
+
};
|
527
|
+
ClientRequest.prototype.setSocketKeepAlive = function() {
|
528
|
+
this._deferToConnect('setKeepAlive', arguments);
|
529
|
+
};
|
530
|
+
|
531
|
+
ClientRequest.prototype.clearTimeout = function(cb) {
|
532
|
+
this.setTimeout(0, cb);
|
533
|
+
};
|