租房小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

435 lines
12 KiB

3 months ago
  1. const debug = require('debug')('ali-oss');
  2. const sendToWormhole = require('stream-wormhole');
  3. const xml = require('xml2js');
  4. const AgentKeepalive = require('agentkeepalive');
  5. const HttpsAgentKeepalive = require('agentkeepalive').HttpsAgent;
  6. const merge = require('merge-descriptors');
  7. const platform = require('platform');
  8. const utility = require('utility');
  9. const urllib = require('urllib');
  10. const pkg = require('../package.json');
  11. const bowser = require('bowser');
  12. const signUtils = require('./common/signUtils');
  13. const _initOptions = require('./common/client/initOptions');
  14. const { createRequest } = require('./common/utils/createRequest');
  15. const { encoder } = require('./common/utils/encoder');
  16. const { getReqUrl } = require('./common/client/getReqUrl');
  17. const { setSTSToken } = require('./common/utils/setSTSToken');
  18. const { retry } = require('./common/utils/retry');
  19. const { isFunction } = require('./common/utils/isFunction');
  20. const { getStandardRegion } = require('./common/utils/getStandardRegion');
  21. const globalHttpAgent = new AgentKeepalive();
  22. const globalHttpsAgent = new HttpsAgentKeepalive();
  23. function Client(options, ctx) {
  24. if (!(this instanceof Client)) {
  25. return new Client(options, ctx);
  26. }
  27. if (options && options.inited) {
  28. this.options = options;
  29. } else {
  30. this.options = Client.initOptions(options);
  31. }
  32. // support custom agent and urllib client
  33. if (this.options.urllib) {
  34. this.urllib = this.options.urllib;
  35. } else {
  36. this.urllib = urllib;
  37. if (this.options.maxSockets) {
  38. globalHttpAgent.maxSockets = this.options.maxSockets;
  39. globalHttpsAgent.maxSockets = this.options.maxSockets;
  40. }
  41. this.agent = this.options.agent || globalHttpAgent;
  42. this.httpsAgent = this.options.httpsAgent || globalHttpsAgent;
  43. }
  44. this.ctx = ctx;
  45. this.userAgent = this._getUserAgent();
  46. this.stsTokenFreshTime = new Date();
  47. }
  48. /**
  49. * Expose `Client`
  50. */
  51. module.exports = Client;
  52. Client.initOptions = function initOptions(options) {
  53. return _initOptions(options);
  54. };
  55. /**
  56. * prototype
  57. */
  58. const proto = Client.prototype;
  59. /**
  60. * Object operations
  61. */
  62. merge(proto, require('./common/object'));
  63. merge(proto, require('./object'));
  64. merge(proto, require('./common/image'));
  65. /**
  66. * Bucket operations
  67. */
  68. merge(proto, require('./common/bucket'));
  69. merge(proto, require('./bucket'));
  70. // multipart upload
  71. merge(proto, require('./managed-upload'));
  72. /**
  73. * RTMP operations
  74. */
  75. merge(proto, require('./rtmp'));
  76. /**
  77. * common multipart-copy support node and browser
  78. */
  79. merge(proto, require('./common/multipart-copy'));
  80. /**
  81. * Common module parallel
  82. */
  83. merge(proto, require('./common/parallel'));
  84. /**
  85. * Multipart operations
  86. */
  87. merge(proto, require('./common/multipart'));
  88. /**
  89. * ImageClient class
  90. */
  91. Client.ImageClient = require('./image')(Client);
  92. /**
  93. * Cluster Client class
  94. */
  95. Client.ClusterClient = require('./cluster')(Client);
  96. /**
  97. * STS Client class
  98. */
  99. Client.STS = require('./sts');
  100. /**
  101. * get OSS signature
  102. * @param {String} stringToSign
  103. * @return {String} the signature
  104. */
  105. proto.signature = function signature(stringToSign) {
  106. debug('authorization stringToSign: %s', stringToSign);
  107. return signUtils.computeSignature(this.options.accessKeySecret, stringToSign, this.options.headerEncoding);
  108. };
  109. proto._getReqUrl = getReqUrl;
  110. /**
  111. * get author header
  112. *
  113. * "Authorization: OSS " + Access Key Id + ":" + Signature
  114. *
  115. * Signature = base64(hmac-sha1(Access Key Secret + "\n"
  116. * + VERB + "\n"
  117. * + CONTENT-MD5 + "\n"
  118. * + CONTENT-TYPE + "\n"
  119. * + DATE + "\n"
  120. * + CanonicalizedOSSHeaders
  121. * + CanonicalizedResource))
  122. *
  123. * @param {String} method
  124. * @param {String} resource
  125. * @param {Object} header
  126. * @return {String}
  127. *
  128. * @api private
  129. */
  130. proto.authorization = function authorization(method, resource, subres, headers) {
  131. const stringToSign = signUtils.buildCanonicalString(method.toUpperCase(), resource, {
  132. headers,
  133. parameters: subres
  134. });
  135. return signUtils.authorization(
  136. this.options.accessKeyId,
  137. this.options.accessKeySecret,
  138. stringToSign,
  139. this.options.headerEncoding
  140. );
  141. };
  142. /**
  143. * get authorization header v4
  144. *
  145. * @param {string} method
  146. * @param {Object} requestParams
  147. * @param {Object} requestParams.headers
  148. * @param {Object} [requestParams.queries]
  149. * @param {string} [bucketName]
  150. * @param {string} [objectName]
  151. * @param {string[]} [additionalHeaders]
  152. * @return {string}
  153. *
  154. * @api private
  155. */
  156. proto.authorizationV4 = function authorizationV4(method, requestParams, bucketName, objectName, additionalHeaders) {
  157. return signUtils.authorizationV4(
  158. this.options.accessKeyId,
  159. this.options.accessKeySecret,
  160. getStandardRegion(this.options.region),
  161. method,
  162. requestParams,
  163. bucketName,
  164. objectName,
  165. additionalHeaders,
  166. this.options.headerEncoding
  167. );
  168. };
  169. /**
  170. * request oss server
  171. * @param {Object} params
  172. * - {String} object
  173. * - {String} bucket
  174. * - {Object} [headers]
  175. * - {Object} [query]
  176. * - {Buffer} [content]
  177. * - {Stream} [stream]
  178. * - {Stream} [writeStream]
  179. * - {String} [mime]
  180. * - {Boolean} [xmlResponse]
  181. * - {Boolean} [customResponse]
  182. * - {Number} [timeout]
  183. * - {Object} [ctx] request context, default is `this.ctx`
  184. *
  185. * @api private
  186. */
  187. proto.request = async function (params) {
  188. if (this.options.retryMax) {
  189. return await retry(request.bind(this), this.options.retryMax, {
  190. errorHandler: err => {
  191. const _errHandle = _err => {
  192. if (params.stream) return false;
  193. const statusErr = [-1, -2].includes(_err.status);
  194. const requestErrorRetryHandle = this.options.requestErrorRetryHandle || (() => true);
  195. return statusErr && requestErrorRetryHandle(_err);
  196. };
  197. if (_errHandle(err)) return true;
  198. return false;
  199. }
  200. })(params);
  201. } else {
  202. return await request.call(this, params);
  203. }
  204. };
  205. async function request(params) {
  206. if (this.options.stsToken && isFunction(this.options.refreshSTSToken)) {
  207. await setSTSToken.call(this);
  208. }
  209. const reqParams = createRequest.call(this, params);
  210. let result;
  211. let reqErr;
  212. try {
  213. result = await this.urllib.request(reqParams.url, reqParams.params);
  214. debug('response %s %s, got %s, headers: %j', params.method, reqParams.url, result.status, result.headers);
  215. } catch (err) {
  216. reqErr = err;
  217. }
  218. let err;
  219. if (result && params.successStatuses && params.successStatuses.indexOf(result.status) === -1) {
  220. err = await this.requestError(result);
  221. err.params = params;
  222. } else if (reqErr) {
  223. err = await this.requestError(reqErr);
  224. }
  225. if (err) {
  226. if (params.customResponse && result && result.res) {
  227. // consume the response stream
  228. await sendToWormhole(result.res);
  229. }
  230. if (err.name === 'ResponseTimeoutError') {
  231. err.message = `${
  232. err.message.split(',')[0]
  233. }, please increase the timeout, see more details at https://github.com/ali-sdk/ali-oss#responsetimeouterror`;
  234. }
  235. if (err.name === 'ConnectionTimeoutError') {
  236. err.message = `${
  237. err.message.split(',')[0]
  238. }, please increase the timeout or reduce the partSize, see more details at https://github.com/ali-sdk/ali-oss#connectiontimeouterror`;
  239. }
  240. throw err;
  241. }
  242. if (params.xmlResponse) {
  243. result.data = await this.parseXML(result.data);
  244. }
  245. return result;
  246. }
  247. proto._getResource = function _getResource(params) {
  248. let resource = '/';
  249. if (params.bucket) resource += `${params.bucket}/`;
  250. if (params.object) resource += encoder(params.object, this.options.headerEncoding);
  251. return resource;
  252. };
  253. proto._escape = function _escape(name) {
  254. return utility.encodeURIComponent(name).replace(/%2F/g, '/');
  255. };
  256. /*
  257. * Get User-Agent for browser & node.js
  258. * @example
  259. * aliyun-sdk-nodejs/4.1.2 Node.js 5.3.0 on Darwin 64-bit
  260. * aliyun-sdk-js/4.1.2 Safari 9.0 on Apple iPhone(iOS 9.2.1)
  261. * aliyun-sdk-js/4.1.2 Chrome 43.0.2357.134 32-bit on Windows Server 2008 R2 / 7 64-bit
  262. */
  263. proto._getUserAgent = function _getUserAgent() {
  264. const agent = process && process.browser ? 'js' : 'nodejs';
  265. const sdk = `aliyun-sdk-${agent}/${pkg.version}`;
  266. let plat = platform.description;
  267. if (!plat && process) {
  268. plat = `Node.js ${process.version.slice(1)} on ${process.platform} ${process.arch}`;
  269. }
  270. return this._checkUserAgent(`${sdk} ${plat}`);
  271. };
  272. proto._checkUserAgent = function _checkUserAgent(ua) {
  273. const userAgent = ua.replace(/\u03b1/, 'alpha').replace(/\u03b2/, 'beta');
  274. return userAgent;
  275. };
  276. /*
  277. * Check Browser And Version
  278. * @param {String} [name] browser name: like IE, Chrome, Firefox
  279. * @param {String} [version] browser major version: like 10(IE 10.x), 55(Chrome 55.x), 50(Firefox 50.x)
  280. * @return {Bool} true or false
  281. * @api private
  282. */
  283. proto.checkBrowserAndVersion = function checkBrowserAndVersion(name, version) {
  284. return bowser.name === name && bowser.version.split('.')[0] === version;
  285. };
  286. /**
  287. * thunkify xml.parseString
  288. * @param {String|Buffer} str
  289. *
  290. * @api private
  291. */
  292. proto.parseXML = function parseXMLThunk(str) {
  293. return new Promise((resolve, reject) => {
  294. if (Buffer.isBuffer(str)) {
  295. str = str.toString();
  296. }
  297. xml.parseString(
  298. str,
  299. {
  300. explicitRoot: false,
  301. explicitArray: false
  302. },
  303. (err, result) => {
  304. if (err) {
  305. reject(err);
  306. } else {
  307. resolve(result);
  308. }
  309. }
  310. );
  311. });
  312. };
  313. /**
  314. * generater a request error with request response
  315. * @param {Object} result
  316. *
  317. * @api private
  318. */
  319. proto.requestError = async function requestError(result) {
  320. let err = null;
  321. const setError = async message => {
  322. let info;
  323. try {
  324. info = (await this.parseXML(message)) || {};
  325. } catch (error) {
  326. debug(message);
  327. error.message += `\nraw xml: ${message}`;
  328. error.status = result.status;
  329. error.requestId = result.headers['x-oss-request-id'];
  330. return error;
  331. }
  332. let msg = info.Message || `unknow request error, status: ${result.status}`;
  333. if (info.Condition) {
  334. msg += ` (condition: ${info.Condition})`;
  335. }
  336. err = new Error(msg);
  337. err.name = info.Code ? `${info.Code}Error` : 'UnknownError';
  338. err.status = result.status;
  339. err.code = info.Code;
  340. err.requestId = info.RequestId;
  341. err.ecCode = info.EC;
  342. err.hostId = info.HostId;
  343. };
  344. if (result.name === 'ResponseTimeoutError') {
  345. err = new Error(result.message);
  346. err.name = result.name;
  347. } else if (!result.data || !result.data.length) {
  348. if (result.status === -1 || result.status === -2) {
  349. // -1 is net error , -2 is timeout
  350. err = new Error(result.message);
  351. err.name = result.name;
  352. err.status = result.status;
  353. err.code = result.name;
  354. } else {
  355. // HEAD not exists resource
  356. if (result.status === 404) {
  357. err = new Error('Object not exists');
  358. err.name = 'NoSuchKeyError';
  359. err.status = 404;
  360. err.code = 'NoSuchKey';
  361. } else if (result.status === 412) {
  362. err = new Error('Pre condition failed');
  363. err.name = 'PreconditionFailedError';
  364. err.status = 412;
  365. err.code = 'PreconditionFailed';
  366. } else {
  367. err = new Error(`Unknow error, status: ${result.status}`);
  368. err.name = 'UnknownError';
  369. err.status = result.status;
  370. err.res = result;
  371. const ossErr = result.headers['x-oss-err'];
  372. if (ossErr) {
  373. const message = Buffer.from(ossErr, 'base64').toString('utf8');
  374. await setError(message);
  375. }
  376. }
  377. err.requestId = result.headers['x-oss-request-id'];
  378. err.host = '';
  379. }
  380. } else {
  381. const message = String(result.data);
  382. debug('request response error data: %s', message);
  383. await setError(message);
  384. }
  385. debug('generate error %j', err);
  386. return err;
  387. };
  388. proto.setSLDEnabled = function setSLDEnabled(enable) {
  389. this.options.sldEnable = !!enable;
  390. return this;
  391. };