├── gopher.png ├── qiniu ├── retry │ ├── index.js │ ├── retryPolicy.js │ └── retrier.js ├── httpc │ ├── middleware │ │ ├── index.js │ │ ├── base.js │ │ ├── ua.js │ │ ├── qiniuAuth.js │ │ └── retryDomains.js │ ├── endpointsProvider.js │ ├── responseWrapper.js │ ├── endpoint.js │ ├── endpointsRetryPolicy.js │ ├── client.js │ ├── regionsRetryPolicy.js │ └── region.js ├── auth │ └── digest.js ├── rtc │ ├── util.js │ ├── credentials.js │ ├── app.js │ └── room.js ├── sms │ └── message.js ├── fop.js ├── cdn.js ├── storage │ └── internal.js ├── zone.js ├── rpc.js └── conf.js ├── test-env.sh ├── .npmignore ├── Makefile ├── .gitignore ├── examples ├── rs_upload_token.js ├── cdn_create_timestamp_antileech_url.js ├── rs_download.js ├── bucket_image_unimage.js ├── rs_prefetch.js ├── rs_delete.js ├── rs_bucket_info.js ├── atlab_check_qiniu_auth.js ├── rs_change_type.js ├── rs_change_mime.js ├── rs_delete_after_days.js ├── rs_move.js ├── rs_copy.js ├── rs_stat.js ├── rs_fetch.js ├── prefop.js ├── cdn_prefetch_urls.js ├── object_lifecycle.js ├── rs_listv2.js ├── rs_batch_stat.js ├── rs_batch_delete.js ├── rs_batch_change_type.js ├── rs_batch_chgm.js ├── video_pfop.js ├── rs_batch_delete_after_days.js ├── form_upload_simple.js ├── rs_batch_move.js ├── rs_batch_copy.js ├── resume_upload_simple.js ├── http_https_proxy.js ├── cdn_get_flux_data.js ├── pfops_video_plus.js ├── cdn_get_bandwidth_data.js ├── cdn_get_log_list.js ├── rs_list_prefix.js ├── form_qvm_upload.js ├── create_uptoken.js ├── rtc_demo.js ├── cdn_refresh_urls_dirs.js └── sms.js ├── .github └── workflows │ ├── version-check.yml │ └── ci-test.yml ├── .eslintrc.js ├── codecov.yml ├── index.js ├── README.md ├── package.json ├── test ├── rtc.test.js ├── conftest.js ├── cdn.test.js ├── retry.test.js ├── conf.test.js └── fop.test.js ├── CHANGELOG.md └── StorageResponseInterface.d.ts /gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiniu/nodejs-sdk/HEAD/gopher.png -------------------------------------------------------------------------------- /qiniu/retry/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Retrier: require('./retrier').Retrier, 3 | RetryPolicy: require('./retryPolicy').RetryPolicy 4 | }; 5 | -------------------------------------------------------------------------------- /test-env.sh: -------------------------------------------------------------------------------- 1 | export QINIU_ACCESS_KEY="" 2 | export QINIU_SECRET_KEY="" 3 | export QINIU_TEST_BUCKET="" 4 | export QINIU_TEST_DOMAIN="" 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | test-env.sh 3 | .travis.yml 4 | .eslintrc.js 5 | coverage.html 6 | gopher.png 7 | lib-cov/ 8 | Makefile 9 | docs/ 10 | examples/ 11 | coverage/ 12 | .nyc_output/ 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @npm test 3 | 4 | test-cov: clean 5 | @npm run cover 6 | @npm run report 7 | 8 | clean: 9 | rm -rf ./coverage ./.nyc_output 10 | 11 | .PHONY: test-cov test 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | coverage.html 3 | *.swp 4 | 5 | lib-cov 6 | *.seed 7 | *.log 8 | *.csv 9 | *.dat 10 | *.out 11 | *.pid 12 | *.gz 13 | test-env.sh 14 | 15 | pids 16 | logs 17 | results 18 | 19 | node_modules 20 | npm-debug.log 21 | test/config.js 22 | package-lock.json 23 | 24 | coverage/ 25 | .nyc_output/ 26 | .idea 27 | yarn.lock 28 | -------------------------------------------------------------------------------- /qiniu/httpc/middleware/index.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); 2 | 3 | module.exports = { 4 | composeMiddlewares: base.composeMiddlewares, 5 | Middleware: base.Middleware, 6 | RetryDomainsMiddleware: require('./retryDomains').RetryDomainsMiddleware, 7 | UserAgentMiddleware: require('./ua').UserAgentMiddleware, 8 | QiniuAuthMiddleware: require('./qiniuAuth').QiniuAuthMiddleware 9 | }; 10 | -------------------------------------------------------------------------------- /qiniu/auth/digest.js: -------------------------------------------------------------------------------- 1 | const conf = require('../conf'); 2 | 3 | exports.Mac = Mac; 4 | 5 | const defaultOptions = { 6 | disableQiniuTimestampSignature: null 7 | }; 8 | 9 | function Mac (accessKey, secretKey, options) { 10 | this.accessKey = accessKey || conf.ACCESS_KEY; 11 | this.secretKey = secretKey || conf.SECRET_KEY; 12 | this.options = Object.assign({}, defaultOptions, options); 13 | } 14 | -------------------------------------------------------------------------------- /examples/rs_upload_token.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const bucket = process.env.QINIU_TEST_BUCKET; 6 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 7 | const putPolicy = new qiniu.rs.PutPolicy({ 8 | scope: bucket 9 | }); 10 | 11 | const uploadToken = putPolicy.uploadToken(mac); 12 | console.log(uploadToken); 13 | -------------------------------------------------------------------------------- /examples/cdn_create_timestamp_antileech_url.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | var domain = 'http://sq.qiniuts.com'; 4 | var fileName = '1491535764000.png'; 5 | // 加密密钥 6 | var encryptKey = '**'; 7 | 8 | var query = 'imageView2/2/w/480/format/jpg'; 9 | 10 | var deadline = parseInt(Date.now() / 1000) + 3600; 11 | 12 | var cdnManager = new qiniu.cdn.CdnManager(null); 13 | 14 | var finalUrl = cdnManager.createTimestampAntiLeechUrl(domain, fileName, query, 15 | encryptKey, deadline); 16 | 17 | console.log(finalUrl); 18 | -------------------------------------------------------------------------------- /qiniu/rtc/util.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | 3 | exports.base64ToUrlSafe = function (v) { 4 | return v.replace(/\//g, '_').replace(/\+/g, '-'); 5 | }; 6 | 7 | exports.urlsafeBase64Encode = function (jsonFlags) { 8 | var encoded = Buffer.from(jsonFlags).toString('base64'); 9 | return exports.base64ToUrlSafe(encoded); 10 | }; 11 | 12 | exports.hmacSha1 = function (encodedFlags, secretKey) { 13 | var hmac = crypto.createHmac('sha1', secretKey); 14 | hmac.update(encodedFlags); 15 | return hmac.digest('base64'); 16 | }; 17 | -------------------------------------------------------------------------------- /.github/workflows/version-check.yml: -------------------------------------------------------------------------------- 1 | name: NodeJS SDK Version Check 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+" 6 | jobs: 7 | linux: 8 | name: Version Check 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Set env 14 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV 15 | - name: Check 16 | run: | 17 | set -e 18 | grep -qF "## ${RELEASE_VERSION}" CHANGELOG.md 19 | grep -qF "\"version\": \"${RELEASE_VERSION}\"" package.json 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | "extends": "standard", 7 | "parserOptions": { 8 | "ecmaVersion": 6 9 | }, 10 | "rules": { 11 | "indent": [ 12 | "error", 13 | 4 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ], 23 | "semi": [ 24 | "error", 25 | "always" 26 | ], 27 | "eqeqeq": [0] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /qiniu/httpc/endpointsProvider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class 3 | * @implements EndpointsProvider 4 | * @property {Endpoint[]} endpoints 5 | * @constructor 6 | * @param {Endpoint[]} endpoints 7 | */ 8 | function StaticEndpointsProvider (endpoints) { 9 | this.endpoints = endpoints; 10 | } 11 | 12 | /** 13 | * @param {Region} region 14 | * @param {string} serviceName 15 | */ 16 | StaticEndpointsProvider.fromRegion = function (region, serviceName) { 17 | return new StaticEndpointsProvider(region.services[serviceName]); 18 | }; 19 | 20 | /** 21 | * @returns {Promise} 22 | */ 23 | StaticEndpointsProvider.prototype.getEndpoints = function () { 24 | return Promise.resolve(this.endpoints); 25 | }; 26 | 27 | exports.StaticEndpointsProvider = StaticEndpointsProvider; 28 | -------------------------------------------------------------------------------- /qiniu/httpc/middleware/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware could be an interface if migrate to typescript 3 | * @class 4 | * @constructor 5 | */ 6 | function Middleware () { 7 | } 8 | 9 | /** 10 | * @memberOf Middleware 11 | * @param _request 12 | * @param _next 13 | */ 14 | Middleware.prototype.send = function (_request, _next) { 15 | throw new Error('The Middleware NOT be Implemented'); 16 | }; 17 | 18 | exports.Middleware = Middleware; 19 | 20 | /** 21 | * @param {Middleware[]} middlewares 22 | * @param {function(ReqOpts):Promise} handler 23 | * @returns {function(ReqOpts):Promise} 24 | */ 25 | exports.composeMiddlewares = function (middlewares, handler) { 26 | return middlewares.reverse() 27 | .reduce( 28 | (h, mw) => request => mw.send(request, h), 29 | handler 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /examples/rs_download.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | const publicBucketDomain = 'http://if-pbl.qiniudn.com'; 9 | const privateBucketDomain = 'http://if-pri.qiniudn.com'; 10 | const key = 'qiniu.mp4'; 11 | 12 | // public 13 | const publicDownloadUrl = bucketManager.publicDownloadUrl(publicBucketDomain, key); 14 | console.log(publicDownloadUrl); 15 | 16 | // private 17 | const deadline = parseInt(Date.now() / 1000) + 3600; // 1小时过期 18 | const privateDownloadUrl = bucketManager.privateDownloadUrl(privateBucketDomain, 19 | key, 20 | deadline); 21 | console.log(privateDownloadUrl); 22 | -------------------------------------------------------------------------------- /examples/bucket_image_unimage.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | const bucket = 'if-pbl'; 9 | const srcSiteUrl = 'http://www.baidu.com/'; 10 | const srcHost = null; 11 | 12 | bucketManager.image( 13 | bucket, 14 | srcSiteUrl, 15 | srcHost 16 | ) 17 | .then(({ data, resp }) => { 18 | console.log('image status code', resp.statusCode); 19 | return bucketManager.unimage(bucket); 20 | }) 21 | .then(({ data, resp }) => { 22 | console.log('unimage status code', resp.statusCode); 23 | }) 24 | .catch(err => { 25 | console.log(err); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/rs_prefetch.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const bucket = process.env.QINIU_TEST_BUCKET; 11 | const key = 'qiniu.mp4'; 12 | 13 | bucketManager.prefetch(bucket, key) 14 | .then(({ data, resp }) => { 15 | if (resp.statusCode === 200) { 16 | console.log(data); 17 | } else { 18 | console.log(resp.statusCode); 19 | console.log(data); 20 | } 21 | }) 22 | .catch(err => { 23 | console.log('failed', err); 24 | }); 25 | -------------------------------------------------------------------------------- /examples/rs_delete.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const bucket = process.env.QINIU_TEST_BUCKET; 11 | const key = 'qiniu_new_copy.mp4'; 12 | 13 | bucketManager.delete(bucket, key) 14 | .then(({ data, resp }) => { 15 | if (resp.statusCode === 200) { 16 | console.log(data); 17 | } else { 18 | console.log(resp.statusCode); 19 | console.log(data); 20 | } 21 | }) 22 | .catch(err => { 23 | console.log('failed', err); 24 | }); 25 | -------------------------------------------------------------------------------- /examples/rs_bucket_info.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | 6 | const bucket = process.env.QINIU_TEST_BUCKET; 7 | 8 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 9 | const config = new qiniu.conf.Config(); 10 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 11 | config.useHttpsDomain = 'https'; 12 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 13 | // @param bucketName 空间名 14 | bucketManager.getBucketInfo(bucket) 15 | .then(({ data, resp }) => { 16 | if (resp.statusCode === 200) { 17 | console.log(data); 18 | } else { 19 | console.log(resp.statusCode); 20 | console.log(data); 21 | } 22 | }) 23 | .catch(err => { 24 | console.log('failed', err); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/atlab_check_qiniu_auth.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index.js'); 2 | const proc = require('process'); 3 | 4 | var accessKey = proc.env.QINIU_ACCESS_KEY; 5 | var secretKey = proc.env.QINIU_SECRET_KEY; 6 | 7 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 8 | var reqURL = 'http://serve.atlab.ai/v1/eval/facex-detect'; 9 | var contentType = 'application/json'; 10 | var reqBody = '{"data":{"uri":"https://ors35x6a7.qnssl.com/atshow-face-detection-20170703/1.png"}}'; 11 | var accessToken = qiniu.util.generateAccessTokenV2(mac, reqURL, 'POST', contentType, reqBody); 12 | var headers = { 13 | Authorization: accessToken, 14 | 'Content-Type': contentType 15 | }; 16 | 17 | qiniu.rpc.post(reqURL, reqBody, headers, function (err, body, info) { 18 | if (err) { 19 | console.error(err); 20 | return; 21 | } 22 | console.log(info); 23 | console.log(body); 24 | }); 25 | -------------------------------------------------------------------------------- /examples/rs_change_type.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.zone = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const bucket = process.env.QINIU_TEST_BUCKET; 11 | const key = 'qiniu.mp4'; 12 | const newType = 1; // 低频存储 13 | 14 | bucketManager.changeType(bucket, key, newType) 15 | .then(({ data, resp }) => { 16 | if (resp.statusCode === 200) { 17 | console.log(data); 18 | } else { 19 | console.log(resp.statusCode); 20 | console.log(data); 21 | } 22 | }) 23 | .catch(err => { 24 | console.log('failed', err); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/rs_change_mime.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.zone = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const bucket = process.env.QINIU_TEST_BUCKET; 11 | const key = 'qiniu.mp4'; 12 | const newMime = 'video/x-mp4'; 13 | 14 | bucketManager.changeMime(bucket, key, newMime) 15 | .then(({ data, resp }) => { 16 | if (resp.statusCode === 200) { 17 | console.log(data); 18 | } else { 19 | console.log(resp.statusCode); 20 | console.log(data); 21 | } 22 | }) 23 | .catch(err => { 24 | console.log('failed', err); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/rs_delete_after_days.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const bucket = process.env.QINIU_TEST_BUCKET; 11 | const key = 'qiniu_new_copy.mp4'; 12 | const days = 10; 13 | 14 | bucketManager.deleteAfterDays(bucket, key, days) 15 | .then(({ data, resp }) => { 16 | if (resp.statusCode === 200) { 17 | console.log(data); 18 | } else { 19 | console.log(resp.statusCode); 20 | console.log(data); 21 | } 22 | }) 23 | .catch(err => { 24 | console.log('failed', err); 25 | }); 26 | -------------------------------------------------------------------------------- /qiniu/httpc/responseWrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class 3 | * @param {any} data 4 | * @param {http.IncomingMessage} resp 5 | * @constructor 6 | */ 7 | function ResponseWrapper ({ 8 | data, 9 | resp 10 | }) { 11 | this.data = data; 12 | this.resp = resp; 13 | } 14 | 15 | /** 16 | * @returns {boolean} 17 | */ 18 | ResponseWrapper.prototype.ok = function () { 19 | return this.resp && Math.floor(this.resp.statusCode / 100) === 2; 20 | }; 21 | 22 | /** 23 | * @returns {boolean} 24 | */ 25 | ResponseWrapper.prototype.needRetry = function () { 26 | if (this.resp.statusCode > 0 && this.resp.statusCode < 500) { 27 | return false; 28 | } 29 | 30 | // https://developer.qiniu.com/fusion/kb/1352/the-http-request-return-a-status-code 31 | if ([ 32 | 501, 509, 573, 579, 608, 612, 614, 616, 618, 630, 631, 632, 640, 701 33 | ].includes(this.resp.statusCode)) { 34 | return false; 35 | } 36 | 37 | return true; 38 | }; 39 | 40 | exports.ResponseWrapper = ResponseWrapper; 41 | -------------------------------------------------------------------------------- /examples/rs_move.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const srcBucket = process.env.QINIU_TEST_BUCKET; 11 | const srcKey = 'qiniu.mp4'; 12 | const destBucket = srcBucket; 13 | const destKey = 'qiniu_new.mp4'; 14 | const options = { 15 | force: true 16 | }; 17 | bucketManager.move(srcBucket, srcKey, destBucket, destKey, options) 18 | .then(({ data, resp }) => { 19 | if (resp.statusCode === 200) { 20 | console.log(data); 21 | } else { 22 | console.log(resp.statusCode); 23 | console.log(data); 24 | } 25 | }) 26 | .catch(err => { 27 | console.log('failed', err); 28 | }); 29 | -------------------------------------------------------------------------------- /examples/rs_copy.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.zone = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const srcBucket = process.env.QINIU_TEST_BUCKET; 11 | const srcKey = 'qiniu.mp4'; 12 | const destBucket = 'destBucket'; 13 | const destKey = 'qiniu_new_copy.mp4'; 14 | const options = { 15 | force: true 16 | }; 17 | 18 | bucketManager.copy(srcBucket, srcKey, destBucket, destKey, options) 19 | .then(({ data, resp }) => { 20 | if (resp.statusCode === 200) { 21 | console.log(data); 22 | } else { 23 | console.log(resp.statusCode); 24 | console.log(data); 25 | } 26 | }) 27 | .catch(err => { 28 | console.log('failed', err); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/rs_stat.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const bucket = process.env.QINIU_TEST_BUCKET; 11 | const key = 'qiniux.mp4'; 12 | 13 | bucketManager.stat(bucket, key) 14 | .then(({ data, resp }) => { 15 | if (resp.statusCode === 200) { 16 | console.log(data.hash); 17 | console.log(data.fsize); 18 | console.log(data.mimeType); 19 | console.log(data.putTime); 20 | console.log(data.type); 21 | } else { 22 | console.log(resp.statusCode); 23 | console.log(data); 24 | } 25 | }) 26 | .catch(err => { 27 | console.log('failed', err); 28 | }); 29 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | ci: 3 | - prow.qiniu.io # prow 里面运行需添加,其他 CI 不要 4 | require_ci_to_pass: no # 改为 no,否则 codecov 会等待其他 GitHub 上所有 CI 通过才会留言。 5 | 6 | github_checks: #关闭github checks 7 | annotations: false 8 | 9 | comment: 10 | layout: "reach, diff, flags, files" 11 | behavior: new # 默认是更新旧留言,改为 new,删除旧的,增加新的。 12 | require_changes: false # if true: only post the comment if coverage changes 13 | require_base: no # [yes :: must have a base report to post] 14 | require_head: yes # [yes :: must have a head report to post] 15 | branches: # branch names that can post comment 16 | - "master" 17 | 18 | coverage: 19 | status: # 评判 pr 通过的标准 20 | patch: off 21 | project: # project 统计所有代码x 22 | default: 23 | # basic 24 | target: 80% # 总体通过标准 25 | threshold: 3% # 允许单次下降的幅度 26 | base: auto 27 | if_not_found: success 28 | if_ci_failed: error 29 | -------------------------------------------------------------------------------- /examples/rs_fetch.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | const accessKey = proc.env.QINIU_ACCESS_KEY; 5 | const secretKey = proc.env.QINIU_SECRET_KEY; 6 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 7 | const config = new qiniu.conf.Config(); 8 | // config.useHttpsDomain = true; 9 | // config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 10 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 11 | const resUrl = 'http://devtools.qiniu.com/qiniu.png'; 12 | const bucket = proc.env.QINIU_TEST_BUCKET; 13 | const key = 'qiniu.png'; 14 | 15 | bucketManager.fetch(resUrl, bucket, key) 16 | .then(({ data, resp }) => { 17 | if (resp.statusCode === 200) { 18 | console.log(data.key); 19 | console.log(data.hash); 20 | console.log(data.fsize); 21 | console.log(data.mimeType); 22 | } else { 23 | console.log(resp.statusCode); 24 | console.log(data); 25 | } 26 | }) 27 | .catch(err => { 28 | console.log('failed', err); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/prefop.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | // var persistentId = 'z0.594b66f745a2650c99aa9e57'; 4 | var persistentId = 'na0.58df4eee92129336c2075195'; 5 | var config = new qiniu.conf.Config(); 6 | config.useHttpsDomain = true; 7 | var operManager = new qiniu.fop.OperationManager(null, config); 8 | // 持久化数据处理返回的是任务的persistentId,可以根据这个id查询处理状态 9 | operManager.prefop(persistentId, function (err, respBody, respInfo) { 10 | if (err) { 11 | console.log(err); 12 | throw err; 13 | } 14 | 15 | if (respInfo.statusCode == 200) { 16 | console.log(respBody.inputBucket); 17 | console.log(respBody.inputKey); 18 | console.log(respBody.pipeline); 19 | console.log(respBody.reqid); 20 | respBody.items.forEach(function (item) { 21 | console.log(item.cmd); 22 | console.log(item.code); 23 | console.log(item.desc); 24 | console.log(item.hash); 25 | console.log(item.key); 26 | }); 27 | } else { 28 | console.log(respInfo.statusCode); 29 | console.log(respBody); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /examples/cdn_prefetch_urls.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | // URL 列表 5 | var urlsToPrefetch = [ 6 | 'http://if-pbl.qiniudn.com/nodejs.png', 7 | 'http://if-pbl.qiniudn.com/qiniu.jpg' 8 | ]; 9 | var accessKey = proc.env.QINIU_ACCESS_KEY; 10 | var secretKey = proc.env.QINIU_SECRET_KEY; 11 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 12 | var cdnManager = new qiniu.cdn.CdnManager(mac); 13 | // 预取链接 14 | cdnManager.prefetchUrls(urlsToPrefetch, function (err, respBody, respInfo) { 15 | if (err) { 16 | throw err; 17 | } 18 | 19 | console.log(respInfo.statusCode); 20 | if (respInfo.statusCode == 200) { 21 | var jsonBody = JSON.parse(respBody); 22 | console.log(jsonBody.code); 23 | console.log(jsonBody.error); 24 | console.log(jsonBody.requestId); 25 | console.log(jsonBody.invalidUrls); 26 | console.log(jsonBody.invalidDirs); 27 | console.log(jsonBody.urlQuotaDay); 28 | console.log(jsonBody.urlSurplusDay); 29 | console.log(jsonBody.dirQuotaDay); 30 | console.log(jsonBody.dirSurplusDay); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /qiniu/httpc/middleware/ua.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | 3 | const middleware = require('./base'); 4 | 5 | /** 6 | * @class 7 | * @extends middleware.Middleware 8 | * @param {string} sdkVersion 9 | * @constructor 10 | */ 11 | function UserAgentMiddleware (sdkVersion) { 12 | this.userAgent = 'QiniuNodejs/' + sdkVersion + 13 | ' (' + 14 | os.type() + '; ' + 15 | os.platform() + '; ' + 16 | os.arch() + '; ' + 17 | 'Node.js ' + process.version + '; )'; 18 | } 19 | UserAgentMiddleware.prototype = Object.create(middleware.Middleware.prototype); 20 | UserAgentMiddleware.prototype.constructor = UserAgentMiddleware; 21 | 22 | /** 23 | * @memberOf UserAgentMiddleware 24 | * @param {ReqOpts} reqOpts 25 | * @param {function(ReqOpts):Promise} next 26 | * @returns {Promise} 27 | */ 28 | UserAgentMiddleware.prototype.send = function (reqOpts, next) { 29 | if (!reqOpts.urllibOptions.headers) { 30 | reqOpts.urllibOptions.headers = {}; 31 | } 32 | reqOpts.urllibOptions.headers['User-Agent'] = this.userAgent; 33 | return next(reqOpts); 34 | }; 35 | 36 | exports.UserAgentMiddleware = UserAgentMiddleware; 37 | -------------------------------------------------------------------------------- /.github/workflows/ci-test.yml: -------------------------------------------------------------------------------- 1 | name: NodeJS CI with NPM 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | node_version: ['6', '8', '10', '12', '14', '16', '18', '20'] 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Setup NodeJS 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: ${{ matrix.node_version }} 19 | - name: Setup dependencies 20 | run: | 21 | npm install 22 | # nyc@14 support nodejs >= 6 23 | npm install -g nyc@14 24 | - name: Run cases 25 | run: | 26 | npm run check-type 27 | nyc --reporter=lcov npm test 28 | env: 29 | QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }} 30 | QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }} 31 | QINIU_TEST_BUCKET: ${{ secrets.QINIU_TEST_BUCKET }} 32 | QINIU_TEST_DOMAIN: ${{ secrets.QINIU_TEST_DOMAIN }} 33 | - name: Upload coverage reports 34 | uses: codecov/codecov-action@v4 35 | with: 36 | token: ${{ secrets.CODECOV_TOKEN }} 37 | -------------------------------------------------------------------------------- /examples/object_lifecycle.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | config.useHttpsDomain = true; 8 | // config.zone = qiniu.zone.Zone_z0; 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const bucket = process.env.QINIU_TEST_BUCKET; 11 | const key = 'test_file'; 12 | 13 | bucketManager.setObjectLifeCycle( 14 | bucket, 15 | key, 16 | { 17 | toIaAfterDays: 10, 18 | toArchiveAfterDays: 20, 19 | toDeepArchiveAfterDays: 30, 20 | deleteAfterDays: 40 21 | }, 22 | function (err, respBody, respInfo) { 23 | if (err) { 24 | console.log(err); 25 | console.log(respInfo); 26 | } 27 | console.log(respBody); 28 | } 29 | ) 30 | .then(({ data, resp }) => { 31 | if (resp.statusCode === 200) { 32 | console.log(data); 33 | } else { 34 | console.log(resp.statusCode); 35 | console.log(data); 36 | } 37 | }) 38 | .catch(err => { 39 | console.log('failed', err); 40 | }); 41 | -------------------------------------------------------------------------------- /examples/rs_listv2.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | const srcBucket = process.env.QINIU_TEST_BUCKET; 11 | // @param options 列举操作的可选参数 12 | // prefix 列举的文件前缀 13 | // marker 上一次列举返回的位置标记,作为本次列举的起点信息 14 | // limit 每次返回的最大列举文件数量 15 | // delimiter 指定目录分隔符 16 | const options = { 17 | limit: 20 18 | }; 19 | 20 | bucketManager.listPrefixV2(srcBucket, options) 21 | .then(({ data, resp }) => { 22 | if (resp.statusCode === 200 && typeof data === 'string') { 23 | data.split('\n').forEach(itemStr => { 24 | const item = JSON.parse(itemStr); 25 | console.log(item.key); 26 | }); 27 | } else { 28 | console.log(resp.statusCode); 29 | console.log(data); 30 | } 31 | }) 32 | .catch(err => { 33 | console.log('failed', err); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/rs_batch_stat.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | 9 | const srcBucket = process.env.QINIU_TEST_BUCKET; 10 | 11 | // 每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送 12 | const statOperations = [ 13 | qiniu.rs.statOp(srcBucket, 'qiniu1.mp4'), 14 | qiniu.rs.statOp(srcBucket, 'qiniu2.mp4'), 15 | qiniu.rs.statOp(srcBucket, 'qiniu3.mp4'), 16 | qiniu.rs.statOp(srcBucket, 'qiniu4x.mp4') 17 | ]; 18 | 19 | bucketManager.batch(statOperations) 20 | .then(({ data, resp }) => { 21 | if (Math.floor(resp.statusCode / 100) === 2) { 22 | data.forEach(function (item) { 23 | if (item.code === 200) { 24 | console.log('success', item.data); 25 | } else { 26 | console.log(item.code + '\t' + item.data.error); 27 | } 28 | }); 29 | } else { 30 | console.log(resp.statusCode); 31 | console.log(data); 32 | } 33 | }) 34 | .catch(err => { 35 | console.log('failed', err); 36 | }); 37 | -------------------------------------------------------------------------------- /qiniu/retry/retryPolicy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef RetryPolicyContext 3 | * @property {any} result 4 | * @property {Error | null} error 5 | * @property {boolean} retried 6 | * @property {...any} 7 | */ 8 | 9 | /** 10 | * @class 11 | * @constructor 12 | */ 13 | function RetryPolicy () { 14 | } 15 | 16 | /** 17 | * @param {RetryPolicyContext} _context 18 | * @returns {boolean} 19 | */ 20 | RetryPolicy.prototype.isImportant = function (_context) { 21 | return false; 22 | }; 23 | 24 | /** 25 | * @abstract 26 | * @param {RetryPolicyContext} _context 27 | * @returns {Promise} 28 | */ 29 | RetryPolicy.prototype.initContext = function (_context) { 30 | throw new Error('Method not implemented.'); 31 | }; 32 | 33 | /** 34 | * @abstract 35 | * @param {RetryPolicyContext} _context 36 | * @returns {boolean} 37 | */ 38 | RetryPolicy.prototype.shouldRetry = function (_context) { 39 | throw new Error('Method not implemented.'); 40 | }; 41 | 42 | /** 43 | * @abstract 44 | * @param {RetryPolicyContext} _context 45 | * @returns {Promise} 46 | */ 47 | RetryPolicy.prototype.prepareRetry = function (_context) { 48 | throw new Error('Method not implemented.'); 49 | }; 50 | 51 | RetryPolicy.prototype.afterRetry = function () { 52 | return Promise.resolve(); 53 | }; 54 | 55 | exports.RetryPolicy = RetryPolicy; 56 | -------------------------------------------------------------------------------- /examples/rs_batch_delete.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | 9 | const srcBucket = process.env.QINIU_TEST_BUCKET; 10 | 11 | // 每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送 12 | const deleteOperations = [ 13 | qiniu.rs.deleteOp(srcBucket, 'qiniu1.mp4'), 14 | qiniu.rs.deleteOp(srcBucket, 'qiniu2.mp4'), 15 | qiniu.rs.deleteOp(srcBucket, 'qiniu3.mp4'), 16 | qiniu.rs.deleteOp(srcBucket, 'qiniu4x.mp4') 17 | ]; 18 | 19 | bucketManager.batch(deleteOperations) 20 | .then(({ data, resp }) => { 21 | if (Math.floor(resp.statusCode / 100) === 2) { 22 | data.forEach(function (item) { 23 | if (item.code === 200) { 24 | console.log('success', item.data); 25 | } else { 26 | console.log(item.code + '\t' + item.data.error); 27 | } 28 | }); 29 | } else { 30 | console.log(resp.statusCode); 31 | console.log(data); 32 | } 33 | }) 34 | .catch(err => { 35 | console.log('failed', err); 36 | }); 37 | -------------------------------------------------------------------------------- /examples/rs_batch_change_type.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | 9 | const srcBucket = process.env.QINIU_TEST_BUCKET; 10 | 11 | // 每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送 12 | const changeTypeOperations = [ 13 | qiniu.rs.changeTypeOp(srcBucket, 'qiniu1.mp4', 1), 14 | qiniu.rs.changeTypeOp(srcBucket, 'qiniu2.mp4', 1), 15 | qiniu.rs.changeTypeOp(srcBucket, 'qiniu3.mp4', 1), 16 | qiniu.rs.changeTypeOp(srcBucket, 'qiniu4.mp4', 1) 17 | ]; 18 | 19 | bucketManager.batch(changeTypeOperations) 20 | .then(({ data, resp }) => { 21 | if (Math.floor(resp.statusCode / 100) === 2) { 22 | data.forEach(function (item) { 23 | if (item.code === 200) { 24 | console.log('success', item.data); 25 | } else { 26 | console.log(item.code + '\t' + item.data.error); 27 | } 28 | }); 29 | } else { 30 | console.log(resp.statusCode); 31 | console.log(data); 32 | } 33 | }) 34 | .catch(err => { 35 | console.log('failed', err); 36 | }); 37 | -------------------------------------------------------------------------------- /examples/rs_batch_chgm.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | 9 | const srcBucket = process.env.QINIU_TEST_BUCKET; 10 | 11 | // 每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送 12 | const chgmOperations = [ 13 | qiniu.rs.changeMimeOp(srcBucket, 'qiniu1.mp4', 'video/x-mp4'), 14 | qiniu.rs.changeMimeOp(srcBucket, 'qiniu2.mp4', 'video/x-mp4'), 15 | qiniu.rs.changeMimeOp(srcBucket, 'qiniu3.mp4', 'video/x-mp4'), 16 | qiniu.rs.changeMimeOp(srcBucket, 'qiniu4.mp4', 'video/x-mp4') 17 | ]; 18 | 19 | bucketManager.batch(chgmOperations) 20 | .then(({ data, resp }) => { 21 | if (Math.floor(resp.statusCode / 100) === 2) { 22 | data.forEach(function (item) { 23 | if (item.code === 200) { 24 | console.log('success', item.data); 25 | } else { 26 | console.log(item.code + '\t' + item.data.error); 27 | } 28 | }); 29 | } else { 30 | console.log(resp.statusCode); 31 | console.log(data); 32 | } 33 | }) 34 | .catch(err => { 35 | console.log('failed', err); 36 | }); 37 | -------------------------------------------------------------------------------- /examples/video_pfop.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | var accessKey = proc.env.QINIU_ACCESS_KEY; 5 | var secretKey = proc.env.QINIU_SECRET_KEY; 6 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 7 | var config = new qiniu.conf.Config(); 8 | // config.useHttpsDomain = true; 9 | config.zone = qiniu.zone.Zone_z1; 10 | var operManager = new qiniu.fop.OperationManager(mac, config); 11 | 12 | // 处理指令集合 13 | var saveBucket = proc.env.QINIU_TEST_BUCKET; 14 | var fops = [ 15 | 'avthumb/mp4/s/480x320/vb/150k|saveas/' + qiniu.util.urlsafeBase64Encode( 16 | saveBucket + ':qiniu_480x320.mp4'), 17 | 'vframe/jpg/offset/10|saveas/' + qiniu.util.urlsafeBase64Encode(saveBucket + 18 | ':qiniu_frame1.jpg') 19 | ]; 20 | var pipeline = 'jemy'; 21 | var srcBucket = 'if-bc'; 22 | var srcKey = 'qiniu.mp4'; 23 | 24 | var options = { 25 | notifyURL: 'http://api.example.com/pfop/callback', 26 | force: false 27 | }; 28 | 29 | // 持久化数据处理返回的是任务的persistentId,可以根据这个id查询处理状态 30 | operManager.pfop(srcBucket, srcKey, fops, pipeline, options, function (err, 31 | respBody, 32 | respInfo) { 33 | if (err) { 34 | throw err; 35 | } 36 | 37 | if (respInfo.statusCode == 200) { 38 | console.log(respBody.persistentId); 39 | } else { 40 | console.log(respInfo.statusCode); 41 | console.log(respBody); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /examples/rs_batch_delete_after_days.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | 9 | const srcBucket = process.env.QINIU_TEST_BUCKET; 10 | 11 | // 每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送 12 | const deleteAfterDaysOperations = [ 13 | qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu1.mp4', 10), 14 | qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu2.mp4', 10), 15 | qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu3.mp4', 10), 16 | qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu4.mp4', 10) 17 | ]; 18 | 19 | bucketManager.batch(deleteAfterDaysOperations) 20 | .then(({ data, resp }) => { 21 | if (Math.floor(resp.statusCode / 100) === 2) { 22 | data.forEach(function (item) { 23 | if (item.code === 200) { 24 | console.log('success', item.data); 25 | } else { 26 | console.log(item.code + '\t' + item.data.error); 27 | } 28 | }); 29 | } else { 30 | console.log(resp.statusCode); 31 | console.log(data); 32 | } 33 | }) 34 | .catch(err => { 35 | console.log('failed', err); 36 | }); 37 | -------------------------------------------------------------------------------- /examples/form_upload_simple.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | 3 | const qiniu = require('../index'); 4 | 5 | const bucket = process.env.QINIU_TEST_BUCKET; 6 | const accessKey = process.env.QINIU_ACCESS_KEY; 7 | const secretKey = process.env.QINIU_SECRET_KEY; 8 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 9 | const options = { 10 | scope: bucket 11 | }; 12 | const putPolicy = new qiniu.rs.PutPolicy(options); 13 | 14 | const uploadToken = putPolicy.uploadToken(mac); 15 | const config = new qiniu.conf.Config(); 16 | const localFile = os.homedir() + '/Downloads/83eda6926b94bb14.css'; 17 | // config.zone = qiniu.zone.Zone_z0; 18 | const formUploader = new qiniu.form_up.FormUploader(config); 19 | const putExtra = new qiniu.form_up.PutExtra(); 20 | // file 21 | // putExtra.fname = 'frontend-static-resource/widgets/_next/static/css/83eda6926b94bb14.css'; 22 | // putExtra.metadata = { 23 | // 'x-qn-meta-name': 'qiniu' 24 | // }; 25 | formUploader.putFile( 26 | uploadToken, 27 | 'frontend-static-resource/widgets/_next/static/css/83eda6926b94bb14.css', 28 | localFile, 29 | putExtra 30 | ) 31 | .then(({ data, resp }) => { 32 | if (resp.statusCode === 200) { 33 | console.log(data); 34 | } else { 35 | console.log(resp.statusCode); 36 | console.log(data); 37 | } 38 | }) 39 | .catch(err => { 40 | console.log('put failed', err); 41 | }); 42 | -------------------------------------------------------------------------------- /examples/rs_batch_move.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | 9 | const srcBucket = process.env.QINIU_TEST_BUCKET; 10 | const destBucket = srcBucket; 11 | 12 | // 每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送 13 | const moveOperations = [ 14 | qiniu.rs.moveOp(srcBucket, 'qiniu1.mp4', destBucket, 'qiniu1_move.mp4'), 15 | qiniu.rs.moveOp(srcBucket, 'qiniu2.mp4', destBucket, 'qiniu2_move.mp4'), 16 | qiniu.rs.moveOp(srcBucket, 'qiniu3.mp4', destBucket, 'qiniu3_move.mp4'), 17 | qiniu.rs.moveOp(srcBucket, 'qiniu4.mp4', destBucket, 'qiniu4_move.mp4') 18 | ]; 19 | 20 | bucketManager.batch(moveOperations) 21 | .then(({ data, resp }) => { 22 | if (Math.floor(resp.statusCode / 100) === 2) { 23 | data.forEach(function (item) { 24 | if (item.code === 200) { 25 | console.log('success', item.data); 26 | } else { 27 | console.log(item.code + '\t' + item.data.error); 28 | } 29 | }); 30 | } else { 31 | console.log(resp.statusCode); 32 | console.log(data); 33 | } 34 | }) 35 | .catch(err => { 36 | console.log('failed', err); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/rs_batch_copy.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 8 | 9 | const srcBucket = process.env.QINIU_TEST_BUCKET; 10 | const srcKey = 'qiniu.mp4'; 11 | const destBucket = srcBucket; 12 | 13 | // 每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送 14 | const copyOperations = [ 15 | qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu1.mp4', { 16 | force: true 17 | }), 18 | qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu2.mp4'), 19 | qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu3.mp4'), 20 | qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu4.mp4') 21 | ]; 22 | 23 | bucketManager.batch(copyOperations) 24 | .then(({ data, resp }) => { 25 | if (Math.floor(resp.statusCode / 100) === 2) { 26 | data.forEach(function (item) { 27 | if (item.code === 200) { 28 | console.log('success', item.data); 29 | } else { 30 | console.log(item.code + '\t' + item.data.error); 31 | } 32 | }); 33 | } else { 34 | console.log(resp.statusCode); 35 | console.log(data); 36 | } 37 | }) 38 | .catch(err => { 39 | console.log('failed', err); 40 | }); 41 | -------------------------------------------------------------------------------- /examples/resume_upload_simple.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index.js'); 2 | 3 | const bucket = process.env.QINIU_TEST_BUCKET; 4 | const accessKey = process.env.QINIU_ACCESS_KEY; 5 | const secretKey = process.env.QINIU_SECRET_KEY; 6 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 7 | const options = { 8 | scope: bucket 9 | }; 10 | const putPolicy = new qiniu.rs.PutPolicy(options); 11 | 12 | const uploadToken = putPolicy.uploadToken(mac); 13 | const config = new qiniu.conf.Config(); 14 | const localFile = '/path/to/file'; 15 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 16 | config.useCdnDomain = true; 17 | const resumeUploader = new qiniu.resume_up.ResumeUploader(config); 18 | const putExtra = new qiniu.resume_up.PutExtra(); 19 | putExtra.params = { 20 | 'x:name': '', 21 | 'x:age': 27 22 | }; 23 | putExtra.metadata = { 24 | 'x-qn-meta-name': 'qiniu' 25 | }; 26 | putExtra.fname = 'testfile.mp4'; 27 | putExtra.resumeRecordFile = 'progress.log'; 28 | putExtra.progressCallback = function (uploadBytes, totalBytes) { 29 | console.log('progress:' + uploadBytes + '(' + totalBytes + ')'); 30 | }; 31 | 32 | // file 33 | resumeUploader.putFile(uploadToken, null, localFile, putExtra) 34 | .then(({ data, resp }) => { 35 | if (resp.statusCode === 200) { 36 | console.log(data); 37 | } else { 38 | console.log(resp.statusCode); 39 | console.log(data); 40 | } 41 | }) 42 | .catch(err => { 43 | console.log('failed', err); 44 | }); 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | auth: { 3 | digest: require('./qiniu/auth/digest.js') 4 | }, 5 | cdn: require('./qiniu/cdn.js'), 6 | form_up: require('./qiniu/storage/form.js'), 7 | resume_up: require('./qiniu/storage/resume.js'), 8 | rs: require('./qiniu/storage/rs.js'), 9 | fop: require('./qiniu/fop.js'), 10 | conf: require('./qiniu/conf.js'), 11 | httpc: { 12 | middleware: require('./qiniu/httpc/middleware'), 13 | HttpClient: require('./qiniu/httpc/client').HttpClient, 14 | ResponseWrapper: require('./qiniu/httpc/responseWrapper').ResponseWrapper, 15 | Endpoint: require('./qiniu/httpc/endpoint').Endpoint, 16 | StaticEndpointsProvider: require('./qiniu/httpc/endpointsProvider').StaticEndpointsProvider, 17 | SERVICE_NAME: require('./qiniu/httpc/region').SERVICE_NAME, 18 | Region: require('./qiniu/httpc/region').Region, 19 | StaticRegionsProvider: require('./qiniu/httpc/regionsProvider').StaticRegionsProvider, 20 | CachedRegionsProvider: require('./qiniu/httpc/regionsProvider').CachedRegionsProvider, 21 | QueryRegionsProvider: require('./qiniu/httpc/regionsProvider').QueryRegionsProvider 22 | }, 23 | rpc: require('./qiniu/rpc.js'), 24 | util: require('./qiniu/util.js'), 25 | zone: require('./qiniu/zone.js'), 26 | app: require('./qiniu/rtc/app.js'), 27 | room: require('./qiniu/rtc/room.js'), 28 | Credentials: require('./qiniu/rtc/credentials.js'), 29 | sms: { 30 | message: require('./qiniu/sms/message.js') 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /qiniu/httpc/endpoint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @interface EndpointsProvider 3 | */ 4 | 5 | /** 6 | * @function 7 | * @name EndpointsProvider#getEndpoints 8 | * @returns {Promise} 9 | */ 10 | 11 | /** 12 | * @interface MutableEndpointsProvider 13 | * @extends EndpointsProvider 14 | */ 15 | 16 | /** 17 | * @function 18 | * @name MutableEndpointsProvider#setEndpoints 19 | * @param {endpoints: Endpoint[]} endpoints 20 | * @returns {Promise} 21 | */ 22 | 23 | // --- could split to files if migrate to typescript --- // 24 | 25 | /** 26 | * @class 27 | * @implements EndpointsProvider 28 | * @param {string} host 29 | * @param {Object} [options] 30 | * @param {string} [options.defaultScheme] 31 | * @constructor 32 | */ 33 | function Endpoint (host, options) { 34 | options = options || {}; 35 | 36 | this.host = host; 37 | this.defaultScheme = options.defaultScheme || 'https'; 38 | } 39 | 40 | /** 41 | * @param {Object} [options] 42 | * @param {string} [options.scheme] 43 | */ 44 | Endpoint.prototype.getValue = function (options) { 45 | options = options || {}; 46 | 47 | const scheme = options.scheme || this.defaultScheme; 48 | const host = this.host; 49 | 50 | return scheme + '://' + host; 51 | }; 52 | 53 | /** 54 | * @returns {Promise} 55 | */ 56 | Endpoint.prototype.getEndpoints = function () { 57 | return Promise.resolve([this]); 58 | }; 59 | 60 | Endpoint.prototype.clone = function () { 61 | return new Endpoint(this.host, { 62 | defaultScheme: this.defaultScheme 63 | }); 64 | }; 65 | 66 | exports.Endpoint = Endpoint; 67 | -------------------------------------------------------------------------------- /examples/http_https_proxy.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | const tunnel = require('tunnel-agent'); 4 | 5 | var bucket = 'if-pbl'; 6 | var accessKey = proc.env.QINIU_ACCESS_KEY; 7 | var secretKey = proc.env.QINIU_SECRET_KEY; 8 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 9 | var options = { 10 | scope: bucket 11 | }; 12 | var putPolicy = new qiniu.rs.PutPolicy(options); 13 | 14 | var uploadToken = putPolicy.uploadToken(mac); 15 | var config = new qiniu.conf.Config(); 16 | // config.zone = qiniu.zone.Zone_z0; 17 | // config.useHttpsDomain = true; 18 | var formUploader = new qiniu.form_up.FormUploader(config); 19 | var putExtra = new qiniu.form_up.PutExtra(); 20 | 21 | // 设置HTTP(s)代理服务器,这里可以参考:https://github.com/request/tunnel-agent 22 | // 有几个方法: 23 | // exports.httpOverHttp = httpOverHttp 24 | // exports.httpsOverHttp = httpsOverHttp 25 | // exports.httpOverHttps = httpOverHttps 26 | // exports.httpsOverHttps = httpsOverHttps 27 | 28 | var proxyAgent = tunnel.httpOverHttp({ 29 | proxy: { 30 | host: 'localhost', 31 | port: 8888 32 | } 33 | }); 34 | 35 | qiniu.conf.RPC_HTTP_AGENT = proxyAgent; 36 | 37 | // qiniu.conf.RPC_HTTPS_AGENT = proxyAgent; 38 | // 以代理方式上传 39 | formUploader.put(uploadToken, null, 'hello', putExtra, function (respErr, 40 | respBody, respInfo) { 41 | if (respErr) { 42 | throw respErr; 43 | } 44 | 45 | if (respInfo.statusCode == 200) { 46 | console.log(respBody); 47 | } else { 48 | console.log(respInfo.statusCode); 49 | console.log(respBody); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /examples/cdn_get_flux_data.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | // 域名列表 5 | var domains = [ 6 | 'if-pbl.qiniudn.com', 7 | 'qdisk.qiniudn.com' 8 | ]; 9 | 10 | // 指定日期 11 | var startDate = '2017-06-20'; 12 | var endDate = '2017-06-22'; 13 | var granularity = 'day'; 14 | var accessKey = proc.env.QINIU_ACCESS_KEY; 15 | var secretKey = proc.env.QINIU_SECRET_KEY; 16 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 17 | 18 | var cdnManager = new qiniu.cdn.CdnManager(mac); 19 | // 获取域名流量 20 | cdnManager.getFluxData(startDate, endDate, granularity, domains, function (err, 21 | respBody, respInfo) { 22 | if (err) { 23 | throw err; 24 | } 25 | 26 | console.log(respInfo.statusCode); 27 | if (respInfo.statusCode == 200) { 28 | var jsonBody = JSON.parse(respBody); 29 | var code = jsonBody.code; 30 | console.log(code); 31 | 32 | var tickTime = jsonBody.time; 33 | console.log(tickTime); 34 | 35 | var fluxData = jsonBody.data; 36 | domains.forEach(function (domain) { 37 | var fluxDataOfDomain = fluxData[domain]; 38 | if (fluxDataOfDomain != null) { 39 | console.log('flux data for:' + domain); 40 | var fluxChina = fluxDataOfDomain.china; 41 | var fluxOversea = fluxDataOfDomain.oversea; 42 | console.log(fluxChina); 43 | console.log(fluxOversea); 44 | } else { 45 | console.log('no flux data for:' + domain); 46 | } 47 | console.log('----------'); 48 | }); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /examples/pfops_video_plus.js: -------------------------------------------------------------------------------- 1 | var qiniu = require('qiniu'); 2 | var urllib = require('urllib'); 3 | 4 | qiniu.conf.ACCESS_KEY = 'ak'; 5 | qiniu.conf.SECRET_KEY = 'sk'; 6 | 7 | var url = 'http://argus.atlab.ai/v1/video/89999sssss'; 8 | 9 | var mac = new qiniu.auth.digest.Mac(qiniu.conf.ACCESS_KEY, qiniu.conf.SECRET_KEY); 10 | 11 | var json = { 12 | 13 | data: { 14 | uri: 'http://test.qiniu.com/Videos/2016-09/39/9d019f7acab742ddbc5f4db02b6f72cb.mp4' 15 | }, 16 | params: { 17 | async: false, 18 | vframe: { 19 | mode: 0, 20 | interval: 5 21 | } 22 | }, 23 | ops: [ 24 | { 25 | op: 'pulp', 26 | params: { 27 | labels: [ 28 | { 29 | label: '1', 30 | select: 2, 31 | score: 0.0002 32 | }, 33 | { 34 | label: '2', 35 | select: 2, 36 | score: 0.0002 37 | } 38 | 39 | ] 40 | } 41 | } 42 | ] 43 | }; 44 | 45 | var accessToken = qiniu.util.generateAccessTokenV2(mac, url, 'POST', 'application/json', JSON.stringify(json)); 46 | 47 | urllib.request(url, { 48 | method: 'POST', 49 | headers: { 50 | Authorization: accessToken, 51 | 'Content-Type': 'application/json' 52 | }, 53 | data: JSON.stringify(json) 54 | }, function (err, data, res) { 55 | if (err) { 56 | console.log(err); 57 | throw err; // you need to handle error 58 | } 59 | console.log(res.statusCode); 60 | console.log(res); 61 | console.log(data.toString()); 62 | }); 63 | -------------------------------------------------------------------------------- /examples/cdn_get_bandwidth_data.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | // 域名列表 5 | var domains = [ 6 | 'if-pbl.qiniudn.com', 7 | 'qdisk.qiniudn.com' 8 | ]; 9 | 10 | // 指定日期 11 | var startDate = '2017-06-20'; 12 | var endDate = '2017-06-22'; 13 | var granularity = 'day'; 14 | var accessKey = proc.env.QINIU_ACCESS_KEY; 15 | var secretKey = proc.env.QINIU_SECRET_KEY; 16 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 17 | 18 | var cdnManager = new qiniu.cdn.CdnManager(mac); 19 | // 获取域名带宽 20 | cdnManager.getBandwidthData(startDate, endDate, granularity, domains, function ( 21 | err, respBody, respInfo) { 22 | if (err) { 23 | console.log(err); 24 | throw err; 25 | } 26 | 27 | console.log(respInfo.statusCode); 28 | if (respInfo.statusCode == 200) { 29 | var jsonBody = JSON.parse(respBody); 30 | var code = jsonBody.code; 31 | console.log(code); 32 | 33 | var tickTime = jsonBody.time; 34 | console.log(tickTime); 35 | 36 | var bandwidthData = jsonBody.data; 37 | domains.forEach(function (domain) { 38 | var bandwidthDataOfDomain = bandwidthData[domain]; 39 | if (bandwidthDataOfDomain != null) { 40 | console.log('bandwidth data for:' + domain); 41 | var bandwidthChina = bandwidthDataOfDomain.china; 42 | var bandwidthOversea = bandwidthDataOfDomain.oversea; 43 | console.log(bandwidthChina); 44 | console.log(bandwidthOversea); 45 | } else { 46 | console.log('no bandwidth data for:' + domain); 47 | } 48 | console.log('----------'); 49 | }); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /qiniu/sms/message.js: -------------------------------------------------------------------------------- 1 | const util = require('../util'); 2 | const urllib = require('urllib'); 3 | exports.sendMessage = function (reqBody,mac,callbackFunc){ 4 | reqBody = JSON.stringify(reqBody); 5 | var args = { 6 | requestURI:"https://sms.qiniuapi.com/v1/message", 7 | reqBody:reqBody, 8 | mac:mac, 9 | } 10 | post(args,callbackFunc); 11 | } 12 | 13 | exports.sendSingleMessage = function (reqBody,mac,callbackFunc){ 14 | reqBody = JSON.stringify(reqBody); 15 | var args = { 16 | requestURI:"https://sms.qiniuapi.com/v1/message/single", 17 | reqBody:reqBody, 18 | mac:mac, 19 | } 20 | post(args,callbackFunc); 21 | } 22 | 23 | exports.sendOverseaMessage = function (reqBody,mac,callbackFunc){ 24 | reqBody = JSON.stringify(reqBody); 25 | var args = { 26 | requestURI:"https://sms.qiniuapi.com/v1/message/oversea", 27 | reqBody:reqBody, 28 | mac:mac, 29 | } 30 | post(args,callbackFunc); 31 | } 32 | 33 | exports.sendFulltextMessage = function (reqBody,mac,callbackFunc){ 34 | reqBody = JSON.stringify(reqBody); 35 | var args = { 36 | requestURI:"https://sms.qiniuapi.com/v1/message/fulltext", 37 | reqBody:reqBody, 38 | mac:mac, 39 | } 40 | post(args,callbackFunc); 41 | } 42 | 43 | function post(args,callbackFunc){ 44 | var contentType = 'application/json'; 45 | var accessToken = util.generateAccessTokenV2(args.mac, args.requestURI, 'POST', contentType, args.reqBody); 46 | var headers = { 47 | 'Authorization': accessToken, 48 | 'Content-Type': contentType, 49 | } 50 | 51 | var data = { 52 | method: 'POST', 53 | headers: headers, 54 | data: args.reqBody, 55 | } 56 | 57 | urllib.request(args.requestURI, data, callbackFunc); 58 | } 59 | -------------------------------------------------------------------------------- /qiniu/httpc/middleware/qiniuAuth.js: -------------------------------------------------------------------------------- 1 | const middleware = require('./base'); 2 | const util = require('../../util'); 3 | 4 | /** 5 | * @class 6 | * @extends middleware.Middleware 7 | * @param {Object} qiniuAuthOptions 8 | * @param {Mac} qiniuAuthOptions.mac 9 | * @constructor 10 | */ 11 | function QiniuAuthMiddleware (qiniuAuthOptions) { 12 | this.mac = qiniuAuthOptions.mac; 13 | } 14 | 15 | QiniuAuthMiddleware.prototype = Object.create(middleware.Middleware.prototype); 16 | QiniuAuthMiddleware.prototype.constructor = QiniuAuthMiddleware; 17 | 18 | /** 19 | * @memberOf QiniuAuthMiddleware 20 | * @param {ReqOpts} reqOpts 21 | * @param {function(ReqOpts):Promise} next 22 | * @returns {Promise} 23 | */ 24 | QiniuAuthMiddleware.prototype.send = function (reqOpts, next) { 25 | const headers = reqOpts.urllibOptions.headers; 26 | if (this._shouldSignTime()) { 27 | headers['X-Qiniu-Date'] = util.formatDateUTC(new Date(), 'YYYYMMDDTHHmmssZ'); 28 | } 29 | 30 | if (this.mac.accessKey) { 31 | headers.Authorization = util.generateAccessTokenV2( 32 | this.mac, 33 | reqOpts.url, 34 | reqOpts.urllibOptions.method, 35 | reqOpts.urllibOptions.contentType, 36 | reqOpts.urllibOptions.content, 37 | headers 38 | ); 39 | } 40 | 41 | return next(reqOpts); 42 | }; 43 | 44 | QiniuAuthMiddleware.prototype._shouldSignTime = function () { 45 | let shouldDisable = this.mac.options.disableQiniuTimestampSignature; 46 | if (shouldDisable === null) { 47 | const envDisable = process.env.DISABLE_QINIU_TIMESTAMP_SIGNATURE || ''; 48 | shouldDisable = envDisable.toLowerCase() === 'true'; 49 | } 50 | return !shouldDisable; 51 | }; 52 | 53 | exports.QiniuAuthMiddleware = QiniuAuthMiddleware; 54 | -------------------------------------------------------------------------------- /qiniu/rtc/credentials.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'); 2 | 3 | function Credentials (accessKey, secretKey) { 4 | this.accessKey = accessKey; 5 | this.secretKey = secretKey; 6 | } 7 | 8 | Credentials.prototype.generateAccessToken = function (options, data) { 9 | var sign = this._signRequest(options, data); 10 | var token = 'Qiniu' + ' ' + this.accessKey + ':' + sign; 11 | 12 | return token; 13 | }; 14 | 15 | Credentials.prototype._signRequest = function (options, body) { 16 | var contentType = options.headers['Content-Type']; 17 | 18 | var host = options.host; 19 | if (options.port && options.port != 80) { 20 | host = host + ':' + options.port; 21 | } 22 | 23 | var data = options.method + ' ' + options.path; 24 | data += '\nHost: ' + host; 25 | if (contentType) { 26 | data += '\nContent-Type: ' + contentType; 27 | } 28 | data += '\n\n'; 29 | 30 | if (body && contentType && contentType != 'application/octet-stream') { 31 | data += body; 32 | } 33 | 34 | var digest = util.hmacSha1(data, this.secretKey); 35 | 36 | var sageDigest = util.base64ToUrlSafe(digest); 37 | 38 | return sageDigest; 39 | }; 40 | 41 | Credentials.prototype.sign = function (data) { 42 | var digest = util.hmacSha1(data, this.secretKey); 43 | var sageDigest = util.base64ToUrlSafe(digest); 44 | return this.accessKey + ':' + sageDigest; 45 | }; 46 | 47 | Credentials.prototype.signJson = function (opt) { 48 | var str = JSON.stringify(opt); 49 | var encodedStr = util.urlsafeBase64Encode(str); 50 | var sign = util.hmacSha1(encodedStr, this.secretKey); 51 | var encodedSign = util.base64ToUrlSafe(sign); 52 | 53 | var token = this.accessKey + ':' + encodedSign + ':' + encodedStr; 54 | return token; 55 | }; 56 | 57 | module.exports = exports = Credentials; 58 | -------------------------------------------------------------------------------- /examples/cdn_get_log_list.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | // 域名列表 5 | var domains = [ 6 | 'if-pbl.qiniudn.com', 7 | 'qdisk.qiniudn.com' 8 | ]; 9 | 10 | // 指定日期 11 | var logDay = '2017-06-20'; 12 | var accessKey = proc.env.QINIU_ACCESS_KEY; 13 | var secretKey = proc.env.QINIU_SECRET_KEY; 14 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 15 | 16 | var cdnManager = new qiniu.cdn.CdnManager(mac); 17 | // 获取域名日志 18 | cdnManager.getCdnLogList(domains, logDay, function (err, respBody, respInfo) { 19 | if (err) { 20 | throw err; 21 | } 22 | 23 | console.log(respInfo.statusCode); 24 | if (respInfo.statusCode == 200) { 25 | /** 26 | { 27 | "code":0, 28 | "error":"", 29 | "data":{ 30 | "if-pbl.qiniudn.com":[ 31 | { 32 | "name":"v2/if-pbl.qiniudn.com_2017-06-20-15_part-00000.gz", 33 | "size":220, 34 | "mtime":1497963801, 35 | "url":"http://fusionlog.qiniu.com/v2/xxxxx" 36 | } 37 | ] 38 | } 39 | } 40 | */ 41 | var jsonBody = JSON.parse(respBody); 42 | var code = jsonBody.code; 43 | console.log(code); 44 | var logData = jsonBody.data; 45 | domains.forEach(function (domain) { 46 | console.log('log for domain: ' + domain); 47 | var domainLogs = logData[domain]; 48 | if (domainLogs != null) { 49 | domainLogs.forEach(function (logItem) { 50 | console.log(logItem.name); 51 | console.log(logItem.size); 52 | console.log(logItem.mtime); 53 | console.log(logItem.url); 54 | }); 55 | console.log('------------------'); 56 | } 57 | }); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /examples/rs_list_prefix.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index'); 2 | 3 | const accessKey = process.env.QINIU_ACCESS_KEY; 4 | const secretKey = process.env.QINIU_SECRET_KEY; 5 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 6 | const config = new qiniu.conf.Config(); 7 | // config.useHttpsDomain = true; 8 | config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0'); 9 | const bucketManager = new qiniu.rs.BucketManager(mac, config); 10 | 11 | const bucket = process.env.QINIU_TEST_BUCKET; 12 | // @param options 列举操作的可选参数 13 | // prefix 列举的文件前缀 14 | // marker 上一次列举返回的位置标记,作为本次列举的起点信息 15 | // limit 每次返回的最大列举文件数量 16 | // delimiter 指定目录分隔符 17 | const options = { 18 | limit: 10, 19 | prefix: 'calculus' 20 | }; 21 | 22 | bucketManager.listPrefix(bucket, options) 23 | .then(({ data, resp }) => { 24 | if (resp.statusCode === 200) { 25 | // 如果这个nextMarker不为空,那么还有未列举完毕的文件列表,下次调用listPrefix的时候, 26 | // 指定options里面的marker为这个值 27 | const nextMarker = data.marker; 28 | const commonPrefixes = data.commonPrefixes; 29 | console.log(nextMarker); 30 | console.log({ commonPrefixes }); 31 | const items = data.items; 32 | items.forEach(function (item) { 33 | console.log(item.key); 34 | // console.log(item.putTime); 35 | // console.log(item.hash); 36 | // console.log(item.fsize); 37 | // console.log(item.mimeType); 38 | // console.log(item.endUser); 39 | // console.log(item.type); 40 | }); 41 | } else { 42 | console.log(resp.statusCode); 43 | console.log(data); 44 | } 45 | }) 46 | .catch(err => { 47 | console.log('failed', err); 48 | }); 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qiniu Cloud SDK for Node.js 2 | 3 | [![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) 5 | [![NodeJS CI](https://github.com/qiniu/nodejs-sdk/actions/workflows/ci-test.yml/badge.svg?branch=master)](https://github.com/qiniu/nodejs-sdk/actions/workflows/ci-test.yml) 6 | [![GitHub release](https://img.shields.io/github/v/tag/qiniu/nodejs-sdk.svg?label=release)](https://github.com/qiniu/nodejs-sdk/releases) 7 | [![Code Climate](https://codeclimate.com/github/qiniu/nodejs-sdk.svg)](https://codeclimate.com/github/qiniu/nodejs-sdk) 8 | [![Coverage Status](https://codecov.io/gh/qiniu/nodejs-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/nodejs-sdk) 9 | [![Latest Stable Version](https://img.shields.io/npm/v/qiniu.svg)](https://www.npmjs.com/package/qiniu) 10 | 11 | ## 下载 12 | 13 | ### 从 npm 安装 14 | 15 | 这是我们建议的方式 16 | 17 | ```bash 18 | $ npm install qiniu 19 | ``` 20 | 21 | ### 从 release 版本下载 22 | 23 | 下载地址:[https://github.com/qiniu/nodejs-sdk/releases](https://github.com/qiniu/nodejs-sdk/releases) 24 | 25 | 这里可以下载到旧版本的SDK,release 版本有版本号,有 [CHANGELOG](https://github.com/qiniu/nodejs-sdk/blob/master/CHANGELOG.md),使用规格也会比较稳定。 26 | 27 | ### 从 git 库下载 28 | 29 | 你可以直接用 git clone 下载源代码来使用。但是请注意非 master 分支的代码可能会变更,应谨慎使用。 30 | 31 | ## 使用 32 | 33 | 参考文档:[七牛云存储 Node.js SDK 使用指南](http://developer.qiniu.com/kodo/sdk/nodejs) 34 | 35 | ## 测试 36 | ``` 37 | $ cd ./test/ 38 | $ source test-env.sh 39 | $ mocha --grep 'bucketinfo' 40 | ``` 41 | 42 | ## 贡献代码 43 | 44 | 1. Fork 45 | 2. 创建您的特性分支 (`git checkout -b my-new-feature`) 46 | 3. 提交您的改动 (`git commit -am 'Added some feature'`) 47 | 4. 将您的修改记录提交到远程 `git` 仓库 (`git push origin my-new-feature`) 48 | 5. 然后到 github 网站的该 `git` 远程仓库的 `my-new-feature` 分支下发起 Pull Request 49 | 50 | ## 许可证 51 | 52 | Copyright (c) 2015 qiniu.com 53 | 54 | 基于 MIT 协议发布: 55 | 56 | * [www.opensource.org/licenses/MIT](http://www.opensource.org/licenses/MIT) 57 | -------------------------------------------------------------------------------- /examples/form_qvm_upload.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index.js'); 2 | 3 | const bucket = process.env.QINIU_TEST_BUCKET; 4 | const accessKey = process.env.QINIU_ACCESS_KEY; 5 | const secretKey = process.env.QINIU_SECRET_KEY; 6 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 7 | const options = { 8 | scope: bucket 9 | }; 10 | const putPolicy = new qiniu.rs.PutPolicy(options); 11 | 12 | const uploadToken = putPolicy.uploadToken(mac); 13 | const config = new qiniu.conf.Config(); 14 | const localFile = '/path/to/file'; 15 | 16 | // construct a new zone 17 | // 华东 18 | const ZONE_QVM_Z0 = qiniu.httpc.Region.fromRegionId('z0'); 19 | ZONE_QVM_Z0.services[qiniu.httpc.SERVICE_NAME.UP] = [ 20 | 'free-qvm-z0-xs.qiniup.com' 21 | ].map(h => new qiniu.httpc.Endpoint(h)); 22 | 23 | // 华北 24 | const ZONE_QVM_Z1 = qiniu.httpc.Region.fromRegionId('z1'); 25 | ZONE_QVM_Z1.services[qiniu.httpc.SERVICE_NAME.UP] = [ 26 | 'free-qvm-z1-zz.qiniup.com' 27 | ].map(h => new qiniu.httpc.Endpoint(h)); 28 | 29 | config.regionsProvider = ZONE_QVM_Z0; 30 | config.regionsProvider = ZONE_QVM_Z1; 31 | const formUploader = new qiniu.form_up.FormUploader(config); 32 | const putExtra = new qiniu.form_up.PutExtra(); 33 | // bytes 34 | formUploader.put( 35 | uploadToken, 36 | null, 37 | 'hello', 38 | null 39 | ) 40 | .then(({ data, resp }) => { 41 | if (resp.statusCode === 200) { 42 | console.log(data); 43 | } else { 44 | console.log(resp.statusCode); 45 | console.log(data); 46 | } 47 | }) 48 | .catch(err => { 49 | console.log('put failed', err); 50 | }); 51 | 52 | // file 53 | formUploader.putFile(uploadToken, null, localFile, putExtra) 54 | .then(({ data, resp }) => { 55 | if (resp.statusCode === 200) { 56 | console.log(data); 57 | } else { 58 | console.log(resp.statusCode); 59 | console.log(data); 60 | } 61 | }) 62 | .catch(err => { 63 | console.log('putFile failed', err); 64 | }); 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qiniu", 3 | "version": "7.14.0", 4 | "description": "Node wrapper for Qiniu Resource (Cloud) Storage API", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "check-type": "tsc --noEmit", 11 | "test": "NODE_ENV=test mocha -t 300000 --retries 3", 12 | "cover": "nyc npm run test", 13 | "report": "nyc report --reporter=html", 14 | "lint": "eslint ." 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/qiniu/nodejs-sdk.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/qiniu/nodejs-sdk/issues" 22 | }, 23 | "keywords": [ 24 | "cloud", 25 | "storage", 26 | "s3", 27 | "qiniu", 28 | "web-service" 29 | ], 30 | "author": "sdk@qiniu.com", 31 | "contributors": [ 32 | { 33 | "name": "Xu Shiwei", 34 | "email": "xushiweizh@gmail.com" 35 | }, 36 | { 37 | "name": "why404", 38 | "email": "awhy.xu@gmail.com" 39 | }, 40 | { 41 | "name": "guhao", 42 | "email": "guhao@qiniu.com" 43 | }, 44 | { 45 | "name": "jinxinxin", 46 | "email": "jinxinxin@qiniu.com" 47 | } 48 | ], 49 | "engines": { 50 | "node": ">= 6" 51 | }, 52 | "dependencies": { 53 | "agentkeepalive": "^4.0.2", 54 | "before": "^0.0.1", 55 | "block-stream2": "^2.0.0", 56 | "crc32": "^0.2.2", 57 | "destroy": "^1.0.4", 58 | "encodeurl": "^1.0.1", 59 | "formstream": "^1.1.0", 60 | "mime": "^2.4.4", 61 | "mkdirp": "^0.5.5", 62 | "mockdate": "^3.0.5", 63 | "tunnel-agent": "^0.6.0", 64 | "typescript": "^4.9.4", 65 | "urllib": "^2.41.0" 66 | }, 67 | "devDependencies": { 68 | "@types/node": "^8.0.3", 69 | "eslint": "^6.5.1", 70 | "eslint-config-standard": "^14.1.0", 71 | "eslint-plugin-import": "^2.11.0", 72 | "eslint-plugin-node": "^10.0.0", 73 | "eslint-plugin-promise": "^4.2.1", 74 | "eslint-plugin-standard": "^4.0.1", 75 | "mocha": "^6.2.1", 76 | "nyc": "^14.1.1", 77 | "should": "^13.2.3" 78 | }, 79 | "license": "MIT" 80 | } 81 | -------------------------------------------------------------------------------- /qiniu/httpc/endpointsRetryPolicy.js: -------------------------------------------------------------------------------- 1 | const { 2 | RetryPolicy 3 | } = require('../retry'); 4 | const { 5 | StaticEndpointsProvider 6 | } = require('./endpointsProvider'); 7 | 8 | /** 9 | * @typedef {RetryPolicyContext} EndpointsRetryPolicyContext 10 | * @property {Endpoint} endpoint 11 | * @property {Endpoint[]} alternativeEndpoints 12 | */ 13 | 14 | /** 15 | * @class 16 | * @extends RetryPolicy 17 | * @param {boolean} [options.skipInitContext] 18 | * @param {Endpoint[]} [options.endpoints] 19 | * @param {EndpointsProvider} [options.endpointsProvider] 20 | * @constructor 21 | */ 22 | function EndpointsRetryPolicy (options) { 23 | this.skipInitContext = options.skipInitContext || false; 24 | this.endpoints = options.endpoints || []; 25 | this.endpointsProvider = options.endpointsProvider || new StaticEndpointsProvider([]); 26 | } 27 | 28 | EndpointsRetryPolicy.prototype = Object.create(RetryPolicy.prototype); 29 | EndpointsRetryPolicy.prototype.constructor = EndpointsRetryPolicy; 30 | 31 | /** 32 | * @param {EndpointsRetryPolicyContext} context 33 | * @returns {Promise} 34 | */ 35 | EndpointsRetryPolicy.prototype.initContext = function (context) { 36 | if (this.skipInitContext) { 37 | return Promise.resolve(); 38 | } 39 | if (this.endpoints.length > 0) { 40 | [context.endpoint, ...context.alternativeEndpoints] = this.endpoints.slice(); 41 | return Promise.resolve(); 42 | } 43 | return this.endpointsProvider.getEndpoints() 44 | .then(endpoints => { 45 | if (endpoints.length < 0) { 46 | return Promise.reject( 47 | new Error('There isn\'t available endpoint') 48 | ); 49 | } 50 | [context.endpoint, ...context.alternativeEndpoints] = endpoints.slice(); 51 | }); 52 | }; 53 | 54 | /** 55 | * @param {EndpointsRetryPolicyContext} context 56 | * @returns {boolean} 57 | */ 58 | EndpointsRetryPolicy.prototype.shouldRetry = function (context) { 59 | return context.alternativeEndpoints.length > 0; 60 | }; 61 | 62 | /** 63 | * @param {EndpointsRetryPolicyContext} context 64 | * @returns {Promise} 65 | */ 66 | EndpointsRetryPolicy.prototype.prepareRetry = function (context) { 67 | context.endpoint = context.alternativeEndpoints.shift(); 68 | if (!context.endpoint) { 69 | return Promise.reject( 70 | new Error('There isn\'t available endpoint for next try') 71 | ); 72 | } 73 | return Promise.resolve(); 74 | }; 75 | 76 | exports.EndpointsRetryPolicy = EndpointsRetryPolicy; 77 | -------------------------------------------------------------------------------- /examples/create_uptoken.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | var accessKey = proc.env.QINIU_ACCESS_KEY; 5 | var secretKey = proc.env.QINIU_SECRET_KEY; 6 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 7 | 8 | var bucket = proc.env.QINIU_TEST_BUCKET; 9 | 10 | // 简单上传凭证 11 | var options = { 12 | scope: bucket 13 | }; 14 | var putPolicy = new qiniu.rs.PutPolicy(options); 15 | console.log(putPolicy.uploadToken(mac)); 16 | 17 | // 自定义凭证有效期(示例2小时) 18 | options = { 19 | scope: bucket, 20 | expires: 7200 21 | }; 22 | putPolicy = new qiniu.rs.PutPolicy(options); 23 | console.log(putPolicy.uploadToken(mac)); 24 | 25 | // 覆盖上传凭证 26 | var keyToOverwrite = 'qiniu.mp4'; 27 | options = { 28 | scope: bucket + ':' + keyToOverwrite 29 | }; 30 | putPolicy = new qiniu.rs.PutPolicy(options); 31 | console.log(putPolicy.uploadToken(mac)); 32 | 33 | // 自定义上传回复(非callback模式)凭证 34 | options = { 35 | scope: bucket, 36 | returnBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}' 37 | }; 38 | putPolicy = new qiniu.rs.PutPolicy(options); 39 | console.log(putPolicy.uploadToken(mac)); 40 | 41 | // 带回调业务服务器的凭证(application/json) 42 | options = { 43 | scope: bucket, 44 | callbackUrl: 'http://api.example.com/qiniu/upload/callback', 45 | callbackBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}', 46 | callbackBodyType: 'application/json' 47 | }; 48 | putPolicy = new qiniu.rs.PutPolicy(options); 49 | console.log(putPolicy.uploadToken(mac)); 50 | 51 | // 带回调业务服务器的凭证(application/x-www-form-urlencoded) 52 | options = { 53 | scope: bucket, 54 | callbackUrl: 'http://api.example.com/qiniu/upload/callback', 55 | callbackBody: 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)' 56 | }; 57 | putPolicy = new qiniu.rs.PutPolicy(options); 58 | console.log(putPolicy.uploadToken(mac)); 59 | 60 | // 带数据处理的凭证 61 | var saveMp4Entry = qiniu.util.urlsafeBase64Encode(bucket + 62 | ':avthumb_test_target.mp4'); 63 | var saveJpgEntry = qiniu.util.urlsafeBase64Encode(bucket + 64 | ':vframe_test_target.jpg'); 65 | var avthumbMp4Fop = 'avthumb/mp4|saveas/' + saveMp4Entry; 66 | var vframeJpgFop = 'vframe/jpg/offset/1|saveas/' + saveJpgEntry; 67 | options = { 68 | scope: bucket, 69 | persistentOps: avthumbMp4Fop + ';' + vframeJpgFop, 70 | persistentPipeline: 'video-pipe', 71 | persistentNotifyUrl: 'http://api.example.com/qiniu/pfop/notify' 72 | }; 73 | putPolicy = new qiniu.rs.PutPolicy(options); 74 | console.log(putPolicy.uploadToken(mac)); 75 | -------------------------------------------------------------------------------- /examples/rtc_demo.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index.js'); 2 | 3 | // ak, sk 获取参考 https://developer.qiniu.com/dora/kb/3702/QiniuToken 4 | var ACCESS_KEY = 'ak'; 5 | var SECRET_KEY = 'sk'; 6 | var credentials = new qiniu.Credentials(ACCESS_KEY, SECRET_KEY); 7 | 8 | // 参考 https://github.com/pili-engineering/QNRTC-Server/blob/master/docs/api.md 9 | 10 | var data = { 11 | hub: 'your hub', 12 | title: 'your title', 13 | maxUsers: 10, 14 | noAutoKickUser: true 15 | }; 16 | 17 | qiniu.app.createApp(data, credentials, function (err, res) { 18 | if (err) { 19 | console.log(err); 20 | } else { 21 | console.log(res); 22 | } 23 | }); 24 | 25 | qiniu.app.getApp('appId', credentials, function (err, res) { 26 | if (err) { 27 | console.log(err); 28 | } else { 29 | console.log(res); 30 | } 31 | }); 32 | 33 | qiniu.app.deleteApp('appId', credentials, function (err, res) { 34 | if (err) { 35 | console.log(err); 36 | } else { 37 | console.log(res); 38 | } 39 | }); 40 | 41 | var data1 = { 42 | hub: 'your hub', 43 | title: 'your title', 44 | maxUsers: 10, 45 | noAutoKickUser: true, 46 | mergePublishRtmp: { 47 | enable: true, 48 | audioOnly: true, 49 | height: 1920, 50 | width: 1080, 51 | fps: 60, 52 | kbps: 1000, 53 | url: 'rtmp://xxx.example.com/test', 54 | streamTitle: 'meeting' 55 | } 56 | }; 57 | qiniu.app.updateApp('appId', data1, credentials, function (err, res) { 58 | if (err) { 59 | console.log(err); 60 | } else { 61 | console.log(res); 62 | } 63 | }); 64 | qiniu.room.listUser('appId', 'roomName', credentials, function (err, res) { 65 | if (err) { 66 | console.log(err); 67 | } else { 68 | console.log(res); 69 | } 70 | }); 71 | 72 | qiniu.room.kickUser('appId', 'roomName', 'userId', credentials, function (err, res) { 73 | if (err) { 74 | console.log(err); 75 | } else { 76 | console.log(res); 77 | } 78 | }); 79 | 80 | // type of(offset limit) = Num such as 5 10 81 | qiniu.room.listActiveRooms('appId', 'prefix', 'offset', 'limit', credentials, function (err, res) { 82 | if (err) { 83 | console.log(err); 84 | } else { 85 | console.log(res); 86 | } 87 | }); 88 | 89 | // expireAt = 1524128577 or empty 90 | var roomAccess = { 91 | appId: 'your appId', 92 | roomName: 'your roomName', 93 | userId: 'userId', 94 | expireAt: 1524128577, 95 | permission: 'admin' 96 | }; 97 | 98 | console.log(qiniu.room.getRoomToken(roomAccess, credentials)); 99 | -------------------------------------------------------------------------------- /examples/cdn_refresh_urls_dirs.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('qiniu'); 2 | const proc = require('process'); 3 | 4 | // URL 列表 5 | var urlsToRefresh = [ 6 | 'http://if-pbl.qiniudn.com/nodejs.png', 7 | 'http://if-pbl.qiniudn.com/qiniu.jpg' 8 | ]; 9 | 10 | // DIR 列表 11 | var dirsToRefresh = [ 12 | 'http://if-pbl.qiniudn.com/examples/', 13 | 'http://if-pbl.qiniudn.com/images/' 14 | ]; 15 | 16 | var accessKey = proc.env.QINIU_ACCESS_KEY; 17 | var secretKey = proc.env.QINIU_SECRET_KEY; 18 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 19 | var cdnManager = new qiniu.cdn.CdnManager(mac); 20 | // 刷新链接 21 | cdnManager.refreshUrls(urlsToRefresh, function (err, respBody, respInfo) { 22 | if (err) { 23 | throw err; 24 | } 25 | 26 | console.log(respInfo.statusCode); 27 | if (respInfo.statusCode == 200) { 28 | var jsonBody = JSON.parse(respBody); 29 | console.log(jsonBody.code); 30 | console.log(jsonBody.error); 31 | console.log(jsonBody.requestId); 32 | console.log(jsonBody.invalidUrls); 33 | console.log(jsonBody.invalidDirs); 34 | console.log(jsonBody.urlQuotaDay); 35 | console.log(jsonBody.urlSurplusDay); 36 | console.log(jsonBody.dirQuotaDay); 37 | console.log(jsonBody.dirSurplusDay); 38 | } 39 | }); 40 | 41 | // 刷新目录,刷新目录需要联系七牛技术支持开通权限 42 | cdnManager.refreshDirs(dirsToRefresh, function (err, respBody, respInfo) { 43 | if (err) { 44 | throw err; 45 | } 46 | 47 | console.log(respInfo.statusCode); 48 | if (respInfo.statusCode == 200) { 49 | var jsonBody = JSON.parse(respBody); 50 | console.log(jsonBody.code); 51 | console.log(jsonBody.error); 52 | console.log(jsonBody.requestId); 53 | console.log(jsonBody.invalidUrls); 54 | console.log(jsonBody.invalidDirs); 55 | console.log(jsonBody.urlQuotaDay); 56 | console.log(jsonBody.urlSurplusDay); 57 | console.log(jsonBody.dirQuotaDay); 58 | console.log(jsonBody.dirSurplusDay); 59 | } 60 | }); 61 | 62 | // 一起刷新 63 | cdnManager.refreshUrlsAndDirs(urlsToRefresh, dirsToRefresh, function (err, 64 | respBody, respInfo) { 65 | if (err) { 66 | throw err; 67 | } 68 | 69 | console.log(respInfo.statusCode); 70 | if (respInfo.statusCode == 200) { 71 | var jsonBody = JSON.parse(respBody); 72 | console.log(jsonBody.code); 73 | console.log(jsonBody.error); 74 | console.log(jsonBody.requestId); 75 | console.log(jsonBody.invalidUrls); 76 | console.log(jsonBody.invalidDirs); 77 | console.log(jsonBody.urlQuotaDay); 78 | console.log(jsonBody.urlSurplusDay); 79 | console.log(jsonBody.dirQuotaDay); 80 | console.log(jsonBody.dirSurplusDay); 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /test/rtc.test.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const assert = require('assert'); 3 | const qiniu = require('../index.js'); 4 | const proc = require('process'); 5 | const console = require('console'); 6 | 7 | // eslint-disable-next-line no-undef 8 | before(function (done) { 9 | if (!process.env.QINIU_ACCESS_KEY || !process.env.QINIU_SECRET_KEY) { 10 | console.log('should run command `source test-env.sh` first\n'); 11 | process.exit(0); 12 | } 13 | done(); 14 | }); 15 | 16 | // eslint-disable-next-line no-undef 17 | describe('test rtc credentials', function () { 18 | this.timeout(10000); 19 | var accessKey = proc.env.QINIU_ACCESS_KEY; 20 | var secretKey = proc.env.QINIU_SECRET_KEY; 21 | 22 | var credentials = new qiniu.Credentials(accessKey, secretKey); 23 | var appId = null; 24 | var appData = { 25 | hub: 'hailong', 26 | title: 'testtitle', 27 | maxUsers: 10, 28 | noAutoKickUser: true 29 | }; 30 | 31 | after(function (done) { 32 | qiniu.app.deleteApp(appId, credentials, function () { 33 | done(); 34 | }); 35 | }); 36 | 37 | // eslint-disable-next-line no-undef 38 | describe('test create app', function () { 39 | // eslint-disable-next-line no-undef 40 | it('create app', function (done) { 41 | qiniu.app.createApp(appData, credentials, function (err, res) { 42 | should.not.exist(err); 43 | should.exist(res.appId); 44 | assert.strictEqual(res.title, 'testtitle'); 45 | appId = res.appId; 46 | done(); 47 | }); 48 | }); 49 | }); 50 | 51 | // eslint-disable-next-line no-undef 52 | describe('test update app', function () { 53 | // eslint-disable-next-line no-undef 54 | it('update app', function (done) { 55 | should.ok(appId, 'appId not found. May create failed'); 56 | appData.title = 'testtitle2'; 57 | qiniu.app.updateApp(appId, appData, credentials, function (err, res) { 58 | should.not.exist(err); 59 | assert.strictEqual(res.title, 'testtitle2'); 60 | assert.strictEqual(res.appId, appId); 61 | done(); 62 | }); 63 | }); 64 | }); 65 | 66 | // eslint-disable-next-line no-undef 67 | describe('test get app', function () { 68 | // eslint-disable-next-line no-undef 69 | it('get app', function (done) { 70 | should.ok(appId, 'appId not found. May create failed'); 71 | qiniu.app.getApp(appId, credentials, function (err, res) { 72 | should.not.exist(err); 73 | assert.strictEqual(res.title, 'testtitle2'); 74 | assert.strictEqual(res.appId, appId); 75 | done(); 76 | }); 77 | }); 78 | }); 79 | 80 | // eslint-disable-next-line no-undef 81 | describe('test delete app', function () { 82 | // eslint-disable-next-line no-undef 83 | it('delete app', function (done) { 84 | should.ok(appId, 'appId not found. May create failed'); 85 | qiniu.app.deleteApp(appId, credentials, function (err) { 86 | should.not.exist(err); 87 | done(); 88 | }); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /qiniu/retry/retrier.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class 3 | * @param {RetryPolicy[]} options.retryPolicies 4 | * @param {function(RetryPolicyContext, RetryPolicy | undefined): Promise} [options.onBeforeRetry] 5 | * @constructor 6 | */ 7 | function Retrier (options) { 8 | this.retryPolicies = options.retryPolicies || []; 9 | this.onBeforeRetry = options.onBeforeRetry; 10 | } 11 | 12 | Retrier.prototype.initContext = function () { 13 | const context = { 14 | error: null, 15 | retried: false 16 | }; 17 | return this.retryPolicies.reduce( 18 | (promiseChain, retryPolicy) => 19 | promiseChain.then(() => 20 | retryPolicy.initContext(context)) 21 | , 22 | Promise.resolve() 23 | ) 24 | .then(() => context); 25 | }; 26 | 27 | Retrier.prototype._afterRetry = function (context) { 28 | return this.retryPolicies.reduce( 29 | (promiseChain, retryPolicy) => 30 | promiseChain.then(() => 31 | retryPolicy.afterRetry(context)) 32 | , 33 | Promise.resolve() 34 | ); 35 | }; 36 | 37 | Retrier.prototype.retry = function (options) { 38 | const { 39 | func, 40 | context 41 | } = options; 42 | return func(context) 43 | .then(result => { 44 | context.result = result; 45 | if (context.retried) { 46 | return this._afterRetry(context); 47 | } 48 | }) 49 | .catch(error => { 50 | context.error = error; 51 | if (context.retried) { 52 | return this._afterRetry(context); 53 | } 54 | }) 55 | .then(() => { 56 | let retryPolicy = this.retryPolicies.find(p => p.isImportant(context)); 57 | if (retryPolicy && !retryPolicy.shouldRetry(context)) { 58 | return [ 59 | false, 60 | retryPolicy 61 | ]; 62 | } 63 | if (!retryPolicy) { 64 | retryPolicy = this.retryPolicies.find(p => p.shouldRetry(context)); 65 | } 66 | const couldRetryPromise = this.onBeforeRetry 67 | ? this.onBeforeRetry(context, retryPolicy) 68 | : retryPolicy !== undefined; 69 | return Promise.all([ 70 | couldRetryPromise, 71 | retryPolicy 72 | ]); 73 | }) 74 | .then(([couldRetry, retryPolicy]) => { 75 | if (!couldRetry || !retryPolicy) { 76 | return; 77 | } 78 | context.error = null; 79 | context.retried = true; 80 | delete context.result; 81 | return retryPolicy.prepareRetry(context) 82 | .catch(err => { 83 | err.message = 'Retrier: prepare retry failed\n' + err.message; 84 | if (context.error) { 85 | err.cause = context.error; 86 | } 87 | return Promise.reject(err); 88 | }) 89 | .then(() => this.retry(options)); 90 | }) 91 | .then(() => { 92 | if (context.error) { 93 | return Promise.reject(context.error); 94 | } 95 | return context.result; 96 | }); 97 | }; 98 | 99 | exports.Retrier = Retrier; 100 | -------------------------------------------------------------------------------- /qiniu/fop.js: -------------------------------------------------------------------------------- 1 | const util = require('./util'); 2 | const rpc = require('./rpc'); 3 | const conf = require('./conf'); 4 | const digest = require('./auth/digest'); 5 | const querystring = require('querystring'); 6 | 7 | exports.OperationManager = OperationManager; 8 | 9 | function OperationManager (mac, config) { 10 | this.mac = mac || new digest.Mac(); 11 | this.config = config || new conf.Config(); 12 | } 13 | 14 | /** 15 | * @typedef {function(Error, any, IncomingMessage)} OperationCallback 16 | */ 17 | 18 | /** 19 | * @param {string} bucket 空间名称 20 | * @param {string} key 文件名称 21 | * @param {string[]} fops 处理指令 22 | * @param {string} pipeline 队列名称 23 | * @param {object} options 可选参数 24 | * @param {string} [options.notifyURL] 回调业务服务器,通知处理结果 25 | * @param {boolean} [options.force] 是否强制覆盖已有的同名文件 26 | * @param {string} [options.type] 为 `1` 时,开启闲时任务 27 | * @param {string} [options.workflowTemplateID] 工作流模板 ID 28 | * @param {OperationCallback} callbackFunc 回调函数 29 | */ 30 | OperationManager.prototype.pfop = function ( 31 | bucket, 32 | key, 33 | fops, 34 | pipeline, 35 | options, 36 | callbackFunc 37 | ) { 38 | options = options || {}; 39 | // 必须参数 40 | const reqParams = { 41 | bucket: bucket, 42 | key: key 43 | }; 44 | // `fops` is optional by could use `options.workflowTemplateID` to work 45 | if (Array.isArray(fops)) { 46 | reqParams.fops = fops.join(';'); 47 | } 48 | 49 | // pipeline 50 | if (!pipeline) { 51 | delete reqParams.pipeline; 52 | } 53 | 54 | // notifyURL 55 | if (options.notifyURL) { 56 | reqParams.notifyURL = options.notifyURL; 57 | } 58 | 59 | // force 60 | if (options.force) { 61 | reqParams.force = 1; 62 | } 63 | 64 | // workflowTemplateID 65 | if (options.workflowTemplateID) { 66 | reqParams.workflowTemplateID = options.workflowTemplateID; 67 | } 68 | 69 | const persistentType = parseInt(options.type, 10); 70 | if (!isNaN(persistentType)) { 71 | reqParams.type = persistentType; 72 | } 73 | 74 | util.prepareZone(this, this.mac.accessKey, bucket, function (err, ctx) { 75 | if (err) { 76 | callbackFunc(err, null, null); 77 | return; 78 | } 79 | pfopReq(ctx.mac, ctx.config, reqParams, callbackFunc); 80 | }); 81 | }; 82 | 83 | function pfopReq (mac, config, reqParams, callbackFunc) { 84 | const scheme = config.useHttpsDomain ? 'https://' : 'http://'; 85 | const requestURI = scheme + config.zone.apiHost + '/pfop/'; 86 | const reqBody = querystring.stringify(reqParams); 87 | const auth = util.generateAccessToken(mac, requestURI, reqBody); 88 | rpc.postWithForm(requestURI, reqBody, auth, callbackFunc); 89 | } 90 | 91 | /** 92 | * 查询持久化数据处理进度 93 | * @param {string} persistentId 94 | * @param {OperationCallback} callbackFunc 回调函数 95 | */ 96 | OperationManager.prototype.prefop = function ( 97 | persistentId, 98 | callbackFunc 99 | ) { 100 | let apiHost = 'api.qiniu.com'; 101 | if (this.config.zone) { 102 | apiHost = this.config.zone.apiHost; 103 | } 104 | 105 | const scheme = this.config.useHttpsDomain ? 'https://' : 'http://'; 106 | const requestURI = scheme + apiHost + '/status/get/prefop'; 107 | const reqParams = { 108 | id: persistentId 109 | }; 110 | const reqBody = querystring.stringify(reqParams); 111 | rpc.postWithForm(requestURI, reqBody, null, callbackFunc); 112 | }; 113 | -------------------------------------------------------------------------------- /qiniu/httpc/middleware/retryDomains.js: -------------------------------------------------------------------------------- 1 | const middleware = require('./base'); 2 | 3 | const URL = require('url').URL; 4 | 5 | /** 6 | * @class 7 | * @extends middleware.Middleware 8 | * @param {Object} retryDomainsOptions 9 | * @param {string[]} retryDomainsOptions.backupDomains 10 | * @param {number} [retryDomainsOptions.maxRetryTimes] 11 | * @param {function(Error || null, ResponseWrapper || null, ReqOpts):boolean} [retryDomainsOptions.retryCondition] 12 | * @constructor 13 | */ 14 | function RetryDomainsMiddleware (retryDomainsOptions) { 15 | this.backupDomains = retryDomainsOptions.backupDomains; 16 | this.maxRetryTimes = retryDomainsOptions.maxRetryTimes || 2; 17 | this.retryCondition = retryDomainsOptions.retryCondition; 18 | 19 | this._retriedTimes = 0; 20 | } 21 | 22 | RetryDomainsMiddleware.prototype = Object.create(middleware.Middleware.prototype); 23 | RetryDomainsMiddleware.prototype.constructor = RetryDomainsMiddleware; 24 | 25 | /** 26 | * @memberOf RetryDomainsMiddleware 27 | * @param {Error || null} err 28 | * @param {ResponseWrapper || null} respWrapper 29 | * @param {ReqOpts} reqOpts 30 | * @returns {boolean} 31 | * @private 32 | */ 33 | RetryDomainsMiddleware.prototype._shouldRetry = function (err, respWrapper, reqOpts) { 34 | if (typeof this.retryCondition === 'function') { 35 | return this.retryCondition(err, respWrapper, reqOpts); 36 | } 37 | 38 | return !respWrapper || respWrapper.needRetry(); 39 | }; 40 | 41 | /** 42 | * @memberOf RetryDomainsMiddleware 43 | * @param {ReqOpts} reqOpts 44 | * @param {function(ReqOpts):Promise} next 45 | * @returns {Promise} 46 | */ 47 | RetryDomainsMiddleware.prototype.send = function (reqOpts, next) { 48 | const url = new URL(reqOpts.url); 49 | const domains = this.backupDomains.slice(); // copy for late pop 50 | 51 | const couldRetry = () => { 52 | // the reason `this.maxRetryTimes - 1` is request send first then add retriedTimes 53 | // and `this.maxRetryTimes` means max request times per domain 54 | if (this._retriedTimes < this.maxRetryTimes - 1) { 55 | this._retriedTimes += 1; 56 | return true; 57 | } 58 | 59 | if (domains.length) { 60 | this._retriedTimes = 0; 61 | const domain = domains.shift(); 62 | const [hostname, port] = domain.split(':'); 63 | url.hostname = hostname; 64 | url.port = port || url.port; 65 | reqOpts.url = url.toString(); 66 | return true; 67 | } 68 | 69 | return false; 70 | }; 71 | 72 | const tryNext = () => { 73 | return next(reqOpts) 74 | .then(respWrapper => { 75 | if (!this._shouldRetry(null, respWrapper, reqOpts)) { 76 | return respWrapper; 77 | } 78 | 79 | if (couldRetry()) { 80 | return tryNext(); 81 | } 82 | 83 | return respWrapper; 84 | }) 85 | .catch(err => { 86 | if (!this._shouldRetry(err, null, reqOpts)) { 87 | return Promise.reject(err); 88 | } 89 | 90 | if (couldRetry()) { 91 | return tryNext(); 92 | } 93 | 94 | return Promise.reject(err); 95 | }); 96 | }; 97 | 98 | return tryNext(); 99 | }; 100 | 101 | exports.RetryDomainsMiddleware = RetryDomainsMiddleware; 102 | -------------------------------------------------------------------------------- /qiniu/rtc/app.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | const host = 'rtc.qiniuapi.com'; 4 | const headers = { 5 | 'Content-Type': 'application/json' 6 | }; 7 | 8 | function get(credentials, options, fn) { 9 | options.headers.Authorization = credentials.generateAccessToken(options, null); 10 | 11 | var req = http.request(options, function (res) { 12 | res.setEncoding('utf-8'); 13 | 14 | var responseString = ''; 15 | 16 | res.on('data', function (data) { 17 | responseString += data; 18 | }); 19 | 20 | res.on('end', function () { 21 | var resultObject = JSON.parse(responseString); 22 | 23 | if (res.statusCode != 200) { 24 | var result = { 25 | code: res.statusCode, 26 | message: resultObject.error || res.statusMessage, 27 | reqId: res.headers['x-reqid'] 28 | }; 29 | fn(result, null); 30 | } else { 31 | fn(null, resultObject); 32 | } 33 | }); 34 | }); 35 | 36 | req.on('error', function (e) { 37 | fn(e, null); 38 | }); 39 | 40 | req.end(); 41 | } 42 | 43 | function post(credentials, options, data, fn) { 44 | var dataString = JSON.stringify(data); 45 | 46 | options.headers.Authorization = credentials.generateAccessToken(options, dataString); 47 | 48 | var req = http.request(options, function (res) { 49 | res.setEncoding('utf-8'); 50 | 51 | var responseString = ''; 52 | 53 | res.on('data', function (data) { 54 | responseString += data; 55 | }); 56 | 57 | res.on('end', function () { 58 | var resultObject = JSON.parse(responseString); 59 | 60 | if (res.statusCode != 200) { 61 | var result = { 62 | code: res.statusCode, 63 | message: resultObject.error || res.statusMessage, 64 | reqId: res.headers['x-reqid'] 65 | }; 66 | fn(result, null); 67 | } else { 68 | fn(null, resultObject); 69 | } 70 | }); 71 | }); 72 | req.on('error', function (e) { 73 | fn(e, null); 74 | }); 75 | 76 | req.write(dataString); 77 | 78 | req.end(); 79 | } 80 | 81 | exports.createApp = function (app, credentials, fn) { 82 | var options = { 83 | host: host, 84 | port: 80, 85 | path: '/v3/apps', 86 | method: 'POST', 87 | headers: headers 88 | }; 89 | post(credentials, options, app, fn); 90 | }; 91 | 92 | exports.getApp = function (appId, credentials, fn) { 93 | var options = { 94 | host: host, 95 | port: 80, 96 | path: '/v3/apps/' + appId, 97 | method: 'GET', 98 | headers: headers 99 | }; 100 | get(credentials, options, fn); 101 | }; 102 | 103 | exports.deleteApp = function (appId, credentials, fn) { 104 | var options = { 105 | host: host, 106 | port: 80, 107 | path: '/v3/apps/' + appId, 108 | method: 'DELETE', 109 | headers: headers 110 | }; 111 | get(credentials, options, fn); 112 | }; 113 | 114 | exports.updateApp = function (appId, app, credentials, fn) { 115 | var options = { 116 | host: host, 117 | port: 80, 118 | path: '/v3/apps/' + appId, 119 | method: 'POST', 120 | headers: headers 121 | }; 122 | post(credentials, options, app, fn); 123 | }; 124 | -------------------------------------------------------------------------------- /qiniu/rtc/room.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | const host = 'rtc.qiniuapi.com'; 4 | const headers = { 5 | 'Content-Type': 'application/json' 6 | }; 7 | 8 | function get (credentials, options, fn) { 9 | options.headers.Authorization = credentials.generateAccessToken(options, null); 10 | 11 | var req = http.request(options, function (res) { 12 | res.setEncoding('utf-8'); 13 | 14 | var responseString = ''; 15 | 16 | res.on('data', function (data) { 17 | responseString += data; 18 | }); 19 | 20 | res.on('end', function () { 21 | // var resultObject = JSON.parse(responseString); 22 | // console.log(JSON.parse(responseString)) 23 | 24 | if (res.statusCode != 200) { 25 | var result = { 26 | code: res.statusCode, 27 | message: res.statusMessage 28 | }; 29 | fn(result, null); 30 | } else { 31 | fn(null, JSON.parse(responseString)); 32 | } 33 | }); 34 | }); 35 | 36 | req.on('error', function (e) { 37 | fn(e, null); 38 | }); 39 | 40 | req.end(); 41 | } 42 | 43 | // function post(credentials, options, data, fn) { 44 | // var dataString = JSON.stringify(data); 45 | 46 | // options.headers['Authorization'] = credentials.generateAccessToken(options, dataString); 47 | 48 | // var req = http.request(options, function(res) { 49 | // res.setEncoding('utf-8'); 50 | 51 | // var responseString = ''; 52 | 53 | // res.on('data', function(data) { 54 | // responseString += data; 55 | // }); 56 | 57 | // res.on('end', function() { 58 | // var resultObject = JSON.parse(responseString); 59 | 60 | // if (res.statusCode != 200) { 61 | // var result = { 62 | // code: res.statusCode, 63 | // message: res.statusMessage 64 | // }; 65 | // fn(result, null); 66 | // } else { 67 | // fn(null, resultObject); 68 | // } 69 | // }); 70 | // }); 71 | // req.on('error', function(e) { 72 | // fn(e, null); 73 | // }); 74 | 75 | // req.write(dataString); 76 | 77 | // req.end(); 78 | // } 79 | 80 | exports.listUser = function (appId, roomName, credentials, fn) { 81 | var options = { 82 | host: host, 83 | port: 80, 84 | path: '/v3/apps/' + appId + '/rooms/' + roomName + '/users', 85 | method: 'GET', 86 | headers: headers 87 | }; 88 | get(credentials, options, fn); 89 | }; 90 | 91 | exports.kickUser = function (appId, roomName, userId, credentials, fn) { 92 | var options = { 93 | host: host, 94 | port: 80, 95 | path: '/v3/apps/' + appId + '/rooms/' + roomName + '/users/' + userId, 96 | method: 'DELETE', 97 | headers: headers 98 | }; 99 | get(credentials, options, fn); 100 | }; 101 | 102 | exports.listActiveRooms = function (appId, roomNamePrefix, offset, limit, credentials, fn) { 103 | var options = { 104 | host: host, 105 | port: 80, 106 | path: '/v3/apps/' + appId + '/rooms?prefix=' + roomNamePrefix + '&offset=' + offset + '&limit=' + limit, 107 | method: 'GET', 108 | headers: headers 109 | }; 110 | get(credentials, options, fn); 111 | }; 112 | 113 | exports.getRoomToken = function (roomAccess, credentials) { 114 | if (!roomAccess.expireAt) { 115 | roomAccess.expireAt = Math.floor(Date.now() / 1000) + 3600; 116 | } 117 | return credentials.signJson(roomAccess); 118 | }; 119 | -------------------------------------------------------------------------------- /examples/sms.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index.js'); 2 | const proc = require('process'); 3 | const should = require('should'); 4 | const assert = require('assert'); 5 | 6 | // eslint-disable-next-line no-undef 7 | before(function(done) { 8 | if (!process.env.QINIU_ACCESS_KEY || !process.env.QINIU_SECRET_KEY) { 9 | console.log('should run command `source test-env.sh` first\n'); 10 | process.exit(0); 11 | } 12 | done(); 13 | }); 14 | 15 | // message 16 | describe('test Message', function() { 17 | var accessKey = proc.env.QINIU_ACCESS_KEY; 18 | var secretKey = proc.env.QINIU_SECRET_KEY; 19 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 20 | // eslint-disable-next-line no-undef 21 | describe('test sendMessage', function() { 22 | // eslint-disable-next-line no-undef 23 | it('test sendMessage', function(done) { 24 | var num = new Array("17321129884","18120582893"); 25 | var reqBody = { 26 | "template_id": "1199572412090290176", 27 | "mobiles": num, 28 | "parameters": { 29 | "prize": "3333", 30 | "name": "sendMessage", 31 | "time": "1238" 32 | } 33 | }; 34 | qiniu.sms.message.sendMessage(reqBody, mac, function(respErr, respBody, 35 | respInfo) { 36 | should.not.exist(respErr); 37 | assert.strictEqual(respInfo.statusCode, 200); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | describe('test sendSingleMessage', function() { 43 | it('test sendSingleMessage', function(done) { 44 | var reqBody = { 45 | "template_id": "1199572412090290176", 46 | "mobile": "17321129884", 47 | "parameters": { 48 | "prize": "3333", 49 | "name": "sendSingleMessage", 50 | "time": "1238" 51 | } 52 | }; 53 | qiniu.sms.message.sendSingleMessage(reqBody, mac, function(respErr, respBody, 54 | respInfo) { 55 | should.not.exist(respErr); 56 | assert.strictEqual(respInfo.statusCode, 200); 57 | done(); 58 | }); 59 | }); 60 | }); 61 | describe('test sendOverseaMessage', function() { 62 | it('test sendOverseaMessage', function(done) { 63 | var reqBody = { 64 | "template_id": "1199572412090290176", 65 | "mobile": "17321129884", 66 | "parameters": { 67 | "prize": "3333", 68 | "name": "1111", 69 | "time": "1238" 70 | } 71 | }; 72 | qiniu.sms.message.sendOverseaMessage(reqBody, mac, function(respErr, respBody, 73 | respInfo) { 74 | should.not.exist(respErr); 75 | assert.strictEqual(respInfo.statusCode, 200); 76 | done(); 77 | }); 78 | }); 79 | }); 80 | describe('test sendFulltextMessage', function() { 81 | it('test sendFulltextMessage', function(done) { 82 | var num = new Array("17321129884","18120582893"); 83 | var reqBody = { 84 | "mobiles": num, 85 | "content": "【七牛云-测试】您的验证码为1121,该验证码5分钟内有效", 86 | "template_type": "verification" 87 | }; 88 | qiniu.sms.message.sendFulltextMessage(reqBody, mac, function(respErr, respBody, 89 | respInfo) { 90 | if(respErr!=null){ 91 | console.log(respErr); 92 | } 93 | should.not.exist(respErr); 94 | assert.strictEqual(respInfo.statusCode, 200); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/conftest.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { Readable } = require('stream'); 3 | const crypto = require('crypto'); 4 | 5 | function getEnvConfig () { 6 | return { 7 | accessKey: process.env.QINIU_ACCESS_KEY, 8 | secretKey: process.env.QINIU_SECRET_KEY, 9 | bucketName: process.env.QINIU_TEST_BUCKET, 10 | domain: process.env.QINIU_TEST_DOMAIN 11 | }; 12 | } 13 | exports.getEnvConfig = getEnvConfig; 14 | 15 | function checkEnvConfigOkOrExit () { 16 | const envConfig = getEnvConfig(); 17 | if ( 18 | Object.keys(envConfig).some(k => !envConfig[k]) 19 | ) { 20 | console.log('should run command `source test-env.sh` first\n'); 21 | process.exit(0); 22 | } 23 | } 24 | exports.checkEnvConfigOrExit = checkEnvConfigOkOrExit; 25 | 26 | /** 27 | * @typedef Param 28 | * @property {string} name 29 | * @property {any[]} values 30 | */ 31 | 32 | /** 33 | * cartesian product 34 | * @param {Param} params 35 | * @returns {Object[]} 36 | */ 37 | function parametrize (...params) { 38 | if (params.length === 0) { 39 | return [{}]; 40 | } 41 | 42 | const [param, ...rest] = params; 43 | 44 | const restParams = parametrize(...rest); 45 | 46 | return param.values 47 | .map(value => 48 | restParams.map(restParam => Object.assign( 49 | {}, 50 | restParam, 51 | { 52 | [param.name]: value 53 | }) 54 | ) 55 | ) 56 | .reduce((acc, val) => acc.concat(val), []); 57 | } 58 | exports.parametrize = parametrize; 59 | 60 | /** 61 | * @param {string} filepath 62 | * @param {number} size 63 | * @returns {Promise} 64 | */ 65 | function createRandomFile (filepath, size) { 66 | return new Promise((resolve, reject) => { 67 | fs.createReadStream('/dev/urandom', { end: size }) 68 | .pipe(fs.createWriteStream(filepath)) 69 | .on('finish', resolve) 70 | .on('error', reject); 71 | }); 72 | } 73 | exports.createRandomFile = createRandomFile; 74 | 75 | function createRandomStreamAndMD5 (size, chunkSize = 1024 * 1024) { 76 | const stream = new Readable(); 77 | const md5 = crypto.createHash('md5'); 78 | for (let offset = 0; offset < size; offset += chunkSize) { 79 | const bytesSize = Math.min(chunkSize, size - offset); 80 | const bytes = crypto.randomBytes(bytesSize); 81 | stream.push(bytes); 82 | md5.update(bytes); 83 | } 84 | stream.push(null); 85 | return { 86 | stream, 87 | md5: md5.digest('hex') 88 | }; 89 | } 90 | exports.createRandomStreamAndMD5 = createRandomStreamAndMD5; 91 | 92 | /** 93 | * for testing compatibility for both callback-style and promise-style 94 | * @param func 95 | * @returns {{callback: Promise<{data: any, resp: any}>, native: Promise}} 96 | */ 97 | function doAndWrapResultPromises (func) { 98 | const promises = {}; 99 | promises.callback = new Promise((resolve, reject) => { 100 | promises.native = func((err, data, resp) => { 101 | if (err) { 102 | err.resp = resp; 103 | reject(err); 104 | return; 105 | } 106 | resolve({ data, resp }); 107 | }) 108 | .catch(err => { 109 | reject(err); 110 | }); 111 | }); 112 | return promises; 113 | } 114 | exports.doAndWrapResultPromises = doAndWrapResultPromises; 115 | 116 | function getManuallyPromise () { 117 | let _resolve; 118 | let _reject; 119 | const promise = new Promise((resolve, reject) => { 120 | _resolve = resolve; 121 | _reject = reject; 122 | }); 123 | 124 | // result.resolve = (...args) => { 125 | // setTimeout(() => result._resolve(...args)); 126 | // }; 127 | // result.reject = (...args) => { 128 | // setTimeout(() => result._reject(...args)); 129 | // }; 130 | 131 | return { 132 | promise, 133 | resolve: _resolve, 134 | reject: _reject 135 | }; 136 | } 137 | exports.getManuallyPromise = getManuallyPromise; 138 | -------------------------------------------------------------------------------- /test/cdn.test.js: -------------------------------------------------------------------------------- 1 | const qiniu = require('../index.js'); 2 | const should = require('should'); 3 | const proc = require('process'); 4 | const console = require('console'); 5 | 6 | // eslint-disable-next-line no-undef 7 | before(function (done) { 8 | if (!process.env.QINIU_ACCESS_KEY || !process.env.QINIU_SECRET_KEY || !process.env.QINIU_TEST_BUCKET || !process.env.QINIU_TEST_DOMAIN) { 9 | console.log('should run command `source test-env.sh` first\n'); 10 | process.exit(0); 11 | } 12 | done(); 13 | }); 14 | 15 | // eslint-disable-next-line no-undef 16 | describe('test start cdn', function () { 17 | this.timeout(0); 18 | 19 | var accessKey = proc.env.QINIU_ACCESS_KEY; 20 | var secretKey = proc.env.QINIU_SECRET_KEY; 21 | var domain = proc.env.QINIU_TEST_DOMAIN; 22 | var mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 23 | var cdnManager = new qiniu.cdn.CdnManager(mac); 24 | 25 | it('test getCdnLogList', function (done) { 26 | var day = (new Date()).toISOString().substring(0, 10); 27 | cdnManager.getCdnLogList([domain], day, 28 | function (err, respBody, respInfo) { 29 | console.log(respBody, respInfo); 30 | should.not.exist(err); 31 | respBody.should.have.keys('code', 'data'); 32 | done(); 33 | }); 34 | }); 35 | 36 | it('test getFluxData', function (done) { 37 | var today = new Date(); 38 | var endDay = today.toISOString().substring(0, 10); 39 | today.setDate(today.getDate() - 30); 40 | var startDay = today.toISOString().substring(0, 10); 41 | cdnManager.getFluxData(startDay, endDay, '5hour', [domain], 42 | function (err, respBody, respInfo) { 43 | console.log(respBody, respInfo); 44 | should.not.exist(err); 45 | respBody.should.have.keys('code', 'data'); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('test getBandwidthData', function (done) { 51 | var today = new Date(); 52 | var endDay = today.toISOString().substring(0, 10); 53 | today.setDate(today.getDate() - 30); 54 | var startDay = today.toISOString().substring(0, 10); 55 | cdnManager.getBandwidthData(startDay, endDay, '5hour', [domain], 56 | function (err, respBody, respInfo) { 57 | console.log(respBody, respInfo); 58 | should.not.exist(err); 59 | respBody.should.have.keys('code', 'data'); 60 | done(); 61 | }); 62 | }); 63 | 64 | it('test prefetchUrls', function (done) { 65 | var urls = ['http://' + domain + '/qiniu.mp4']; 66 | cdnManager.prefetchUrls(urls, 67 | function (err, respBody, respInfo) { 68 | console.log(respBody, respInfo); 69 | should.not.exist(err); 70 | respBody.should.have.keys('code', 'taskIds', 'requestId'); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('test refreshUrls', function (done) { 76 | var urls = ['http://' + domain + '/qiniu.mp4']; 77 | cdnManager.refreshUrls(urls, 78 | function (err, respBody, respInfo) { 79 | console.log(respBody, respInfo); 80 | should.not.exist(err); 81 | respBody.should.have.keys('code', 'taskIds', 'requestId'); 82 | done(); 83 | }); 84 | }); 85 | 86 | it('test refreshDirs', function (done) { 87 | var dirs = ['http://' + domain + '/']; 88 | cdnManager.refreshDirs(dirs, 89 | function (err, respBody, respInfo) { 90 | console.log(respBody, respInfo); 91 | should.not.exist(err); 92 | respBody.should.have.keys('code', 'taskIds', 'requestId'); 93 | done(); 94 | }); 95 | }); 96 | 97 | it('test refreshUrlsAndDirs', function (done) { 98 | var urls = ['http://' + domain + '/qiniu.mp4']; 99 | var dirs = ['http://' + domain + '/']; 100 | cdnManager.refreshUrlsAndDirs(urls, dirs, 101 | function (err, respBody, respInfo) { 102 | console.log(respBody, respInfo); 103 | should.not.exist(err); 104 | respBody.should.have.keys('code', 'taskIds', 'requestId'); 105 | done(); 106 | }); 107 | }); 108 | 109 | it('test createTimestampAntiLeechUrl', function (done) { 110 | var host = 'http://' + domain; 111 | var url = cdnManager.createTimestampAntiLeechUrl(host, 'qiniu.mp4', 'aa=23', 'encryptKey', 20); 112 | should.equal(url, host + '/qiniu.mp4?aa=23&sign=1a530a8baafe126145f49cb738a50c09&t=14'); 113 | done(); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /qiniu/cdn.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | const crypto = require('crypto'); 3 | const urllib = require('urllib'); 4 | const util = require('./util'); 5 | const digest = require('./auth/digest.js'); 6 | const encodeUrl = require('encodeurl'); 7 | 8 | exports.CdnManager = CdnManager; 9 | 10 | function CdnManager (mac) { 11 | this.mac = mac || new digest.Mac(); 12 | } 13 | 14 | // 获取域名日志下载链接 15 | // @link http://developer.qiniu.com/article/fusion/api/log.html 16 | // 17 | // @param domains 域名列表 domains = ['obbid7qc6.qnssl.com','7xkh68.com1.z0.glb.clouddn.com'] 18 | // @param logDay 日期,例如 2016-07-01 19 | // @param callbackFunc(err, respBody, respInfo) 20 | CdnManager.prototype.getCdnLogList = function (domains, logDay, callbackFunc) { 21 | var postBody = { 22 | day: logDay, 23 | domains: domains.join(';') 24 | }; 25 | req(this.mac, '/v2/tune/log/list', postBody, callbackFunc); 26 | }; 27 | 28 | // 获取域名访问流量数据 29 | // @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html#batch-flux 30 | // 31 | // @param startDate 开始日期,例如:2016-07-01 32 | // @param endDate 结束日期,例如:2016-07-03 33 | // @param granularity 粒度,取值:5min/hour/day 34 | // @param domains 域名列表 domain = ['obbid7qc6.qnssl.com','obbid7qc6.qnssl.com']; 35 | // @param callbackFunc(err, respBody, respInfo) 36 | CdnManager.prototype.getFluxData = function (startDate, endDate, granularity, 37 | domains, 38 | callbackFunc) { 39 | var data = { 40 | startDate: startDate, 41 | endDate: endDate, 42 | granularity: granularity, 43 | domains: domains.join(';') 44 | }; 45 | req(this.mac, '/v2/tune/flux', data, callbackFunc); 46 | }; 47 | 48 | // 获取域名访问带宽数据 49 | // @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html 50 | // @param startDate 开始日期,例如:2016-07-01 51 | // @param endDate 结束日期,例如:2016-07-03 52 | // @param granularity 粒度,取值:5min/hour/day 53 | // @param domains 域名列表 domain = ['obbid7qc6.qnssl.com','obbid7qc6.qnssl.com'] 54 | // @param callbackFunc(err, respBody, respInfo) 55 | CdnManager.prototype.getBandwidthData = function (startDate, endDate, 56 | granularity, domains, 57 | callbackFunc) { 58 | var data = { 59 | startDate: startDate, 60 | endDate: endDate, 61 | granularity: granularity, 62 | domains: domains.join(';') 63 | }; 64 | req(this.mac, '/v2/tune/bandwidth', data, callbackFunc); 65 | }; 66 | 67 | // 预取文件链接 68 | // @link http://developer.qiniu.com/article/fusion/api/prefetch.html 69 | // 70 | // @param 预取urls urls = ['http://obbid7qc6.qnssl.com/023','http://obbid7qc6.qnssl.com/025'] 71 | // @param callbackFunc(err, respBody, respInfo) 72 | CdnManager.prototype.prefetchUrls = function (urls, callbackFunc) { 73 | var postBody = { 74 | urls: urls 75 | }; 76 | req(this.mac, '/v2/tune/prefetch', postBody, callbackFunc); 77 | }; 78 | 79 | // 刷新链接 80 | // @link http://developer.qiniu.com/article/fusion/api/refresh.html 81 | // 刷新urls refreshUrls = ['http://obbid7qc6.qnssl.com/023','http://obbid7qc6.qnssl.com/025'] 82 | CdnManager.prototype.refreshUrls = function (urls, callbackFunc) { 83 | this.refreshUrlsAndDirs(urls, null, callbackFunc); 84 | }; 85 | 86 | // 刷新目录 87 | // 刷新目录列表,每次最多不可以超过10个目录, 刷新目录需要额外开通权限,可以联系七牛技术支持处理 88 | // @link http://developer.qiniu.com/article/fusion/api/refresh.html 89 | // 刷新dirs refreshDirs = ['http://obbid7qc6.qnssl.com/wo/','http://obbid7qc6.qnssl.com/'] 90 | CdnManager.prototype.refreshDirs = function (dirs, callbackFunc) { 91 | this.refreshUrlsAndDirs(null, dirs, callbackFunc); 92 | }; 93 | 94 | CdnManager.prototype.refreshUrlsAndDirs = function (urls, dirs, callbackFunc) { 95 | var postBody = { 96 | urls: urls, 97 | dirs: dirs 98 | }; 99 | req(this.mac, '/v2/tune/refresh', postBody, callbackFunc); 100 | }; 101 | 102 | // post 请求 103 | function req (mac, reqPath, reqBody, callbackFunc) { 104 | var url = 'http://fusion.qiniuapi.com' + reqPath; 105 | var accessToken = util.generateAccessToken(mac, url, ''); 106 | var headers = { 107 | 'Content-Type': 'application/json', 108 | Authorization: accessToken 109 | }; 110 | urllib.request(url, { 111 | method: 'POST', 112 | headers: headers, 113 | data: reqBody, 114 | dataType: 'json' 115 | }, callbackFunc); 116 | } 117 | 118 | // 构建标准的基于时间戳的防盗链 119 | // @param domain 自定义域名,例如 http://img.abc.com 120 | // @param fileName 待访问的原始文件名,必须是utf8编码,不需要进行urlencode 121 | // @param query 业务自身的查询参数,必须是utf8编码,不需要进行urlencode, 122 | // 例如 {aa:"23", attname:"11111111111111"} 123 | // @param encryptKey 时间戳防盗链的签名密钥,从七牛后台获取 124 | // @param deadline 链接的有效期时间戳,是以秒为单位的Unix时间戳 125 | // @return signedUrl 最终的带时间戳防盗链的url 126 | CdnManager.prototype.createTimestampAntiLeechUrl = function (domain, fileName, 127 | query, encryptKey, deadline) { 128 | var urlToSign; 129 | if (query != null) { 130 | urlToSign = domain + '/' + encodeUrl(fileName) + '?' + query; 131 | } else { 132 | urlToSign = domain + '/' + encodeUrl(fileName); 133 | } 134 | 135 | var urlObj = new url.URL(urlToSign); 136 | var pathname = urlObj.pathname; 137 | 138 | var expireHex = deadline.toString(16); 139 | var signedStr = encryptKey + pathname + expireHex; 140 | 141 | var md5 = crypto.createHash('md5'); 142 | var toSignStr = md5.update(signedStr).digest('hex'); 143 | 144 | if (query != null) { 145 | return urlToSign + '&sign=' + toSignStr + '&t=' + expireHex; 146 | } else { 147 | return urlToSign + '?sign=' + toSignStr + '&t=' + expireHex; 148 | } 149 | }; 150 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGE LOG 2 | 3 | ## 7.14.0 4 | - 对象存储,持久化处理支持工作流模版 5 | - 对象存储,修复持久化处理闲时任务的类型声明 6 | 7 | ## 7.13.0 8 | - 对象存储,新增空间级别上传加速开关 9 | - 对象存储,优化断点续传开启方式 10 | - 对象存储,优化回调签名验证函数,新增兼容 Qiniu 签名 11 | - 对象存储,修复上传失败无法完成自动重试 12 | - 在 Node.js 18 及以上触发 13 | - 7.12.0 引入 14 | - 对象存储,新增空间的创建、删除、按标签列举 15 | - 对象存储,调整查询区域主备域名 16 | - 修复内部 HttpClient 部分方法参数类型声明不正确 17 | 18 | ## 7.12.0 19 | - 对象存储,新增支持 Promise 风格异步 20 | - 对象存储,修复分片上传 v1 在特定条件可能无法从断点继续上传 21 | - 对象存储,新增支持新的区域对象 Region 22 | - 对象存储,管理接口支持双活 23 | - 对象存储,修复缺失的部分类型声明,新增响应的类型声明 24 | 25 | ## 7.11.1 26 | - 对象存储,上传策略移除严格模式,支持任意策略选项 27 | 28 | ## 7.11.0 29 | - 对象存储,新增支持归档直读存储 30 | - 对象存储,批量操作、解冻操作支持自动查询 rs 服务域名 31 | 32 | ## 7.10.1 33 | - 对象存储,修复无法上传带有英文双引号的文件 34 | 35 | ## 7.10.0 36 | - 对象存储,上传支持双活 37 | - 对象存储,上传回调支持 Promise 风格 38 | - 对象存储,修复分片上传v2在创建 uploadId 失败后仍尝试上传 39 | 40 | ## 7.9.0 41 | - 对象存储,修复无法对 key 为空字符串的对象进行操作 42 | - 对象存储,查询区域域名支持配置 UC 地址 43 | - 对象存储,查询区域域名接口升级 44 | - 对象存储,更新设置镜像源的域名 45 | - 对象存储,新增请求中间件逻辑,方便拓展请求逻辑 46 | - 对象存储,新增备用 UC 域名用于查询区域域名 47 | - 对象存储,移除首尔区域 48 | - 对象存储,新增 华东浙江2 区域的类型声明 49 | 50 | ## 7.8.0 51 | - 移除不推荐域名,并增加 亚太-首尔 和 华东-浙江2 固定区域 52 | - RTC,优化请求失败的错误信息 53 | - 对象存储,优化分片上传 ctx 超时检测 54 | - 对象存储,修复事件、跨域部分 API 身份认证错误(v7.7.0 引入) 55 | - 对象存储,新增事件、跨域 API 的类型声明(.d.ts) 56 | 57 | ## 7.7.0 58 | - 对象存储,管理类 API 发送请求时增加 [X-Qiniu-Date](https://developer.qiniu.com/kodo/3924/common-request-headers) (生成请求的时间) header 59 | 60 | ## 7.6.1 61 | - 添加sms typing 62 | 63 | ## 7.6.0 64 | - 对象存储,新增 setObjectLifeCycle 设置对象生命周期 API 65 | 66 | ## 7.5.0 67 | - 对象存储,新增支持 [深度归档存储类型](https://developer.qiniu.com/kodo/3956/kodo-category#deep_archive) 68 | - 对象存储,修复在不制定区域信息情况下自动获取区域信息异常问题 69 | - 修复 Qiniu 签名算法在对部分 http header 处理异常问题 70 | 71 | ## 7.4.0 72 | - 支持 [分片上传 V2](https://developer.qiniu.com/kodo/6364/multipartupload-interface) 73 | 74 | 75 | ## 7.3.3 76 | - 修复上传策略中forceSaveKey指定无效 77 | 78 | ## 7.3.2 79 | - 修复crc32指定值无效 80 | - 修复checkCrc指定无效 81 | - 统一checkCrc类型 82 | 83 | ## 7.3.1 84 | - 新增归档存储解冻接口 85 | - 支持上传时key值为空字符串 86 | 87 | ## v7.3.0 88 | - 新增 19 个bucket及文件相关操作 89 | - 新增文件列举 v2 接口功能 90 | - 新增短信功能 91 | - 更新上传加速域名策略 92 | - 修复自动获取上传区域时的无效缓存 93 | - 修复大文件上传异常 94 | - 增加测试用例 95 | 96 | ## v7.2.2 97 | - 一些log输出问题,travis增加eslint 检查 98 | 99 | ## v7.2.1 100 | - 修复rtc获取回复存在的问题 101 | 102 | ## v7.2.0 103 | - 修复node的stream读取的chunk大小比较随意的问题 104 | 105 | ## v7.1.9 106 | - 修复新版node下resume up方式文件内容被缓存而导致的上传失败 107 | 108 | ## v7.1.8 109 | - 修复 index.d.ts 文件中zone的设置 110 | 111 | ## v7.1.7 112 | - 修复form上传在升级mime库后的错误 113 | 114 | ## v7.1.6 115 | - 修复rs和rsf的https默认域名 116 | - 升级修复mime库的安全风险 117 | 118 | ## v7.1.5 119 | - 增加连麦功能 120 | 121 | ## v7.1.3 122 | - 增加新加坡机房 123 | 124 | ## v7.1.2 125 | - 增加自定义资源访问响应头部的功能 126 | - 改进时间戳签名方法,支持复杂urlencode 127 | 128 | ### v7.1.1 129 | - 修复 index.d.ts 中的函数声明错误 130 | 131 | ### v7.1.0 132 | - 修复时间戳防盗链中存在特殊字符引发的签名错误 133 | - 修复分片上传的时候最后一块小于2056字节引发的错误 134 | 135 | ### v7.0.9 136 | - 增加Qiniu方式的管理凭证生成方法以支持新的产品鉴权 137 | 138 | ### v7.0.8 139 | - 修复分片上传小文件的时候文件读取end的事件bug 140 | 141 | ### v7.0.7 142 | - 给form upload添加默认的crc32校验以避免网络层面的字节反转导致上传内容不正确 143 | - 修复resume upload在上传小文件的时候出现的上传失败情况 144 | 145 | ### v7.0.6 146 | - 修复时间戳防盗链算法中对文件名的urlencode不兼容问题 147 | - 发布index.d.ts文件 148 | 149 | ### v7.0.5 150 | - 修复zone获取失败时callbackFunc不存在的问题 151 | - 增加分片上传的时候的progressCallback 152 | 153 | ### v7.0.4 154 | - 增加http&https代理功能 155 | 156 | ### v7.0.2 157 | - 修复cdn刷新文件和目录中方法引用错误 158 | 159 | ### v7.0.1 160 | 161 | - 重构文件表单上传和分片上传代码 162 | - 重构CDN操作相关的代码 163 | - 重构资源管理相关的代码 164 | - 重构数据处理相关的代码 165 | - 重构上传策略的相关代码 166 | 167 | ### v6.1.14 168 | 169 | 2017-01-16 170 | 171 | - 增加CDN刷新、预取 172 | - 增加获取带宽、流量数据 173 | - 增加获取日志列表 174 | - 增加时间戳防盗链签名 175 | 176 | ### v6.1.13 177 | 178 | 2016-10-13 179 | 180 | - 修正从uptoken获取bucket 181 | 182 | ### v6.1.12 183 | 184 | 2016-10-10 185 | 186 | - 增加多机房接口调用支持 187 | 188 | ### v6.1.11 189 | 190 | 2016-05-06 191 | 192 | - npm 通过travis 自动发布 193 | 194 | ### v6.1.10 195 | 196 | 2016-04-25 197 | 198 | - list 增加delimiter 支持 199 | - 增加强制copy/move 200 | - 底层使用putReadable 谢谢 @thesadabc 201 | - 修正result 处理 谢谢 @loulin 202 | - fix Unhandled stream error in pipe 谢谢@loulin 203 | - putExtra 修正 paras 为 params 204 | 205 | ### v6.1.9 206 | 207 | 2015-12-03 208 | 209 | - Make secure base url 210 | - policy add fsizeMin 211 | - 修正 getEncodedEntryUri(bucket, key) 212 | - 文档修正 213 | 214 | ### v6.1.8 215 | 216 | 2015-05-13 217 | 218 | - 上传增加putpolicy2 219 | 220 | ### v6.1.7 221 | 222 | 2015-05-09 223 | 224 | - 上传putpolicy2增加 callbackHost、persistentPipeline, callbackFetchKey 225 | - 增加fetch 函数 226 | - imageview -> imageview2 227 | 228 | 229 | ### v6.1.6 230 | 231 | 2014-10-31 232 | 233 | - 上传putpolicy2增加fsizelimit, insertonly 234 | 235 | 236 | ### v6.1.5 237 | 238 | 2014-7-23 issue [#111](https://github.com/qiniu/nodejs-sdk/pull/111) 239 | 240 | - [#109] 统一user agent 241 | - [#110] 更新put policy 242 | 243 | ### v6.1.4 244 | 245 | 2014-7-10 issue [#108](https://github.com/qiniu/nodejs-sdk/pull/108) 246 | 247 | - [#107] 调整上传host 248 | 249 | 250 | ### v6.1.3 251 | 252 | 2014-4-03 issue [#102](https://github.com/qiniu/nodejs-sdk/pull/102) 253 | 254 | - [#98] 增加pfop 功能 255 | - [#99] 增加针对七牛callback的检查 256 | 257 | ### v6.1.2 258 | 259 | 2014-2-17 issue [#96](https://github.com/qiniu/nodejs-sdk/pull/96) 260 | 261 | - Content-Length = 0 时的细节修复 262 | 263 | 264 | ### v6.1.1 265 | 266 | 2013-12-5 issue [#90](https://github.com/qiniu/nodejs-sdk/pull/90) 267 | 268 | - 创建buffer前检测 269 | 270 | 271 | ### v6.1.0 272 | 273 | 2013-10-08 issues [#81](https://github.com/qiniu/nodejs-sdk/pull/81) 274 | 275 | - 使用urllib 276 | - 修复callbackUrl的bug 277 | - 调整bucket的下载域名 278 | 279 | ### v6.0.0 280 | 281 | 2013-07-16 issue [#56](https://github.com/qiniu/nodejs-sdk/pull/56) 282 | 283 | - 遵循 [sdkspec v6.0.4](https://github.com/qiniu/sdkspec/tree/v6.0.4) 284 | -------------------------------------------------------------------------------- /qiniu/storage/internal.js: -------------------------------------------------------------------------------- 1 | // internal 2 | // DO NOT use this file, unless you know what you're doing. 3 | // Because its API may make broken change for internal usage. 4 | 5 | const { RetryPolicy } = require('../retry'); 6 | 7 | // --- split to files --- // 8 | /** 9 | * @typedef {RetryPolicyContext} TokenExpiredRetryPolicyContext 10 | * @property {string} resumeRecordFilePath 11 | * @property {'v1' | 'v2' | string} uploadApiVersion 12 | */ 13 | 14 | /** 15 | * @class 16 | * @extends RetryPolicy 17 | * @param {Object} options 18 | * @param {string} options.uploadApiVersion 19 | * @param {function} options.recordDeleteHandler 20 | * @param {function} options.recordExistsHandler 21 | * @param {number} [options.maxRetryTimes] 22 | * @constructor 23 | */ 24 | function TokenExpiredRetryPolicy (options) { 25 | this.id = Symbol(this.constructor.name); 26 | this.uploadApiVersion = options.uploadApiVersion; 27 | this.recordDeleteHandler = options.recordDeleteHandler; 28 | this.recordExistsHandler = options.recordExistsHandler; 29 | this.maxRetryTimes = options.maxRetryTimes || 1; 30 | } 31 | 32 | TokenExpiredRetryPolicy.prototype = Object.create(RetryPolicy.prototype); 33 | TokenExpiredRetryPolicy.prototype.constructor = TokenExpiredRetryPolicy; 34 | 35 | /** 36 | * @param {Object} context 37 | * @returns {Promise} 38 | */ 39 | TokenExpiredRetryPolicy.prototype.initContext = function (context) { 40 | context[this.id] = { 41 | retriedTimes: 0, 42 | uploadApiVersion: this.uploadApiVersion 43 | }; 44 | return Promise.resolve(); 45 | }; 46 | 47 | /** 48 | * @param {Object} context 49 | * @returns {boolean} 50 | */ 51 | TokenExpiredRetryPolicy.prototype.shouldRetry = function (context) { 52 | const { 53 | retriedTimes, 54 | uploadApiVersion 55 | } = context[this.id]; 56 | 57 | if ( 58 | retriedTimes >= this.maxRetryTimes || 59 | !this.recordExistsHandler() 60 | ) { 61 | return false; 62 | } 63 | 64 | if (!context.result) { 65 | return false; 66 | } 67 | 68 | if (uploadApiVersion === 'v1' && 69 | context.result.resp.statusCode === 701 70 | ) { 71 | return true; 72 | } 73 | 74 | if (uploadApiVersion === 'v2' && 75 | context.result.resp.statusCode === 612 76 | ) { 77 | return true; 78 | } 79 | 80 | return false; 81 | }; 82 | 83 | /** 84 | * @param {Object} context 85 | * @returns {Promise} 86 | */ 87 | TokenExpiredRetryPolicy.prototype.prepareRetry = function (context) { 88 | context[this.id].retriedTimes += 1; 89 | return new Promise(resolve => { 90 | if (!this.recordExistsHandler()) { 91 | resolve(); 92 | return; 93 | } 94 | this.recordDeleteHandler(); 95 | resolve(); 96 | }); 97 | }; 98 | 99 | exports.TokenExpiredRetryPolicy = TokenExpiredRetryPolicy; 100 | 101 | /** 102 | * @class 103 | * @extends RetryPolicy 104 | * @constructor 105 | */ 106 | function AccUnavailableRetryPolicy () { 107 | } 108 | 109 | AccUnavailableRetryPolicy.prototype = Object.create(RetryPolicy.prototype); 110 | AccUnavailableRetryPolicy.prototype.constructor = AccUnavailableRetryPolicy; 111 | 112 | AccUnavailableRetryPolicy.prototype.initContext = function (context) { 113 | return Promise.resolve(); 114 | }; 115 | 116 | AccUnavailableRetryPolicy.prototype.isAccNotAvailable = function (context) { 117 | try { 118 | return context.result.resp.statusCode === 400 && 119 | context.result.resp.data.error.includes('transfer acceleration is not configured on this bucket'); 120 | } catch (_err) { 121 | return false; 122 | } 123 | }; 124 | 125 | AccUnavailableRetryPolicy.prototype.shouldRetry = function (context) { 126 | if (!context.result) { 127 | return false; 128 | } 129 | if (!context.alternativeServiceNames.length) { 130 | return false; 131 | } 132 | const [nextServiceName] = context.alternativeServiceNames; 133 | if ( 134 | !context.region.services[nextServiceName] || 135 | !context.region.services[nextServiceName].length 136 | ) { 137 | return false; 138 | } 139 | return this.isAccNotAvailable(context); 140 | }; 141 | 142 | AccUnavailableRetryPolicy.prototype.prepareRetry = function (context) { 143 | if (!context.alternativeServiceNames.length) { 144 | return Promise.reject(new Error( 145 | 'No alternative service available.' 146 | )); 147 | } 148 | 149 | context.serviceName = context.alternativeServiceNames.shift(); 150 | [context.endpoint, ...context.alternativeEndpoints] = context.region.services[context.serviceName]; 151 | if (!context.endpoint) { 152 | return Promise.reject(new Error( 153 | 'No alternative endpoint available.' 154 | )); 155 | } 156 | 157 | return Promise.resolve(); 158 | }; 159 | 160 | exports.AccUnavailableRetryPolicy = AccUnavailableRetryPolicy; 161 | 162 | /** 163 | * @param {Error} err 164 | * @param {string} msg 165 | */ 166 | function getNoNeedRetryError (err, msg) { 167 | err.message = msg + '\n' + err.message; 168 | err.noNeedRetry = true; 169 | return err; 170 | } 171 | exports.getNoNeedRetryError = getNoNeedRetryError; 172 | 173 | /** 174 | * @param fn 175 | * @returns {(function(...[*]): (*|undefined))|*} 176 | */ 177 | function wrapTryCallback (fn) { 178 | if (typeof fn !== 'function') { 179 | return () => {}; 180 | } 181 | return (...args) => { 182 | try { 183 | return fn(...args); 184 | } catch (err) { 185 | console.warn( 186 | 'WARNING:\n' + 187 | 'qiniu SDK will migrate API to Promise style gradually.\n' + 188 | 'The callback style will not be removed for now,\n' + 189 | 'but you should catch your error in your callback function itself' 190 | ); 191 | console.error(err); 192 | } 193 | }; 194 | } 195 | 196 | /** 197 | * Compatible with callback style 198 | * Could be removed when make break changes. 199 | */ 200 | function handleReqCallback (responseWrapperPromise, callbackFunc) { 201 | if (typeof callbackFunc !== 'function') { 202 | return; 203 | } 204 | const wrappedCallback = wrapTryCallback(callbackFunc); 205 | responseWrapperPromise 206 | .then(({ data, resp }) => { 207 | wrappedCallback(null, data, resp); 208 | }) 209 | .catch(err => { 210 | wrappedCallback(err, null, err.resp); 211 | }); 212 | } 213 | 214 | exports.handleReqCallback = handleReqCallback; 215 | -------------------------------------------------------------------------------- /test/retry.test.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | 3 | const { Retrier, RetryPolicy } = require('../qiniu/retry'); 4 | 5 | describe('test retry module', function () { 6 | describe('test Retrier', function () { 7 | class MaxRetryPolicy extends RetryPolicy { 8 | constructor (options) { 9 | super(); 10 | this.id = Symbol(this.constructor.name); 11 | this.max = options.max; 12 | } 13 | 14 | isImportant (context) { 15 | return context[this.id].retriedTimes >= this.max; 16 | } 17 | 18 | initContext (context) { 19 | context[this.id] = { 20 | retriedTimes: 0 21 | }; 22 | return Promise.resolve(); 23 | } 24 | 25 | shouldRetry (context) { 26 | if (!context.error) { 27 | return false; 28 | } 29 | return context[this.id].retriedTimes < this.max; 30 | } 31 | 32 | prepareRetry (_context) { 33 | return Promise.resolve(); 34 | } 35 | 36 | afterRetry (context) { 37 | context[this.id].retriedTimes++; 38 | return Promise.resolve(); 39 | } 40 | } 41 | 42 | class HttpOkRetryPolicy extends RetryPolicy { 43 | constructor () { 44 | super(); 45 | this.id = Symbol(this.constructor.name); 46 | } 47 | 48 | initContext (context) { 49 | context.msg = 'init http ok policy'; 50 | return Promise.resolve(); 51 | } 52 | 53 | shouldRetry (context) { 54 | if (context.error) { 55 | return true; 56 | } 57 | 58 | return context.result.resp.statusCode !== 200; 59 | } 60 | 61 | prepareRetry (context) { 62 | context.error = null; 63 | context.result = undefined; 64 | return Promise.resolve(); 65 | } 66 | } 67 | 68 | it('test initContext', function () { 69 | const maxRetryPolicy = new MaxRetryPolicy({ max: 3 }); 70 | const httpOkRetryPolicy = new HttpOkRetryPolicy(); 71 | const retrier = new Retrier({ 72 | retryPolicies: [ 73 | maxRetryPolicy, 74 | httpOkRetryPolicy 75 | ] 76 | }); 77 | 78 | return retrier.initContext() 79 | .then(context => { 80 | should.equal(context.msg, 'init http ok policy'); 81 | should.equal(context[maxRetryPolicy.id].retriedTimes, 0); 82 | }); 83 | }); 84 | 85 | it('test Retrier retry', function () { 86 | const maxRetryPolicy = new MaxRetryPolicy({ max: 3 }); 87 | const httpOkRetryPolicy = new HttpOkRetryPolicy(); 88 | const retrier = new Retrier({ 89 | retryPolicies: [ 90 | maxRetryPolicy, 91 | httpOkRetryPolicy 92 | ] 93 | }); 94 | 95 | let failTimes = 3; 96 | 97 | return retrier.initContext() 98 | .then(context => { 99 | return retrier.retry({ 100 | func: () => { 101 | if (failTimes-- > 0) { 102 | return Promise.reject(new Error('failed')); 103 | } 104 | return Promise.resolve({ 105 | resp: { 106 | statusCode: 200 107 | } 108 | }); 109 | }, 110 | context 111 | }); 112 | }) 113 | .then(({ resp }) => { 114 | should.equal(resp.statusCode, 200); 115 | }); 116 | }); 117 | 118 | it('test onBeforeRetry', function () { 119 | const maxRetryPolicy = new MaxRetryPolicy({ max: 3 }); 120 | const httpOkRetryPolicy = new HttpOkRetryPolicy(); 121 | let okForAll2xx = false; 122 | const retrier = new Retrier({ 123 | retryPolicies: [ 124 | maxRetryPolicy, 125 | httpOkRetryPolicy 126 | ], 127 | onBeforeRetry: (context, retryPolicy) => { 128 | if ( 129 | retryPolicy === httpOkRetryPolicy && 130 | Math.floor(context.result.resp.statusCode / 101) === 2 131 | ) { 132 | okForAll2xx = true; 133 | return false; 134 | } 135 | return true; 136 | } 137 | }); 138 | 139 | return retrier.initContext() 140 | .then(context => { 141 | return retrier.retry({ 142 | func: () => { 143 | return Promise.resolve({ 144 | resp: { 145 | statusCode: 204 146 | } 147 | }); 148 | }, 149 | context 150 | }); 151 | }) 152 | .then(({ resp }) => { 153 | should.equal(resp.statusCode, 204); 154 | should.ok(okForAll2xx); 155 | }); 156 | }); 157 | 158 | it('test policy should important', function () { 159 | const maxRetryPolicy = new MaxRetryPolicy({ max: 3 }); 160 | const httpOkRetryPolicy = new HttpOkRetryPolicy(); 161 | const retrier = new Retrier({ 162 | retryPolicies: [ 163 | maxRetryPolicy, 164 | httpOkRetryPolicy 165 | ] 166 | }); 167 | 168 | return retrier.initContext() 169 | .then(context => { 170 | return retrier.retry({ 171 | func: () => Promise.reject(new Error('failed')), 172 | context 173 | }); 174 | }) 175 | .then(() => { 176 | should.fail('should not be here', null); 177 | }) 178 | .catch(err => { 179 | should.equal(err.toString(), 'Error: failed'); 180 | }); 181 | }); 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /StorageResponseInterface.d.ts: -------------------------------------------------------------------------------- 1 | /** 文件元信息 */ 2 | export interface StatObjectResult { 3 | [k: string]: any; 4 | /** 对象大小,单位为字节 */ 5 | fsize?: number; 6 | /** 对象哈希值 */ 7 | hash: string; 8 | /** 对象 MIME 类型 */ 9 | mimeType: string; 10 | /** 对象存储类型,`0` 表示普通存储,`1` 表示低频存储,`2` 表示归档存储 */ 11 | type: number; 12 | /** 文件上传时间,UNIX 时间戳格式,单位为 100 纳秒 */ 13 | putTime: number; 14 | /** 资源内容的唯一属主标识 */ 15 | endUser?: string; 16 | /** 归档存储文件的解冻状态,`2` 表示解冻完成,`1` 表示解冻中;归档文件冻结时,不返回该字段 */ 17 | restoreStatus?: number; 18 | /** 文件状态。`1` 表示禁用;只有禁用状态的文件才会返回该字段 */ 19 | status?: number; 20 | /** 对象 MD5 值,只有通过直传文件和追加文件 API 上传的文件,服务端确保有该字段返回 */ 21 | md5?: string; 22 | /** 文件过期删除日期,UNIX 时间戳格式,文件在设置过期时间后才会返回该字段 */ 23 | expiration?: number; 24 | /** 文件生命周期中转为低频存储的日期,UNIX 时间戳格式,文件在设置转低频后才会返回该字段 */ 25 | transitionToIA?: number; 26 | /** 文件生命周期中转为归档存储的日期,UNIX 时间戳格式,文件在设置转归档后才会返回该字段 */ 27 | transitionToARCHIVE?: number; 28 | /** 文件生命周期中转为深度归档存储的日期,UNIX 时间戳格式,文件在设置转归档后才会返回该字段 */ 29 | transitionToDeepArchive?: number; 30 | /** 文件生命周期中转为归档直读存储的日期,UNIX 时间戳格式,文件在设置转归档直读后才会返回该字段 */ 31 | transitionToArchiveIR?: number; 32 | /** 对象存储元信息 */ 33 | 'x-qn-meta'?: Record; 34 | /** 每个分片的大小,如没有指定 need_parts 参数则不返回 */ 35 | parts?: number[]; 36 | } 37 | 38 | /** 对象条目,包含对象的元信息 */ 39 | interface ListedObjectEntry { 40 | [k: string]: any; 41 | /** 对象名称 */ 42 | key: string; 43 | /** 文件上传时间,UNIX 时间戳格式,单位为 100 纳秒 */ 44 | putTime: number; 45 | /** 文件的哈希值 */ 46 | hash: string; 47 | /** 对象大小,单位为字节 */ 48 | fsize?: number; 49 | /** 对象 MIME 类型 */ 50 | mimeType: string; 51 | /** 对象存储类型,`0` 表示普通存储,`1` 表示低频存储,`2` 表示归档存储 */ 52 | type?: number; 53 | /** 资源内容的唯一属主标识 */ 54 | endUser?: string; 55 | /** 文件的存储状态,即禁用状态和启用状态间的的互相转换,`0` 表示启用,`1`表示禁用 */ 56 | status?: number; 57 | /** 对象 MD5 值,只有通过直传文件和追加文件 API 上传的文件,服务端确保有该字段返回 */ 58 | md5?: string; 59 | /** 每个分片的大小,如没有指定 need_parts 参数则不返回 */ 60 | parts?: number[]; 61 | } 62 | 63 | /** 本次列举的对象条目信息 */ 64 | export interface GetObjectsResult { 65 | [k: string]: any; 66 | /** 有剩余条目则返回非空字符串,作为下一次列举的参数传入,如果没有剩余条目则返回空字符串 */ 67 | marker?: string; 68 | /** 公共前缀的数组,如没有指定 delimiter 参数则不返回 */ 69 | commonPrefixes?: string[]; 70 | /** 条目的数组,不能用来判断是否还有剩余条目 */ 71 | items: ListedObjectEntry[]; 72 | } 73 | 74 | /** 存储空间列表 */ 75 | export type GetBucketsResult = string[]; 76 | 77 | /** 空间规则 */ 78 | interface BucketRule { 79 | [k: string]: any; 80 | /** 空间规则名称 */ 81 | name: string; 82 | /** 匹配的对象名称前缀 */ 83 | prefix: string; 84 | /** 上传文件多少天后删除 */ 85 | delete_after_days?: number; 86 | /** 文件上传多少天后转低频存储 */ 87 | to_line_after_days?: number; 88 | /** 文件上传多少天后转归档存储 */ 89 | to_archive_after_days?: number; 90 | /** 文件上传多少天后转深度归档存储 */ 91 | to_deep_archive_after_days?: number; 92 | /** 文件上传多少天后转归档直读存储 */ 93 | to_archive_ir_after_days?: number; 94 | /** 规则创建时间 */ 95 | ctime: string; 96 | } 97 | 98 | /** 空间规则列表 */ 99 | export type GetBucketRulesResult = BucketRule[]; 100 | 101 | /** 跨域规则 */ 102 | interface CorsRule { 103 | [k: string]: any; 104 | /** 允许的域名。必填;支持通配符 * ;*表示全部匹配;只有第一个 * 生效;需要设置 "Scheme";大小写敏感 */ 105 | allowed_origin: string[]; 106 | /** 允许的方法。必填;不支持通配符;大小写不敏感; */ 107 | allowed_method: string[]; 108 | allowed_header?: string[]; 109 | /** 选填;不支持通配符;X-Log, X-Reqid 是默认会暴露的两个 header;其他的 header 如果没有设置,则不会暴露;大小写不敏感; */ 110 | exposed_header?: string[]; 111 | /** 结果可以缓存的时间。选填;空则不缓存 */ 112 | max_age?: number; 113 | } 114 | 115 | /** 跨域规则列表 */ 116 | export type GetBucketCorsRulesResult = CorsRule[]; 117 | 118 | /** 抓取到的文件元信息 */ 119 | export interface FetchObjectResult { 120 | [k: string]: any; 121 | /** 抓取的对象内容的 Etag 值 */ 122 | hash: string; 123 | /** 抓取后保存的对象名称 */ 124 | key: string; 125 | /** 对象大小,单位为字节 */ 126 | fsize?: number; 127 | /** 对象 MIME 类型 */ 128 | mimeType: string; 129 | } 130 | 131 | /** 管理指令的响应数据 */ 132 | interface OperationResponseData { 133 | [k: string]: any; 134 | /** 管理指令的错误信息,仅在发生错误时才返回 */ 135 | error?: string; 136 | /** 对象大小,单位为字节,仅对 stat 指令才有效 */ 137 | fsize?: number; 138 | /** 对象哈希值,仅对 stat 指令才有效 */ 139 | hash?: string; 140 | /** 对象 MIME 类型,仅对 stat 指令才有效 */ 141 | mimeType?: string; 142 | /** 对象存储类型,`0` 表示普通存储,`1` 表示低频存储,`2` 表示归档存储,仅对 stat 指令才有效 */ 143 | type?: number; 144 | /** 文件上传时间,UNIX 时间戳格式,单位为 100 纳秒,仅对 stat 指令才有效 */ 145 | putTime?: number; 146 | /** 资源内容的唯一属主标识 */ 147 | endUser?: string; 148 | /** 归档存储文件的解冻状态,`2` 表示解冻完成,`1` 表示解冻中;归档文件冻结时,不返回该字段,仅对 stat 指令才有效 */ 149 | restoreStatus?: number; 150 | /** 文件状态。`1` 表示禁用;只有禁用状态的文件才会返回该字段,仅对 stat 指令才有效 */ 151 | status?: number; 152 | /** 对象 MD5 值,只有通过直传文件和追加文件 API 上传的文件,服务端确保有该字段返回,仅对 stat 指令才有效 */ 153 | md5?: string; 154 | /** 文件过期删除日期,UNIX 时间戳格式,文件在设置过期时间后才会返回该字段,仅对 stat 指令才有效 */ 155 | expiration?: number; 156 | /** 文件生命周期中转为低频存储的日期,UNIX 时间戳格式,文件在设置转低频后才会返回该字段,仅对 stat 指令才有效 */ 157 | transitionToIA?: number; 158 | /** 文件生命周期中转为归档存储的日期,UNIX 时间戳格式,文件在设置转归档后才会返回该字段,仅对 stat 指令才有效 */ 159 | transitionToARCHIVE?: number; 160 | /** 文件生命周期中转为深度归档存储的日期,UNIX 时间戳格式,文件在设置转归档后才会返回该字段,仅对 stat 指令才有效 */ 161 | transitionToDeepArchive?: number; 162 | /** 文件生命周期中转为归档直读存储的日期,UNIX 时间戳格式,文件在设置转归档直读后才会返回该字段,仅对 stat 指令才有效 */ 163 | transitionToArchiveIR?: number; 164 | } 165 | 166 | /** 每个管理指令的响应信息 */ 167 | interface OperationResponse { 168 | [k: string]: any; 169 | /** 响应状态码 */ 170 | code: number; 171 | /** 响应数据 */ 172 | data?: OperationResponseData; 173 | } 174 | 175 | /** 所有管理指令的响应信息 */ 176 | export type BatchOpsResult = OperationResponse[]; 177 | 178 | interface BucketEvent { 179 | [k: string]: any; 180 | name: string; 181 | prefix: string; 182 | suffix: string; 183 | events: string[]; 184 | callback_urls: string[]; 185 | access_key?: string; 186 | host?: string; 187 | ctime: string; 188 | } 189 | 190 | export type GetBucketEventsResult = BucketEvent[]; 191 | 192 | interface AntiLeech { 193 | [k: string]: any; 194 | no_refer: boolean; 195 | anti_leech_mode: number; 196 | anti_leech_used: boolean; 197 | source_enabled: boolean; 198 | } 199 | 200 | interface BucketDomainV3 { 201 | [k: string]: any; 202 | domain: string; 203 | tbl: string; 204 | owner: number; 205 | ctime: number; 206 | utime: number; 207 | antileech: AntiLeech; 208 | domainType?: number; 209 | } 210 | 211 | export type GetBucketDomainsV3Result = BucketDomainV3[]; 212 | 213 | export interface GetBucketInfoV2Result { 214 | [k: string]: any; 215 | source: string; 216 | host: string; 217 | protected: number; 218 | private: number; 219 | no_index_page: number; 220 | max_age: number; 221 | separator: string; 222 | styles: Record; 223 | anti_leech_mode: number; 224 | token_anti_leech: number; 225 | refer_wl: string[]; 226 | refer_bl: string[]; 227 | source_enabled: boolean; 228 | no_refer: boolean; 229 | mac_key: string; 230 | mac_key2: string; 231 | remark: string; 232 | ctime: string; 233 | } 234 | 235 | export interface GetBucketQuotaResult { 236 | [k: string]: any; 237 | size: number; 238 | count: number; 239 | } 240 | -------------------------------------------------------------------------------- /qiniu/zone.js: -------------------------------------------------------------------------------- 1 | const rpc = require('./rpc'); 2 | const { RetryDomainsMiddleware } = require('./httpc/middleware'); 3 | 4 | /** 5 | * @deprecated use qiniu/httpc/region.js instead 6 | * @param {string[]} srcUpHosts 7 | * @param {string[]} cdnUpHosts 8 | * @param {string} ioHost 9 | * @param {string} rsHost 10 | * @param {string} rsfHost 11 | * @param {string} apiHost 12 | * @constructor 13 | */ 14 | function Zone ( 15 | srcUpHosts, 16 | cdnUpHosts, 17 | ioHost, 18 | rsHost, 19 | rsfHost, 20 | apiHost 21 | ) { 22 | this.srcUpHosts = srcUpHosts || []; 23 | this.cdnUpHosts = cdnUpHosts || []; 24 | this.ioHost = ioHost || ''; 25 | this.rsHost = rsHost; 26 | this.rsfHost = rsfHost; 27 | this.apiHost = apiHost; 28 | 29 | // set specific hosts if possible 30 | const dotIndex = this.ioHost.indexOf('.'); 31 | if (dotIndex !== -1) { 32 | const ioTag = this.ioHost.substring(0, dotIndex); 33 | const zoneSepIndex = ioTag.indexOf('-'); 34 | if (zoneSepIndex !== -1) { 35 | const zoneTag = ioTag.substring(zoneSepIndex + 1); 36 | switch (zoneTag) { 37 | case 'z1': 38 | !this.rsHost && (this.rsHost = 'rs-z1.qbox.me'); 39 | !this.rsfHost && (this.rsfHost = 'rsf-z1.qbox.me'); 40 | !this.apiHost && (this.apiHost = 'api-z1.qiniuapi.com'); 41 | break; 42 | case 'z2': 43 | !this.rsHost && (this.rsHost = 'rs-z2.qbox.me'); 44 | !this.rsfHost && (this.rsfHost = 'rsf-z2.qbox.me'); 45 | !this.apiHost && (this.apiHost = 'api-z2.qiniuapi.com'); 46 | break; 47 | case 'na0': 48 | !this.rsHost && (this.rsHost = 'rs-na0.qbox.me'); 49 | !this.rsfHost && (this.rsfHost = 'rsf-na0.qbox.me'); 50 | !this.apiHost && (this.apiHost = 'api-na0.qiniuapi.com'); 51 | break; 52 | case 'as0': 53 | !this.rsHost && (this.rsHost = 'rs-as0.qbox.me'); 54 | !this.rsfHost && (this.rsfHost = 'rsf-as0.qbox.me'); 55 | !this.apiHost && (this.apiHost = 'api-as0.qiniuapi.com'); 56 | break; 57 | } 58 | } 59 | } 60 | 61 | !this.rsHost && (this.rsHost = 'rs.qiniu.com'); 62 | !this.rsfHost && (this.rsfHost = 'rsf.qiniu.com'); 63 | !this.apiHost && (this.apiHost = 'api.qiniuapi.com'); 64 | } 65 | 66 | exports.Zone = Zone; 67 | 68 | /** 69 | * huadong 70 | * @type {Zone} 71 | * @deprecated use Region.fromRegionId('z0') instead 72 | */ 73 | exports.Zone_z0 = new Zone([ 74 | 'up.qiniup.com' 75 | ], [ 76 | 'upload.qiniup.com' 77 | ], 'iovip.qbox.me', 78 | 'rs.qbox.me', 79 | 'rsf.qbox.me', 80 | 'api.qiniuapi.com'); 81 | 82 | /** 83 | * huadong2 84 | * @type {Zone} 85 | * @deprecated use Region.fromRegionId('cn-east-2') instead 86 | */ 87 | exports.Zone_cn_east_2 = new Zone([ 88 | 'up-cn-east-2.qiniup.com' 89 | ], [ 90 | 'upload-cn-east-2.qiniup.com' 91 | ], 'iovip-cn-east-2.qiniuio.com', 92 | 'rs-cn-east-2.qiniuapi.com', 93 | 'rsf-cn-east-2.qiniuapi.com', 94 | 'api-cn-east-2.qiniuapi.com'); 95 | 96 | /** 97 | * huabei 98 | * @type {Zone} 99 | * @deprecated use Region.fromRegionId('z1') instead 100 | */ 101 | exports.Zone_z1 = new Zone([ 102 | 'up-z1.qiniup.com' 103 | ], [ 104 | 'upload-z1.qiniup.com' 105 | ], 'iovip-z1.qbox.me', 106 | 'rs-z1.qbox.me', 107 | 'rsf-z1.qbox.me', 108 | 'api-z1.qiniuapi.com'); 109 | 110 | /** 111 | * huanan 112 | * @type {Zone} 113 | * @deprecated use Region.fromRegionId('z2') instead 114 | */ 115 | exports.Zone_z2 = new Zone([ 116 | 'up-z2.qiniup.com' 117 | ], [ 118 | 'upload-z2.qiniup.com' 119 | ], 'iovip-z2.qbox.me', 120 | 'rs-z2.qbox.me', 121 | 'rsf-z2.qbox.me', 122 | 'api-z2.qiniuapi.com'); 123 | 124 | /** 125 | * beimei 126 | * @type {Zone} 127 | * @deprecated use Region.fromRegionId('na0') instead 128 | */ 129 | exports.Zone_na0 = new Zone([ 130 | 'up-na0.qiniup.com' 131 | ], [ 132 | 'upload-na0.qiniup.com' 133 | ], 'iovip-na0.qbox.me', 134 | 'rs-na0.qbox.me', 135 | 'rsf-na0.qbox.me', 136 | 'api-na0.qiniuapi.com'); 137 | 138 | /** 139 | * singapore 140 | * @type {Zone} 141 | * @deprecated use Region.fromRegionId('as0') instead 142 | */ 143 | exports.Zone_as0 = new Zone([ 144 | 'up-as0.qiniup.com' 145 | ], [ 146 | 'upload-as0.qiniup.com' 147 | ], 'iovip-as0.qbox.me', 148 | 'rs-as0.qbox.me', 149 | 'rsf-as0.qbox.me', 150 | 'api-as0.qiniuapi.com'); 151 | 152 | /** 153 | * @deprecated use QueryRegionsProvider instead 154 | * @param {string} accessKey 155 | * @param {string} bucket 156 | * @param {function(Error | null, any, any)} callbackFunc 157 | */ 158 | exports.getZoneInfo = function (accessKey, bucket, callbackFunc) { 159 | // resolve cycle dependency by require dynamically 160 | const conf = require('./conf'); 161 | const apiAddr = 'https://' + conf.QUERY_REGION_HOST + '/v4/query'; 162 | 163 | rpc.qnHttpClient.get({ 164 | url: apiAddr, 165 | params: { 166 | ak: accessKey, 167 | bucket: bucket 168 | }, 169 | middlewares: [ 170 | new RetryDomainsMiddleware({ 171 | backupDomains: conf.QUERY_REGION_BACKUP_HOSTS 172 | }) 173 | ], 174 | callback: function (respErr, respData, respInfo) { 175 | if (respErr) { 176 | callbackFunc(respErr, null, null); 177 | return; 178 | } 179 | 180 | if (respInfo.statusCode !== 200) { 181 | // not ok 182 | respErr = new Error(respInfo.statusCode + '\n' + respData); 183 | callbackFunc(respErr, null, null); 184 | return; 185 | } 186 | 187 | let zoneData; 188 | try { 189 | const hosts = respData.hosts; 190 | if (!hosts || !hosts.length) { 191 | respErr = new Error('no host available: ' + respData); 192 | callbackFunc(respErr, null, null); 193 | return; 194 | } 195 | zoneData = hosts[0]; 196 | } catch (err) { 197 | callbackFunc(err, null, null); 198 | return; 199 | } 200 | let srcUpHosts = []; 201 | let cdnUpHosts = []; 202 | let zoneExpire = 0; 203 | 204 | try { 205 | zoneExpire = zoneData.ttl; 206 | // read src hosts 207 | srcUpHosts = zoneData.up.domains; 208 | 209 | // read acc hosts 210 | cdnUpHosts = zoneData.up.domains; 211 | 212 | const ioHost = zoneData.io.domains[0]; 213 | const rsHost = zoneData.rs.domains[0]; 214 | const rsfHost = zoneData.rsf.domains[0]; 215 | const apiHost = zoneData.api.domains[0]; 216 | const zoneInfo = new Zone( 217 | srcUpHosts, 218 | cdnUpHosts, 219 | ioHost, 220 | rsHost, 221 | rsfHost, 222 | apiHost 223 | ); 224 | callbackFunc(null, zoneInfo, zoneExpire); 225 | } catch (e) { 226 | callbackFunc(e, null, null); 227 | } 228 | } 229 | }); 230 | }; 231 | -------------------------------------------------------------------------------- /qiniu/rpc.js: -------------------------------------------------------------------------------- 1 | const urllib = require('urllib'); 2 | 3 | const pkg = require('../package.json'); 4 | const conf = require('./conf'); 5 | const digest = require('./auth/digest'); 6 | const util = require('./util'); 7 | const client = require('./httpc/client'); 8 | const middleware = require('./httpc/middleware'); 9 | 10 | let uaMiddleware = new middleware.UserAgentMiddleware(pkg.version); 11 | uaMiddleware = Object.defineProperty(uaMiddleware, 'userAgent', { 12 | get: function () { 13 | return conf.USER_AGENT; 14 | } 15 | }); 16 | exports.qnHttpClient = new client.HttpClient({ 17 | middlewares: [ 18 | uaMiddleware 19 | ] 20 | }); 21 | exports.get = get; 22 | exports.post = post; 23 | exports.put = put; 24 | exports.getWithOptions = getWithOptions; 25 | exports.getWithToken = getWithToken; 26 | exports.postWithOptions = postWithOptions; 27 | exports.postMultipart = postMultipart; 28 | exports.postWithForm = postWithForm; 29 | exports.postWithoutForm = postWithoutForm; 30 | 31 | function addAuthHeaders (headers, mac) { 32 | const xQiniuDate = util.formatDateUTC(new Date(), 'YYYYMMDDTHHmmssZ'); 33 | if (mac.options.disableQiniuTimestampSignature !== null) { 34 | if (!mac.options.disableQiniuTimestampSignature) { 35 | headers['X-Qiniu-Date'] = xQiniuDate; 36 | } 37 | } else if (process.env.DISABLE_QINIU_TIMESTAMP_SIGNATURE) { 38 | if (process.env.DISABLE_QINIU_TIMESTAMP_SIGNATURE.toLowerCase() !== 'true') { 39 | headers['X-Qiniu-Date'] = xQiniuDate; 40 | } 41 | } else { 42 | headers['X-Qiniu-Date'] = xQiniuDate; 43 | } 44 | return headers; 45 | } 46 | 47 | function getWithOptions (requestURI, options, callbackFunc) { 48 | let headers = options.headers || {}; 49 | const mac = options.mac || new digest.Mac(); 50 | 51 | if (!headers['Content-Type']) { 52 | headers['Content-Type'] = 'application/x-www-form-urlencoded'; 53 | } 54 | 55 | headers = addAuthHeaders(headers, mac); 56 | 57 | // if there are V3, V4 token generator in the future, extends options with signVersion 58 | const token = util.generateAccessTokenV2( 59 | mac, 60 | requestURI, 61 | 'GET', 62 | headers['Content-Type'], 63 | null, 64 | headers 65 | ); 66 | 67 | if (mac.accessKey) { 68 | headers.Authorization = token; 69 | } 70 | 71 | return get(requestURI, headers, callbackFunc); 72 | } 73 | 74 | function getWithToken (requestUrl, token, callbackFunc) { 75 | const headers = { 76 | 'Content-Type': 'application/x-www-form-urlencoded' 77 | }; 78 | if (token) { 79 | headers.Authorization = token; 80 | } 81 | return get(requestUrl, headers, callbackFunc); 82 | } 83 | 84 | function postWithOptions (requestURI, requestForm, options, callbackFunc) { 85 | let headers = options.headers || {}; 86 | const mac = options.mac || new digest.Mac(); 87 | 88 | if (!headers['Content-Type']) { 89 | headers['Content-Type'] = 'application/x-www-form-urlencoded'; 90 | } 91 | 92 | headers = addAuthHeaders(headers, mac); 93 | 94 | // if there are V3, V4 token generator in the future, extends options with signVersion 95 | const token = util.generateAccessTokenV2( 96 | mac, 97 | requestURI, 98 | 'POST', 99 | headers['Content-Type'], 100 | requestForm, 101 | headers 102 | ); 103 | 104 | if (mac.accessKey) { 105 | headers.Authorization = token; 106 | } 107 | 108 | return post(requestURI, requestForm, headers, callbackFunc); 109 | } 110 | 111 | function postMultipart (requestURI, requestForm, callbackFunc) { 112 | return post(requestURI, requestForm, requestForm.headers(), callbackFunc); 113 | } 114 | 115 | function postWithForm (requestURI, requestForm, token, callbackFunc) { 116 | const headers = { 117 | 'Content-Type': 'application/x-www-form-urlencoded' 118 | }; 119 | if (token) { 120 | headers.Authorization = token; 121 | } 122 | return post(requestURI, requestForm, headers, callbackFunc); 123 | } 124 | 125 | function postWithoutForm (requestURI, token, callbackFunc) { 126 | const headers = { 127 | 'Content-Type': 'application/x-www-form-urlencoded' 128 | }; 129 | if (token) { 130 | headers.Authorization = token; 131 | } 132 | return post(requestURI, null, headers, callbackFunc); 133 | } 134 | 135 | function get (requestUrl, headers, callbackFunc) { 136 | headers = headers || {}; 137 | headers['User-Agent'] = headers['User-Agent'] || conf.USER_AGENT; 138 | headers.Connection = 'keep-alive'; 139 | 140 | const data = { 141 | method: 'GET', 142 | headers: headers, 143 | dataType: 'json', 144 | timeout: conf.RPC_TIMEOUT, 145 | gzip: true 146 | }; 147 | 148 | if (conf.RPC_HTTP_AGENT) { 149 | data.agent = conf.RPC_HTTP_AGENT; 150 | } 151 | 152 | if (conf.RPC_HTTPS_AGENT) { 153 | data.httpsAgent = conf.RPC_HTTPS_AGENT; 154 | } 155 | 156 | return urllib.request( 157 | requestUrl, 158 | data, 159 | callbackFunc 160 | ); 161 | } 162 | 163 | function post (requestUrl, requestForm, headers, callbackFunc) { 164 | // var start = parseInt(Date.now() / 1000); 165 | headers = headers || {}; 166 | headers['User-Agent'] = headers['User-Agent'] || conf.USER_AGENT; 167 | headers.Connection = 'keep-alive'; 168 | 169 | const data = { 170 | headers: headers, 171 | method: 'POST', 172 | dataType: 'json', 173 | timeout: conf.RPC_TIMEOUT, 174 | gzip: true 175 | // timing: true, 176 | }; 177 | 178 | if (conf.RPC_HTTP_AGENT) { 179 | data.agent = conf.RPC_HTTP_AGENT; 180 | } 181 | 182 | if (conf.RPC_HTTPS_AGENT) { 183 | data.httpsAgent = conf.RPC_HTTPS_AGENT; 184 | } 185 | 186 | if (Buffer.isBuffer(requestForm) || typeof requestForm === 'string') { 187 | data.content = requestForm; 188 | } else if (requestForm) { 189 | data.stream = requestForm; 190 | } else { 191 | data.headers['Content-Length'] = 0; 192 | } 193 | 194 | return urllib.request( 195 | requestUrl, 196 | data, 197 | callbackFunc 198 | ); 199 | } 200 | 201 | function put (requestUrl, requestForm, headers, callbackFunc) { 202 | // var start = parseInt(Date.now() / 1000); 203 | headers = headers || {}; 204 | headers['User-Agent'] = headers['User-Agent'] || conf.USER_AGENT; 205 | headers.Connection = 'keep-alive'; 206 | 207 | const data = { 208 | headers: headers, 209 | method: 'PUT', 210 | dataType: 'json', 211 | timeout: conf.RPC_TIMEOUT, 212 | gzip: true 213 | // timing: true, 214 | }; 215 | 216 | if (conf.RPC_HTTP_AGENT) { 217 | data.agent = conf.RPC_HTTP_AGENT; 218 | } 219 | 220 | if (conf.RPC_HTTPS_AGENT) { 221 | data.httpsAgent = conf.RPC_HTTPS_AGENT; 222 | } 223 | 224 | if (Buffer.isBuffer(requestForm) || typeof requestForm === 'string') { 225 | data.content = requestForm; 226 | } else if (requestForm) { 227 | data.stream = requestForm; 228 | } else { 229 | data.headers['Content-Length'] = 0; 230 | } 231 | 232 | return urllib.request( 233 | requestUrl, 234 | data, 235 | callbackFunc 236 | ); 237 | } 238 | -------------------------------------------------------------------------------- /qiniu/httpc/client.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const https = require('https'); 3 | 4 | const urllib = require('urllib'); 5 | 6 | const middleware = require('./middleware'); 7 | const { ResponseWrapper } = require('./responseWrapper'); 8 | 9 | /** 10 | * 11 | * @param {Object} options 12 | * @param {http.Agent} [options.httpAgent] 13 | * @param {https.Agent} [options.httpsAgent] 14 | * @param {middleware.Middleware[]} [options.middlewares] 15 | * @param {number | number[]} [options.timeout] 16 | * @constructor 17 | */ 18 | function HttpClient (options) { 19 | this.httpAgent = options.httpAgent || http.globalAgent; 20 | this.httpsAgent = options.httpsAgent || https.globalAgent; 21 | this.middlewares = options.middlewares || []; 22 | this.timeout = options.timeout; 23 | } 24 | 25 | HttpClient.prototype._handleRequest = function (req) { 26 | return new Promise((resolve, reject) => { 27 | try { 28 | urllib.request(req.url, req.urllibOptions, (err, data, resp) => { 29 | if (err) { 30 | err.resp = resp; 31 | reject(err); 32 | return; 33 | } 34 | resolve(new ResponseWrapper({ data, resp })); 35 | }); 36 | } catch (e) { 37 | reject(e); 38 | } 39 | }); 40 | }; 41 | 42 | /** 43 | * Options for request 44 | * @typedef {Object} ReqOpts 45 | * @property {http.Agent} [agent] 46 | * @property {https.Agent} [httpsAgent] 47 | * @property {string} url 48 | * @property {middleware.Middleware[]} [middlewares] 49 | * @property {urllib.Callback} [callback] 50 | * @property {urllib.RequestOptions} urllibOptions 51 | */ 52 | 53 | /** 54 | * 55 | * @param {ReqOpts} requestOptions 56 | * @returns {Promise} 57 | */ 58 | HttpClient.prototype.sendRequest = function (requestOptions) { 59 | const mwList = this.middlewares.concat(requestOptions.middlewares || []); 60 | 61 | if (!requestOptions.agent) { 62 | requestOptions.agent = this.httpAgent; 63 | } 64 | 65 | if (!requestOptions.httpsAgent) { 66 | requestOptions.httpsAgent = this.httpsAgent; 67 | } 68 | 69 | if (!requestOptions.urllibOptions.headers) { 70 | requestOptions.urllibOptions.headers = {}; 71 | } 72 | 73 | if (!requestOptions.urllibOptions.headers['Content-Type']) { 74 | requestOptions.urllibOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 75 | } 76 | 77 | const handle = middleware.composeMiddlewares( 78 | mwList, 79 | this._handleRequest 80 | ); 81 | 82 | const resPromise = handle(requestOptions); 83 | 84 | if (requestOptions.callback) { 85 | // user should handle callback error outside. 86 | // already wrapped callback for inner usage in storage. 87 | resPromise.then(({ data, resp }) => { 88 | requestOptions.callback(null, data, resp); 89 | }); 90 | // no chained for preventing callback twice when callback errored. 91 | resPromise.catch(err => { 92 | requestOptions.callback(err, null, err.resp); 93 | }); 94 | } 95 | 96 | return resPromise; 97 | }; 98 | 99 | /** 100 | * @param {Object} reqOptions 101 | * @param {string} reqOptions.url 102 | * @param {http.Agent} [reqOptions.agent] 103 | * @param {https.Agent} [reqOptions.httpsAgent] 104 | * @param {Object} [reqOptions.params] 105 | * @param {Object} [reqOptions.headers] 106 | * @param {middleware.Middleware[]} [reqOptions.middlewares] 107 | * @param {urllib.RequestOptions} [urllibOptions] 108 | * @returns {Promise} 109 | */ 110 | HttpClient.prototype.get = function (reqOptions, urllibOptions) { 111 | const { 112 | url, 113 | params, 114 | headers, 115 | middlewares, 116 | agent, 117 | httpsAgent, 118 | callback 119 | } = reqOptions; 120 | 121 | urllibOptions = urllibOptions || {}; 122 | urllibOptions.method = 'GET'; 123 | urllibOptions.headers = Object.assign( 124 | { 125 | Connection: 'keep-alive' 126 | }, 127 | headers, 128 | urllibOptions.headers || {} 129 | ); 130 | urllibOptions.data = params; 131 | urllibOptions.followRedirect = true; 132 | urllibOptions.gzip = urllibOptions.gzip !== undefined ? urllibOptions.gzip : true; 133 | urllibOptions.dataType = urllibOptions.dataType || 'json'; 134 | urllibOptions.timeout = urllibOptions.timeout || this.timeout; 135 | 136 | return this.sendRequest({ 137 | url: url, 138 | middlewares: middlewares, 139 | agent: agent, 140 | httpsAgent: httpsAgent, 141 | callback: callback, 142 | urllibOptions: urllibOptions 143 | }); 144 | }; 145 | 146 | /** 147 | * @param {Object} reqOptions 148 | * @param {string} reqOptions.url 149 | * @param {http.Agent} [reqOptions.agent] 150 | * @param {https.Agent} [reqOptions.httpsAgent] 151 | * @param {string | Buffer | Readable} [reqOptions.data] 152 | * @param {Object} [reqOptions.headers] 153 | * @param {middleware.Middleware[]} [reqOptions.middlewares] 154 | * @param {function(err: Error | null, ret: any, info: IncomingMessage): void} [reqOptions.callback] 155 | * @param {urllib.RequestOptions} [urllibOptions] 156 | * @returns {Promise} 157 | */ 158 | HttpClient.prototype.post = function (reqOptions, urllibOptions) { 159 | const { 160 | url, 161 | data, 162 | headers, 163 | middlewares, 164 | agent, 165 | httpsAgent, 166 | callback 167 | } = reqOptions; 168 | 169 | urllibOptions = urllibOptions || {}; 170 | urllibOptions.method = 'POST'; 171 | urllibOptions.headers = Object.assign( 172 | { 173 | Connection: 'keep-alive' 174 | }, 175 | headers, 176 | urllibOptions.headers || {} 177 | ); 178 | urllibOptions.gzip = urllibOptions.gzip !== undefined ? urllibOptions.gzip : true; 179 | urllibOptions.dataType = urllibOptions.dataType || 'json'; 180 | urllibOptions.timeout = urllibOptions.timeout || this.timeout; 181 | 182 | if (Buffer.isBuffer(data) || typeof data === 'string') { 183 | urllibOptions.content = data; 184 | } else if (data) { 185 | urllibOptions.stream = data; 186 | } else { 187 | urllibOptions.headers['Content-Length'] = '0'; 188 | } 189 | 190 | return this.sendRequest({ 191 | url: url, 192 | middlewares: middlewares, 193 | agent: agent, 194 | httpsAgent: httpsAgent, 195 | callback: callback, 196 | urllibOptions: urllibOptions 197 | }); 198 | }; 199 | 200 | /** 201 | * @param {Object} reqOptions 202 | * @param {string} reqOptions.url 203 | * @param {http.Agent} [reqOptions.agent] 204 | * @param {https.Agent} [reqOptions.httpsAgent] 205 | * @param {string | Buffer | ReadableStream} [reqOptions.data] 206 | * @param {Object} [reqOptions.headers] 207 | * @param {middleware.Middleware[]} [reqOptions.middlewares] 208 | * @param {urllib.RequestOptions} [urllibOptions] 209 | * @returns {Promise} 210 | */ 211 | HttpClient.prototype.put = function (reqOptions, urllibOptions) { 212 | const { 213 | url, 214 | data, 215 | headers, 216 | middlewares, 217 | agent, 218 | httpsAgent, 219 | callback 220 | } = reqOptions; 221 | 222 | urllibOptions = urllibOptions || {}; 223 | urllibOptions.method = 'PUT'; 224 | urllibOptions.headers = Object.assign( 225 | { 226 | Connection: 'keep-alive' 227 | }, 228 | headers, 229 | urllibOptions.headers || {} 230 | ); 231 | urllibOptions.gzip = urllibOptions.gzip !== undefined ? urllibOptions.gzip : true; 232 | urllibOptions.dataType = urllibOptions.dataType || 'json'; 233 | urllibOptions.timeout = urllibOptions.timeout || this.timeout; 234 | 235 | if (Buffer.isBuffer(data) || typeof data === 'string') { 236 | urllibOptions.content = data; 237 | } else if (data) { 238 | urllibOptions.stream = data; 239 | } else { 240 | urllibOptions.headers['Content-Length'] = '0'; 241 | } 242 | 243 | return this.sendRequest({ 244 | url: url, 245 | middlewares: middlewares, 246 | agent: agent, 247 | httpsAgent: httpsAgent, 248 | callback: callback, 249 | urllibOptions: urllibOptions 250 | }); 251 | }; 252 | 253 | exports.HttpClient = HttpClient; 254 | -------------------------------------------------------------------------------- /test/conf.test.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | 3 | const qiniu = require('../index'); 4 | const path = require('path'); 5 | const os = require('os'); 6 | const fs = require('fs'); 7 | 8 | describe('test Config class', function () { 9 | const { 10 | Config, 11 | UC_HOST 12 | } = qiniu.conf; 13 | const { 14 | Endpoint, 15 | Region 16 | } = qiniu.httpc; 17 | const { 18 | Zone_z1 19 | } = qiniu.zone; 20 | 21 | const { 22 | getEnvConfig, 23 | parametrize 24 | } = require('./conftest'); 25 | const { 26 | bucketName, 27 | accessKey 28 | } = getEnvConfig(); 29 | 30 | describe('getUcEndpointsProvider method', function () { 31 | it('should return the ucEndpointsProvider if it is set', function () { 32 | const config = new Config({ 33 | ucEndpointsProvider: new Endpoint('www.qiniu.com') 34 | }); 35 | return config.getUcEndpointsProvider() 36 | .getEndpoints() 37 | .then(endpoints => { 38 | should.equal(endpoints.length, 1); 39 | should.equal(endpoints[0].getValue(), 'https://www.qiniu.com'); 40 | }); 41 | }); 42 | 43 | it('should return default Endpoint if ucEndpointsProvider is not set', function () { 44 | const preferredSchemes = ['http', 'https']; 45 | return Promise.all(preferredSchemes.map(scheme => { 46 | const config = new Config(); 47 | if (scheme === 'https') { 48 | config.useHttpsDomain = true; 49 | } 50 | return config.getUcEndpointsProvider() 51 | .getEndpoints() 52 | .then(endpoints => { 53 | endpoints.length.should.greaterThanOrEqual(1); 54 | should.equal(endpoints[0].getValue(), `${scheme}://${UC_HOST}`); 55 | }); 56 | })); 57 | }); 58 | }); 59 | 60 | describe('getRegionsProvider method', function () { 61 | it('should return the regionsProvider if it is set', function () { 62 | const region = Region.fromRegionId('z1'); 63 | const config = new Config({ 64 | regionsProvider: region 65 | }); 66 | return config.getRegionsProvider() 67 | .then(regionsProvider => regionsProvider.getRegions()) 68 | .then(regions => { 69 | should.equal(regions.length, 1); 70 | should.deepEqual(regions[0], region); 71 | }); 72 | }); 73 | 74 | const testParams = parametrize( 75 | { 76 | name: 'useHttps', 77 | values: [ 78 | undefined, 79 | true 80 | ] 81 | }, 82 | { 83 | name: 'useCdnUp', 84 | values: [ 85 | undefined, 86 | false 87 | ] 88 | } 89 | ); 90 | 91 | testParams.forEach(param => { 92 | const { 93 | useHttps, 94 | useCdnUp 95 | } = param; 96 | const msg = `params(${JSON.stringify(param)})`; 97 | it(`test compatibility with zone; ${msg}`, function () { 98 | const zone = Zone_z1; 99 | const config = new Config({ 100 | zone, 101 | zoneExpire: Math.floor(Date.now() / 1000) + 604800, 102 | useHttpsDomain: useHttps, 103 | useCdnDomain: useCdnUp 104 | }); 105 | 106 | return config.getRegionsProvider() 107 | .then(regionsProvider => regionsProvider.getRegions()) 108 | .then(regions => { 109 | should.equal(regions.length, 1); 110 | regions[0].should.containEql({ 111 | services: Region.fromZone( 112 | zone, 113 | { 114 | preferredScheme: useHttps ? 'https' : 'http', 115 | isPreferCdnHost: useCdnUp 116 | } 117 | ).services, 118 | ttl: 604800 119 | }); 120 | }); 121 | }); 122 | it(`should return the defaultProvider; ${msg}`, function () { 123 | const config = new Config({ 124 | useHttpsDomain: useHttps, 125 | useCdnDomain: useCdnUp 126 | }); 127 | 128 | return config.getRegionsProvider({ 129 | bucketName, 130 | accessKey 131 | }) 132 | .then(regionsProvider => regionsProvider.getRegions()) 133 | .then(regions => { 134 | should.ok(regions.length > 0, 'regions length should great than 0'); 135 | }); 136 | }); 137 | }); 138 | }); 139 | 140 | describe('test _getQueryRegionEndpointsProvider', function () { 141 | it('should return queryRegionsEndpointsProvider', function () { 142 | const endpointProvider = new Endpoint('www.qiniu.com'); 143 | const config = new Config({ 144 | queryRegionsEndpointsProvider: endpointProvider 145 | }); 146 | 147 | should.equal( 148 | config._getQueryRegionEndpointsProvider(), 149 | endpointProvider 150 | ); 151 | }); 152 | 153 | it('should return ucEndpointsProvider', function () { 154 | const endpointProvider = new Endpoint('www.qiniu.com'); 155 | const config = new Config({ 156 | ucEndpointsProvider: endpointProvider 157 | }); 158 | 159 | should.equal( 160 | config._getQueryRegionEndpointsProvider(), 161 | endpointProvider 162 | ); 163 | }); 164 | 165 | const testParams = parametrize( 166 | { 167 | name: 'useHttps', 168 | values: [ 169 | undefined, 170 | true 171 | ] 172 | } 173 | ); 174 | 175 | testParams.forEach(param => { 176 | const msg = `params(${JSON.stringify(param)})`; 177 | it(`should return default EndpointsProvider; ${msg}`, function () { 178 | const { useHttps } = param; 179 | const config = new Config({ 180 | useHttpsDomain: useHttps 181 | }); 182 | 183 | const preferredScheme = useHttps ? 'https' : 'http'; 184 | 185 | return config._getQueryRegionEndpointsProvider() 186 | .getEndpoints() 187 | .then(endpoints => { 188 | const endpointsValues = endpoints.map(e => e.getValue()); 189 | should.deepEqual(endpointsValues, [ 190 | `${preferredScheme}://uc.qiniuapi.com`, 191 | `${preferredScheme}://kodo-config.qiniuapi.com`, 192 | `${preferredScheme}://uc.qbox.me` 193 | ]); 194 | }); 195 | }); 196 | }); 197 | }); 198 | 199 | describe('test disable file cache', function () { 200 | it('test disable file cache', function () { 201 | const defaultPersistPath = path.join(os.tmpdir(), 'qn-regions-cache.jsonl'); 202 | try { 203 | fs.unlinkSync(defaultPersistPath); 204 | } catch (e) { 205 | if (e.code !== 'ENOENT') { 206 | throw e; 207 | } 208 | } 209 | 210 | const config = new qiniu.conf.Config(); 211 | config.regionsQueryResultCachePath = null; 212 | 213 | return config.getRegionsProvider({ 214 | bucketName, 215 | accessKey 216 | }) 217 | .then(regionsProvider => regionsProvider.getRegions()) 218 | .then(regions => { 219 | should.ok(regions.length > 0); 220 | should.ok(!fs.existsSync(defaultPersistPath)); 221 | }); 222 | }); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /qiniu/conf.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const pkg = require('../package.json'); 3 | const { Endpoint } = require('./httpc/endpoint'); 4 | const { Region } = require('./httpc/region'); 5 | const { StaticEndpointsProvider } = require('./httpc/endpointsProvider'); 6 | const crypto = require('crypto'); 7 | const { 8 | CachedRegionsProvider, 9 | QueryRegionsProvider 10 | } = require('./httpc/regionsProvider'); 11 | 12 | exports.ACCESS_KEY = ''; 13 | exports.SECRET_KEY = ''; 14 | 15 | const defaultUserAgent = function () { 16 | return 'QiniuNodejs/' + pkg.version + ' (' + os.type() + '; ' + os.platform() + 17 | '; ' + os.arch() + '; )'; 18 | }; 19 | 20 | exports.USER_AGENT = defaultUserAgent(); 21 | exports.BLOCK_SIZE = 4 * 1024 * 1024; // 4MB, never change 22 | 23 | // define api form mime type 24 | exports.FormMimeUrl = 'application/x-www-form-urlencoded'; 25 | exports.FormMimeJson = 'application/json'; 26 | exports.FormMimeRaw = 'application/octet-stream'; 27 | exports.RS_HOST = 'rs.qiniu.com'; 28 | exports.RPC_TIMEOUT = 600000; // 600s 29 | let QUERY_REGION_BACKUP_HOSTS = [ 30 | 'kodo-config.qiniuapi.com', 31 | 'uc.qbox.me' 32 | ]; 33 | Object.defineProperty(exports, 'QUERY_REGION_BACKUP_HOSTS', { 34 | get: () => QUERY_REGION_BACKUP_HOSTS, 35 | set: v => { 36 | QUERY_REGION_BACKUP_HOSTS = v; 37 | } 38 | }); 39 | let QUERY_REGION_HOST = 'uc.qiniuapi.com'; 40 | Object.defineProperty(exports, 'QUERY_REGION_HOST', { 41 | get: () => QUERY_REGION_HOST, 42 | set: v => { 43 | QUERY_REGION_HOST = v; 44 | QUERY_REGION_BACKUP_HOSTS = []; 45 | } 46 | }); 47 | let UC_BACKUP_HOSTS = QUERY_REGION_BACKUP_HOSTS.slice(); 48 | let UC_HOST = QUERY_REGION_HOST; 49 | Object.defineProperty(exports, 'UC_HOST', { 50 | get: () => UC_HOST, 51 | set: v => { 52 | UC_HOST = v; 53 | UC_BACKUP_HOSTS = []; 54 | QUERY_REGION_HOST = v; 55 | QUERY_REGION_BACKUP_HOSTS = []; 56 | } 57 | }); 58 | 59 | // proxy 60 | exports.RPC_HTTP_AGENT = null; 61 | exports.RPC_HTTPS_AGENT = null; 62 | 63 | const Config = (function () { 64 | /** 65 | * @class 66 | * @constructor 67 | * @param {Object} [options] 68 | * @param {boolean} [options.useHttpsDomain] 69 | * @param {boolean} [options.accelerateUploading] enable accelerate uploading. should active the domains in portal before using 70 | * @param {EndpointsProvider} [options.ucEndpointsProvider] 71 | * @param {EndpointsProvider} [options.queryRegionsEndpointsProvider] 72 | * @param {RegionsProvider} [options.regionsProvider] 73 | * @param {string} [options.regionsQueryResultCachePath] 74 | * 75 | * @param {boolean} [options.useCdnDomain] DEPRECATED: use accelerateUploading instead 76 | * @param {Zone} [options.zone] DEPRECATED: use RegionsProvider instead 77 | * @param {number} [options.zoneExpire] DEPRECATED 78 | */ 79 | function Config (options) { 80 | options = options || {}; 81 | // use http or https protocol 82 | this.useHttpsDomain = !!(options.useHttpsDomain || false); 83 | 84 | // use accelerate upload domains 85 | this.accelerateUploading = !!(options.accelerateUploading || false); 86 | 87 | // custom uc endpoints 88 | this.ucEndpointsProvider = options.ucEndpointsProvider || null; 89 | // custom query region endpoints 90 | this.queryRegionsEndpointsProvider = options.queryRegionsEndpointsProvider || null; 91 | // custom regions 92 | this.regionsProvider = options.regionsProvider || null; 93 | // custom cache persisting path for regions query result 94 | // only worked with default CachedRegionsProvider 95 | this.regionsQueryResultCachePath = options.regionsQueryResultCachePath; 96 | 97 | // deprecated 98 | // use cdn accelerated domains, this is not work with auto query region 99 | this.useCdnDomain = !!(options.useCdnDomain && true); 100 | // zone of the bucket 101 | this.zone = options.zone || null; 102 | this.zoneExpire = options.zoneExpire || -1; 103 | } 104 | 105 | /** 106 | * @returns {EndpointsProvider} 107 | */ 108 | Config.prototype.getUcEndpointsProvider = function () { 109 | if (this.ucEndpointsProvider) { 110 | return this.ucEndpointsProvider; 111 | } 112 | 113 | return new StaticEndpointsProvider( 114 | [UC_HOST].concat(UC_BACKUP_HOSTS).map(h => new Endpoint(h, { 115 | defaultScheme: this.useHttpsDomain ? 'https' : 'http' 116 | })) 117 | ); 118 | }; 119 | 120 | /** 121 | * @private 122 | * @returns {EndpointsProvider} 123 | */ 124 | Config.prototype._getQueryRegionEndpointsProvider = function () { 125 | if (this.queryRegionsEndpointsProvider) { 126 | return this.queryRegionsEndpointsProvider; 127 | } 128 | 129 | if (this.ucEndpointsProvider) { 130 | return this.ucEndpointsProvider; 131 | } 132 | 133 | const queryRegionsHosts = [QUERY_REGION_HOST].concat(QUERY_REGION_BACKUP_HOSTS); 134 | return new StaticEndpointsProvider(queryRegionsHosts.map(h => 135 | new Endpoint( 136 | h, 137 | { 138 | defaultScheme: this.useHttpsDomain ? 'https' : 'http' 139 | } 140 | ) 141 | )); 142 | }; 143 | 144 | /** 145 | * @param {Object} [options] 146 | * @param {string} [options.bucketName] 147 | * @param {string} [options.accessKey] 148 | * @returns {Promise} 149 | */ 150 | Config.prototype.getRegionsProvider = function (options) { 151 | // returns custom regions provider, if it's configured 152 | if (this.regionsProvider) { 153 | return Promise.resolve(this.regionsProvider); 154 | } 155 | 156 | // backward compatibility with custom zone configuration 157 | const zoneProvider = getRegionsProviderFromZone.call(this); 158 | if (zoneProvider) { 159 | return Promise.resolve(zoneProvider); 160 | } 161 | 162 | // get default regions provider 163 | const { 164 | bucketName, 165 | accessKey 166 | } = options || {}; 167 | if (!bucketName || !accessKey) { 168 | return Promise.reject( 169 | new Error('bucketName and accessKey is required for query regions') 170 | ); 171 | } 172 | return getDefaultRegionsProvider.call(this, { 173 | bucketName, 174 | accessKey 175 | }); 176 | }; 177 | 178 | /** 179 | * @private 180 | * @returns {RegionsProvider | undefined} 181 | */ 182 | function getRegionsProviderFromZone () { 183 | let zoneTTL; 184 | let shouldUseZone; 185 | if (this.zoneExpire > 0) { 186 | zoneTTL = this.zoneExpire - Math.trunc(Date.now() / 1000); 187 | shouldUseZone = this.zone && zoneTTL > 0; 188 | } else { 189 | zoneTTL = -1; 190 | shouldUseZone = Boolean(this.zone); 191 | } 192 | if (!shouldUseZone) { 193 | return; 194 | } 195 | return Region.fromZone(this.zone, { 196 | ttl: zoneTTL, 197 | isPreferCdnHost: this.useCdnDomain, 198 | preferredScheme: this.useHttpsDomain ? 'https' : 'http' 199 | }); 200 | } 201 | 202 | /** 203 | * @private 204 | * @param {Object} options 205 | * @param {string} options.bucketName 206 | * @param {string} options.accessKey 207 | * @returns {Promise} 208 | */ 209 | function getDefaultRegionsProvider (options) { 210 | const { 211 | bucketName, 212 | accessKey 213 | } = options; 214 | 215 | const queryRegionsEndpointsProvider = this._getQueryRegionEndpointsProvider(); 216 | return queryRegionsEndpointsProvider 217 | .getEndpoints() 218 | .then(endpoints => { 219 | const endpointsMd5 = endpoints 220 | .map(e => e.host) 221 | .sort() 222 | .reduce( 223 | (hash, host) => hash.update(host), 224 | crypto.createHash('md5') 225 | ) 226 | .digest('hex'); 227 | const cacheKey = [ 228 | endpointsMd5, 229 | accessKey, 230 | bucketName, 231 | this.accelerateUploading.toString() 232 | ].join(':'); 233 | return new CachedRegionsProvider({ 234 | persistPath: this.regionsQueryResultCachePath, 235 | cacheKey, 236 | baseRegionsProvider: new QueryRegionsProvider({ 237 | accessKey: accessKey, 238 | bucketName: bucketName, 239 | endpointsProvider: queryRegionsEndpointsProvider, 240 | preferredScheme: this.useHttpsDomain ? 'https' : 'http' 241 | }) 242 | }); 243 | }); 244 | } 245 | 246 | return Config; 247 | })(); 248 | 249 | exports.Config = Config; 250 | /** 251 | * backward compatibility 252 | * @deprecated use qiniu/httpc/region.js instead 253 | */ 254 | exports.Zone = require('./zone').Zone; 255 | -------------------------------------------------------------------------------- /qiniu/httpc/regionsRetryPolicy.js: -------------------------------------------------------------------------------- 1 | const { 2 | RetryPolicy 3 | } = require('../retry'); 4 | const { Region } = require('./region'); 5 | const { 6 | StaticRegionsProvider 7 | } = require('./regionsProvider'); 8 | const { 9 | StaticEndpointsProvider 10 | } = require('./endpointsProvider'); 11 | 12 | /** 13 | * @typedef {EndpointsRetryPolicyContext} RegionsRetryPolicyContext 14 | * @property {Region} region 15 | * @property {Region[]} alternativeRegions 16 | * @property {SERVICE_NAME} serviceName 17 | * @property {SERVICE_NAME[]} alternativeServiceNames 18 | */ 19 | 20 | /** 21 | * @class 22 | * @extends RetryPolicy 23 | * @param {SERVICE_NAME} options.serviceName DEPRECATE: use options.serviceNames instead 24 | * @param {SERVICE_NAME[]} options.serviceNames 25 | * @param {Region[]} [options.regions] 26 | * @param {RegionsProvider} [options.regionsProvider] 27 | * @param {Endpoint[]} [options.preferredEndpoints] 28 | * @param {EndpointsProvider} [options.preferredEndpointsProvider] 29 | * @param {function(RegionsRetryPolicyContext): Promise} [options.onChangedRegion] 30 | * @constructor 31 | */ 32 | function RegionsRetryPolicy (options) { 33 | /** 34 | * @type {SERVICE_NAME[]} 35 | */ 36 | this.serviceNames = options.serviceNames || []; 37 | if (!this.serviceNames.length) { 38 | this.serviceNames = [options.serviceName]; 39 | } 40 | if (!this.serviceNames.length) { 41 | throw new TypeError('Must provide one service name at least'); 42 | } 43 | // compatible, remove when make break changes 44 | this.serviceName = this.serviceNames[0]; 45 | 46 | this.regions = options.regions || []; 47 | this.regionsProvider = options.regionsProvider || new StaticRegionsProvider([]); 48 | this.preferredEndpoints = options.preferredEndpoints || []; 49 | this.preferredEndpointsProvider = options.preferredEndpointsProvider || new StaticEndpointsProvider([]); 50 | this.onChangedRegion = options.onChangedRegion; 51 | } 52 | 53 | RegionsRetryPolicy.prototype = Object.create(RetryPolicy.prototype); 54 | RegionsRetryPolicy.prototype.constructor = RegionsRetryPolicy; 55 | 56 | /** 57 | * @param {RegionsRetryPolicyContext} context 58 | * @returns {Promise} 59 | */ 60 | RegionsRetryPolicy.prototype.initContext = function (context) { 61 | const regionsPromise = this.regions.length > 0 62 | ? this.regions 63 | : this.regionsProvider.getRegions(); 64 | const preferredEndpointsPromise = this.preferredEndpoints.length > 0 65 | ? this.preferredEndpoints 66 | : this.preferredEndpointsProvider.getEndpoints(); 67 | return Promise.all([ 68 | regionsPromise, 69 | preferredEndpointsPromise 70 | ]) 71 | .then(([regions, preferredEndpoints]) => this._initRegions({ 72 | context, 73 | regions: regions.slice(), 74 | preferredEndpoints: preferredEndpoints.slice() 75 | })) 76 | .then(() => this._prepareEndpoints(context)); 77 | }; 78 | 79 | /** 80 | * @param {RegionsRetryPolicyContext} context 81 | * @returns {boolean} 82 | */ 83 | RegionsRetryPolicy.prototype.shouldRetry = function (context) { 84 | return context.alternativeRegions.length > 0 || context.alternativeServiceNames.length > 0; 85 | }; 86 | 87 | /** 88 | * @param {RegionsRetryPolicyContext} context 89 | * @returns {Promise} 90 | */ 91 | RegionsRetryPolicy.prototype.prepareRetry = function (context) { 92 | let handleChangedRegionPromise = Promise.resolve(); 93 | if (context.alternativeServiceNames.length) { 94 | context.serviceName = context.alternativeServiceNames.shift(); 95 | } else if (context.alternativeRegions.length) { 96 | context.region = context.alternativeRegions.shift(); 97 | [context.serviceName, ...context.alternativeServiceNames] = this.serviceNames; 98 | if (typeof this.onChangedRegion === 'function') { 99 | handleChangedRegionPromise = this.onChangedRegion(context); 100 | } 101 | } else { 102 | return Promise.reject( 103 | new Error('There isn\'t available region or service for next try') 104 | ); 105 | } 106 | return handleChangedRegionPromise 107 | .then(() => this._prepareEndpoints(context)); 108 | }; 109 | 110 | /** 111 | * @typedef GetPreferredRegionInfoResult 112 | * @property {number} preferredServiceIndex 113 | * @property {number} preferredRegionIndex 114 | */ 115 | 116 | /** 117 | * @param {Region[]} options.regions 118 | * @param {Endpoint[]} options.preferredEndpoints 119 | * @returns {GetPreferredRegionInfoResult} 120 | * @protected 121 | */ 122 | RegionsRetryPolicy.prototype._getPreferredRegionInfo = function (options) { 123 | const { 124 | regions, 125 | preferredEndpoints 126 | } = options; 127 | 128 | const serviceNames = this.serviceNames.slice(); 129 | 130 | let preferredServiceIndex = -1; 131 | const preferredRegionIndex = regions.findIndex(r => 132 | serviceNames.some((s, si) => 133 | r.services[s].some(e => { 134 | const res = preferredEndpoints.some(pe => pe.host === e.host); 135 | if (res) { 136 | preferredServiceIndex = si; 137 | } 138 | return res; 139 | }) 140 | ) 141 | ); 142 | return { 143 | preferredServiceIndex, 144 | preferredRegionIndex 145 | }; 146 | }; 147 | 148 | /** 149 | * @param {RegionsRetryPolicyContext} options.context 150 | * @param {Region[]} options.regions 151 | * @param {Endpoint[]} options.preferredEndpoints 152 | * @returns {Promise} 153 | * @private 154 | */ 155 | RegionsRetryPolicy.prototype._initRegions = function (options) { 156 | const { 157 | context, 158 | regions, 159 | preferredEndpoints 160 | } = options; 161 | // initial region and alternative regions 162 | if (!regions.length && !preferredEndpoints.length) { 163 | return Promise.reject( 164 | new Error('There isn\'t available region or preferred endpoint') 165 | ); 166 | } 167 | 168 | if (!preferredEndpoints.length) { 169 | [context.region, ...context.alternativeRegions] = regions; 170 | [context.serviceName, ...context.alternativeServiceNames] = this.serviceNames; 171 | return Promise.resolve(); 172 | } 173 | 174 | // find preferred serviceName and region by preferred endpoints 175 | const { 176 | preferredRegionIndex, 177 | preferredServiceIndex 178 | } = this._getPreferredRegionInfo({ 179 | regions, 180 | preferredEndpoints 181 | }); 182 | 183 | // initialize the order of serviceNames and regions 184 | if (preferredRegionIndex < 0) { 185 | // preferred endpoints is not a region, then make all regions alternative 186 | [context.serviceName, ...context.alternativeServiceNames] = this.serviceNames; 187 | // compatible, remove when make break changes 188 | this.serviceName = context.serviceName; 189 | 190 | context.region = new Region({ 191 | services: { 192 | [context.serviceName]: preferredEndpoints 193 | } 194 | }); 195 | context.alternativeRegions = regions; 196 | } else { 197 | // preferred endpoints in a known region, then reorder the regions and services 198 | context.alternativeRegions = regions; 199 | [context.region] = context.alternativeRegions.splice(preferredRegionIndex, 1); 200 | context.alternativeServiceNames = this.serviceNames.slice(); 201 | [context.serviceName] = context.alternativeServiceNames.splice(preferredServiceIndex, 1); 202 | // compatible, remove when make break changes 203 | this.serviceName = context.serviceName; 204 | } 205 | return Promise.resolve(); 206 | }; 207 | 208 | /** 209 | * @param {RegionsRetryPolicyContext} context 210 | * @returns {Promise} 211 | * @protected 212 | */ 213 | RegionsRetryPolicy.prototype._prepareEndpoints = function (context) { 214 | let handleChangedRegionsPromise = Promise.resolve(); 215 | [context.endpoint, ...context.alternativeEndpoints] = context.region.services[context.serviceName] || []; 216 | while (!context.endpoint) { 217 | if (context.alternativeServiceNames.length) { 218 | context.serviceName = context.alternativeServiceNames.shift(); 219 | // compatible, remove when make break changes 220 | this.serviceName = context.serviceName; 221 | [context.endpoint, ...context.alternativeEndpoints] = context.region.services[context.serviceName] || []; 222 | } else if (context.alternativeRegions.length) { 223 | context.region = context.alternativeRegions.shift(); 224 | [context.serviceName, ...context.alternativeServiceNames] = this.serviceNames; 225 | // compatible, remove when make break changes 226 | this.serviceName = context.serviceName; 227 | [context.endpoint, ...context.alternativeEndpoints] = context.region.services[context.serviceName] || []; 228 | if (typeof this.onChangedRegion === 'function') { 229 | handleChangedRegionsPromise = this.onChangedRegion(context); 230 | } 231 | } else { 232 | return Promise.reject(new Error( 233 | 'There isn\'t available endpoint for ' + 234 | this.serviceNames.join(', ') + 235 | ' service(s) in any available regions' 236 | )); 237 | } 238 | } 239 | return handleChangedRegionsPromise; 240 | }; 241 | 242 | exports.RegionsRetryPolicy = RegionsRetryPolicy; 243 | -------------------------------------------------------------------------------- /test/fop.test.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const should = require('should'); 6 | const qiniu = require('../index.js'); 7 | const { 8 | checkEnvConfigOrExit, 9 | getEnvConfig, 10 | parametrize, 11 | createRandomFile 12 | } = require('./conftest'); 13 | 14 | // file to upload 15 | const testFilePath = path.join(os.tmpdir(), 'nodejs-sdk-test-fop.bin'); 16 | 17 | before(function () { 18 | checkEnvConfigOrExit(); 19 | return Promise.all([ 20 | createRandomFile(testFilePath, (1 << 20) * 5) 21 | ]); 22 | }); 23 | 24 | after(() => { 25 | return Promise.all( 26 | [ 27 | testFilePath 28 | ].map(p => new Promise(resolve => { 29 | fs.unlink(p, err => { 30 | if (err && err.code !== 'ENOENT') { 31 | console.log(`unlink ${p} failed`, err); 32 | } 33 | resolve(); 34 | }); 35 | })) 36 | ); 37 | }); 38 | 39 | describe('test start fop', function () { 40 | this.timeout(0); 41 | 42 | const { 43 | accessKey, 44 | secretKey, 45 | bucketName 46 | } = getEnvConfig(); 47 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey); 48 | const config = new qiniu.conf.Config(); 49 | config.useHttpsDomain = true; 50 | config.zone = qiniu.zone.Zone_na0; 51 | 52 | let persistentId; 53 | 54 | const testParams = parametrize( 55 | { 56 | name: 'persistentType', 57 | values: [ 58 | undefined, 59 | 0, 60 | 1 61 | ] 62 | } 63 | ); 64 | 65 | testParams.forEach(function (testParam) { 66 | const { 67 | persistentType 68 | } = testParam; 69 | const msg = `params(${JSON.stringify(testParam)})`; 70 | 71 | it(`test video fop; ${msg}`, function (done) { 72 | let pipeline = 'sdktest'; 73 | const srcKey = 'qiniu.mp4'; 74 | const operationManager = new qiniu.fop.OperationManager(mac, config); 75 | 76 | // 处理指令集合 77 | const srcBucket = bucketName; 78 | const saveBucket = bucketName; 79 | const fop1 = [ 80 | 'avthumb/mp4/s/480x320/vb/150k|saveas/', 81 | qiniu.util.urlsafeBase64Encode( 82 | `${saveBucket}:qiniu_480x320.mp4` 83 | ) 84 | ].join(''); 85 | const fop2 = [ 86 | 'vframe/jpg/offset/10|saveas/', 87 | qiniu.util.urlsafeBase64Encode( 88 | `${saveBucket}:qiniu_frame1.jpg` 89 | ) 90 | ].join(''); 91 | const fops = [fop1, fop2]; 92 | 93 | const options = { 94 | notifyURL: 'http://api.example.com/pfop/callback', 95 | force: false 96 | }; 97 | 98 | if (persistentType !== undefined) { 99 | options.type = persistentType; 100 | pipeline = null; 101 | } 102 | 103 | // 持久化数据处理返回的是任务的persistentId,可以根据这个id查询处理状态 104 | operationManager.pfop(srcBucket, srcKey, fops, pipeline, options, 105 | function (err, respBody, respInfo) { 106 | console.log(respBody, respInfo); 107 | should.not.exist(err); 108 | respBody.should.have.keys('persistentId'); 109 | persistentId = respBody.persistentId; 110 | done(); 111 | }); 112 | }); 113 | 114 | it(`test video prefop; ${msg}`, function (done) { 115 | const operationManager = new qiniu.fop.OperationManager(mac, config); 116 | // 查询处理状态 117 | operationManager.prefop(persistentId, 118 | function (err, respBody, respInfo) { 119 | console.log(respBody, respInfo); 120 | should.not.exist(err); 121 | respBody.should.have.keys('id', 'pipeline', 'inputBucket', 'inputKey'); 122 | respBody.should.have.property('id', persistentId); 123 | if (persistentType) { 124 | should.equal(respBody.type, persistentType); 125 | } 126 | done(); 127 | }); 128 | }); 129 | 130 | it(`test pfop with upload; ${msg}`, function () { 131 | const formUploader = new qiniu.form_up.FormUploader(config); 132 | const key = 'test-pfop/upload-file'; 133 | const persistentKey = [ 134 | 'test-pfop/test-pfop-by-upload', 135 | 'type', 136 | persistentType 137 | ].join('_'); 138 | 139 | const fop1 = [ 140 | 'avinfo|saveas/', 141 | qiniu.util.urlsafeBase64Encode( 142 | `${bucketName}:${persistentKey}` 143 | ) 144 | ].join(''); 145 | const options = { 146 | scope: bucketName, 147 | persistentOps: [ 148 | fop1 149 | ].join(';'), 150 | persistentType 151 | }; 152 | const putPolicy = new qiniu.rs.PutPolicy(options); 153 | const uploadToken = putPolicy.uploadToken(mac); 154 | const putExtra = new qiniu.form_up.PutExtra(); 155 | 156 | return formUploader.put(uploadToken, key, testFilePath, putExtra) 157 | .then(({ data }) => { 158 | data.should.have.keys('key', 'persistentId'); 159 | 160 | return new Promise((resolve, reject) => { 161 | new qiniu.fop.OperationManager(mac, config) 162 | .prefop( 163 | data.persistentId, 164 | function (err, respBody, respInfo) { 165 | if (err) { 166 | reject(err); 167 | return; 168 | } 169 | resolve({ data: respBody, resp: respInfo }); 170 | } 171 | ); 172 | }); 173 | }) 174 | .then(({ data }) => { 175 | data.should.have.keys('creationDate'); 176 | if (persistentType) { 177 | should.equal(data.type, persistentType); 178 | } 179 | }); 180 | }); 181 | }); 182 | 183 | it('test pfop by templateID with api', function () { 184 | const srcKey = 'qiniu.mp4'; 185 | const srcBucket = bucketName; 186 | 187 | const templateID = 'test-workflow'; 188 | const operationManager = new qiniu.fop.OperationManager(mac, config); 189 | 190 | new Promise((resolve, reject) => { 191 | operationManager.pfop( 192 | srcBucket, 193 | srcKey, 194 | null, 195 | null, 196 | { workflowTemplateID: templateID }, 197 | function (err, respBody, respInfo) { 198 | if (err) { 199 | reject(err); 200 | return; 201 | } 202 | resolve({ data: respBody, resp: respInfo }); 203 | } 204 | ); 205 | }) 206 | .then(({ data }) => { 207 | data.should.have.keys('persistentId'); 208 | return new Promise((resolve, reject) => { 209 | operationManager.prefop( 210 | data.persistentId, 211 | function (err, respBody, respInfo) { 212 | if (err) { 213 | reject(err); 214 | return; 215 | } 216 | resolve({ data: respBody, resp: respInfo }); 217 | } 218 | ); 219 | }); 220 | }) 221 | .then(({ data }) => { 222 | data.should.have.keys( 223 | 'creationDate', 224 | 'taskFrom' 225 | ); 226 | }); 227 | }); 228 | 229 | it('test pfop by templateID with upload', function () { 230 | const formUploader = new qiniu.form_up.FormUploader(config); 231 | const key = 'qiniu-pfop-tplid-upload-file'; 232 | const templateID = 'test-workflow'; 233 | const options = { 234 | scope: bucketName, 235 | persistentWorkflowTemplateID: templateID 236 | }; 237 | const putPolicy = new qiniu.rs.PutPolicy(options); 238 | const uploadToken = putPolicy.uploadToken(mac); 239 | const putExtra = new qiniu.form_up.PutExtra(); 240 | 241 | return formUploader.put(uploadToken, key, testFilePath, putExtra) 242 | .then(({ data }) => { 243 | data.should.have.keys('key', 'persistentId'); 244 | return new Promise((resolve, reject) => { 245 | new qiniu.fop.OperationManager(mac, config) 246 | .prefop( 247 | data.persistentId, 248 | function (err, respBody, respInfo) { 249 | if (err) { 250 | reject(err); 251 | return; 252 | } 253 | resolve({ data: respBody, resp: respInfo }); 254 | } 255 | ); 256 | }); 257 | }) 258 | .then(({ data }) => { 259 | data.should.have.keys( 260 | 'creationDate', 261 | 'taskFrom' 262 | ); 263 | }); 264 | }); 265 | }); 266 | -------------------------------------------------------------------------------- /qiniu/httpc/region.js: -------------------------------------------------------------------------------- 1 | const { Endpoint } = require('./endpoint'); 2 | 3 | /** 4 | * @interface RegionsProvider 5 | */ 6 | 7 | /** 8 | * @function 9 | * @name RegionsProvider#getRegions 10 | * @returns {Promise} 11 | */ 12 | 13 | /** 14 | * @interface MutableRegionsProvider 15 | * @extends RegionsProvider 16 | */ 17 | 18 | /** 19 | * @function 20 | * @name MutableRegionsProvider#setRegions 21 | * @param {Region[]} regions 22 | * @returns {Promise} 23 | */ 24 | 25 | // --- could split to files if migrate to typescript --- // 26 | 27 | /** 28 | * @readonly 29 | * @enum {string} 30 | */ 31 | const SERVICE_NAME = { 32 | UC: 'uc', 33 | UP: 'up', 34 | UP_ACC: 'up_acc', 35 | IO: 'io', 36 | RS: 'rs', 37 | RSF: 'rsf', 38 | API: 'api', 39 | S3: 's3' 40 | }; 41 | 42 | // --- could split to files if migrate to typescript --- // 43 | 44 | /** 45 | * @typedef {SERVICE_NAME | string} ServiceKey 46 | */ 47 | 48 | /** 49 | * @class 50 | * @implements RegionsProvider 51 | * @param {Object} options 52 | * @param {string} [options.regionId] 53 | * @param {string} [options.s3RegionId] 54 | * @param {Object.} [options.services] 55 | * @param {number} [options.ttl] seconds. default 1 day. 56 | * @param {Date} [options.createTime] 57 | * @constructor 58 | */ 59 | function Region (options) { 60 | this.regionId = options.regionId; 61 | this.s3RegionId = options.s3RegionId || options.regionId; 62 | 63 | this.services = options.services || {}; 64 | // use Object.values when min version of Node.js update to ≥ v7.5.0 65 | Object.keys(SERVICE_NAME).map(k => { 66 | const v = SERVICE_NAME[k]; 67 | if (!Array.isArray(this.services[v])) { 68 | this.services[v] = []; 69 | } 70 | }); 71 | 72 | this.ttl = options.ttl || 86400; 73 | this.createTime = options.createTime || new Date(); 74 | } 75 | 76 | /** 77 | * This is used to be compatible with Zone. 78 | * So this function will be removed after remove Zone. 79 | * NOTE: The Region instance obtained using this method 80 | * can only be used for the following services: up, io, rs, rsf, api. 81 | * Because the Zone not support other services. 82 | * @param {conf.Zone} zone 83 | * @param {Object} [options] 84 | * @param {string} [options.regionId] 85 | * @param {string} [options.s3RegionId] 86 | * @param {number} [options.ttl] 87 | * @param {boolean} [options.isPreferCdnHost] 88 | * @param {string} [options.preferredScheme] 89 | */ 90 | Region.fromZone = function (zone, options) { 91 | options = options || {}; 92 | options.ttl = options.ttl || -1; 93 | 94 | const upHosts = options.isPreferCdnHost 95 | ? zone.cdnUpHosts.concat(zone.srcUpHosts) 96 | : zone.srcUpHosts.concat(zone.cdnUpHosts); 97 | 98 | const endpointOptions = {}; 99 | if (options.preferredScheme) { 100 | endpointOptions.defaultScheme = options.preferredScheme; 101 | } 102 | 103 | const services = { 104 | // use array destructure if migrate to typescript 105 | [SERVICE_NAME.UP]: upHosts.map( 106 | h => new Endpoint(h, endpointOptions) 107 | ), 108 | [SERVICE_NAME.IO]: [ 109 | new Endpoint(zone.ioHost, endpointOptions) 110 | ], 111 | [SERVICE_NAME.RS]: [ 112 | new Endpoint(zone.rsHost, endpointOptions) 113 | ], 114 | [SERVICE_NAME.RSF]: [ 115 | new Endpoint(zone.rsfHost, endpointOptions) 116 | ], 117 | [SERVICE_NAME.API]: [ 118 | new Endpoint(zone.apiHost, endpointOptions) 119 | ] 120 | }; 121 | 122 | return new Region({ 123 | regionId: options.regionId, 124 | s3RegionId: options.s3RegionId || options.regionId, 125 | services: services, 126 | ttl: options.ttl 127 | }); 128 | }; 129 | 130 | /** 131 | * @param {string} regionId 132 | * @param {Object} [options] 133 | * @param {string} [options.s3RegionId] 134 | * @param {number} [options.ttl] 135 | * @param {Date} [options.createTime] 136 | * @param {string} [options.preferredScheme] 137 | * @param {boolean} [options.isPreferCdnUpHost] 138 | * @param {Object.} [options.extendedServices] 139 | * @returns {Region} 140 | */ 141 | Region.fromRegionId = function (regionId, options) { 142 | options = options || {}; 143 | 144 | const s3RegionId = options.s3RegionId || regionId; 145 | const ttl = options.ttl; 146 | const createTime = options.createTime; 147 | const isPreferCdnUpHost = typeof options.isPreferCdnUpHost === 'boolean' 148 | ? options.isPreferCdnUpHost 149 | : true; 150 | 151 | const endpointOptions = {}; 152 | if (options.preferredScheme) { 153 | endpointOptions.defaultScheme = options.preferredScheme; 154 | } 155 | 156 | const isZ0 = regionId === 'z0'; 157 | let upCdnEndpoints; 158 | let upSourceEndpoints; 159 | if (isZ0) { 160 | upCdnEndpoints = [ 161 | new Endpoint( 162 | 'upload.qiniup.com', 163 | endpointOptions 164 | ) 165 | ]; 166 | upSourceEndpoints = [ 167 | new Endpoint( 168 | 'up.qiniup.com', 169 | endpointOptions 170 | ), 171 | new Endpoint( 172 | 'up.qbox.me', 173 | endpointOptions 174 | ) 175 | ]; 176 | } else { 177 | upCdnEndpoints = [ 178 | new Endpoint( 179 | 'upload-' + regionId + '.qiniup.com', 180 | endpointOptions 181 | ) 182 | ]; 183 | upSourceEndpoints = [ 184 | new Endpoint( 185 | 'up-' + regionId + '.qiniup.com', 186 | endpointOptions 187 | ), 188 | new Endpoint( 189 | 'up-' + regionId + '.qbox.me', 190 | endpointOptions 191 | ) 192 | ]; 193 | } 194 | 195 | /** 196 | * @type {Object.} 197 | */ 198 | let services = { 199 | [SERVICE_NAME.UC]: [ 200 | new Endpoint( 201 | 'uc.qiniuapi.com', 202 | endpointOptions 203 | ) 204 | ], 205 | [SERVICE_NAME.UP]: isPreferCdnUpHost 206 | ? upCdnEndpoints.concat(upSourceEndpoints) 207 | : upSourceEndpoints.concat(upCdnEndpoints), 208 | [SERVICE_NAME.IO]: isZ0 209 | ? [ 210 | new Endpoint( 211 | 'iovip.qiniuio.com', 212 | endpointOptions 213 | ), 214 | new Endpoint( 215 | 'iovip.qbox.me', 216 | endpointOptions 217 | ) 218 | ] 219 | : [ 220 | new Endpoint( 221 | 'iovip-' + regionId + '.qiniuio.com', 222 | endpointOptions 223 | ), 224 | new Endpoint( 225 | 'iovip-' + regionId + '.qbox.me', 226 | endpointOptions 227 | ) 228 | ], 229 | [SERVICE_NAME.RS]: [ 230 | new Endpoint( 231 | 'rs-' + regionId + '.qiniuapi.com', 232 | endpointOptions 233 | ), 234 | new Endpoint( 235 | 'rs-' + regionId + '.qbox.me', 236 | endpointOptions 237 | ) 238 | ], 239 | [SERVICE_NAME.RSF]: [ 240 | new Endpoint( 241 | 'rsf-' + regionId + '.qiniuapi.com', 242 | endpointOptions 243 | ), 244 | new Endpoint( 245 | 'rsf-' + regionId + '.qbox.me', 246 | endpointOptions 247 | ) 248 | ], 249 | [SERVICE_NAME.API]: [ 250 | new Endpoint( 251 | 'api-' + regionId + '.qiniuapi.com', 252 | endpointOptions 253 | ), 254 | new Endpoint( 255 | 'api-' + regionId + '.qbox.me', 256 | endpointOptions 257 | ) 258 | ], 259 | [SERVICE_NAME.S3]: [ 260 | new Endpoint( 261 | 's3.' + s3RegionId + '.qiniucs.com', 262 | endpointOptions 263 | ) 264 | ] 265 | }; 266 | 267 | services = Object.assign(services, options.extendedServices || {}); 268 | 269 | return new Region({ 270 | regionId: regionId, 271 | s3RegionId: s3RegionId, 272 | services: services, 273 | ttl: ttl, 274 | createTime: createTime 275 | }); 276 | }; 277 | 278 | /** 279 | * @param {Region} regions 280 | * @returns {Region} 281 | */ 282 | Region.merge = function (...regions) { 283 | const [source, ...rest] = regions; 284 | const target = source.clone(); 285 | rest.forEach(s => { 286 | // use Object.values when min version of Node.js update to ≥ v7.5.0 287 | Object.keys(s.services).forEach(n => { 288 | if (!target.services[n]) { 289 | target.services[n] = s.services[n].map(endpoint => endpoint.clone()); 290 | return; 291 | } 292 | s.services[n].forEach(endpoint => { 293 | if (!target.services[n].some(existsEndpoint => existsEndpoint.getValue() === endpoint.getValue())) { 294 | target.services[n].push(endpoint.clone()); 295 | } 296 | }); 297 | }); 298 | }); 299 | return target; 300 | }; 301 | 302 | /** 303 | * @returns {Promise} 304 | */ 305 | Region.prototype.getRegions = function () { 306 | return Promise.resolve([this]); 307 | }; 308 | 309 | Region.prototype.clone = function () { 310 | // use Object.entries when min version of Node.js update to ≥ v7.5.0 311 | const services = Object.keys(this.services).reduce((s, n) => { 312 | s[n] = this.services[n].map(endpoint => endpoint.clone()); 313 | return s; 314 | }, {}); 315 | return new Region({ 316 | regionId: this.regionId, 317 | s3RegionId: this.s3RegionId, 318 | services: services, 319 | ttl: this.ttl, 320 | createTime: this.createTime 321 | }); 322 | }; 323 | 324 | /** 325 | * @param {Region} regions 326 | * @returns {Region} 327 | */ 328 | Region.prototype.merge = function (...regions) { 329 | return Region.merge(this, ...regions); 330 | }; 331 | 332 | Object.defineProperty(Region.prototype, 'isLive', { 333 | /** 334 | * @returns {boolean} 335 | */ 336 | get: function () { 337 | if (this.ttl < 0) { 338 | return true; 339 | } 340 | // convert ms to s 341 | const liveTime = Math.round((Date.now() - this.createTime) / 1000); 342 | return liveTime < this.ttl; 343 | }, 344 | enumerable: false, 345 | configurable: true 346 | }); 347 | 348 | exports.SERVICE_NAME = SERVICE_NAME; 349 | exports.Region = Region; 350 | --------------------------------------------------------------------------------