Name: js-handler/node_modules/restify/lib/clients/http_client.js 
1:
// Copyright 2012 Mark Cavage, Inc.  All rights reserved.
2:
 
3:
var crypto = require('crypto');
4:
var EventEmitter = require('events').EventEmitter;
5:
var fs = require('fs');
6:
var http = require('http');
7:
var https = require('https');
8:
var os = require('os');
9:
var querystring = require('querystring');
10:
var url = require('url');
11:
var util = require('util');
12:
var zlib = require('zlib');
13:
 
14:
var assert = require('assert-plus');
15:
var backoff = require('backoff');
16:
var KeepAliveAgent = require('keep-alive-agent');
17:
var mime = require('mime');
18:
var once = require('once');
19:
var uuid = require('node-uuid');
20:
 
21:
var dtrace = require('../dtrace');
22:
var errors = require('../errors');
23:
var bunyan = require('../bunyan_helper');
24:
 
25:
 
26:
 
27:
///--- Globals
28:
 
29:
/* JSSTYLED */
30:
var VERSION = JSON.parse(fs.readFileSync(require('path').normalize(__dirname + '/../../package.json'), 'utf8')).version;
31:
 
32:
 
33:
 
34:
///--- Helpers
35:
 
36:
function cloneRetryOptions(options, defaults) {
37:
        if (options === false) {
38:
                return ({
39:
                        minTimeout: 1,
40:
                        maxTimeout: 2,
41:
                        retries: 1
42:
                });
43:
        }
44:
 
45:
        assert.optionalObject(options, 'options.retry');
46:
        var r = options || {};
47:
        assert.optionalNumber(r.minTimeout, 'options.retry.minTimeout');
48:
        assert.optionalNumber(r.maxTimeout, 'options.retry.maxTimeout');
49:
        assert.optionalNumber(r.retries, 'options.retry.retries');
50:
        assert.optionalObject(defaults, 'defaults');
51:
        defaults = defaults || {};
52:
 
53:
        return ({
54:
                minTimeout: r.minTimeout || defaults.minTimeout || 1000,
55:
                maxTimeout: r.maxTimeout || defaults.maxTimeout || Infinity,
56:
                retries: r.retries || defaults.retries || 4
57:
        });
58:
}
59:
 
60:
 
61:
function defaultUserAgent() {
62:
        var UA = 'restify/' + VERSION +
63:
                ' (' + os.arch() + '-' + os.platform() + '; ' +
64:
                'v8/' + process.versions.v8 + '; ' +
65:
                'OpenSSL/' + process.versions.openssl + ') ' +
66:
                'node/' + process.versions.node;
67:
 
68:
        return (UA);
69:
}
70:
 
71:
 
72:
function ConnectTimeoutError(ms) {
73:
        if (Error.captureStackTrace)
74:
                Error.captureStackTrace(this, ConnectTimeoutError);
75:
 
76:
        this.message = 'connect timeout after ' + ms + 'ms';
77:
        this.name = 'ConnectTimeoutError';
78:
}
79:
util.inherits(ConnectTimeoutError, Error);
80:
 
81:
 
