| const debug = require('debug')('ali-oss'); | |
| const sendToWormhole = require('stream-wormhole'); | |
| const xml = require('xml2js'); | |
| const AgentKeepalive = require('agentkeepalive'); | |
| const HttpsAgentKeepalive = require('agentkeepalive').HttpsAgent; | |
| const merge = require('merge-descriptors'); | |
| const platform = require('platform'); | |
| const utility = require('utility'); | |
| const urllib = require('urllib'); | |
| const pkg = require('../package.json'); | |
| const bowser = require('bowser'); | |
| const signUtils = require('./common/signUtils'); | |
| const _initOptions = require('./common/client/initOptions'); | |
| const { createRequest } = require('./common/utils/createRequest'); | |
| const { encoder } = require('./common/utils/encoder'); | |
| const { getReqUrl } = require('./common/client/getReqUrl'); | |
| const { setSTSToken } = require('./common/utils/setSTSToken'); | |
| const { retry } = require('./common/utils/retry'); | |
| const { isFunction } = require('./common/utils/isFunction'); | |
| const { getStandardRegion } = require('./common/utils/getStandardRegion'); | |
| 
 | |
| const globalHttpAgent = new AgentKeepalive(); | |
| const globalHttpsAgent = new HttpsAgentKeepalive(); | |
| 
 | |
| function Client(options, ctx) { | |
|   if (!(this instanceof Client)) { | |
|     return new Client(options, ctx); | |
|   } | |
| 
 | |
|   if (options && options.inited) { | |
|     this.options = options; | |
|   } else { | |
|     this.options = Client.initOptions(options); | |
|   } | |
| 
 | |
|   // support custom agent and urllib client | |
|   if (this.options.urllib) { | |
|     this.urllib = this.options.urllib; | |
|   } else { | |
|     this.urllib = urllib; | |
|     if (this.options.maxSockets) { | |
|       globalHttpAgent.maxSockets = this.options.maxSockets; | |
|       globalHttpsAgent.maxSockets = this.options.maxSockets; | |
|     } | |
|     this.agent = this.options.agent || globalHttpAgent; | |
|     this.httpsAgent = this.options.httpsAgent || globalHttpsAgent; | |
|   } | |
|   this.ctx = ctx; | |
|   this.userAgent = this._getUserAgent(); | |
|   this.stsTokenFreshTime = new Date(); | |
| } | |
| 
 | |
| /** | |
|  * Expose `Client` | |
|  */ | |
| 
 | |
| module.exports = Client; | |
| 
 | |
| Client.initOptions = function initOptions(options) { | |
|   return _initOptions(options); | |
| }; | |
| 
 | |
| /** | |
|  * prototype | |
|  */ | |
| 
 | |
| const proto = Client.prototype; | |
| 
 | |
| /** | |
|  * Object operations | |
|  */ | |
| merge(proto, require('./common/object')); | |
| merge(proto, require('./object')); | |
| merge(proto, require('./common/image')); | |
| /** | |
|  * Bucket operations | |
|  */ | |
| merge(proto, require('./common/bucket')); | |
| merge(proto, require('./bucket')); | |
| // multipart upload | |
| merge(proto, require('./managed-upload')); | |
| /** | |
|  * RTMP operations | |
|  */ | |
| merge(proto, require('./rtmp')); | |
| 
 | |
| /** | |
|  * common multipart-copy support node and browser | |
|  */ | |
| merge(proto, require('./common/multipart-copy')); | |
| /** | |
|  * Common module parallel | |
|  */ | |
| merge(proto, require('./common/parallel')); | |
| /** | |
|  * Multipart operations | |
|  */ | |
| merge(proto, require('./common/multipart')); | |
| /** | |
|  * ImageClient class | |
|  */ | |
| Client.ImageClient = require('./image')(Client); | |
| /** | |
|  * Cluster Client class | |
|  */ | |
| Client.ClusterClient = require('./cluster')(Client); | |
| 
 | |
| /** | |
|  * STS Client class | |
|  */ | |
| Client.STS = require('./sts'); | |
| 
 | |
