| 
						 | 
						- const Base = require('sdk-base');
 - const util = require('util');
 - const ready = require('get-ready');
 - const copy = require('copy-to');
 - const currentIP = require('address').ip();
 - 
 - const RR = 'roundRobin';
 - const MS = 'masterSlave';
 - 
 - module.exports = function (OssClient) {
 -   function Client(options) {
 -     if (!(this instanceof Client)) {
 -       return new Client(options);
 -     }
 - 
 -     if (!options || !Array.isArray(options.cluster)) {
 -       throw new Error('require options.cluster to be an array');
 -     }
 - 
 -     Base.call(this);
 - 
 -     this.clients = [];
 -     this.availables = {};
 - 
 -     for (let i = 0; i < options.cluster.length; i++) {
 -       const opt = options.cluster[i];
 -       copy(options).pick('timeout', 'agent', 'urllib').to(opt);
 -       this.clients.push(new OssClient(opt));
 -       this.availables[i] = true;
 -     }
 - 
 -     this.schedule = options.schedule || RR;
 -     // only read from master, default is false
 -     this.masterOnly = !!options.masterOnly;
 -     this.index = 0;
 - 
 -     const heartbeatInterval = options.heartbeatInterval || 10000;
 -     this._checkAvailableLock = false;
 -     this._timerId = this._deferInterval(this._checkAvailable.bind(this, true), heartbeatInterval);
 -     this._ignoreStatusFile = options.ignoreStatusFile || false;
 -     this._init();
 -   }
 - 
 -   util.inherits(Client, Base);
 -   const proto = Client.prototype;
 -   ready.mixin(proto);
 - 
 -   const GET_METHODS = ['head', 'get', 'getStream', 'list', 'getACL'];
 - 
 -   const PUT_METHODS = ['put', 'putStream', 'delete', 'deleteMulti', 'copy', 'putMeta', 'putACL'];
 - 
 -   GET_METHODS.forEach(method => {
 -     proto[method] = async function (...args) {
 -       const client = this.chooseAvailable();
 -       let lastError;
 -       try {
 -         return await client[method](...args);
 -       } catch (err) {
 -         if (err.status && err.status >= 200 && err.status < 500) {
 -           // 200 ~ 499 belong to normal response, don't try again
 -           throw err;
 -         }
 -         // < 200 || >= 500 need to retry from other cluser node
 -         lastError = err;
 -       }
 - 
 -       for (let i = 0; i < this.clients.length; i++) {
 -         const c = this.clients[i];
 -         if (c !== client) {
 -           try {
 -             return await c[method].apply(client, args);
 -           } catch (err) {
 -             if (err.status && err.status >= 200 && err.status < 500) {
 -               // 200 ~ 499 belong to normal response, don't try again
 -               throw err;
 -             }
 -             // < 200 || >= 500 need to retry from other cluser node
 -             lastError = err;
 -           }
 -         }
 -       }
 - 
 -       lastError.message += ' (all clients are down)';
 -       throw lastError;
 -     };
 -   });
 - 
 -   // must cluster node write success
 -   PUT_METHODS.forEach(method => {
 -     proto[method] = async function (...args) {
 -       const res = await Promise.all(this.clients.map(client => client[method](...args)));
 -       return res[0];
 -     };
 -   });
 - 
 -   proto.signatureUrl = function signatureUrl(/* name */ ...args) {
 -     const client = this.chooseAvailable();
 -     return client.signatureUrl(...args);
 -   };
 - 
 -   proto.getObjectUrl = function getObjectUrl(/* name, baseUrl */ ...args) {
 -     const client = this.chooseAvailable();
 -     return client.getObjectUrl(...args);
 -   };
 - 
 -   proto._init = function _init() {
 -     const that = this;
 -     (async () => {
 -       await that._checkAvailable(that._ignoreStatusFile);
 -       that.ready(true);
 -     })().catch(err => {
 -       that.emit('error', err);
 -     });
 -   };
 - 
 -   proto._checkAvailable = async function _checkAvailable(ignoreStatusFile) {
 -     const name = `._ali-oss/check.status.${currentIP}.txt`;
 -     if (!ignoreStatusFile) {
 -       // only start will try to write the file
 -       await this.put(name, Buffer.from(`check available started at ${Date()}`));
 -     }
 - 
 -     if (this._checkAvailableLock) {
 -       return;
 -     }
 -     this._checkAvailableLock = true;
 -     const downStatusFiles = [];
 -     for (let i = 0; i < this.clients.length; i++) {
 -       const client = this.clients[i];
 -       // check 3 times
 -       let available = await this._checkStatus(client, name);
 -       if (!available) {
 -         // check again
 -         available = await this._checkStatus(client, name);
 -       }
 -       if (!available) {
 -         // check again
 -         /* eslint no-await-in-loop: [0] */
 -         available = await this._checkStatus(client, name);
 -         if (!available) {
 -           downStatusFiles.push(client._objectUrl(name));
 -         }
 -       }
 -       this.availables[i] = available;
 -     }
 -     this._checkAvailableLock = false;
 - 
 -     if (downStatusFiles.length > 0) {
 -       const err = new Error(
 -         `${downStatusFiles.length} data node down, please check status file: ${downStatusFiles.join(', ')}`
 -       );
 -       err.name = 'CheckAvailableError';
 -       this.emit('error', err);
 -     }
 -   };
 - 
 -   proto._checkStatus = async function _checkStatus(client, name) {
 -     let available = true;
 -     try {
 -       await client.head(name);
 -     } catch (err) {
 -       // 404 will be available too
 -       if (!err.status || err.status >= 500 || err.status < 200) {
 -         available = false;
 -       }
 -     }
 -     return available;
 -   };
 - 
 -   proto.chooseAvailable = function chooseAvailable() {
 -     if (this.schedule === MS) {
 -       // only read from master
 -       if (this.masterOnly) {
 -         return this.clients[0];
 -       }
 -       for (let i = 0; i < this.clients.length; i++) {
 -         if (this.availables[i]) {
 -           return this.clients[i];
 -         }
 -       }
 -       // all down, try to use this first one
 -       return this.clients[0];
 -     }
 - 
 -     // RR
 -     let n = this.clients.length;
 -     while (n > 0) {
 -       const i = this._nextRRIndex();
 -       if (this.availables[i]) {
 -         return this.clients[i];
 -       }
 -       n--;
 -     }
 -     // all down, try to use this first one
 -     return this.clients[0];
 -   };
 - 
 -   proto._nextRRIndex = function _nextRRIndex() {
 -     const index = this.index++;
 -     if (this.index >= this.clients.length) {
 -       this.index = 0;
 -     }
 -     return index;
 -   };
 - 
 -   proto._error = function error(err) {
 -     if (err) throw err;
 -   };
 - 
 -   proto._createCallback = function _createCallback(ctx, gen, cb) {
 -     return () => {
 -       cb = cb || this._error;
 -       gen.call(ctx).then(() => {
 -         cb();
 -       }, cb);
 -     };
 -   };
 -   proto._deferInterval = function _deferInterval(gen, timeout, cb) {
 -     return setInterval(this._createCallback(this, gen, cb), timeout);
 -   };
 - 
 -   proto.close = function close() {
 -     clearInterval(this._timerId);
 -     this._timerId = null;
 -   };
 - 
 -   return Client;
 - };
 
 
  |