UNPKG

zater-cep

Version:

ZAter cep correios e via cep

496 lines (327 loc) 13.4 kB
// Load modules var Events = require('events'); var Url = require('url'); var Http = require('http'); var Https = require('https'); var Stream = require('stream'); var Hoek = require('hoek'); var Boom = require('boom'); var Payload = require('./payload'); var Recorder = require('./recorder'); var Tap = require('./tap'); // Declare internals var internals = { jsonRegex: /^application\/[a-z.+-]*json$/, shallowOptions: ['agent', 'payload', 'downstreamRes', 'beforeRedirect', 'redirected'] }; // new instance is exported as module.exports internals.Client = function (defaults) { Events.EventEmitter.call(this); this.agents = { https: new Https.Agent({ maxSockets: Infinity }), http: new Http.Agent({ maxSockets: Infinity }), httpsAllowUnauthorized: new Https.Agent({ maxSockets: Infinity, rejectUnauthorized: false }) }; this._defaults = defaults || {}; }; Hoek.inherits(internals.Client, Events.EventEmitter); internals.Client.prototype.defaults = function (options) { options = Hoek.applyToDefaultsWithShallow(options, this._defaults, internals.shallowOptions); return new internals.Client(options); }; internals.resolveUrl = function (baseUrl, path) { if (!path) { return baseUrl; } var parsedBase = Url.parse(baseUrl); var parsedPath = Url.parse(path); parsedBase.pathname += parsedPath.pathname; parsedBase.pathname = parsedBase.pathname.replace(/[/]{2,}/g, '/'); parsedBase.search = parsedPath.search; // Always use the querystring from the path argument return Url.format(parsedBase); }; internals.Client.prototype.request = function (method, url, options, callback, _trace) { var self = this; options = Hoek.applyToDefaultsWithShallow(options || {}, this._defaults, internals.shallowOptions); Hoek.assert(options.payload === null || options.payload === undefined || typeof options.payload === 'string' || options.payload instanceof Stream || Buffer.isBuffer(options.payload), 'options.payload must be a string, a Buffer, or a Stream'); Hoek.assert((options.agent === undefined || options.agent === null) || (typeof options.rejectUnauthorized !== 'boolean'), 'options.agent cannot be set to an Agent at the same time as options.rejectUnauthorized is set'); Hoek.assert(options.beforeRedirect === undefined || options.beforeRedirect === null || typeof options.beforeRedirect === 'function', 'options.beforeRedirect must be a function'); Hoek.assert(options.redirected === undefined || options.redirected === null || typeof options.redirected === 'function', 'options.redirected must be a function'); if (options.baseUrl) { url = internals.resolveUrl(options.baseUrl, url); delete options.baseUrl; } var uri = Url.parse(url); var timeoutId; uri.method = method.toUpperCase(); uri.headers = options.headers; var payloadSupported = (uri.method !== 'GET' && uri.method !== 'HEAD' && options.payload !== null && options.payload !== undefined); if (payloadSupported && (typeof options.payload === 'string' || Buffer.isBuffer(options.payload))) { uri.headers = Hoek.clone(uri.headers) || {}; uri.headers['Content-Length'] = Buffer.isBuffer(options.payload) ? options.payload.length : Buffer.byteLength(options.payload); } var redirects = (options.hasOwnProperty('redirects') ? options.redirects : false); // Needed to allow 0 as valid value when passed recursively _trace = (_trace || []); _trace.push({ method: uri.method, url: url }); var client = (uri.protocol === 'https:' ? Https : Http); if (options.rejectUnauthorized !== undefined && uri.protocol === 'https:') { uri.agent = options.rejectUnauthorized ? this.agents.https : this.agents.httpsAllowUnauthorized; } else if (options.agent || options.agent === false) { uri.agent = options.agent; } else { uri.agent = uri.protocol === 'https:' ? this.agents.https : this.agents.http; } if (options.secureProtocol !== undefined) { uri.secureProtocol = options.secureProtocol; } var start = Date.now(); var req = client.request(uri); var shadow = null; // A copy of the streamed request payload when redirects are enabled // Register handlers var finish = function (err, res) { if (!callback || err) { req.abort(); } req.removeListener('response', onResponse); req.removeListener('error', onError); req.on('error', Hoek.ignore); clearTimeout(timeoutId); self.emit('response', err, req, res, start, uri); if (callback) { return callback(err, res); } }; finish = Hoek.once(finish); var onError = function (err) { err.trace = _trace; return finish(Boom.badGateway('Client request error', err)); }; req.once('error', onError); var onResponse = function (res) { // Pass-through response var statusCode = res.statusCode; if (redirects === false || [301, 302, 307, 308].indexOf(statusCode) === -1) { return finish(null, res); } // Redirection var redirectMethod = (statusCode === 301 || statusCode === 302 ? 'GET' : uri.method); var location = res.headers.location; res.destroy(); if (redirects === 0) { return finish(Boom.badGateway('Maximum redirections reached', _trace)); } if (!location) { return finish(Boom.badGateway('Received redirection without location', _trace)); } if (!/^https?:/i.test(location)) { location = Url.resolve(uri.href, location); } var redirectOptions = Hoek.cloneWithShallow(options, internals.shallowOptions); redirectOptions.payload = shadow || options.payload; // shadow must be ready at this point if set redirectOptions.redirects = --redirects; if (options.beforeRedirect) { options.beforeRedirect(redirectMethod, statusCode, location, redirectOptions); } var redirectReq = self.request(redirectMethod, location, redirectOptions, finish, _trace); if (options.redirected) { options.redirected(statusCode, location, redirectReq); } }; req.once('response', onResponse); if (options.timeout) { timeoutId = setTimeout(function () { return finish(Boom.gatewayTimeout('Client request timeout')); }, options.timeout); delete options.timeout; } // Write payload if (payloadSupported) { if (options.payload instanceof Stream) { var stream = options.payload; if (redirects) { var collector = new Tap(); collector.once('finish', function () { shadow = collector.collect(); }); stream = options.payload.pipe(collector); } stream.pipe(req); return; } req.write(options.payload); } // Custom abort method to detect early aborts var _abort = req.abort; var aborted = false; req.abort = function () { if (!aborted && !req.res && !req.socket) { process.nextTick(function () { // Fake an ECONNRESET error var error = new Error('socket hang up'); error.code = 'ECONNRESET'; finish(error); }); } aborted = true; return _abort.call(req); }; // Finalize request req.end(); return req; }; // read() internals.Client.prototype.read = function (res, options, callback) { options = Hoek.applyToDefaultsWithShallow(options || {}, this._defaults, internals.shallowOptions); // Set stream timeout var clientTimeout = options.timeout; var clientTimeoutId = null; if (clientTimeout && clientTimeout > 0) { clientTimeoutId = setTimeout(function () { finish(Boom.clientTimeout()); }, clientTimeout); } // Finish once var finish = function (err, buffer) { clearTimeout(clientTimeoutId); reader.removeListener('error', onReaderError); reader.removeListener('finish', onReaderFinish); res.removeListener('error', onResError); res.removeListener('close', onResClose); res.on('error', Hoek.ignore); if (err || !options.json) { return callback(err, buffer); } // Parse JSON var result; if (buffer.length === 0) { return callback(null, null); } if (options.json === 'force') { result = internals.tryParseBuffer(buffer); return callback(result.err, result.json); } // mode is "smart" or true var contentType = (res.headers && res.headers['content-type']) || ''; var mime = contentType.split(';')[0].trim().toLowerCase(); if (!internals.jsonRegex.test(mime)) { return callback(null, buffer); } result = internals.tryParseBuffer(buffer); return callback(result.err, result.json); }; finish = Hoek.once(finish); // Hander errors var onResError = function (err) { return finish(Boom.internal('Payload stream error', err)); }; var onResClose = function () { return finish(Boom.internal('Payload stream closed prematurely')); }; res.once('error', onResError); res.once('close', onResClose); // Read payload var reader = new Recorder({ maxBytes: options.maxBytes }); var onReaderError = function (err) { if (res.destroy) { // GZip stream has no destroy() method res.destroy(); } return finish(err); }; reader.once('error', onReaderError); var onReaderFinish = function () { return finish(null, reader.collect()); }; reader.once('finish', onReaderFinish); res.pipe(reader); }; // toReadableStream() internals.Client.prototype.toReadableStream = function (payload, encoding) { return new Payload(payload, encoding); }; // parseCacheControl() internals.Client.prototype.parseCacheControl = function (field) { /* Cache-Control = 1#cache-directive cache-directive = token [ "=" ( token / quoted-string ) ] token = [^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+ quoted-string = "(?:[^"\\]|\\.)*" */ // 1: directive = 2: token 3: quoted-string var regex = /(?:^|(?:\s*\,\s*))([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)(?:\=(?:([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)|(?:\"((?:[^"\\]|\\.)*)\")))?/g; var header = {}; var error = field.replace(regex, function ($0, $1, $2, $3) { var value = $2 || $3; header[$1] = value ? value.toLowerCase() : true; return ''; }); if (header['max-age']) { try { var maxAge = parseInt(header['max-age'], 10); if (isNaN(maxAge)) { return null; } header['max-age'] = maxAge; } catch (err) { } } return (error ? null : header); }; // Shortcuts internals.Client.prototype.get = function (uri, options, callback) { return this._shortcutWrap('GET', uri, options, callback); }; internals.Client.prototype.post = function (uri, options, callback) { return this._shortcutWrap('POST', uri, options, callback); }; internals.Client.prototype.patch = function (uri, options, callback) { return this._shortcutWrap('PATCH', uri, options, callback); }; internals.Client.prototype.put = function (uri, options, callback) { return this._shortcutWrap('PUT', uri, options, callback); }; internals.Client.prototype.delete = function (uri, options, callback) { return this._shortcutWrap('DELETE', uri, options, callback); }; // Wrapper so that shortcut can be optimized with required params internals.Client.prototype._shortcutWrap = function (method, uri /* [options], callback */) { var options = (typeof arguments[2] === 'function' ? {} : arguments[2]); var callback = (typeof arguments[2] === 'function' ? arguments[2] : arguments[3]); return this._shortcut(method, uri, options, callback); }; internals.Client.prototype._shortcut = function (method, uri, options, callback) { var self = this; return this.request(method, uri, options, function (err, res) { if (err) { return callback(err); } self.read(res, options, function (err, payload) { return callback(err, res, payload); }); }); }; internals.tryParseBuffer = function (buffer) { var result = { json: null, err: null }; try { var json = JSON.parse(buffer.toString()); result.json = json; } catch (err) { result.err = err; } return result; }; module.exports = new internals.Client();