| /** | |
|  * get OSS signature | |
|  * @param {String} stringToSign | |
|  * @return {String} the signature | |
|  */ | |
| proto.signature = function signature(stringToSign) { | |
|   debug('authorization stringToSign: %s', stringToSign); | |
| 
 | |
|   return signUtils.computeSignature(this.options.accessKeySecret, stringToSign, this.options.headerEncoding); | |
| }; | |
| 
 | |
| proto._getReqUrl = getReqUrl; | |
| 
 | |
| /** | |
|  * get author header | |
|  * | |
|  * "Authorization: OSS " + Access Key Id + ":" + Signature | |
|  * | |
|  * Signature = base64(hmac-sha1(Access Key Secret + "\n" | |
|  *  + VERB + "\n" | |
|  *  + CONTENT-MD5 + "\n" | |
|  *  + CONTENT-TYPE + "\n" | |
|  *  + DATE + "\n" | |
|  *  + CanonicalizedOSSHeaders | |
|  *  + CanonicalizedResource)) | |
|  * | |
|  * @param {String} method | |
|  * @param {String} resource | |
|  * @param {Object} header | |
|  * @return {String} | |
|  * | |
|  * @api private | |
|  */ | |
| 
 | |
| proto.authorization = function authorization(method, resource, subres, headers) { | |
|   const stringToSign = signUtils.buildCanonicalString(method.toUpperCase(), resource, { | |
|     headers, | |
|     parameters: subres | |
|   }); | |
| 
 | |
|   return signUtils.authorization( | |
|     this.options.accessKeyId, | |
|     this.options.accessKeySecret, | |
|     stringToSign, | |
|     this.options.headerEncoding | |
|   ); | |
| }; | |
| 
 | |
| /** | |
|  * get authorization header v4 | |
|  * | |
|  * @param {string} method | |
|  * @param {Object} requestParams | |
|  * @param {Object} requestParams.headers | |
|  * @param {Object} [requestParams.queries] | |
|  * @param {string} [bucketName] | |
|  * @param {string} [objectName] | |
|  * @param {string[]} [additionalHeaders] | |
|  * @return {string} | |
|  * | |
|  * @api private | |
|  */ | |
| proto.authorizationV4 = function authorizationV4(method, requestParams, bucketName, objectName, additionalHeaders) { | |
|   return signUtils.authorizationV4( | |
|     this.options.accessKeyId, | |
|     this.options.accessKeySecret, | |
|     getStandardRegion(this.options.region), | |
|     method, | |
|     requestParams, | |
|     bucketName, | |
|     objectName, | |
|     additionalHeaders, | |
|     this.options.headerEncoding | |
|   ); | |
| }; | |
| 
 | |
| /** | |
|  * request oss server | |
|  * @param {Object} params | |
|  *   - {String} object | |
|  *   - {String} bucket | |
|  *   - {Object} [headers] | |
|  *   - {Object} [query] | |
|  *   - {Buffer} [content] | |
|  *   - {Stream} [stream] | |
|  *   - {Stream} [writeStream] | |
|  *   - {String} [mime] | |
|  *   - {Boolean} [xmlResponse] | |
|  *   - {Boolean} [customResponse] | |
|  *   - {Number} [timeout] | |
|  *   - {Object} [ctx] request context, default is `this.ctx` | |
|  * | |
|  * @api private | |
|  */ | |
| 
 | |
| proto.request = async function (params) { | |
|   if (this.options.retryMax) { | |
|     return await retry(request.bind(this), this.options.retryMax, { | |
|       errorHandler: err => { | |
|         const _errHandle = _err => { | |
|           if (params.stream) return false; | |
|           const statusErr = [-1, -2].includes(_err.status); | |
|           const requestErrorRetryHandle = this.options.requestErrorRetryHandle || (() => true); | |
|           return statusErr && requestErrorRetryHandle(_err); | |
|         }; | |
|         if (_errHandle(err)) return true; | |
|         return false; | |
|       } | |
|     })(params); | |
|   } else { | |
|     return await request.call(this, params); | |
|   } | |
| }; | |
| 
 | |
