UNPKG

node-etcd

Version:

etcd library for node.js (etcd v2 api)

164 lines (120 loc) 4.56 kB
request = require 'request' deasync = require 'deasync' _ = require 'lodash' # Default options for request library defaultRequestOptions = pool: maxSockets: 100 followAllRedirects: true defaultClientOptions = maxRetries: 3 # CancellationToken to abort a request class CancellationToken constructor: (@servers, @maxRetries, @retries = 0, @errors = []) -> @aborted = false setRequest: (req) => @req = req isAborted: () => @aborted abort: () => @aborted = true @req.abort() if @req? cancel: @::abort wasAborted: @::isAborted # HTTP Client for connecting to etcd servers class Client constructor: (@hosts, @options, @sslopts) -> @syncmsg = {} execute: (method, options, callback) => opt = _.defaults (_.clone options), @options, defaultRequestOptions, { method: method } opt.clientOptions = _.defaults opt.clientOptions, defaultClientOptions servers = _.shuffle @hosts token = new CancellationToken servers, opt.clientOptions.maxRetries syncResp = @_multiserverHelper servers, opt, token, callback if options.synchronous is true return syncResp else return token put: (options, callback) => @execute "PUT", options, callback get: (options, callback) => @execute "GET", options, callback post: (options, callback) => @execute "POST", options, callback patch: (options, callback) => @execute "PATCH", options, callback delete: (options, callback) => @execute "DELETE", options, callback # Multiserver (cluster) executer _multiserverHelper: (servers, options, token, callback) => host = _.first(servers) options.url = "#{host}#{options.path}" return if token.isAborted() # Aborted by user? if not host? # No servers left? return @_retry token, options, callback if @_shouldRetry token return @_error token, callback reqRespHandler = (err, resp, body) => return if token.isAborted() if @_isHttpError err, resp token.errors.push server: host httperror: err httpstatus: resp?.statusCode httpbody: resp?.body response: resp timestamp: new Date() # Recurse: return @_multiserverHelper _.drop(servers), options, token, callback # Deliver response @_handleResponse err, resp, body, callback syncRespHandler = (err, body, headers) => options.syncdone = true @syncmsg = err: err body: body headers: headers callback = syncRespHandler if options.synchronous is true req = @_doRequest options, reqRespHandler token.setRequest req if options.synchronous is true and options.syncdone is undefined options.syncdone = false deasync.loopWhile(() => return !options.syncdone); delete options.syncdone return @syncmsg else return req _doRequest: (options, reqRespHandler) -> request options, reqRespHandler _retry: (token, options, callback) => doRetry = () => @_multiserverHelper token.servers, options, token, callback waitTime = @_waitTime token.retries token.retries += 1 setTimeout doRetry, waitTime _waitTime: (retries) -> return 1 if process.env.RUNNING_UNIT_TESTS is 'true' ### !pragma no-coverage-next ### return 100 * Math.pow 16, retries _shouldRetry: (token) => token.retries < token.maxRetries and @_isPossibleLeaderElection token.errors # All tries (all servers, all retries) failed _error: (token, callback) -> error = new Error 'All servers returned error' error.errors = token.errors error.retries = token.retries callback error if callback # If all servers reject connect or return raft error it's possible the # cluster is in leader election mode. _isPossibleLeaderElection: (errors) -> checkError = (e) -> e?.httperror?.code in ['ECONNREFUSED', 'ECONNRESET'] or e?.httpbody?.errorCode in [300, 301] or /Not current leader/.test e?.httpbody errors? and _.every errors, checkError _isHttpError: (err, resp) -> err or (resp?.statusCode? and resp.statusCode >= 500) _handleResponse: (err, resp, body, callback) -> return if not callback? if body?.errorCode? # http ok, but etcd gave us an error error = new Error body?.message || 'Etcd error' error.errorCode = body.errorCode error.error = body callback error, "", (resp?.headers or {}) else callback null, body, (resp?.headers or {}) exports = module.exports = Client