82:
function rawRequest(opts, cb) {
83:
        assert.object(opts, 'options');
84:
        assert.object(opts.log, 'options.log');
85:
        assert.func(cb, 'callback');
86:
 
87:
        cb = once(cb);
88:
 
89:
        var id = dtrace.nextId();
90:
        var log = opts.log;
91:
        var proto = opts.protocol === 'https:' ? https : http;
92:
        var timer;
93:
 
94:
        if (opts.cert && opts.key)
95:
                opts.agent = false;
96:
 
97:
        if (opts.connectTimeout) {
98:
                timer = setTimeout(function connectTimeout() {
99:
                        timer = null;
100:
                        if (req) {
101:
                                req.abort();
102:
                        }
103:
 
104:
                        var err = new ConnectTimeoutError(opts.connectTimeout);
105:
                        dtrace._rstfy_probes['client-error'].fire(function () {
106:
                                return ([id, err.toString()]);
107:
                        });
108:
                        cb(err, req);
109:
                }, opts.connectTimeout);
110:
        }
111:
 
112:
        dtrace._rstfy_probes['client-request'].fire(function () {
113:
                return ([
114:
                        opts.method,
115:
                        opts.path,
116:
                        opts.headers,
117:
                        id
118:
                ]);
119:
        });
120:
 
121:
        var req = proto.request(opts, function onResponse(res) {
122:
                clearTimeout(timer);
123:
                dtrace._rstfy_probes['client-response'].fire(function () {
124:
                        return ([ id, res.statusCode, res.headers ]);
125:
                });
126:
                log.trace({client_res: res}, 'Response received');
127:
 
128:
                res.log = log;
129:
 
130:
                var err;
131:
                if (res.statusCode >= 400)
132:
                        err = errors.codeToHttpError(res.statusCode);
133:
 
134:
                req.removeAllListeners('error');
135:
                req.removeAllListeners('socket');
136:
                req.emit('result', (err || null), res);
137:
        });
138:
        req.log = log;
139:
 
140:
        req.on('error', function onError(err) {
141:
                dtrace._rstfy_probes['client-error'].fire(function () {
142:
                        return ([id, (err || {}).toString()]);
143:
                });
144:
                log.trace({err: err}, 'Request failed');
145:
                clearTimeout(timer);
146:
 
147:
                cb(err, req);
148:
                if (req) {
149:
                        process.nextTick(function () {
150:
                                req.emit('result', err, null);
151:
                        });
152:
                }
153:
        });
154:
 
155:
        req.once('upgrade', function onUpgrade(res, socket, _head) {
156:
                clearTimeout(timer);
157:
                dtrace._rstfy_probes['client-response'].fire(function () {
158:
                        return ([ id, res.statusCode, res.headers ]);
159:
                });
160:
                log.trace({client_res: res}, 'Upgrade Response received');
161:
 
162:
                res.log = log;
163:
 
164:
                var err;
165:
                if (res.statusCode >= 400)
166:
                        err = errors.codeToHttpError(res.statusCode);
167:
 
168:
                req.removeAllListeners('error');
169:
                req.removeAllListeners('socket');
170:
                req.emit('upgradeResult', (err || null), res, socket, _head);
171:
        });
172:
 
173:
        req.once('socket', function onSocket(socket) {
174:
                if (socket.writable && !socket._connecting) {
175:
                        clearTimeout(timer);
176:
                        cb(null, req);
177:
                        return;
178:
                }
179:
 
180:
                socket.once('connect', function onConnect() {
181:
                        clearTimeout(timer);
182:
                        cb(null, req);
183:
                });
184:
        });
185:
 
186:
        if (opts.signRequest)
187:
                opts.signRequest(req);
188:
 
189:
        if (log.trace())
190:
                log.trace({client_req: opts}, 'request sent');
191:
} // end `rawRequest`
192:
 
193:
 
194:
 
195:
///--- API
196:
 