| async function request(params) { | |
|   if (this.options.stsToken && isFunction(this.options.refreshSTSToken)) { | |
|     await setSTSToken.call(this); | |
|   } | |
|   const reqParams = createRequest.call(this, params); | |
|   let result; | |
|   let reqErr; | |
|   try { | |
|     result = await this.urllib.request(reqParams.url, reqParams.params); | |
|     debug('response %s %s, got %s, headers: %j', params.method, reqParams.url, result.status, result.headers); | |
|   } catch (err) { | |
|     reqErr = err; | |
|   } | |
|   let err; | |
|   if (result && params.successStatuses && params.successStatuses.indexOf(result.status) === -1) { | |
|     err = await this.requestError(result); | |
|     err.params = params; | |
|   } else if (reqErr) { | |
|     err = await this.requestError(reqErr); | |
|   } | |
| 
 | |
|   if (err) { | |
|     if (params.customResponse && result && result.res) { | |
|       // consume the response stream | |
|       await sendToWormhole(result.res); | |
|     } | |
| 
 | |
|     if (err.name === 'ResponseTimeoutError') { | |
|       err.message = `${ | |
|         err.message.split(',')[0] | |
|       }, please increase the timeout, see more details at https://github.com/ali-sdk/ali-oss#responsetimeouterror`; | |
|     } | |
|     if (err.name === 'ConnectionTimeoutError') { | |
|       err.message = `${ | |
|         err.message.split(',')[0] | |
|       }, please increase the timeout or reduce the partSize, see more details at https://github.com/ali-sdk/ali-oss#connectiontimeouterror`; | |
|     } | |
|     throw err; | |
|   } | |
| 
 | |
|   if (params.xmlResponse) { | |
|     result.data = await this.parseXML(result.data); | |
|   } | |
|   return result; | |
| } | |
| 
 | |
| proto._getResource = function _getResource(params) { | |
|   let resource = '/'; | |
|   if (params.bucket) resource += `${params.bucket}/`; | |
|   if (params.object) resource += encoder(params.object, this.options.headerEncoding); | |
| 
 | |
|   return resource; | |
| }; | |
| 
 | |
| proto._escape = function _escape(name) { | |
|   return utility.encodeURIComponent(name).replace(/%2F/g, '/'); | |
| }; | |
| 
 | |
| /* | |
|  * Get User-Agent for browser & node.js | |
|  * @example | |
|  *   aliyun-sdk-nodejs/4.1.2 Node.js 5.3.0 on Darwin 64-bit | |
|  *   aliyun-sdk-js/4.1.2 Safari 9.0 on Apple iPhone(iOS 9.2.1) | |
|  *   aliyun-sdk-js/4.1.2 Chrome 43.0.2357.134 32-bit on Windows Server 2008 R2 / 7 64-bit | |
|  */ | |
| 
 | |
| proto._getUserAgent = function _getUserAgent() { | |
|   const agent = process && process.browser ? 'js' : 'nodejs'; | |
|   const sdk = `aliyun-sdk-${agent}/${pkg.version}`; | |
|   let plat = platform.description; | |
|   if (!plat && process) { | |
|     plat = `Node.js ${process.version.slice(1)} on ${process.platform} ${process.arch}`; | |
|   } | |
| 
 | |
|   return this._checkUserAgent(`${sdk} ${plat}`); | |
| }; | |
| 
 | |
| proto._checkUserAgent = function _checkUserAgent(ua) { | |
|   const userAgent = ua.replace(/\u03b1/, 'alpha').replace(/\u03b2/, 'beta'); | |
|   return userAgent; | |
| }; | |
| 
 | |
| /* | |
|  * Check Browser And Version | |
|  * @param {String} [name] browser name: like IE, Chrome, Firefox | |
|  * @param {String} [version] browser major version: like 10(IE 10.x), 55(Chrome 55.x), 50(Firefox 50.x) | |
|  * @return {Bool} true or false | |
|  * @api private | |
|  */ | |
| 
 | |
