diff --git a/lib/cradle.js b/lib/cradle.js index fc62a27..fb89724 100644 --- a/lib/cradle.js +++ b/lib/cradle.js @@ -18,66 +18,52 @@ cradle.Response = require('./cradle/response').Response; cradle.Cache = require('./cradle/cache').Cache; cradle.options = { + // Global options, unused for alternate servers + cache: true, + servers: [], + // Options overridden by alternate server options + auth: null, host: '127.0.0.1', port: 5984, - auth: null, - cache: true, raw: false, - timeout: 0, secure: false, - headers: {} + headers: {}, + retry: 1 }; -cradle.setup = function (settings) { - this.host = settings.host; - this.auth = settings.auth; - this.port = parseInt(settings.port); - cradle.merge(this.options, settings); +var protocolPattern = /^(https?):\/\//; +cradle.setup = function (options) { + this.options = this._parseOptions(options); return this; }; -var protocolPattern = /^(https?):\/\//; +cradle._parseOptions = function(options, localOptions) { + if (!options) return cradle.merge({}, this.options); -cradle.Connection = function Connection(/* variable args */) { - var args = Array.prototype.slice.call(arguments), - host, port, remote, auth, options = {}; - - args.forEach(function (a) { - if (typeof(a) === 'number' || (typeof(a) === 'string' && /^\d{2,5}$/.test(a))) { - port = parseInt(a); - } else if (typeof(a) === 'object') { - options = a; - host = host || options.host; - port = port || options.port; - auth = options.auth; - } else { - host = a; - } - }); - - options.host = host || cradle.options.host; - options.port = port || cradle.options.port; - options.auth = auth || cradle.options.auth; + if (options.port) options.port = parseInt(options.port); + if (options.retry) options.retry = parseInt(options.retry); - this.options = cradle.merge({}, cradle.options, options); - this.options.secure = this.options.secure || this.options.ssl; - - if (protocolPattern.test(this.options.host)) { - this.options.protocol = this.options.host.match(protocolPattern)[1]; - this.options.host = this.options.host.replace(protocolPattern, ''); + if (protocolPattern.test(options.host)) { + options.protocol = options.host.match(protocolPattern)[1]; + options.host = options.host.replace(protocolPattern, ''); + options.secure = options.protocol === 'https'; } - if (this.options.protocol === 'https') this.options.secure = true; + options.socket = this.options.secure ? https : http; - if (this.options.auth && this.options.auth.user) { // Deprecation warning - console.log('Warning: "user" & "pass" parameters ignored. Use "username" & "password"'); - } - if (this.options.ssl) { // Deprecation warning - console.log('Warning: "ssl" option is deprecated. Use "secure" instead.'); + if (!localOptions && options.servers) { + for (var i = 0, serverOptions; serverOptions = options.servers[i]; i++) { + serverOptions = this._parseOptions(serverOptions, true); + options.servers[i] = cradle.merge({}, this.options, serverOptions); + } } - this.socket = (this.options.secure) ? https : http; + return cradle.merge({}, this.options, options); +}; + +cradle.Connection = function Connection(options) { + this.options = cradle._parseOptions(options); }; // @@ -95,54 +81,73 @@ cradle.Connection = function Connection(/* variable args */) { // `retry > 0`: retry `retry` times // `retry < 0`: always retry // -cradle.Connection.prototype.rawRequest = function (method, path, options, data, headers) { - var promise = new(events.EventEmitter), request, retry, that = this; +cradle.Connection.prototype.rawRequest = function (method, path, query, data, headers) { + var promise = new(events.EventEmitter); - // Default to trying once - retry = retry || 1; - - // HTTP Headers - headers = headers || {}; - - // Set HTTP Basic Auth - if (this.options.auth) { - headers['Authorization'] = "Basic " + new Buffer(this.options.auth.username + ':' + this.options.auth.password).toString('base64'); - } - - // Set client-wide headers - for (var h in this.options.headers) { - headers[h] = this.options.headers[h]; + // Parse path + if (path) { + path = path.replace(/https?:\/\//, '').replace(/\/{2,}/g, '/'); + if (path[0] !== '/') path = '/' + path; + } else { + path = '/'; } - path = (path || '/').replace(/https?:\/\//, '').replace(/\/{2,}/g, '/'); - if (path.charAt(0) !== '/') { path = '/' + path } - - if (options) { - for (var k in options) { - if (typeof(options[k]) === 'boolean') { - options[k] = String(options[k]); + // Add query + if (query) { + for (var k in query) { + if (typeof query[k] === 'boolean') { + query[k] = String(query[k]); } } - path += '?' + querystring.stringify(options); + path += '?'; + path += querystring.stringify(query); } + // Keep this connection alive for future requests + headers = headers || {}; headers['Connection'] = 'keep-alive'; + // Handle data if (data && data.on) { headers['Transfer-Encoding'] = 'chunked' } - this._rawRequest(promise, { - host: this.options.host, - port: this.options.port, - method: method.toUpperCase(), - path: path, - headers: headers - }, data, retry); + // Service this request + this._rawRequest(promise, method.toUpperCase(), path, data, headers, + this.options, this.options.retry || 0, -1); return promise; } -cradle.Connection.prototype._rawRequest = function (promise, options, data, retry) { - var request = this.socket.request(options), that = this; +cradle.Connection.prototype._rawRequest = function (promise, method, path, data, + headers, options, retry, server) { + var reqHeaders = {}, that = this; + + // Set HTTP Basic Auth + if (options.auth) { + reqHeaders['Authorization'] = "Basic " + new Buffer(options.auth.username + ':' + options.auth.password).toString('base64'); + } + + // Merge headers + cradle.merge(reqHeaders, options.headers, headers); + + var request = options.socket.request({ + host: options.host, + port: options.port, + path: path, + method: method, + headers: reqHeaders + }); + + if (data) { + if (data.on) { + data.on('data', function (chunk) { request.write(chunk) }); + data.on('end', function () { request.end() }); + } else { + request.write(data, 'utf8'); + request.end(); + } + } else { + request.end(); + } request.on('response', function (res) { promise.emit('response', res); @@ -163,32 +168,33 @@ cradle.Connection.prototype._rawRequest = function (promise, options, data, retr } // Attempt to retry for supported errors - if (retry-- && ( - // Ignore broken pipe + if (// Unlimited tries if negative, stop at zero otherwise + retry-- && ( + // Retry for broken pipe err.code === 'EPIPE' || - // Ignore connection reset - err.code === 'ECONNRESET' - )) + // Retry for connection reset + err.code === 'ECONNRESET')) { - return that._rawRequest(promise, options, data, retry); + return that._rawRequest(promise, method, path, data, headers, + options, retry, server); + } + + // Try alternate servers + if (// This server is dead + err.code === 'ECONNREFUSED' && + // Are there alternate servers? + that.options.servers && + // Is there a next alternate server to use? + // If so, use that server's options + (options = that.options.servers[++server])) + { + return that._rawRequest(promise, method, path, data, headers, + options, options.retry || 0, server); } promise.emit('error', err); promise.emit('end'); }); - - - if (data) { - if (data.on) { - data.on('data', function (chunk) { request.write(chunk) }); - data.on('end', function () { request.end() }); - } else { - request.write(data, 'utf8'); - request.end(); - } - } else { - request.end(); - } } //