197:
function HttpClient(options) {
198:
        assert.object(options, 'options');
199:
        assert.optionalObject(options.headers, 'options.headers');
200:
        assert.object(options.log, 'options.log');
201:
        assert.optionalFunc(options.signRequest, 'options.signRequest');
202:
        assert.optionalString(options.socketPath, 'options.socketPath');
203:
        assert.optionalString(options.url, 'options.url');
204:
 
205:
        EventEmitter.call(this);
206:
 
207:
        var self = this;
208:
 
209:
        this.agent = options.agent;
210:
        this.ca = options.ca;
211:
        this.cert = options.cert;
212:
        this.ciphers = options.ciphers;
213:
        this.connectTimeout = options.connectTimeout || false;
214:
        this.headers = options.headers || {};
215:
        this.log = options.log;
216:
        if (!this.log.serializers.client_res) {
217:
            // Ensure logger has a reasonable serializer for `client_res`
218:
            // logged in this module.
219:
            this.log = this.log.child({
220:
                serializers: {client_res: bunyan.serializers.client_res}
221:
            });
222:
        }
223:
        this.key = options.key;
224:
        this.name = options.name || 'HttpClient';
225:
        this.passphrase = options.passphrase;
226:
        this.pfx = options.pfx;
227:
        if (options.rejectUnauthorized !== undefined) {
228:
                this.rejectUnauthorized = options.rejectUnauthorized;
229:
        } else {
230:
                this.rejectUnauthorized = true;
231:
        }
232:
 
233:
        this.retry = cloneRetryOptions(options.retry);
234:
        this.signRequest = options.signRequest || false;
235:
        this.socketPath = options.socketPath || false;
236:
        this.url = options.url ? url.parse(options.url) : {};
237:
 
238:
        if (options.accept) {
239:
                if (options.accept.indexOf('/') === -1)
240:
                        options.accept = mime.lookup(options.accept);
241:
 
242:
                this.headers.accept = options.accept;
243:
        }
244:
 
245:
        if (options.contentType) {
246:
                if (options.contentType.indexOf('/') === -1)
247:
                        options.type = mime.lookup(options.contentType);
248:
 
249:
                this.headers['content-type'] = options.contentType;
250:
        }
251:
 
252:
        if (options.userAgent !== false) {
253:
                this.headers['user-agent'] = options.userAgent ||
254:
                        defaultUserAgent();
255:
        }
256:
 
257:
        if (options.version)
258:
                this.headers['accept-version'] = options.version;
259:
 
260:
        if (this.agent === undefined) {
261:
                var Agent;
262:
                var maxSockets;
263:
 
264:
                if (this.url.protocol === 'https:') {
265:
                        Agent = KeepAliveAgent.Secure;
266:
                        maxSockets = https.globalAgent.maxSockets;
267:
                } else {
268:
                        Agent = KeepAliveAgent;
269:
                        maxSockets = http.globalAgent.maxSockets;
270:
                }
271:
 
272:
                this.agent = new Agent({
273:
                        cert: self.cert,
274:
                        ca: self.ca,
275:
                        ciphers: self.ciphers,
276:
                        key: self.key,
277:
                        maxSockets: maxSockets,
278:
                        maxKeepAliveRequests: 0,
279:
                        maxKeepAliveTime: 0,
280:
                        passphrase: self.passphrase,
281:
                        pfx: self.pfx,
282:
                        rejectUnauthorized: self.rejectUnauthorized
283:
                });
284:
        }
285:
}
286:
util.inherits(HttpClient, EventEmitter);
287:
module.exports = HttpClient;
288:
 
289:
 
290:
HttpClient.prototype.close = function close() {
291:
        var sockets = this.agent.sockets;
292:
        Object.keys((sockets || {})).forEach(function (k) {
293:
                sockets[k].forEach(function (s) {
294:
                        s.end();
295:
                });
296:
        });
297:
 
298:
        sockets = this.agent.idleSockets;
299:
        Object.keys((sockets || {})).forEach(function (k) {
300:
                sockets[k].forEach(function (s) {
301:
                        s.end();
302:
                });
303:
        });
304:
};
305:
 
306:
 
307:
HttpClient.prototype.del = function del(options, callback) {
308:
        var opts = this._options('DELETE', options);
309:
 
310:
        return (this.read(opts, callback));
311:
};
312:
 
313:
 
314:
HttpClient.prototype.get = function get(options, callback) {
315:
        var opts = this._options('GET', options);
316:
 
317:
        return (this.read(opts, callback));
318:
};
319:
 
320:
 
321:
HttpClient.prototype.head = function head(options, callback) {
322:
        var opts = this._options('HEAD', options);
323:
 
324:
        return (this.read(opts, callback));
325:
};
326:
 
327:
HttpClient.prototype.opts = function http_options(options, callback) {
328:
        var _opts = this._options('OPTIONS', options);
329:
 
330:
        return (this.read(_opts, callback));
331:
};
332:
 
333:
 
334:
HttpClient.prototype.post = function post(options, callback) {
335:
        var opts = this._options('POST', options);
336:
 
337:
        return (this.request(opts, callback));
338:
};
339:
 
340:
 
341:
HttpClient.prototype.put = function put(options, callback) {
342:
        var opts = this._options('PUT', options);
343:
 
344:
        return (this.request(opts, callback));
345:
};
346:
 
347:
 
348:
HttpClient.prototype.patch = function patch(options, callback) {
349:
        var opts = this._options('PATCH', options);
350:
 
351:
 
352:
        return (this.request(opts, callback));
353:
};
354:
 