| proto.checkBrowserAndVersion = function checkBrowserAndVersion(name, version) { | |
|   return bowser.name === name && bowser.version.split('.')[0] === version; | |
| }; | |
| 
 | |
| /** | |
|  * thunkify xml.parseString | |
|  * @param {String|Buffer} str | |
|  * | |
|  * @api private | |
|  */ | |
| 
 | |
| proto.parseXML = function parseXMLThunk(str) { | |
|   return new Promise((resolve, reject) => { | |
|     if (Buffer.isBuffer(str)) { | |
|       str = str.toString(); | |
|     } | |
|     xml.parseString( | |
|       str, | |
|       { | |
|         explicitRoot: false, | |
|         explicitArray: false | |
|       }, | |
|       (err, result) => { | |
|         if (err) { | |
|           reject(err); | |
|         } else { | |
|           resolve(result); | |
|         } | |
|       } | |
|     ); | |
|   }); | |
| }; | |
| 
 | |
| /** | |
|  * generater a request error with request response | |
|  * @param {Object} result | |
|  * | |
|  * @api private | |
|  */ | |
| 
 | |
| proto.requestError = async function requestError(result) { | |
|   let err = null; | |
| 
 | |
|   const setError = async message => { | |
|     let info; | |
|     try { | |
|       info = (await this.parseXML(message)) || {}; | |
|     } catch (error) { | |
|       debug(message); | |
|       error.message += `\nraw xml: ${message}`; | |
|       error.status = result.status; | |
|       error.requestId = result.headers && result.headers['x-oss-request-id']; | |
|       return error; | |
|     } | |
| 
 | |
|     let msg = info.Message || `unknow request error, status: ${result.status}`; | |
|     if (info.Condition) { | |
|       msg += ` (condition: ${info.Condition})`; | |
|     } | |
|     err = new Error(msg); | |
|     err.name = info.Code ? `${info.Code}Error` : 'UnknownError'; | |
|     err.status = result.status; | |
|     err.code = info.Code; | |
|     err.requestId = info.RequestId; | |
|     err.ecCode = info.EC; | |
|     err.hostId = info.HostId; | |
|     return err; | |
|   }; | |
| 
 | |
|   if (result.name === 'ResponseTimeoutError') { | |
|     err = new Error(result.message); | |
|     err.name = result.name; | |
|   } else if (!result.data || !result.data.length) { | |
|     if (result.status === -1 || result.status === -2) { | |
|       // -1 is net error , -2 is timeout | |
|       err = new Error(result.message); | |
|       err.name = result.name; | |
|       err.status = result.status; | |
|       err.code = result.name; | |
|     } else { | |
|       // HEAD not exists resource | |
|       if (result.status === 404) { | |
|         err = new Error('Object not exists'); | |
|         err.name = 'NoSuchKeyError'; | |
|         err.status = 404; | |
|         err.code = 'NoSuchKey'; | |
|       } else if (result.status === 412) { | |
|         err = new Error('Pre condition failed'); | |
|         err.name = 'PreconditionFailedError'; | |
|         err.status = 412; | |
|         err.code = 'PreconditionFailed'; | |
|       } else { | |
|         err = new Error(`Unknow error, status: ${result.status}`); | |
|         err.name = 'UnknownError'; | |
|         err.status = result.status; | |
|         err.res = result; | |
|         const ossErr = result.headers && result.headers['x-oss-err']; | |
|         if (ossErr) { | |
|           const message = Buffer.from(ossErr, 'base64').toString('utf8'); | |
|           err = await setError(message); | |
|         } | |
|       } | |
|       err.requestId = result.headers && result.headers['x-oss-request-id']; | |
|       err.host = ''; | |
|     } | |
|   } else { | |
|     const message = String(result.data); | |
|     debug('request response error data: %s', message); | |
| 
 | |
|     err = await setError(message); | |
|   } | |
| 
 | |
|   debug('generate error %j', err); | |
|   return err; | |
| }; | |
| 
 | |
| proto.setSLDEnabled = function setSLDEnabled(enable) { | |
|   this.options.sldEnable = !!enable; | |
|   return this; | |
| };
 |