355:
 
356:
HttpClient.prototype.read = function read(options, callback) {
357:
        var r = this.request(options, function readRequestCallback(err, req) {
358:
                if (!err)
359:
                        req.end();
360:
 
361:
                return (callback(err, req));
362:
        });
363:
        return (r);
364:
};
365:
 
366:
 
367:
HttpClient.prototype.basicAuth = function basicAuth(username, password) {
368:
        if (username === false) {
369:
                delete this.headers.authorization;
370:
        } else {
371:
                assert.string(username, 'username');
372:
                assert.string(password, 'password');
373:
 
374:
                var buffer = new Buffer(username + ':' + password, 'utf8');
375:
                this.headers.authorization = 'Basic ' +
376:
                        buffer.toString('base64');
377:
        }
378:
 
379:
        return (this);
380:
};
381:
 
382:
 
383:
HttpClient.prototype.request = function request(opts, cb) {
384:
        assert.object(opts, 'options');
385:
        assert.func(cb, 'callback');
386:
 
387:
        cb = once(cb);
388:
 
389:
        var call;
390:
        var retry = cloneRetryOptions(opts.retry);
391:
 
392:
        call = backoff.call(rawRequest, opts, cb);
393:
        call.setStrategy(new backoff.ExponentialStrategy({
394:
                initialDelay: retry.minTimeout,
395:
                maxDelay: retry.maxTimeout
396:
        }));
397:
        call.failAfter(retry.retries);
398:
        call.on('backoff', this.emit.bind(this, 'attempt'));
399:
 
400:
        call.start();
401:
};
402:
 
403:
 
404:
HttpClient.prototype._options = function (method, options) {
405:
        if (typeof (options) !== 'object')
406:
                options = { path: options };
407:
 
408:
        var self = this;
409:
        var opts = {
410:
                agent: options.agent || self.agent,
411:
                ca: options.ca || self.ca,
412:
                cert: options.cert || self.cert,
413:
                ciphers: options.ciphers || self.ciphers,
414:
                connectTimeout: options.connectTimeout || self.connectTimeout,
415:
                headers: options.headers || {},
416:
                key: options.key || self.key,
417:
                log: options.log || self.log,
418:
                method: method,
419:
                passphrase: options.passphrase || self.passphrase,
420:
                path: options.path || self.path,
421:
                pfx: options.pfx || self.pfx,
422:
                rejectUnauthorized: options.rejectUnauthorized ||
423:
                        self.rejectUnauthorized,
424:
                retry: options.retry || self.retry,
425:
                signRequest: options.signRequest || self.signRequest
426:
        };
427:
 
428:
        // Backwards compatibility with restify < 1.0
429:
        if (options.query &&
430:
            Object.keys(options.query).length &&
431:
            opts.path.indexOf('?') === -1) {
432:
                opts.path += '?' + querystring.stringify(options.query);
433:
        }
434:
 
435:
        if (this.socketPath)
436:
                opts.socketPath = this.socketPath;
437:
 
438:
        Object.keys(self.url).forEach(function (k) {
439:
                if (!opts[k])
440:
                        opts[k] = self.url[k];
441:
        });
442:
 
443:
        Object.keys(self.headers).forEach(function (k) {
444:
                if (!opts.headers[k])
445:
                        opts.headers[k] = self.headers[k];
446:
        });
447:
 
448:
        if (!opts.headers.date)
449:
                opts.headers.date = new Date().toUTCString();
450:
 
451:
        if (method === 'GET' || method === 'HEAD' || method === 'DELETE') {
452:
                if (opts.headers['content-type'])
453:
                        delete opts.headers['content-type'];
454:
                if (opts.headers['content-md5'])
455:
                        delete opts.headers['content-md5'];
456:
                if (opts.headers['content-length'])
457:
                        delete opts.headers['content-length'];
458:
                if (opts.headers['transfer-encoding'])
459:
                        delete opts.headers['transfer-encoding'];
460:
        }
461:
 
462:
        return (opts);
463:
};
464:
// vim: set ts=8 sts=8 sw=8 et: