diff --git a/demo/PutObject.js b/demo/PutObject.js deleted file mode 100644 index 0ebbc61..0000000 --- a/demo/PutObject.js +++ /dev/null @@ -1,21 +0,0 @@ -var path = require('path'); -var fs = require('fs'); -var COS = require('../sdk/cos'); -var filename = '1mb.zip' -var filepath = path.resolve(__dirname, '1mb.zip'); - -var params = { - Bucket : 'test', /* 必须 */ - Region : 'cn-south', //cn-south、cn-north、cn-east /* 必须 */ - Key : '1mb.zip', /* 必须 */ - Body : filepath, /* 必须 */ - ContentLength : fs.statSync(filepath).size, /* 必须 */ -}; - -COS.putObject(params, function(err, data) { - if(err) { - console.log(err); - } else { - console.log(data); - } -}); \ No newline at end of file diff --git a/demo/SliceUploadFile.js b/demo/SliceUploadFile.js deleted file mode 100644 index fa5b418..0000000 --- a/demo/SliceUploadFile.js +++ /dev/null @@ -1,24 +0,0 @@ -var path = require('path'); -var COS = require('../sdk/advanced_api'); -var filepath = path.resolve(__dirname, '40mb.zip'); - -var params = { - Bucket : 'test', /* 必须 */ - Region : 'cn-south', //cn-south、cn-north、cn-east /* 必须 */ - Key : '40mb.zip', /* 必须 */ - FilePath : filepath, /* 必须 */ - SliceSize : 1024 * 1024, //1MB /* 非必须 */ - AsyncLimit : 5 /* 非必须 */ -}; - -var ProgressCallback = function(progressData) { - console.log(progressData); -}; - -COS.sliceUploadFile(params, function(err, data) { - if(err) { - console.log(err); - } else { - console.log(data); - } -}, ProgressCallback); \ No newline at end of file diff --git a/demo/config.js b/demo/config.js new file mode 100644 index 0000000..743c747 --- /dev/null +++ b/demo/config.js @@ -0,0 +1,7 @@ +var config = { + AppId: '1250000000', + SecretId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + SecretKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +}; + +module.exports = config; \ No newline at end of file diff --git a/demo/demo.js b/demo/demo.js new file mode 100644 index 0000000..f9d7e3e --- /dev/null +++ b/demo/demo.js @@ -0,0 +1,325 @@ +var fs = require('fs'); +var path = require('path'); +var COS = require('../index'); +var util = require('./util'); +var config = require('./config'); + +var cos = new COS(config); + +function getService() { + cos.getService({}, function (err, data) { + return console.log(err || data); + }); +} + +function putObject() { + // 创建测试文件 + var filename = '1mb.zip'; + util.createFile(path.resolve(__dirname, filename), 1024 * 1024, function (err) { + // 调用方法 + var filepath = path.resolve(__dirname, filename); + cos.putObject({ + Bucket: 'test', /* 必须 */ + Region: 'cn-south', + Key: filename, /* 必须 */ + Body: fs.createReadStream(filepath), /* 必须 */ + ContentLength: fs.statSync(filepath).size, /* 必须 */ + }, function (err, data) { + if (err) { + console.log(err); + } else { + console.log(JSON.stringify(data, null, ' ')); + } + }); + }); +} + +function deleteObject() { + cos.deleteObject({ + Bucket: 'test', + Region: 'cn-south', + Key: '1mb.zip' + }, function (err, data) { + if (err) { + return console.log(err); + } + + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function getBucket() { + cos.getBucket({ + Bucket: 'test', + Region: 'cn-south' + }, function (err, data) { + if (err) { + return console.log(err); + } + + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function headBucket() { + cos.headBucket({ + Bucket: 'test', + Region: 'cn-south' + }, function (err, data) { + if (err) { + return console.log(err); + } + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function putBucket() { + cos.putBucket({ + Bucket: 'test-new', + Region: 'cn-south' + }, function (err, data) { + if (err) { + return console.log(err); + } + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function deleteBucket() { + cos.deleteBucket({ + Bucket: 'test-new', + Region: 'cn-south' + }, function (err, data) { + if (err) { + return console.log(err); + } + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function getBucketACL() { + cos.getBucketACL({ + Bucket: 'test', + Region: 'cn-south' + }, function (err, data) { + if (err) { + return console.log(err); + } + + console.log(data.AccessControlList.Grant); + }); +} + +function putBucketACL() { + // 该接口存在问题,不可以设置 ACL 为 'public-read' 也不能设置 GrandWrite 等 + cos.putBucketACL({ + Bucket: 'test', + Region: 'cn-south', + //GrantWrite : 'uin="1111", uin="2222"', + ACL: 'public-read', + // ACL: 'private' + }, function (err, data) { + if (err) { + return console.log(err); + } + + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function getBucketCORS() { + cos.getBucketCORS({ + Bucket: 'test', + Region: 'cn-south' + }, function (err, data) { + if (err) { + return console.log(err); + } + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function putBucketCORS() { + // 该接口存在问题,Content-MD5 错误 + cos.putBucketCORS({ + Bucket: 'test', + Region: 'cn-south', + CORSRules: [{ + "AllowedOrigin": ["*"], + "AllowedMethod": ["PUT", "GET", "POST", "DELETE", "HEAD"], + "AllowedHeader": ["origin", "accept", "content-type", "authorzation"], + "ExposeHeader": ["ETag"], + "MaxAgeSeconds": "300" + }] + }, function (err, data) { + if (err) { + return console.log(err); + } + + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function getBucketLocation() { + cos.getBucketLocation({ + Bucket: 'test', + Region: 'cn-south' + }, function (err, data) { + if (err) { + return console.log(err); + } + }); +} + +function getObject() { + cos.getObject({ + Bucket: 'test', + Region: 'cn-south', + Key: '1mb.zip', + Output: fs.createWriteStream(path.resolve(__dirname, '1mb.out.zip')) + }, function (err, data) { + if (err) { + return console.log(err); + } + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function headObject() { + cos.headObject({ + Bucket: 'test', + Region: 'cn-south', + Key: '1mb.zip' + }, function (err, data) { + if (err) { + return console.log(err); + } + console.log(JSON.stringify(data, null, ' ')); + }); +} + +var util = require('util'); +var inspect = require('eyes').inspector({maxLength: false}) +function getObjectACL() { + cos.getObjectACL({ + Bucket: 'test', + Region: 'cn-south', + Key: '1mb.zip' + }, function (err, data) { + if (err) { + return console.log(err); + } + console.log(JSON.stringify(data, null, ' ')); + }); +} + +function sliceUploadFile() { + // 创建测试文件 + var filename = '3mb.zip'; + util.createFile(path.resolve(__dirname, filename), 1024 * 1024 * 3, function (err) { + // 调用方法 + cos.sliceUploadFile({ + Bucket: 'test', /* 必须 */ + Region: 'cn-south', + Key: 'p.exe', /* 必须 */ + FilePath: filepath, /* 必须 */ + SliceSize: 1024 * 1024, //1MB /* 非必须 */ + AsyncLimit: 5, /* 非必须 */ + onProgress: function (progressData, percent) { + console.log(progressData, percent); + }, + onHashProgress: function (hashProgress, percent) { + console.log(hashProgress, percent); + } + }, function (err, data) { + if (err) { + console.log(err); + } else { + console.log(JSON.stringify(data, null, ' ')); + } + }); + }); +} + +function putBucketPolicy() { + cos.putBucketPolicy({ + Policy: { + "version": "2.0", + "principal": {"qcs": ["qcs::cam::uin/909600000:uin/909600000"]}, // 这里的 909600000 是 QQ 号 + "statement": [ + { + "effect": "allow", + "action": [ + "name/cos:GetBucket", + "name/cos:PutObject", + "name/cos:PostObject", + "name/cos:PutObjectCopy", + "name/cos:InitiateMultipartUpload", + "name/cos:UploadPart", + "name/cos:UploadPartCopy", + "name/cos:CompleteMultipartUpload", + "name/cos:AbortMultipartUpload", + "name/cos:AppendObject" + ], + "resource": ["qcs::cos:cn-south:uid/1250000000:test-1250000000.cn-south.myqcloud.com//1250000000/test/*"] // 1250000000 是 appid + } + ] + }, + Bucket: 'test', + Region: 'cn-south' + }, function (err, data) { + if (err) { + console.log(err); + } else { + getBucketPolicy(); + } + }); +} + +function getBucketPolicy() { + cos.getBucketPolicy({ + Bucket: 'test', + Region: 'cn-south' + }, function (err, data) { + if (err) { + console.log(err); + } else { + console.log(JSON.stringify(data, null, ' ')); + } + }); +} + +function putObjectCopy() { + cos.putObjectCopy({ + Bucket: 'test', + Region: 'cn-south', + Key: '1mb.copy.zip', + CopySource: 'test-1251902136.cn-south.myqcloud.com/1mb.zip', + }, function (err, data) { + if (err) { + console.log(err); + } else { + console.log(JSON.stringify(data, null, ' ')); + } + }); +} + +getService(); +// getBucket(); +// headBucket(); +// putBucket(); +// deleteBucket(); +// getBucketACL(); +// putBucketACL(); +// getBucketCORS(); +// putBucketCORS(); +// putBucketPolicy(); +// getBucketPolicy(); +// getBucketLocation(); +// getObject(); +// putObject(); +// putObjectCopy(); +// headObject(); +// deleteObject(); +// getObjectACL(); +// sliceUploadFile(); \ No newline at end of file diff --git a/demo/util.js b/demo/util.js new file mode 100644 index 0000000..15f2cb3 --- /dev/null +++ b/demo/util.js @@ -0,0 +1,27 @@ +var os = require('os'); +var fs = require('fs'); +var path = require('path'); +var platform = os.platform(); + +var createFile = function (path, size, callback) { + var cb = function (err) { + console.log('1mb.zip create', err ? 'error' : 'success'); + callback && callback(); + }; + if (fs.existsSync(path)) { + cb('file exist.'); + } else { + var cmd; + if (platform === 'win32') { + cmd = 'fsutil file createnew ' + path + ' ' + size; + } else if (platform === 'linux') { + cmd = 'dd if=/dev/zero of=' + path + ' bs=1 count=' + size; + } + var exec = require('child_process').exec; + exec(cmd, function (err, stdout, stderr) { + cb(err); + }); + } +}; + +exports.createFile = createFile; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..89732d2 --- /dev/null +++ b/index.js @@ -0,0 +1,5 @@ +/** + * 提供 npm 包引用的入口 + */ +var COS = require('./sdk/cos'); +module.exports = COS; \ No newline at end of file diff --git a/package.json b/package.json index 9e7f7e1..6b98dff 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "cos-nodejs-sdk-v5", - "version": "1.0.0", + "version": "1.1.0", "description": "cos nodejs sdk v5", "main": "index.js", "devDependencies": {}, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node demo/demo.js" }, "repository": { "type": "git", @@ -19,8 +19,9 @@ }, "homepage": "https://github.com/tencentyun/cos-nodejs-sdk-v5#readme", "dependencies": { - "eventproxy": "^0.3.5", "async": "^2.2.0", + "eventproxy": "^0.3.5", + "lodash": "^4.17.4", "request": "^2.81.0", "xml2js": "^0.4.17" } diff --git a/sdk/advance.js b/sdk/advance.js new file mode 100644 index 0000000..c1178f7 --- /dev/null +++ b/sdk/advance.js @@ -0,0 +1,815 @@ +var fs = require('fs'); +var EventProxy = require('eventproxy'); +var Async = require('async'); +var util = require('./util'); + + + +// 分片大小 +var SLICE_SIZE = 1 * 1024 * 1024; + +// 获取文件大小 +function getFileSize(params, callback) { + var FilePath = params.FilePath; + fs.stat(FilePath, function (err, stats) { + if (err) { + return callback(err); + } + + callback(null, { + FileSize: stats.size + }); + }); +} + + +// 文件分块上传全过程,暴露的分块上传接口 +function sliceUploadFile (params, callback) { + var proxy = new EventProxy(); + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var FilePath = params.FilePath; + var SliceSize = params.SliceSize || SLICE_SIZE; + var AsyncLimit = params.AsyncLimit || 1; + var StorageClass = params.StorageClass || 'Standard'; + var self = this; + + var onProgress = params.onProgress; + var onHashProgress = params.onHashProgress; + + // 上传过程中出现错误,返回错误 + proxy.all('error', function (errData) { + return callback(errData); + }); + + // 获取文件大小和 UploadId 成功之后,开始获取上传成功的分片信息 + proxy.all('get_file_size', 'get_upload_id', function (FileSizeData, UploadIdData) { + var FileSize = FileSizeData.FileSize; + var UploadId = UploadIdData.UploadId; + + params.FileSize = FileSize; + params.UploadId = UploadId; + + getUploadedParts.call(self, { + Bucket: Bucket, + Region: Region, + Key: Key, + UploadId: UploadId + }, function (err, data) { + if (err) { + return proxy.emit('error', err); + } + + proxy.emit('get_uploaded_parts', data); + }); + }); + + // 获取文件大小之后,开始计算分块 ETag 值(也就是 sha1值,需要前后加双引号 " ),HashProgressCallback 是计算每个分片 ETag 值之后的进度回调 + proxy.all('get_file_size', function (FileSizeData) { + var FileSize = FileSizeData.FileSize; + getSliceETag.call(self, { + FilePath: FilePath, + FileSize: FileSize, + SliceSize: SliceSize, + HashProgressCallback: onHashProgress + }, function (err, data) { + if (err) { + return proxy.emit('error', err); + } + + proxy.emit('get_slice_etag', data); + }); + + }); + + // 计算完分块的 ETag 值,以及获取到上传成功的文件分块的 ETag ,然后合并两者,更新需要上传的分块 + proxy.all('get_slice_etag', 'get_uploaded_parts', function (SliceETagData, UploadedPartsData) { + var Parts = UploadedPartsData.Parts || []; + var SliceETag = SliceETagData.SliceETag || []; + + var SliceList = fixSliceList.call(self, { + SliceETag: SliceETag, + Parts: Parts + }); + + uploadSliceList.call(self, { + Bucket: Bucket, + Region: Region, + Key: Key, + FilePath: FilePath, + SliceSize: SliceSize, + AsyncLimit: AsyncLimit, + SliceList: SliceList, + UploadId: params.UploadId, + FileSize: params.FileSize, + ProgressCallback: onProgress + }, function (err, data) { + if (err) { + return proxy.emit('error', err); + } + + proxy.emit('upload_slice_list', data); + + }); + }); + + // 上传分块完成,开始 uploadSliceComplete 操作 + proxy.all('upload_slice_list', function (SliceListData) { + var SliceList = SliceListData.SliceList; + + uploadSliceComplete.call(self, { + Bucket: Bucket, + Region: Region, + Key: Key, + UploadId: params.UploadId, + SliceList: SliceList + }, function (err, data) { + if (err) { + return proxy.emit('error', err); + } + proxy.emit('upload_slice_complete', data); + }); + }); + + // uploadSliceComplete 完成,成功回调 + proxy.all('upload_slice_complete', function (UploadCompleteData) { + callback(null, UploadCompleteData); + }); + + // 获取上传文件大小 + getFileSize({ + FilePath: FilePath + }, function (err, data) { + if (err) { + return proxy.emit('error', err); + } + proxy.emit('get_file_size', data); + }); + + // 获取文件 UploadId + getUploadId.call(self, { + Bucket: Bucket, + Region: Region, + Key: Key, + StorageClass: StorageClass + }, function (err, data) { + if (err) { + return proxy.emit('error', err); + } + + proxy.emit('get_upload_id', data); + }); +} + +// 获取上传的 UploadId +function getUploadIds(params, callback) { + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var StorageClass = params.StorageClass; + var self = this; + + + getAllListParts.call(self, { + Bucket: Bucket, + Region: Region, + Prefix: Key + }, function (err, data) { + if (err) { + return callback(err); + } + + var Upload = data || []; + + var UploadIds = []; + + for (var i = 0, len = Upload.length; i < len; i++) { + var item = Upload[i]; + if (item['Key'] == Key) { + if (StorageClass && item['StorageClass'] != StorageClass) { + continue; + } + UploadIds.push(item['UploadID']); + } + } + + return callback(null, { + UploadIds: UploadIds + }); + + }); +} + +// 获取符合条件的全部上传任务 (条件包括 Bucket, Region, Prefix) +function getAllListParts(params, callback) { + var UploadList = params.UploadList || []; + params.UploadList = UploadList; + var self = this; + + self.MultipartList(params, function (err, data) { + if (err) { + return callback(err); + } + + UploadList = UploadList.concat(data.Upload || []); + + if (data.IsTruncated == 'true') { + params.UploadList = UploadList; + params.KeyMarker = data.NextKeyMarker; + params.UploadIdMarker = data.NextUploadIdMarker; + return getAllListParts.call(self, params, callback); + } else { + delete params.UploadList; + return callback(null, UploadList); + } + + }); +} + +// 获取上传任务的 UploadId +function getUploadId(params, callback) { + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var StorageClass = params.StorageClass; + var self = this; + + var proxy = new EventProxy(); + + proxy.all('error', function (errData) { + return callback(errData); + }); + + // 获取已经存在的 UploadId + proxy.all('get_upload_id', function (UploadId) { + + // 如果已经有 UploadId 已存在,则无需重新创建 UploadId + if (UploadId) { + return callback(null, { + UploadId: UploadId + }); + } else { + // 不存在 UploadId, 直接初始化生成 UploadId + self.MultipartInit({ + Bucket: Bucket, + Region: Region, + Key: Key, + StorageClass: StorageClass + }, function (err, data) { + if (err) { + return callback(err); + } + + var UploadId = data.UploadId; + + if (!UploadId) { + return callback({ + Message: 'no upload id' + }); + } + + return callback(null, { + UploadId: UploadId + }); + }); + + } + }); + + // 获取符合条件的 UploadId 列表,因为同一个文件可以有多个上传任务。 + getUploadIds.call(self, { + Bucket: Bucket, + Region: Region, + Key: Key, + StorageClass: StorageClass + }, function (err, data) { + if (err) { + return proxy.emit('error', err); + } + var UploadIds = data.UploadIds || []; + var UploadId; + + // 取最后一个 UploadId 返回 + if (UploadIds.length) { + var len = UploadIds.length; + UploadId = UploadIds[len - 1]; + } + proxy.emit('get_upload_id', UploadId); + }); +} + +// 获取特定上传任务的分块列表 +function getUploadedParts(params, callback) { + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var UploadId = params.UploadId; + var PartNumberMarker = params.PartNumberMarker; + var Parts = params.Parts || []; + var self = this; + + params.Parts = Parts; + + self.MultipartListPart({ + Bucket: Bucket, + Region: Region, + Key: Key, + PartNumberMarker: PartNumberMarker, + UploadId: UploadId + }, function (err, data) { + if (err) { + return callback(err); + } + + var PartList = params.Parts || []; + PartList = PartList.concat(data.Part || []); + + // 分块结果被截断,分块结果不完整 + if (data.IsTruncated == 'true') { + params.Parts = PartList; + params.PartNumberMarker = data.NextPartNumberMarker; + return getUploadedParts.call(self, params, callback); + + } else { + delete params.Parts; + return callback(null, { + Parts: PartList + }); + } + }); +} + +function getSliceETag(params, cb) { + var FilePath = params.FilePath; + var SliceSize = params.SliceSize; + var FileSize = params.FileSize; + var SliceCount = Math.ceil(FileSize / SliceSize); + var FinishSliceCount = 0; + var HashProgressCallback = params.HashProgressCallback; + var self = this; + + var SliceETag = []; + var HashAsyncLimit = 1; + + for (var i = 0; i < SliceCount; i++) { + var item = { + PartNumber: i + 1, + Uploaded: false, + ETag: false + }; + + SliceETag.push(item); + } + + Async.mapLimit(SliceETag, HashAsyncLimit, function (SliceItem, callback) { + + var PartNumber = SliceItem['PartNumber'] * 1; + + getSliceSHA1({ + FileSize: FileSize, + FilePath: FilePath, + SliceSize: SliceSize, + PartNumber: PartNumber + }, function (err, sha1) { + if (err) { + return callback(err); + } + + SliceETag[PartNumber - 1].ETag = '"' + sha1 + '"'; + + if (HashProgressCallback && (typeof HashProgressCallback == 'function')) { + ++FinishSliceCount; + HashProgressCallback({ + PartNumber: PartNumber, + SliceSize: SliceSize, + FileSize: FileSize, + ETag: '"' + sha1 + '"' + }, parseInt(FinishSliceCount / SliceCount * 100) / 100); + } + + callback(null, sha1); + + }); + + }, function (err, datas) { + if (err) { + return cb(err); + } + + cb(null, { + SliceETag: SliceETag + }); + + }); +} + +function getSliceSHA1(params, callback) { + var FilePath = params.FilePath; + var SliceSize = params.SliceSize; + var FileSize = params.FileSize; + var PartNumber = params.PartNumber; + + var start = SliceSize * (PartNumber - 1); + var end = start + SliceSize; + + + if (end > FileSize) { + end = FileSize; + } + + end--; + + var Body = fs.createReadStream(FilePath, { + start: start, + end: end + }); + + util.getFileSHA(Body, function (err, data) { + if (err) { + return callback(err); + } + + callback(null, data); + }); + +} + +function fixSliceList(params) { + var SliceETag = params.SliceETag; + var Parts = params.Parts; + + var SliceCount = SliceETag.length; + + for (var i = 0, len = Parts.length; i < len; i++) { + var item = Parts[i]; + + var PartNumber = item['PartNumber'] * 1; + var ETag = item['ETag'] || ''; + + if (PartNumber > SliceCount) { + continue; + } + + if (SliceETag[PartNumber - 1].ETag == ETag) { + SliceETag[PartNumber - 1].Uploaded = true; + } + } + + return SliceETag; +} + +// 上传文件分块,包括 +/* + UploadId (上传任务编号) + AsyncLimit (并发量), + SliceList (上传的分块数组), + FilePath (本地文件的位置), + SliceSize (文件分块大小) + FileSize (文件大小) + ProgressCallback (上传成功之后的回调函数) + + */ +function uploadSliceList(params, cb) { + console.log('---------------- upload file -----------------'); + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var UploadId = params.UploadId; + var FileSize = params.FileSize; + var SliceSize = params.SliceSize; + var AsyncLimit = params.AsyncLimit; + var SliceList = params.SliceList; + var FilePath = params.FilePath; + var ProgressCallback = params.ProgressCallback; + var SliceCount = Math.ceil(FileSize / SliceSize); + var FinishSliceCount = 0; + var self = this; + + console.log('file name : ' + Key); + + Async.mapLimit(SliceList, AsyncLimit, function (SliceItem, callback) { + var PartNumber = SliceItem['PartNumber']; + var ETag = SliceItem['ETag']; + var Uploaded = SliceItem['Uploaded']; + + if (Uploaded) { + process.nextTick(function () { + + ++FinishSliceCount; + if (ProgressCallback && (typeof ProgressCallback == 'function')) { + // 分块上传成功,触发进度回调 + ProgressCallback({ + PartNumber: PartNumber, + SliceSize: SliceSize, + FileSize: FileSize + }, parseInt(FinishSliceCount / SliceCount * 100) / 100); + } + + callback(null, { + ETag: ETag, + PartNumber: PartNumber + }); + }); + + return; + } + + console.log('Async uploading...----- ' + PartNumber); + + + uploadSliceItem.call(self, { + Bucket: Bucket, + Region: Region, + Key: Key, + SliceSize: SliceSize, + FileSize: FileSize, + PartNumber: PartNumber, + UploadId: UploadId, + FilePath: FilePath, + SliceList: SliceList + }, function (err, data) { + if (err) { + return callback(err); + } + ++FinishSliceCount; + + callback(null, data); + + if (ProgressCallback && (typeof ProgressCallback == 'function')) { + // 分块上传成功,触发进度回调 + ProgressCallback({ + PartNumber: PartNumber, + SliceSize: SliceSize, + FileSize: FileSize + }, parseInt(FinishSliceCount / SliceCount * 100) / 100); + } + + return; + + }); + + }, function (err, datas) { + if (err) { + return cb(err); + } + + var data = { + datas: datas, + UploadId: UploadId, + SliceList: SliceList + }; + + cb(null, data); + }); +} + +// 上传指定分片 +function uploadSliceItem(params, callback) { + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var UploadId = params.UploadId; + var FileSize = params.FileSize; + var FilePath = params.FilePath; + var PartNumber = params.PartNumber * 1; + var SliceSize = params.SliceSize; + var SliceList = params.SliceList; + var self = this; + + var start = SliceSize * (PartNumber - 1); + + var ContentLength = SliceSize; + + var end = start + SliceSize; + + if (end > FileSize) { + end = FileSize; + ContentLength = end - start; + } + + end--; + + + var Body = fs.createReadStream(FilePath, { + start: start, + end: end + }); + + var ContentSha1 = SliceList[PartNumber * 1 - 1].ETag; + + self.MultipartUpload({ + Bucket: Bucket, + Region: Region, + Key: Key, + ContentLength: ContentLength, + ContentSha1: ContentSha1, + PartNumber: PartNumber, + UploadId: UploadId, + Body: Body + }, function (err, data) { + if (err) { + return callback(err); + } + + return callback(null, data); + }); +} + +// 完成分块上传 +function uploadSliceComplete(params, callback) { + console.log('---------------- upload complete -----------------'); + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var UploadId = params.UploadId; + var SliceList = params.SliceList; + var self = this; + + var Parts = []; + + for (var i = 0, len = SliceList.length; i < len; i++) { + var item = SliceList[i]; + var PartItem = { + PartNumber: item['PartNumber'], + ETag: item['ETag'] + }; + + Parts.push(PartItem); + } + + self.MultipartComplete({ + Bucket: Bucket, + Region: Region, + Key: Key, + UploadId: UploadId, + Parts: Parts + }, function (err, data) { + if (err) { + return callback(err); + } + + callback(null, data); + }); +} + +// 抛弃分块上传任务 +/* + AsyncLimit (抛弃上传任务的并发量), + UploadId (上传任务的编号,当 Level 为 task 时候需要) + Level (抛弃分块上传任务的级别,task : 抛弃指定的上传任务,file : 抛弃指定的文件对应的上传任务,其他值 :抛弃指定Bucket 的全部上传任务) + */ +function abortUploadTask(params, callback) { + console.log('----------------- abort upload task ------------------------'); + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var UploadId = params.UploadId; + var Level = params.Level || 'task'; + var AsyncLimit = params.AsyncLimit || 1; + var self = this; + + var ep = new EventProxy(); + + ep.all('error', function (errData) { + return callback(errData); + }); + + // 已经获取到需要抛弃的任务列表 + ep.all('get_abort_array', function (AbortArray) { + abortUploadTaskArray({ + Bucket: Bucket, + Region: Region, + Key: Key, + AsyncLimit: AsyncLimit, + AbortArray: AbortArray + }, function (err, data) { + if (err) { + return callback(err); + } + callback(null, data); + }); + }); + + + if (Level == 'task') { + // 单个任务级别的任务抛弃,抛弃指定 UploadId 的上传任务 + if (!UploadId) { + return callback('abort_upload_task_no_id'); + } + if (!Key) { + return callback('abort_upload_task_no_key'); + } + + ep.emit('get_abort_array', [{ + Key: Key, + UploadId: UploadId + }]); + + } else if (Level == 'file') { + // 文件级别的任务抛弃,抛弃该文件的全部上传任务 + if (!Key) { + return callback('abort_upload_task_no_key'); + } + + getAllListParts.call(self, { + Bucket: Bucket, + Region: Region, + Prefix: Key + }, function (err, data) { + if (err) { + return callback(err); + } + ep.emit('get_abort_array', data || []); + }); + + } else { + // Bucket 级别的任务抛弃,抛弃该 Bucket 下的全部上传任务 + + getAllListParts.call(self, { + Bucket: Bucket, + Region: Region + }, function (err, data) { + if (err) { + return callback(err); + } + ep.emit('get_abort_array', data || []); + }); + } +} + +// 批量抛弃分块上传任务 +function abortUploadTaskArray(params, callback) { + console.log('----------------- abort upload task array ------------------------'); + + var Bucket = params.Bucket; + var Region = params.Region; + var Key = params.Key; + var AbortArray = params.AbortArray; + var AsyncLimit = params.AsyncLimit; + var self = this; + + console.log(AbortArray); + + Async.mapLimit(AbortArray, AsyncLimit, function (AbortItem, callback) { + console.log('--- abort item ---' + AbortItem.Key); + if (Key && Key != AbortItem.Key) { + return callback(null, { + KeyNotMatch: true + }); + } + + var UploadId = AbortItem.UploadID; + + self.MultipartAbort({ + Bucket: Bucket, + Region: Region, + Key: AbortItem.Key, + UploadId: UploadId + }, function (err, data) { + var task = { + Bucket: Bucket, + Region: Region, + Key: AbortItem.Key, + UploadId: UploadId + }; + if (err) { + return callback(null, { + error: err, + task: task + }); + } + + return callback(null, { + error: false, + task: task + }); + }); + + }, function (err, datas) { + if (err) { + return callback(err); + } + + var successList = []; + var errorList = []; + + for (var i = 0, len = datas.length; i < len; i++) { + var item = datas[i]; + if (item['error']) { + errorList.push(item['task']); + } else { + successList.push(item['task']); + } + } + + return callback(null, { + successList: successList, + errorList: errorList + }); + }); +} + + +exports.sliceUploadFile = sliceUploadFile; +exports.abortUploadTask = abortUploadTask; \ No newline at end of file diff --git a/sdk/advanced_api.js b/sdk/advanced_api.js deleted file mode 100644 index ba2473b..0000000 --- a/sdk/advanced_api.js +++ /dev/null @@ -1,837 +0,0 @@ -var fs = require('fs'); -var EventProxy = require('eventproxy'); -var Async = require('async'); -var cos = require('./cos'); -var util = require('./util'); - -// 分片大小 -var SLICE_SIZE = 1 * 1024 * 1024; - -var MultipartInit = cos.MultipartInit; -var MultipartUpload = cos.MultipartUpload; -var MultipartComplete = cos.MultipartComplete; -var MultipartList = cos.MultipartList; -var MultipartListPart = cos.MultipartListPart; -var MultipartAbort = cos.MultipartAbort; - - - -// 获取文件大小 -function getFileSize(params, callback) { - var FilePath = params.FilePath; - fs.stat(FilePath, function(err, stats) { - if (err) { - return callback(err); - } - - callback(null, { - FileSize : stats.size - }); - }); -} - - -// 文件分块上传全过程,暴露的分块上传接口 -function sliceUploadFile(params, callback) { - var proxy = new EventProxy(); - var Bucket = params.Bucket; - var Region = params.Region; - var Key = params.Key; - var FilePath = params.FilePath; - var SliceSize = params.SliceSize || SLICE_SIZE; - var AsyncLimit = params.AsyncLimit || 1; - var StorageClass = params.StorageClass || 'Standard'; - - var onProgress = params.onProgress; - var onHashProgress = params.onHashProgress; - - - // 上传过程中出现错误,返回错误 - proxy.all('error', function(errData) { - return callback(errData); - }); - - // 获取文件大小和 UploadId 成功之后,开始获取上传成功的分片信息 - proxy.all('get_file_size', 'get_upload_id', function(FileSizeData, UploadIdData) { - var FileSize = FileSizeData.FileSize; - var UploadId = UploadIdData.UploadId; - - params.FileSize = FileSize; - params.UploadId = UploadId; - - getUploadedParts({ - Bucket : Bucket, - Region : Region, - Key : Key, - UploadId : UploadId - }, function(err, data) { - if (err) { - return proxy.emit('error', err); - } - - proxy.emit('get_uploaded_parts', data); - }); - }); - - // 获取文件大小之后,开始计算分块 ETag 值(也就是 sha1值,需要前后加双引号 " ),HashProgressCallback 是计算每个分片 ETag 值之后的进度回调 - proxy.all('get_file_size', function(FileSizeData) { - var FileSize = FileSizeData.FileSize; - getSliceETag({ - FilePath : FilePath, - FileSize : FileSize, - SliceSize : SliceSize, - HashProgressCallback : onHashProgress - }, function(err, data) { - if (err) { - return proxy.emit('error', err); - } - - proxy.emit('get_slice_etag', data); - }); - - }); - - // 计算完分块的 ETag 值,以及获取到上传成功的文件分块的 ETag ,然后合并两者,更新需要上传的分块 - proxy.all('get_slice_etag', 'get_uploaded_parts', function(SliceETagData ,UploadedPartsData) { - var Parts = UploadedPartsData.Parts || []; - var SliceETag = SliceETagData.SliceETag || []; - - var SliceList = fixSliceList({ - SliceETag : SliceETag, - Parts : Parts - }); - - uploadSliceList({ - Bucket : Bucket, - Region : Region, - Key : Key, - FilePath : FilePath, - SliceSize : SliceSize, - AsyncLimit : AsyncLimit, - SliceList : SliceList, - UploadId : params.UploadId, - FileSize : params.FileSize, - ProgressCallback : onProgress - }, function(err, data) { - if (err) { - return proxy.emit('error', err); - } - - proxy.emit('upload_slice_list', data); - - }); - }); - - // 上传分块完成,开始 uploadSliceComplete 操作 - proxy.all('upload_slice_list', function(SliceListData) { - var SliceList = SliceListData.SliceList; - - uploadSliceComplete({ - Bucket : Bucket, - Region : Region, - Key : Key, - UploadId : params.UploadId, - SliceList : SliceList - }, function(err, data) { - if (err) { - return proxy.emit('error', err); - } - - proxy.emit('upload_slice_complete', data); - }); - }); - - // uploadSliceComplete 完成,成功回调 - proxy.all('upload_slice_complete', function(UploadCompleteData) { - callback(UploadCompleteData); - }); - - - - // 获取上传文件大小 - getFileSize({ - FilePath : FilePath - }, function(err, data) { - if (err) { - return proxy.emit('error', err); - } - proxy.emit('get_file_size', data); - }); - - // 获取文件 UploadId - getUploadId({ - Bucket : Bucket, - Region : Region, - Key : Key, - StorageClass : StorageClass - }, function(err, data) { - if (err) { - return proxy.emit('error', err); - } - - proxy.emit('get_upload_id', data); - }); -} - -// 获取上传的 UploadId -function getUploadIds(params, callback) { - var Bucket = params.Bucket; - var Region = params.Region; - var Key = params.Key; - var StorageClass = params.StorageClass; - - - getAllListParts({ - Bucket : Bucket, - Region : Region, - Prefix : Key - }, function(err, data) { - if (err) { - return callback(err); - } - - var Upload = data || []; - - var UploadIds = []; - - for (var i=0,len=Upload.length;i FileSize) { - end = FileSize; - } - - end --; - - var Body = fs.createReadStream(FilePath, { - start : start, - end : end - }); - - util.getFileSHA(Body, function(err, data) { - if (err) { - return callback(err); - } - - callback(null, data); - }); - -} - -function fixSliceList(params) { - var SliceETag = params.SliceETag; - var Parts = params.Parts; - - var SliceCount = SliceETag.length; - - for (var i=0,len = Parts.length; i SliceCount) { - continue; - } - - if (SliceETag[PartNumber - 1].ETag == ETag) { - SliceETag[PartNumber - 1].Uploaded = true; - } - } - - return SliceETag; -} - -// 上传文件分块,包括 -/* - UploadId (上传任务编号) - AsyncLimit (并发量), - SliceList (上传的分块数组), - FilePath (本地文件的位置), - SliceSize (文件分块大小) - FileSize (文件大小) - ProgressCallback (上传成功之后的回调函数) - -*/ -function uploadSliceList(params, cb) { - console.log('---------------- upload file -----------------'); - var Bucket = params.Bucket; - var Region = params.Region; - var Key = params.Key; - var UploadId = params.UploadId; - var FileSize = params.FileSize; - var SliceSize = params.SliceSize; - var AsyncLimit = params.AsyncLimit; - var SliceList = params.SliceList; - var FilePath = params.FilePath; - var ProgressCallback = params.ProgressCallback; - - console.log('file name : ' + Key); - - Async.mapLimit(SliceList, AsyncLimit, function(SliceItem, callback) { - var PartNumber = SliceItem['PartNumber']; - - var ETag = SliceItem['ETag']; - - var Uploaded = SliceItem['Uploaded']; - - if (Uploaded) { - process.nextTick(function() { - - if (ProgressCallback && (typeof ProgressCallback == 'function')) { - // 分块上传成功,触发进度回调 - ProgressCallback({ - PartNumber : PartNumber, - SliceSize : SliceSize, - FileSize : FileSize - }); - } - - callback(null, { - ETag : ETag, - PartNumber : PartNumber - }); - }); - - return; - } - - console.log('Async uploading...----- ' + PartNumber); - - - uploadSliceItem({ - Bucket : Bucket, - Region : Region, - Key : Key, - SliceSize : SliceSize, - FileSize : FileSize, - PartNumber : PartNumber, - UploadId : UploadId, - FilePath : FilePath, - SliceList : SliceList - }, function(err, data) { - if (err) { - return callback(err); - } - - callback(null, data); - - if (ProgressCallback && (typeof ProgressCallback == 'function')) { - // 分块上传成功,触发进度回调 - ProgressCallback({ - PartNumber : PartNumber, - SliceSize : SliceSize, - FileSize : FileSize - }); - } - - return; - - }); - - }, function(err, datas) { - if (err) { - return cb(err); - } - - var data = { - datas : datas, - UploadId : UploadId, - SliceList : SliceList - }; - - cb(null, data); - }); -} - -// 上传指定分片 -function uploadSliceItem(params, callback) { - var Bucket = params.Bucket; - var Region = params.Region; - var Key = params.Key; - var UploadId = params.UploadId; - var FileSize = params.FileSize; - var FilePath = params.FilePath; - var PartNumber = params.PartNumber * 1; - var SliceSize = params.SliceSize; - var SliceList = params.SliceList; - - var start = SliceSize * (PartNumber - 1); - - var ContentLength = SliceSize; - - var end = start + SliceSize; - - if (end > FileSize) { - end = FileSize; - ContentLength = end - start; - } - - end -- ; - - - var Body = fs.createReadStream(FilePath, { - start : start, - end : end - }); - - var ContentSha1 = SliceList[PartNumber * 1 - 1].ETag; - - MultipartUpload({ - Bucket : Bucket, - Region : Region, - Key : Key, - ContentLength : ContentLength, - ContentSha1 : ContentSha1, - PartNumber : PartNumber, - UploadId : UploadId, - Body : Body - }, function(err, data) { - if (err) { - return callback(err); - } - - return callback(null, data); - }); -} - -// 完成分块上传 -function uploadSliceComplete(params, callback) { - console.log('---------------- upload complete -----------------'); - var Bucket = params.Bucket; - var Region = params.Region; - var Key = params.Key; - var UploadId = params.UploadId; - var SliceList = params.SliceList; - - var Parts = []; - - for (var i=0,len=SliceList.length; i -1) { + headers[key] = params[key]; + } + } + + var body = params['Body']; + + var readStream = body; + + if (body && (typeof body == 'string')) { + readStream = fs.createReadStream(body); + } + + return submitRequest.call(this, { + method: 'PUT', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + headers: headers, + body: readStream, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + if (data && data.headers && data.headers['etag']) { + return callback(null, { + 'ETag': data.headers['etag'] + }); + } + + return callback(null, data); + }); +} + +/** + * 删除 object + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {function} callback 回调函数,必须 + * @param {object} err 请求失败的错误,如果请求成功,则为空。 + * @param {object} data 删除操作成功之后返回的数据,如果删除操作成功,则返回 success 为 true, 并且附带原先 object 的 url + * @param {Boolean} data.Success 删除操作是否成功,成功则为 true,否则为 false + * @param {Boolean} data.BucketNotFound 请求的 object 所在的 bucket 是否不存在,如果为 true,则说明该 bucket 不存在 + */ +function deleteObject(params, callback) { + return submitRequest.call(this, { + method: 'DELETE', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + var statusCode = err.statusCode; + if (statusCode && statusCode == 204) { + return callback(null, { + DeleteObjectSuccess: true + }); + } else if (statusCode && statusCode == 404) { + return callback(null, { + BucketNotFound: true + }); + } else { + return callback(err); + } + } + + return callback(null, { + DeleteObjectSuccess: true + }); + + }); +} + +/** + * 获取 object 的 权限列表 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + * @return {object} data.AccessControlPolicy 权限列表 + */ +function getObjectACL(params, callback) { + + return submitRequest.call(this, { + method: 'GET', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + action: '?acl', + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + var Grant = data.AccessControlPolicy.AccessControlList.Grant || []; + + if (!(Grant instanceof Array)) { + Grant = [Grant]; + } + + data.AccessControlPolicy.AccessControlList.Grant = Grant; + + return callback(null, data.AccessControlPolicy || {}); + }); +} + +/** + * 设置 object 的 权限列表 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + */ +function putObjectACL(params, callback) { + var headers = {}; + + headers['x-cos-acl'] = params['ACL']; + headers['x-cos-grant-read'] = params['GrantRead']; + headers['x-cos-grant-write'] = params['GrantWrite']; + headers['x-cos-grant-full-control'] = params['GrantFullControl']; + + return submitRequest.call(this, { + method: 'PUT', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + action: '?acl', + headers: headers, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + return callback(null, { + PutObjectACLSuccess: true + }); + }); +} + +/** + * Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + */ +function optionsObject(params, callback) { + var headers = {}; + + headers['Origin'] = params['Origin']; + headers['Access-Control-Request-Method'] = params['AccessControlRequestMethod']; + headers['Access-Control-Request-Headers'] = params['AccessControlRequestHeaders']; + + return submitRequest.call(this, { + method: 'OPTIONS', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + headers: headers, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + if (err.statusCode && err.statusCode == 403) { + return callback(null, { + OptionsForbidden: true + }); + } + return callback(err); + } + + data = data || {}; + + var resHeaders = data.headers || {}; + + var retData = {}; + + retData['AccessControlAllowOrigin'] = resHeaders['access-control-allow-origin']; + retData['AccessControlAllowMethods'] = resHeaders['access-control-allow-methods']; + retData['AccessControlAllowHeaders'] = resHeaders['access-control-allow-headers']; + retData['AccessControlExposeHeaders'] = resHeaders['access-control-expose-headers']; + retData['AccessControlMaxAge'] = resHeaders['access-control-max-age']; + + return callback(null, retData); + }); +} + +/** + * @params** (Object) : 参数列表 + * Bucket —— (String) : Bucket 名称 + * Region —— (String) : 地域名称 + * Key —— (String) : 文件名称 + * CopySource —— (String) : 源文件URL绝对路径,可以通过versionid子资源指定历史版本 + * ACL —— (String) : 允许用户自定义文件权限。有效值:private,public-read默认值:private。 + * GrantRead —— (String) : 赋予被授权者读的权限,格式 x-cos-grant-read: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。 + * GrantWrite —— (String) : 赋予被授权者写的权限,格式 x-cos-grant-write: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。 + * GrantFullControl —— (String) : 赋予被授权者读写权限,格式 x-cos-grant-full-control: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。 + * MetadataDirective —— (String) : 是否拷贝元数据,枚举值:Copy, Replaced,默认值Copy。假如标记为Copy,忽略Header中的用户元数据信息直接复制;假如标记为Replaced,按Header信息修改元数据。当目标路径和原路径一致,即用户试图修改元数据时,必须为Replaced + * CopySourceIfModifiedSince —— (String) : 当Object在指定时间后被修改,则执行操作,否则返回412。可与x-cos-copy-source-If-None-Match一起使用,与其他条件联合使用返回冲突。 + * CopySourceIfUnmodifiedSince —— (String) : 当Object在指定时间后未被修改,则执行操作,否则返回412。可与x-cos-copy-source-If-Match一起使用,与其他条件联合使用返回冲突。 + * CopySourceIfMatch —— (String) : 当Object的Etag和给定一致时,则执行操作,否则返回412。可与x-cos-copy-source-If-Unmodified-Since一起使用,与其他条件联合使用返回冲突。 + * CopySourceIfNoneMatch —— (String) : 当Object的Etag和给定不一致时,则执行操作,否则返回412。可与x-cos-copy-source-If-Modified-Since一起使用,与其他条件联合使用返回冲突。 + * StorageClass —— (String) : 存储级别,枚举值:存储级别,枚举值:Standard, Standard_IA,Nearline;默认值:Standard + * CacheControl —— (String) : 指定所有缓存机制在整个请求/响应链中必须服从的指令。 + * ContentDisposition —— (String) : MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件 + * ContentEncoding —— (String) : HTTP 中用来对「采用何种编码格式传输正文」进行协定的一对头部字段 + * ContentLength —— (String) : 设置响应消息的实体内容的大小,单位为字节 + * ContentType —— (String) : RFC 2616 中定义的 HTTP 请求内容类型(MIME),例如text/plain + * Expect —— (String) : 请求的特定的服务器行为 + * Expires —— (String) : 响应过期的日期和时间 + * ContentLanguage —— (String) : 指定内容语言 + * x-cos-meta-* —— (String) : 允许用户自定义的头部信息,将作为 Object 元数据返回。大小限制2K。 + */ +function putObjectCopy(params, callback) { + var headers = {}; + + headers['x-cos-copy-source'] = params['CopySource']; + headers['x-cos-metadata-directive'] = params['MetadataDirective']; + headers['x-cos-copy-source-If-Modified-Since'] = params['CopySourceIfModifiedSince']; + headers['x-cos-copy-source-If-Unmodified-Since'] = params['CopySourceIfUnmodifiedSince']; + headers['x-cos-copy-source-If-Match'] = params['CopySourceIfMatch']; + headers['x-cos-copy-source-If-None-Match'] = params['CopySourceIfNoneMatch']; + headers['x-cos-storage-class'] = params['StorageClass']; + headers['x-cos-acl'] = params['ACL']; + headers['x-cos-grant-read'] = params['GrantRead']; + headers['x-cos-grant-write'] = params['GrantWrite']; + headers['x-cos-grant-full-control'] = params['GrantFullControl']; + headers['Cache-Control'] = params['CacheControl']; + headers['Content-Disposition'] = params['ContentDisposition']; + headers['Content-Encoding'] = params['ContentEncoding']; + headers['Content-Length'] = params['ContentLength']; + headers['Content-Type'] = params['ContentType']; + headers['Expect'] = params['Expect']; + headers['Expires'] = params['Expires']; + headers['x-cos-content-sha1'] = params['ContentSha1']; + + for (var key in params) { + if (key.indexOf('x-cos-meta-') > -1) { + headers[key] = params[key]; + } + } + + return submitRequest.call(this, { + method: 'PUT', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + headers: headers, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + return callback(null, data.CopyObjectResult || {}); + }); +} + + +function deleteMultiplDeleteMultipleObjectObject(params, callback) { + var headers = {}; + + headers['Content-Type'] = 'application/xml'; + + var Objects = params.Objects || {}; + var Quiet = params.Quiet; + + var DeleteConfiguration = { + Delete: { + Object: Objects, + Quiet: Quiet || false + } + }; + + var xml = util.json2xml(DeleteConfiguration); + + headers['Content-MD5'] = util.binaryBase64(util.md5(xml)); + headers['Content-Length'] = Buffer.byteLength(xml, 'utf8'); + + return submitRequest.call(this, { + method: 'POST', + Bucket: params.Bucket, + Region: params.Region, + body: xml, + action: '/?delete', + headers: headers, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + var Deleted = data.DeleteResult.Deleted || []; + var Errors = data.DeleteResult.Error || []; + + if (!(Deleted instanceof Array)) { + Deleted = [Deleted]; + } + + if (!(Errors instanceof Array)) { + Errors = [Errors]; + } + + data.DeleteResult.Error = Errors; + data.DeleteResult.Deleted = Deleted; + + return callback(null, data.DeleteResult || {}); + }); +} + + +// ----------------------------------------- 分块上传相关部分 ---------------------------------- + + +/** + * 初始化分块上传 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {string} params.CacheControl RFC 2616 中定义的缓存策略,将作为 Object 元数据保存,非必须 + * @param {string} params.ContentDisposition RFC 2616 中定义的文件名称,将作为 Object 元数据保存 ,非必须 + * @param {string} params.ContentEncoding RFC 2616 中定义的编码格式,将作为 Object 元数据保存,非必须 + * @param {string} params.ContentType RFC 2616 中定义的内容类型(MIME),将作为 Object 元数据保存,非必须 + * @param {string} params.Expires RFC 2616 中定义的过期时间,将作为 Object 元数据保存,非必须 + * @param {string} params.ACL 允许用户自定义文件权限,非必须 + * @param {string} params.GrantRead 赋予被授权者读的权限 ,非必须 + * @param {string} params.GrantWrite 赋予被授权者写的权限 ,非必须 + * @param {string} params.GrantFullControl 赋予被授权者读写权限 ,非必须 + * @param {string} params.StorageClass 设置Object的存储级别,枚举值:Standard,Standard_IA,Nearline,非必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + * @return {object} data.InitiateMultipartUploadResult 初始化上传信息,包括 Bucket(Bucket名称), Key(文件名称) 和 UploadId (上传任务ID) + */ +function MultipartInit(params, callback) { + var headers = {}; + + headers['Cache-Control'] = params['CacheControl']; + headers['Content-Disposition'] = params['ContentDisposition']; + headers['Content-Encoding'] = params['ContentEncoding']; + headers['Content-Type'] = params['ContentType']; + headers['Expires'] = params['Expires']; + + headers['x-cos-acl'] = params['ACL']; + headers['x-cos-grant-read'] = params['GrantRead']; + headers['x-cos-grant-write'] = params['GrantWrite']; + headers['x-cos-grant-full-control'] = params['GrantFullControl']; + headers['x-cos-storage-class'] = params['StorageClass']; + + for (var key in params) { + if (key.indexOf('x-cos-meta-') > -1) { + headers[key] = params[key]; + } + } + + return submitRequest.call(this, { + method: 'POST', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + action: '?uploads', + headers: headers, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + if (data && data.InitiateMultipartUploadResult) { + return callback(null, data.InitiateMultipartUploadResult); + } + + return callback(null, data); + + + }); +} + +/** + * 分块上传 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {string} params.ContentLength RFC 2616 中定义的 HTTP 请求内容长度(字节),非必须 + * @param {string} params.Expect 当使用 Expect: 100-continue 时,在收到服务端确认后,才会发送请求内容,非必须 + * @param {string} params.ContentSha1 RFC 3174 中定义的 160-bit 内容 SHA-1 算法校验值,非必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + * @return {object} data.ETag 返回的文件分块 sha1 值 + */ +function MultipartUpload(params, callback) { + var headers = {}; + + headers['Content-Length'] = params['ContentLength']; + headers['Expect'] = params['Expect']; + headers['x-cos-content-sha1'] = params['ContentSha1']; + + var PartNumber = params['PartNumber']; + var UploadId = params['UploadId']; + + var action = '?partNumber=' + PartNumber + '&uploadId=' + UploadId; + + var body = params.Body; + + var req = submitRequest.call(this, { + method: 'PUT', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + action: action, + headers: headers, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + data['headers'] = data['headers'] || {}; + + return callback(null, { + ETag: data['headers']['etag'] || '' + }); + + }); + + return body.pipe(req); +} + +/** + * 完成分块上传 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {array} params.Parts 分块信息列表,必须 + * @param {string} params.Parts[i].PartNumber 块编号,必须 + * @param {string} params.Parts[i].ETag 分块的 sha1 校验值 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + * @return {object} data.CompleteMultipartUpload 完成分块上传后的文件信息,包括Location, Bucket, Key 和 ETag + */ +function MultipartComplete(params, callback) { + var headers = {}; + + headers['Content-Type'] = 'application/xml'; + + var UploadId = params.UploadId; + + var action = '?uploadId=' + UploadId; + + var Parts = params['Parts']; + + for (var i = 0, len = Parts.length; i < len; i++) { + if (Parts[i]['ETag'].indexOf('"') == 0) { + continue; + } + Parts[i]['ETag'] = '"' + Parts[i]['ETag'] + '"'; + } + + var PartData = { + 'CompleteMultipartUpload': { + 'Part': Parts + } + }; + + var xml = util.json2xml(PartData); + + headers['Content-length'] = Buffer.byteLength(xml, 'utf8'); + + return submitRequest.call(this, { + method: 'POST', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + action: action, + body: xml, + headers: headers, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + return callback(null, data.CompleteMultipartUpload || {}); + }); +} + +/** + * 分块上传任务列表查询 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Delimiter 定界符为一个符号,如果有Prefix,则将Prefix到delimiter之间的相同路径归为一类,定义为Common Prefix,然后列出所有Common Prefix。如果没有Prefix,则从路径起点开始,非必须 + * @param {string} params.EncodingType 规定返回值的编码方式,非必须 + * @param {string} params.Prefix 前缀匹配,用来规定返回的文件前缀地址,非必须 + * @param {string} params.MaxUploads 单次返回最大的条目数量,默认1000,非必须 + * @param {string} params.KeyMarker 与upload-id-marker一起使用
当upload-id-marker未被指定时,ObjectName字母顺序大于key-marker的条目将被列出
当upload-id-marker被指定时,ObjectName字母顺序大于key-marker的条目被列出,ObjectName字母顺序等于key-marker同时UploadID大于upload-id-marker的条目将被列出,非必须 + * @param {string} params.UploadIdMarker 与key-marker一起使用
当key-marker未被指定时,upload-id-marker将被忽略
当key-marker被指定时,ObjectName字母顺序大于key-marker的条目被列出,ObjectName字母顺序等于key-marker同时UploadID大于upload-id-marker的条目将被列出,非必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + * @return {object} data.ListMultipartUploadsResult 分块上传任务信息 + */ +function MultipartList(params, callback) { + var reqParams = {}; + + reqParams['delimiter'] = params['Delimiter']; + reqParams['encoding-type'] = params['EncodingType']; + reqParams['prefix'] = params['Prefix']; + + reqParams['max-uploads'] = params['MaxUploads']; + + reqParams['key-marker'] = params['KeyMarker']; + reqParams['upload-id-marker'] = params['UploadIdMarker']; + + reqParams = util.clearKey(reqParams); + + + return submitRequest.call(this, { + method: 'GET', + Bucket: params.Bucket, + Region: params.Region, + action: '/?uploads&' + querystring.stringify(reqParams), + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + if (data && data.ListMultipartUploadsResult) { + var Upload = data.ListMultipartUploadsResult.Upload || []; + + var CommonPrefixes = data.ListMultipartUploadsResult.CommonPrefixes || []; + + + if (!(CommonPrefixes instanceof Array)) { + CommonPrefixes = [CommonPrefixes]; + } + + if (!(Upload instanceof Array)) { + Upload = [Upload]; + } + + data.ListMultipartUploadsResult.Upload = Upload; + data.ListMultipartUploadsResult.CommonPrefixes = CommonPrefixes; + } + + return callback(null, data.ListMultipartUploadsResult || {}); + }); +} + +/** + * 上传的分块列表查询 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {string} params.UploadId 标示本次分块上传的ID,必须 + * @param {string} params.EncodingType 规定返回值的编码方式,非必须 + * @param {string} params.MaxParts 单次返回最大的条目数量,默认1000,非必须 + * @param {string} params.PartNumberMarker 默认以UTF-8二进制顺序列出条目,所有列出条目从marker开始,非必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + * @return {object} data.ListMultipartUploadsResult 分块信息 + */ +function MultipartListPart(params, callback) { + var reqParams = {}; + + reqParams['uploadId'] = params['UploadId']; + reqParams['encoding-type'] = params['EncodingType']; + reqParams['max-parts'] = params['MaxParts']; + reqParams['part-number-marker'] = params['PartNumberMarker']; + + + return submitRequest.call(this, { + method: 'GET', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + qs: reqParams, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + data = data || {}; + + var Part = data.ListPartsResult.Part || []; + + if (!(Part instanceof Array)) { + Part = [Part]; + } + + data.ListPartsResult.Part = Part; + + return callback(null, data.ListPartsResult || {}); + }); +} + +/** + * 抛弃分块上传 + * @param {object} params 参数对象,必须 + * @param {string} params.Bucket Bucket名称,必须 + * @param {string} params.Region 地域名称,必须 + * @param {string} params.Key object名称,必须 + * @param {string} params.UploadId 标示本次分块上传的ID,必须 + * @param {function} callback 回调函数,必须 + * @return {object} err 请求失败的错误,如果请求成功,则为空。 + * @return {object} data 返回的数据 + */ +function MultipartAbort(params, callback) { + var reqParams = {}; + + reqParams['uploadId'] = params['UploadId']; + + return submitRequest.call(this, { + method: 'DELETE', + Bucket: params.Bucket, + Region: params.Region, + Key: params.Key, + qs: reqParams, + needHeaders: true, + Appid: params.Appid, + SecretId: params.SecretId, + SecretKey: params.SecretKey + }, function (err, data) { + if (err) { + return callback(err); + } + + return callback(null, { + MultipartAbortSuccess: true + }); + }); +} + +/** + * 获取签名 + * @param {object} params 参数对象,必须 + * @param {string} params.method Bucket名称,必须 + * @param {string} params.pathname 地域名称,必须 + * @param {string} params.params object名称,必须 + * @param {string} params.headers object名称,必须 + * @return {string} data 返回签名字符串 + */ +function getAuth(params) { + var options = util.extend({}, params); + options.SecretId = params.SecretId || this.SecretId || ''; + options.SecretKey = params.SecretKey || this.SecretKey || ''; + return util.getAuth(options); +} + + +/** + * 私有方法 + */ + +// 生成操作 url +function getUrl(params) { + var bucket = params.bucket; + var region = params.region; + var object = params.object; + var action = params.action; + var appid = params.appid; + + var url = 'http://' + bucket + '-' + appid + '.' + region + '.myqcloud.com'; + + if (object) { + url += '/' + encodeURIComponent(object); + } + + if (action) { + url += action; + } + + return url; +} + +// 检测参数是否填写完全 +function checkParamsRequire(callerName, params) { + var bucket = params.Bucket; + var region = params.Region; + var object = params.Key; + + if (callerName.indexOf('Bucket') > -1 || callerName == 'deleteMultipleObject' || callerName == 'MultipartList') { + if (!bucket || !region) { + return false; + } + + return true; + } + + if (callerName.indexOf('Object') > -1) { + if (!bucket || !region || !object) { + return false; + } + + return true; + } + + if (callerName.indexOf('Multipart') > -1) { + if (!bucket || !region || !object) { + return false; + } + + return true; + } + +} + +// 发起请求 +function submitRequest(params, callback) { + var bucket = params.Bucket; + var region = params.Region; + var object = params.Key; + var action = params.action; + var method = params.method || 'GET'; + var headers = params.headers || {}; + var url = params.url; + var body = params.body; + var json = params.json; + + // 通过调用的函数名确定需要的参数 + var callerName = arguments.callee.caller.name; + if (!checkParamsRequire(callerName, params) && callerName !== 'getService') { + return callback({ + error: 'lack of required params' + }); + } + + var needHeaders = params.needHeaders; + var rawBody = params.rawBody; + + var qs = params.qs; + + var appid = params.Appid || this.Appid || ''; + var secretId = params.SecretId || this.SecretId || ''; + var secretKey = params.SecretKey || this.SecretKey || ''; + + var opt = { + url: url || getUrl({ + bucket: bucket, + region: region, + object: object, + action: action, + appid: appid, + secretId: secretId, + secretKey: secretKey + }), + method: method, + headers: headers, + qs: qs, + body: body, + json: json, + // 这里的 proxy 用于处理内网网关限制,代理转发华南园区的请求,华北园区无需代理 + //'proxy':'http://dev-proxy.oa.com:8080' + }; + + + if (object) { + object = '/' + object; + } + + // 获取签名 + opt.headers.Authorization = util.getAuth({ + method: opt.method, + pathname: object || '/', + appid: appid, + secretId: secretId, + secretKey: secretKey + }); + + // 预先处理 undefine 的属性 + if (opt.headers) { + opt.headers = util.clearKey(opt.headers); + } + + if (opt.qs) { + opt.qs = util.clearKey(opt.qs); + } + + var p = REQUEST(opt, function (err, response, body) { + + // 请求错误,发生网络错误 + if (err) { + return callback({ + error: err + }); + } + + var statusCode = response.statusCode; + var jsonRes; + + try { + jsonRes = util.xml2json(body) || {}; + } catch (e) { + jsonRes = body || {}; + } + + // 请求返回码不为 200 + if (statusCode !== 200) { + return callback({ + statusCode: statusCode, + error: jsonRes.Error || jsonRes + }); + } + + // 不对 body 进行转换,body 直接挂载返回 + if (rawBody) { + jsonRes = {}; + jsonRes.body = body; + } + + // 如果需要头部信息,则 headers 挂载返回 + if (needHeaders) { + jsonRes.headers = response.headers || {}; + } + + if (jsonRes.Error) { + return callback({ + statusCode: statusCode, + error: jsonRes.Error + }); + } + + return callback(null, jsonRes); + }); + + return p; +} + + +// Bucket 相关方法 +exports.getService = getService; +exports.getBucket = getBucket; +exports.headBucket = headBucket; +exports.putBucket = putBucket; +exports.deleteBucket = deleteBucket; +exports.getBucketACL = getBucketACL; +exports.putBucketACL = putBucketACL; +exports.getBucketCORS = getBucketCORS; +exports.putBucketCORS = putBucketCORS; +exports.deleteBucketCORS = deleteBucketCORS; +exports.getBucketLocation = getBucketLocation; +exports.getBucketTagging = getBucketTagging; +exports.putBucketTagging = putBucketTagging; +exports.deleteBucketTagging = deleteBucketTagging; +exports.getBucketPolicy = getBucketPolicy; +exports.putBucketPolicy = putBucketPolicy; +// exports.getBucketLifecycle = getBucketLifecycle; +// exports.putBucketLifecycle = putBucketLifecycle; +// exports.deleteBucketLifecycle = deleteBucketLifecycle; + +// Object 相关方法 +exports.getObject = getObject; +exports.headObject = headObject; +exports.putObject = putObject; +exports.deleteObject = deleteObject; +exports.getObjectACL = getObjectACL; +exports.putObjectACL = putObjectACL; +exports.optionsObject = optionsObject; +exports.putObjectCopy = putObjectCopy; + +// 分块上传相关方法 +exports.MultipartInit = MultipartInit; +exports.MultipartUpload = MultipartUpload; +exports.MultipartComplete = MultipartComplete; +exports.MultipartList = MultipartList; +exports.MultipartListPart = MultipartListPart; +exports.MultipartAbort = MultipartAbort; +exports.DeleteMultipleObject = deleteMultiplDeleteMultipleObjectObject; + +// 工具方法 +exports.getAuth = getAuth; \ No newline at end of file diff --git a/sdk/config.js b/sdk/config.js deleted file mode 100644 index fd5e7cd..0000000 --- a/sdk/config.js +++ /dev/null @@ -1,30 +0,0 @@ -var fs = require('fs'); -var path = require('path'); - -// TODO -// 查看您对应的appid相关信息并填充 -// TODO - -exports.APPID = ''; -exports.SECRET_ID = ''; -exports.SECRET_KEY = ''; - -var pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../', 'package.json'))); -var ua = function() { - return 'cos-node-sdk-'+pkg.version; -}; - -// 签名的有效时间 -exports.EXPIRED_SECONDS = 60; - -exports.recvTimeout = 30000; -exports.USER_AGENT = ua; - -// timeout单位秒 -exports.setAppInfo = function(appid, secretId, secretKey, timeout) { - var timeout = timeout || 30; - module.exports.APPID = appid; - module.exports.SECRET_ID = secretId; - module.exports.SECRET_KEY = secretKey; - module.exports.recvTimeout = timeout * 1000; -}; \ No newline at end of file diff --git a/sdk/cos.js b/sdk/cos.js index d631c92..0ca230f 100644 --- a/sdk/cos.js +++ b/sdk/cos.js @@ -1,1992 +1,17 @@ -var querystring = require('querystring'); -var REQUEST = require('request'); -var util = require('./util'); -var config = require('./config'); -var fs = require('fs'); - - -// ---------------------------------------- Bucket 相关 api ------------------------------------ - -/** - * 获取用户的 bucket 列表 - * @param {Object} callback 回调函数,必须,下面为参数列表 - * 无特殊参数 - */ -function getService(params, callback) { - return submitRequest({ - url : 'http://service.cos.myqcloud.com', - method : 'GET', - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - if (data && data.ListAllMyBucketsResult - && data.ListAllMyBucketsResult.Buckets - && data.ListAllMyBucketsResult.Buckets.Bucket) { - var buckets = data.ListAllMyBucketsResult.Buckets.Bucket || []; - - if (!(buckets instanceof Array)) { - buckets = [buckets]; - } - - data.ListAllMyBucketsResult.Buckets = buckets; - } - - return callback(null, data.ListAllMyBucketsResult); - }); -} - -/** - * 查看是否存在该Bucket,是否有权限访问 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {Boolean} data.BucketExist Bucket是否存在 - * @return {Boolean} data.BucketAuth 是否有 Bucket 的访问权限 - */ -function headBucket(params, callback) { - return submitRequest({ - Bucket : params.Bucket, - Region : params.Region, - method : 'HEAD', - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, body) { - if (err) { - var statusCode = err.statusCode; - if (statusCode && statusCode == 404) { - return callback(null, { - BucketExist : false, - BucketAuth : false - }); - } else if (statusCode && statusCode == 403) { - return callback(null, { - BucketExist : true, - BucketAuth : false - }); - } else { - return callback(err); - } - } - - return callback(null, { - BucketExist : true, - BucketAuth : true - }); - }); -} - -/** - * 获取 Bucket 下的 object 列表 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Prefix 前缀匹配,用来规定返回的文件前缀地址,非必须 - * @param {string} params.Delimiter 定界符为一个符号,如果有Prefix,则将Prefix到delimiter之间的相同路径归为一类,非必须 - * @param {string} params.Marker 默认以UTF-8二进制顺序列出条目,所有列出条目从marker开始,非必须 - * @param {string} params.MaxKeys 单次返回最大的条目数量,默认1000,非必须 - * @param {string} params.EncodingType 规定返回值的编码方式,非必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.ListBucketResult 返回的 object 列表信息 - */ -function getBucket(params, callback) { - var reqParams = {}; - reqParams['prefix'] = params['Prefix']; - reqParams['delimiter'] = params['Delimiter']; - reqParams['marker'] = params['Marker']; - reqParams['max-keys'] = params['MaxKeys']; - reqParams['encoding-type'] = params['EncodingType']; - - return submitRequest({ - method : 'GET', - Bucket : params.Bucket, - Region : params.Region, - qs : reqParams, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - var contents = data.ListBucketResult.Contents || []; - var CommonPrefixes = data.ListBucketResult.CommonPrefixes || []; - - if (!(contents instanceof Array)) { - contents = [contents]; - } - - if (!(CommonPrefixes instanceof Array)) { - CommonPrefixes = [CommonPrefixes]; - } - - data.ListBucketResult.Contents = contents; - data.ListBucketResult.CommonPrefixes = CommonPrefixes; - - return callback(null, data.ListBucketResult || {}); - }); -} - -/** - * 创建 Bucket,并初始化访问权限 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.ACL 用户自定义文件权限,可以设置:private,public-read;默认值:private,非必须 - * @param {string} params.GrantRead 赋予被授权者读的权限,格式x-cos-grant-read: uin=" ",uin=" ",非必须 - * @param {string} params.GrantWrite 赋予被授权者写的权限,格式x-cos-grant-write: uin=" ",uin=" ",非必须 - * @param {string} params.GrantFullControl 赋予被授权者读写权限,格式x-cos-grant-full-control: uin=" ",uin=" ",非必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {string} data.Location 操作地址 - */ -function putBucket (params, callback) { - var headers = {}; - headers['x-cos-acl'] = params['ACL']; - headers['x-cos-grant-read'] = params['GrantRead']; - headers['x-cos-grant-write'] = params['GrantWrite']; - headers['x-cos-grant-full-control'] = params['GrantFullControl']; - - - return submitRequest({ - method : 'PUT', - Bucket : params.Bucket, - Region : params.Region, - headers : headers, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - return callback(null, { - Location : getUrl({ - bucket : params.Bucket, - region : params.Region, - appid : params.Appid || config.APPID - }) - }); - }); -} - -/** - * 删除 Bucket - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {string} data.Location 操作地址 - */ -function deleteBucket (params, callback) { - return submitRequest({ - method : 'DELETE', - Bucket : params.Bucket, - Region : params.Region, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - if (err.statusCode && err.statusCode == 204) { - return callback(null, { - DeleteBucketSuccess : true - }); - } - return callback(err); - } - - return callback(null, { - DeleteBucketSuccess : true - }); - }); -} - -/** - * 获取 Bucket 的 权限列表 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.AccessControlPolicy 访问权限信息 - */ -function getBucketACL (params, callback) { - - return submitRequest({ - method : 'GET', - Bucket : params.Bucket, - Region : params.Region, - action : '/?acl', - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - var Grant = data.AccessControlPolicy.AccessControlList.Grant || []; - - if (!(Grant instanceof Array)) { - Grant = [Grant]; - } - - data.AccessControlPolicy.AccessControlList.Grant = Grant; - - return callback(null, data.AccessControlPolicy || {}); - }); -} - -/** - * 设置 Bucket 的 权限列表 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.ACL 用户自定义文件权限,可以设置:private,public-read;默认值:private,非必须 - * @param {string} params.GrantRead 赋予被授权者读的权限,格式x-cos-grant-read: uin=" ",uin=" ",非必须 - * @param {string} params.GrantWrite 赋予被授权者写的权限,格式x-cos-grant-write: uin=" ",uin=" ",非必须 - * @param {string} params.GrantFullControl 赋予被授权者读写权限,格式x-cos-grant-full-control: uin=" ",uin=" ",非必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - */ -function putBucketACL (params, callback) { - var headers = {}; - - headers['x-cos-acl'] = params['ACL']; - headers['x-cos-grant-read'] = params['GrantRead']; - headers['x-cos-grant-write'] = params['GrantWrite']; - headers['x-cos-grant-full-control'] = params['GrantFullControl']; - - - return submitRequest({ - method : 'PUT', - Bucket : params.Bucket, - Region : params.Region, - action : '/?acl', - headers : headers, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - return callback(null, { - BucketGrantSuccess : true - }); - }); -} - -/** - * 获取 Bucket 的 跨域设置 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.CORSConfiguration Bucket的跨域设置 - */ -function getBucketCORS (params, callback) { - return submitRequest({ - method : 'GET', - Bucket : params.Bucket, - Region : params.Region, - action : '/?cors', - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - data.CORSConfiguration = data.CORSConfiguration || {}; - - var CORSRule = data.CORSConfiguration.CORSRule || []; - - - - if (!(CORSRule instanceof Array)) { - CORSRule = [CORSRule]; - } - - for (var i=0,len=CORSRule.length; i -1) { - headers[key] = params[key]; - } - } - - var body = params['Body']; - - var readStream = body; - - if (body && (typeof body == 'string')) { - readStream = fs.createReadStream(body); - } - - return submitRequest({ - method : 'PUT', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - headers : headers, - body : readStream, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - if (data && data.headers && data.headers['etag']) { - return callback(null, { - 'ETag' : data.headers['etag'] - }); - } - - return callback(null, data); - }); -} - -/** - * 删除 object - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {function} callback 回调函数,必须 - * @param {object} err 请求失败的错误,如果请求成功,则为空。 - * @param {object} data 删除操作成功之后返回的数据,如果删除操作成功,则返回 success 为 true, 并且附带原先 object 的 url - * @param {Boolean} data.Success 删除操作是否成功,成功则为 true,否则为 false - * @param {Boolean} data.BucketNotFound 请求的 object 所在的 bucket 是否不存在,如果为 true,则说明该 bucket 不存在 - */ -function deleteObject(params, callback) { - submitRequest({ - method : 'DELETE', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - var statusCode = err.statusCode; - if (statusCode && statusCode == 204) { - return callback(null, { - DeleteObjectSuccess : true - }); - } else if (statusCode && statusCode == 404) { - return callback(null, { - BucketNotFound : true - }); - } else { - return callback(err); - } - } - - return callback(null, { - DeleteObjectSuccess : true - }); - - }); -} - -/** - * 获取 object 的 权限列表 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.AccessControlPolicy 权限列表 - */ -function getObjectACL (params, callback) { +'use strict'; - return submitRequest({ - method : 'GET', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - action : '?acl', - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - var Grant = data.AccessControlPolicy.AccessControlList.Grant || []; - - if (!(Grant instanceof Array)) { - Grant = [Grant]; - } - - data.AccessControlPolicy.AccessControlList.Grant = Grant; - - return callback(null, data.AccessControlPolicy || {}); - }); -} - -/** - * 设置 object 的 权限列表 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - */ -function putObjectACL (params, callback) { - var headers = {}; - - headers['x-cos-acl'] = params['ACL']; - headers['x-cos-grant-read'] = params['GrantRead']; - headers['x-cos-grant-write'] = params['GrantWrite']; - headers['x-cos-grant-full-control'] = params['GrantFullControl']; - - return submitRequest({ - method : 'PUT', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - action : '?acl', - headers : headers, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - return callback(null, { - PutObjectACLSuccess : true - }); - }); -} - -/** - * Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - */ -function optionsObject (params, callback) { - var headers = {}; - - headers['Origin'] = params['Origin']; - headers['Access-Control-Request-Method'] = params['AccessControlRequestMethod']; - headers['Access-Control-Request-Headers'] = params['AccessControlRequestHeaders']; - - return submitRequest({ - method : 'OPTIONS', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - headers : headers, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - if (err.statusCode && err.statusCode == 403) { - return callback(null, { - OptionsForbidden : true - }); - } - return callback(err); - } - - data = data || {}; - - var resHeaders = data.headers || {}; - - var retData = {}; - - retData['AccessControlAllowOrigin'] = resHeaders['access-control-allow-origin']; - retData['AccessControlAllowMethods'] = resHeaders['access-control-allow-methods']; - retData['AccessControlAllowHeaders'] = resHeaders['access-control-allow-headers']; - retData['AccessControlExposeHeaders'] = resHeaders['access-control-expose-headers']; - retData['AccessControlMaxAge'] = resHeaders['access-control-max-age']; - - return callback(null, retData); - }); -} - -/** - * @params** (Object) : 参数列表 - * Bucket —— (String) : Bucket 名称 - * Region —— (String) : 地域名称 - * Key —— (String) : 文件名称 - * CopySource —— (String) : 源文件URL绝对路径,可以通过versionid子资源指定历史版本 - * ACL —— (String) : 允许用户自定义文件权限。有效值:private,public-read默认值:private。 - * GrantRead —— (String) : 赋予被授权者读的权限,格式 x-cos-grant-read: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。 - * GrantWrite —— (String) : 赋予被授权者写的权限,格式 x-cos-grant-write: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。 - * GrantFullControl —— (String) : 赋予被授权者读写权限,格式 x-cos-grant-full-control: uin=" ",uin=" ",当需要给子账户授权时,uin="RootAcountID/SubAccountID",当需要给根账户授权时,uin="RootAcountID"。 - * MetadataDirective —— (String) : 是否拷贝元数据,枚举值:Copy, Replaced,默认值Copy。假如标记为Copy,忽略Header中的用户元数据信息直接复制;假如标记为Replaced,按Header信息修改元数据。当目标路径和原路径一致,即用户试图修改元数据时,必须为Replaced - * CopySourceIfModifiedSince —— (String) : 当Object在指定时间后被修改,则执行操作,否则返回412。可与x-cos-copy-source-If-None-Match一起使用,与其他条件联合使用返回冲突。 - * CopySourceIfUnmodifiedSince —— (String) : 当Object在指定时间后未被修改,则执行操作,否则返回412。可与x-cos-copy-source-If-Match一起使用,与其他条件联合使用返回冲突。 - * CopySourceIfMatch —— (String) : 当Object的Etag和给定一致时,则执行操作,否则返回412。可与x-cos-copy-source-If-Unmodified-Since一起使用,与其他条件联合使用返回冲突。 - * CopySourceIfNoneMatch —— (String) : 当Object的Etag和给定不一致时,则执行操作,否则返回412。可与x-cos-copy-source-If-Modified-Since一起使用,与其他条件联合使用返回冲突。 - * StorageClass —— (String) : 存储级别,枚举值:存储级别,枚举值:Standard, Standard_IA,Nearline;默认值:Standard - * CacheControl —— (String) : 指定所有缓存机制在整个请求/响应链中必须服从的指令。 - * ContentDisposition —— (String) : MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件 - * ContentEncoding —— (String) : HTTP 中用来对「采用何种编码格式传输正文」进行协定的一对头部字段 - * ContentLength —— (String) : 设置响应消息的实体内容的大小,单位为字节 - * ContentType —— (String) : RFC 2616 中定义的 HTTP 请求内容类型(MIME),例如text/plain - * Expect —— (String) : 请求的特定的服务器行为 - * Expires —— (String) : 响应过期的日期和时间 - * ContentLanguage —— (String) : 指定内容语言 - * x-cos-meta-* —— (String) : 允许用户自定义的头部信息,将作为 Object 元数据返回。大小限制2K。 -*/ -function putObjectCopy (params, callback) { - var headers = {}; - - headers['x-cos-copy-source'] = params['CopySource']; - headers['x-cos-metadata-directive'] = params['MetadataDirective']; - headers['x-cos-copy-source-If-Modified-Since'] = params['CopySourceIfModifiedSince']; - headers['x-cos-copy-source-If-Unmodified-Since'] = params['CopySourceIfUnmodifiedSince']; - headers['x-cos-copy-source-If-Match'] = params['CopySourceIfMatch']; - headers['x-cos-copy-source-If-None-Match'] = params['CopySourceIfNoneMatch']; - headers['x-cos-storage-class'] = params['StorageClass']; - headers['x-cos-acl'] = params['ACL']; - headers['x-cos-grant-read'] = params['GrantRead']; - headers['x-cos-grant-write'] = params['GrantWrite']; - headers['x-cos-grant-full-control'] = params['GrantFullControl']; - headers['Cache-Control'] = params['CacheControl']; - headers['Content-Disposition'] = params['ContentDisposition']; - headers['Content-Encoding'] = params['ContentEncoding']; - headers['Content-Length'] = params['ContentLength']; - headers['Content-Type'] = params['ContentType']; - headers['Expect'] = params['Expect']; - headers['Expires'] = params['Expires']; - headers['x-cos-content-sha1'] = params['ContentSha1']; - - for (var key in params) { - if (key.indexOf('x-cos-meta-') > -1) { - headers[key] = params[key]; - } - } - - return submitRequest({ - method : 'PUT', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - headers : headers, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - return callback(null, data.CopyObjectResult || {}); - }); -} - - - -function deleteMultipleObject(params, callback) { - var headers = {}; - - headers['Content-Type'] = 'application/xml'; - - var Objects = params.Objects || {}; - var Quiet = params.Quiet; - - var DeleteConfiguration = { - Delete : { - Object : Objects, - Quiet : Quiet || false - } - }; - - var xml = util.json2xml(DeleteConfiguration); - - headers['Content-MD5'] = util.md5(xml); - headers['Content-Length'] = Buffer.byteLength(xml, 'utf8'); - - return submitRequest({ - method : 'POST', - Bucket : params.Bucket, - Region : params.Region, - body : xml, - action : '/?delete', - headers : headers, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - var Deleted = data.DeleteResult.Deleted || []; - var Errors = data.DeleteResult.Error || []; - - if (!(Deleted instanceof Array)) { - Deleted = [Deleted]; - } - - if (!(Errors instanceof Array)) { - Errors = [Errors]; - } - - data.DeleteResult.Error = Errors; - data.DeleteResult.Deleted = Deleted; - - return callback(null, data.DeleteResult || {}); - }); -} - - -// ----------------------------------------- 分块上传相关部分 ---------------------------------- - - -/** - * 初始化分块上传 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {string} params.CacheControl RFC 2616 中定义的缓存策略,将作为 Object 元数据保存,非必须 - * @param {string} params.ContentDisposition RFC 2616 中定义的文件名称,将作为 Object 元数据保存 ,非必须 - * @param {string} params.ContentEncoding RFC 2616 中定义的编码格式,将作为 Object 元数据保存,非必须 - * @param {string} params.ContentType RFC 2616 中定义的内容类型(MIME),将作为 Object 元数据保存,非必须 - * @param {string} params.Expires RFC 2616 中定义的过期时间,将作为 Object 元数据保存,非必须 - * @param {string} params.ACL 允许用户自定义文件权限,非必须 - * @param {string} params.GrantRead 赋予被授权者读的权限 ,非必须 - * @param {string} params.GrantWrite 赋予被授权者写的权限 ,非必须 - * @param {string} params.GrantFullControl 赋予被授权者读写权限 ,非必须 - * @param {string} params.StorageClass 设置Object的存储级别,枚举值:Standard,Standard_IA,Nearline,非必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.InitiateMultipartUploadResult 初始化上传信息,包括 Bucket(Bucket名称), Key(文件名称) 和 UploadId (上传任务ID) - */ -function MultipartInit(params, callback) { - var headers = {}; - - headers['Cache-Control'] = params['CacheControl']; - headers['Content-Disposition'] = params['ContentDisposition']; - headers['Content-Encoding'] = params['ContentEncoding']; - headers['Content-Type'] = params['ContentType']; - headers['Expires'] = params['Expires']; - - headers['x-cos-acl'] = params['ACL']; - headers['x-cos-grant-read'] = params['GrantRead']; - headers['x-cos-grant-write'] = params['GrantWrite']; - headers['x-cos-grant-full-control'] = params['GrantFullControl']; - headers['x-cos-storage-class'] = params['StorageClass']; - - for (var key in params) { - if (key.indexOf('x-cos-meta-') > -1) { - headers[key] = params[key]; - } - } - - return submitRequest({ - method : 'POST', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - action : '?uploads', - headers : headers, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - if (data && data.InitiateMultipartUploadResult) { - return callback(null, data.InitiateMultipartUploadResult); - } - - return callback(null, data); - - - }); -} - -/** - * 分块上传 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {string} params.ContentLength RFC 2616 中定义的 HTTP 请求内容长度(字节),非必须 - * @param {string} params.Expect 当使用 Expect: 100-continue 时,在收到服务端确认后,才会发送请求内容,非必须 - * @param {string} params.ContentSha1 RFC 3174 中定义的 160-bit 内容 SHA-1 算法校验值,非必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.ETag 返回的文件分块 sha1 值 - */ -function MultipartUpload(params, callback) { - var headers = {}; - - headers['Content-Length'] = params['ContentLength']; - headers['Expect'] = params['Expect']; - headers['x-cos-content-sha1'] = params['ContentSha1']; - - var PartNumber = params['PartNumber']; - var UploadId = params['UploadId']; - - var action = '?partNumber=' + PartNumber + '&uploadId=' + UploadId; - - var body = params.Body; - - var req = submitRequest({ - method : 'PUT', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - action : action, - headers : headers, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - data['headers'] = data['headers'] || {}; - - return callback(null, { - ETag : data['headers']['etag'] || '' - }); - - }); - - return body.pipe(req); -} - -/** - * 完成分块上传 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {array} params.Parts 分块信息列表,必须 - * @param {string} params.Parts[i].PartNumber 块编号,必须 - * @param {string} params.Parts[i].ETag 分块的 sha1 校验值 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.CompleteMultipartUpload 完成分块上传后的文件信息,包括Location, Bucket, Key 和 ETag - */ -function MultipartComplete(params, callback) { - var headers = {}; - - headers['Content-Type'] = 'application/xml'; - - var UploadId = params.UploadId; - - var action = '?uploadId=' + UploadId; - - var Parts = params['Parts']; - - for (var i=0,len=Parts.length;i当upload-id-marker未被指定时,ObjectName字母顺序大于key-marker的条目将被列出
当upload-id-marker被指定时,ObjectName字母顺序大于key-marker的条目被列出,ObjectName字母顺序等于key-marker同时UploadID大于upload-id-marker的条目将被列出,非必须 - * @param {string} params.UploadIdMarker 与key-marker一起使用
当key-marker未被指定时,upload-id-marker将被忽略
当key-marker被指定时,ObjectName字母顺序大于key-marker的条目被列出,ObjectName字母顺序等于key-marker同时UploadID大于upload-id-marker的条目将被列出,非必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.ListMultipartUploadsResult 分块上传任务信息 - */ -function MultipartList(params, callback) { - var reqParams = {}; - - reqParams['delimiter'] = params['Delimiter']; - reqParams['encoding-type'] = params['EncodingType']; - reqParams['prefix'] = params['Prefix']; - - reqParams['max-uploads'] = params['MaxUploads']; - - reqParams['key-marker'] = params['KeyMarker']; - reqParams['upload-id-marker'] = params['UploadIdMarker']; - - reqParams = util.clearKey(reqParams); - - - return submitRequest({ - method : 'GET', - Bucket : params.Bucket, - Region : params.Region, - action : '/?uploads&' + querystring.stringify(reqParams), - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - if (data && data.ListMultipartUploadsResult) { - var Upload = data.ListMultipartUploadsResult.Upload || []; - - var CommonPrefixes = data.ListMultipartUploadsResult.CommonPrefixes || []; - - - if (!(CommonPrefixes instanceof Array)) { - CommonPrefixes = [CommonPrefixes]; - } - - if (!(Upload instanceof Array)) { - Upload = [Upload]; - } - - data.ListMultipartUploadsResult.Upload = Upload; - data.ListMultipartUploadsResult.CommonPrefixes = CommonPrefixes; - } - - return callback(null, data.ListMultipartUploadsResult || {}); - }); -} - -/** - * 上传的分块列表查询 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {string} params.UploadId 标示本次分块上传的ID,必须 - * @param {string} params.EncodingType 规定返回值的编码方式,非必须 - * @param {string} params.MaxParts 单次返回最大的条目数量,默认1000,非必须 - * @param {string} params.PartNumberMarker 默认以UTF-8二进制顺序列出条目,所有列出条目从marker开始,非必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - * @return {object} data.ListMultipartUploadsResult 分块信息 - */ -function MultipartListPart(params, callback) { - var reqParams = {}; - - reqParams['uploadId'] = params['UploadId']; - reqParams['encoding-type'] = params['EncodingType']; - reqParams['max-parts'] = params['MaxParts']; - reqParams['part-number-marker'] = params['PartNumberMarker']; - - - return submitRequest({ - method : 'GET', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - qs : reqParams, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - data = data || {}; - - var Part = data.ListPartsResult.Part || []; - - if (!(Part instanceof Array)) { - Part = [Part]; - } - - data.ListPartsResult.Part = Part; - - return callback(null, data.ListPartsResult || {}); - }); -} - -/** - * 抛弃分块上传 - * @param {object} params 参数对象,必须 - * @param {string} params.Bucket Bucket名称,必须 - * @param {string} params.Region 地域名称,必须 - * @param {string} params.Key object名称,必须 - * @param {string} params.UploadId 标示本次分块上传的ID,必须 - * @param {function} callback 回调函数,必须 - * @return {object} err 请求失败的错误,如果请求成功,则为空。 - * @return {object} data 返回的数据 - */ -function MultipartAbort(params, callback) { - var reqParams = {}; - - reqParams['uploadId'] = params['UploadId']; - - return submitRequest({ - method : 'DELETE', - Bucket : params.Bucket, - Region : params.Region, - Key : params.Key, - qs : reqParams, - needHeaders : true, - Appid : params.Appid, - SecretId : params.SecretId, - SecretKey : params.SecretKey - }, function(err, data) { - if (err) { - return callback(err); - } - - return callback(null, { - MultipartAbortSuccess : true - }); - }); -} - - -/** -* String 方法添加 -*/ - -String.prototype.strip = function() { - return this.replace(/(^\/*)|(\/*$)/g, ''); -}; - -String.prototype.lstrip = function() { - return this.replace(/(^\/*)/g, ''); -}; - -String.prototype.rstrip = function() { - return this.replace(/(\/*$)/g, ''); +var util = require('./util'); +var base = require('./base'); +var advance = require('./advance'); + +// 对外暴露的类 +var COS = function (options) { + options = options || {}; + this.Appid = options.AppId || options.Appid; + this.SecretId = options.SecretId; + this.SecretKey = options.SecretKey; }; +util.extend(COS.prototype, base); +util.extend(COS.prototype, advance); -/** -* 私有方法 -*/ - -// 生成操作 url -function getUrl(params) { - var bucket = params.bucket; - var region = params.region; - var object = params.object; - var action = params.action; - var appid = params.appid; - - var url = 'http://' + bucket + '-' + appid + '.' + region + '.myqcloud.com'; - - if (object) { - url += '/' + encodeURIComponent(object); - } - - if (action) { - url += action; - } - - return url; -} - -// 检测参数是否填写完全 -function checkParamsRequire(callerName, params) { - var bucket = params.Bucket; - var region = params.Region; - var object = params.Key; - - if(callerName.indexOf('Bucket') > -1 || callerName == 'deleteMultipleObject' || callerName == 'MultipartList') { - if(!bucket || !region) { - return false; - } - - return true; - } - - if(callerName.indexOf('Object') > -1) { - if(!bucket || !region || !object) { - return false; - } - - return true; - } - - if(callerName.indexOf('Multipart') > -1) { - if(!bucket || !region || !object) { - return false; - } - - return true; - } - -} - -// 发起请求 -function submitRequest(params, callback) { - - // 获取默认秘钥信息 - var defaultAuth = { - Appid : config.APPID, - SecretId : config.SECRET_ID, - SecretKey : config.SECRET_KEY - }; - - var bucket = params.Bucket; - var region = params.Region; - var object = params.Key; - var action = params.action; - var method = params.method || 'GET'; - var headers = params.headers || {}; - var url = params.url; - var body = params.body; - var json = params.json; - - // 通过调用的函数名确定需要的参数 - var callerName = arguments.callee.caller.name; - - - if(!checkParamsRequire(callerName, params) && callerName!=='getService') { - return callback({ - error : 'lack of required params' - }); - } - - var needHeaders = params.needHeaders; - var rawBody = params.rawBody; - - var qs = params.qs; - - var appid = params.Appid || defaultAuth.Appid; - var secretId = params.SecretId || defaultAuth.SecretId; - var secretKey = params.SecretKey || defaultAuth.SecretKey; - - var opt = { - url: url || getUrl({ - bucket : bucket, - region : region, - object : object, - action : action, - appid : appid, - secretId : secretId, - secretKey : secretKey - }), - method: method, - headers: headers, - qs: qs, - body : body, - json : json, - // 这里的 proxy 用于处理内网网关限制,代理转发华南园区的请求,华北园区无需代理 - //'proxy':'http://dev-proxy.oa.com:8080' - }; - - - if (object) { - object = '/' + object; - } - - // 获取签名 - opt.headers.Authorization = util.getAuth({ - method: opt.method, - pathname : object || '/', - appid : appid, - secretId : secretId, - secretKey : secretKey - }); - - // 预先处理 undefine 的属性 - if (opt.headers) { - opt.headers = util.clearKey(opt.headers); - } - - if (opt.qs) { - opt.qs = util.clearKey(opt.qs); - } - - return REQUEST(opt, function (err, response, body) { - - // 请求错误,发生网络错误 - if (err) { - return callback({ - error : err - }); - } - - var statusCode = response.statusCode; - var jsonRes; - - try { - jsonRes = util.xml2json(body) || {}; - } catch (e) { - jsonRes = body || {}; - } - - // 请求返回码不为 200 - if (statusCode != 200) { - return callback({ - statusCode : statusCode, - error : jsonRes.Error || jsonRes - }); - } - - // 不对 body 进行转换,body 直接挂载返回 - if (rawBody) { - jsonRes = {}; - jsonRes.body = body; - } - - // 如果需要头部信息,则 headers 挂载返回 - if (needHeaders) { - jsonRes.headers = response.headers || {}; - } - - if (jsonRes.Error) { - return callback({ - statusCode : statusCode, - error : jsonRes.Error - }); - } - - return callback(null, jsonRes); - }); -} - - -// bucket 相关 -exports.getService = getService; -exports.getBucket = getBucket; -exports.headBucket = headBucket; -exports.putBucket = putBucket; -exports.deleteBucket = deleteBucket; -exports.getBucketACL = getBucketACL; -exports.putBucketACL = putBucketACL; -exports.getBucketCORS = getBucketCORS; -exports.putBucketCORS = putBucketCORS; -exports.deleteBucketCORS = deleteBucketCORS; -exports.getBucketLocation = getBucketLocation; -//exports.getBucketPolicy = getBucketPolicy; -//exports.putBucketPolicy = putBucketPolicy; -exports.getBucketTagging = getBucketTagging; -exports.putBucketTagging = putBucketTagging; -exports.deleteBucketTagging = deleteBucketTagging; -/* -exports.getBucketLifecycle = getBucketLifecycle; -exports.putBucketLifecycle = putBucketLifecycle; -exports.deleteBucketLifecycle = deleteBucketLifecycle; -*/ - -// object 相关 -exports.getObject = getObject; -exports.headObject = headObject; -exports.putObject = putObject; -exports.deleteObject = deleteObject; -exports.getObjectACL = getObjectACL; -exports.putObjectACL = putObjectACL; -exports.optionsObject = optionsObject; -//exports.putObjectCopy = putObjectCopy; -exports.deleteMultipleObject = deleteMultipleObject; - -// 分块上传相关 -exports.MultipartInit = MultipartInit; -exports.MultipartUpload = MultipartUpload; -exports.MultipartComplete = MultipartComplete; -exports.MultipartList = MultipartList; -exports.MultipartListPart = MultipartListPart; -exports.MultipartAbort = MultipartAbort; +module.exports = COS; diff --git a/sdk/test.js b/sdk/test.js deleted file mode 100644 index 8337712..0000000 --- a/sdk/test.js +++ /dev/null @@ -1 +0,0 @@ -// diff --git a/sdk/util.js b/sdk/util.js index 29eb700..8be7f56 100644 --- a/sdk/util.js +++ b/sdk/util.js @@ -1,166 +1,186 @@ +'use strict'; + var crypto = require('crypto'); var xml2js = require('xml2js'); -var xmlParser = new xml2js.Parser({explicitArray : false, ignoreAttrs : true}); +var xmlParser = new xml2js.Parser({explicitArray: false, ignoreAttrs: true}); var xmlBuilder = new xml2js.Builder(); //测试用的key后面可以去掉 var getAuth = function (opt) { - opt = opt || {}; - - var secretId = opt.secretId; - var secretKey = opt.secretKey; - - var method = opt.method || 'get'; - method = method.toLowerCase(); - var pathname = opt.pathname || '/'; - var queryParams = opt.params || ''; - var headers = opt.headers || ''; - - var getObjectKeys = function (obj) { - var list = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - list.push(key); - } - } - return list.sort(); - }; - - var obj2str = function (obj) { - var i, key, val; - var list = []; - var keyList = Object.keys(obj); - for (i = 0; i < keyList.length; i++) { - key = keyList[i]; - val = obj[key] || ''; - key = key.toLowerCase(); - key = encodeURIComponent(key); - list.push(key + '=' + encodeURIComponent(val)); - } - return list.join('&'); - }; - - // 签名有效起止时间 - var now = parseInt(new Date().getTime() / 1000) - 1; - var expired = now; // now + ';' + (now + 60) + ''; // 签名过期时间为当前 + 3600s - - if (opt.expires) { - expired += (opt.expires * 1); - } else { - expired += 3600; - } - - // 要用到的 Authorization 参数列表 - var qSignAlgorithm = 'sha1'; - var qAk = secretId; - var qSignTime = now + ';' + expired; - var qKeyTime = now + ';' + expired; - var qHeaderList = getObjectKeys(headers).join(';').toLowerCase(); - var qUrlParamList = getObjectKeys(queryParams).join(';').toLowerCase(); - - // 签名算法说明文档:https://www.qcloud.com/document/product/436/7778 - // 步骤一:计算 SignKey - var signKey = crypto.createHmac('sha1', secretKey).update(qKeyTime).digest('hex');//CryptoJS.HmacSHA1(qKeyTime, secretKey).toString(); - - // 新增修改,formatString 添加 encodeURIComponent - - //pathname = encodeURIComponent(pathname); - - - // 步骤二:构成 FormatString - var formatString = [method, pathname, obj2str(queryParams), obj2str(headers), ''].join('\n'); - - formatString = new Buffer(formatString,'utf8'); - - // 步骤三:计算 StringToSign - var sha1Algo = crypto.createHash('sha1'); - sha1Algo.update(formatString); - var res = sha1Algo.digest('hex'); - var stringToSign = ['sha1', qSignTime, res, ''].join('\n'); - - // 步骤四:计算 Signature - var qSignature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');//CryptoJS.HmacSHA1(stringToSign, signKey).toString(); - - // 步骤五:构造 Authorization - var authorization = [ - 'q-sign-algorithm=' + qSignAlgorithm, - 'q-ak=' + qAk, - 'q-sign-time=' + qSignTime, - 'q-key-time=' + qKeyTime, - 'q-header-list=' + qHeaderList, - 'q-url-param-list=' + qUrlParamList, - 'q-signature=' + qSignature - ].join('&'); - - return authorization; + opt = opt || {}; + + var secretId = opt.secretId; + var secretKey = opt.secretKey; + var method = (opt.method || 'get').toLowerCase(); + var pathname = opt.pathname || '/'; + var queryParams = opt.params || ''; + var headers = opt.headers || ''; + + var getObjectKeys = function (obj) { + var list = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + list.push(key); + } + } + return list.sort(); + }; + + var obj2str = function (obj) { + var i, key, val; + var list = []; + var keyList = Object.keys(obj); + for (i = 0; i < keyList.length; i++) { + key = keyList[i]; + val = obj[key] || ''; + key = key.toLowerCase(); + key = encodeURIComponent(key); + list.push(key + '=' + encodeURIComponent(val)); + } + return list.join('&'); + }; + + // 签名有效起止时间 + var now = parseInt(new Date().getTime() / 1000) - 1; + var expired = now; // now + ';' + (now + 60) + ''; // 签名过期时间为当前 + 3600s + + if (opt.expires) { + expired += (opt.expires * 1); + } else { + expired += 3600; + } + + // 要用到的 Authorization 参数列表 + var qSignAlgorithm = 'sha1'; + var qAk = secretId; + var qSignTime = now + ';' + expired; + var qKeyTime = now + ';' + expired; + var qHeaderList = getObjectKeys(headers).join(';').toLowerCase(); + var qUrlParamList = getObjectKeys(queryParams).join(';').toLowerCase(); + + // 签名算法说明文档:https://www.qcloud.com/document/product/436/7778 + // 步骤一:计算 SignKey + var signKey = crypto.createHmac('sha1', secretKey).update(qKeyTime).digest('hex');//CryptoJS.HmacSHA1(qKeyTime, secretKey).toString(); + + // 新增修改,formatString 添加 encodeURIComponent + + //pathname = encodeURIComponent(pathname); + + + // 步骤二:构成 FormatString + var formatString = [method, pathname, obj2str(queryParams), obj2str(headers), ''].join('\n'); + + formatString = new Buffer(formatString, 'utf8'); + + // 步骤三:计算 StringToSign + var sha1Algo = crypto.createHash('sha1'); + sha1Algo.update(formatString); + var res = sha1Algo.digest('hex'); + var stringToSign = ['sha1', qSignTime, res, ''].join('\n'); + + // 步骤四:计算 Signature + var qSignature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');//CryptoJS.HmacSHA1(stringToSign, signKey).toString(); + + // 步骤五:构造 Authorization + var authorization = [ + 'q-sign-algorithm=' + qSignAlgorithm, + 'q-ak=' + qAk, + 'q-sign-time=' + qSignTime, + 'q-key-time=' + qKeyTime, + 'q-header-list=' + qHeaderList, + 'q-url-param-list=' + qUrlParamList, + 'q-signature=' + qSignature + ].join('&'); + + return authorization; }; +// XML 对象转 JSON 对象 +var xml2json = function (bodyStr) { + var d = {}; + xmlParser.parseString(bodyStr, function (err, result) { + d = result; + }); -// XML格式转json -var xml2json = function(bodyStr) { - var d = {}; - xmlParser.parseString(bodyStr, function(err, result) { - d = result; - }); - - return d; + return d; }; -var json2xml = function(json) { - var xml = xmlBuilder.buildObject(json); - return xml; +// JSON 对象转 XML 对象 +var json2xml = function (json) { + var xml = xmlBuilder.buildObject(json); + return xml; }; -var md5 = function(str, encoding) { - var md5 = crypto.createHash('md5'); - md5.update(str); - encoding = encoding || 'hex'; - return md5.digest(encoding); +// 计算 MD5 +var md5 = function (str, encoding) { + var md5 = crypto.createHash('md5'); + md5.update(str); + encoding = encoding || 'hex'; + return md5.digest(encoding); }; - -// 用于清除值为 undefine 或者 null 的属性 -var clearKey = function(obj) { - var retObj = {}; - for (var key in obj) { - if (obj[key]) { - retObj[key] = obj[key]; - } - } - - return retObj; +// 清除对象里值为的 undefined 或 null 的属性 +var clearKey = function (obj) { + var retObj = {}; + for (var key in obj) { + if (obj[key]) { + retObj[key] = obj[key]; + } + } + return retObj; }; +// 获取文件 sha1 值 +var getFileSHA = function (readStream, callback) { + var SHA = crypto.createHash('sha1'); -var getFileSHA = function(readStream, callback) { - var SHA = crypto.createHash('sha1'); + readStream.on('data', function (chunk) { + SHA.update(chunk); + }); - readStream.on('data', function(chunk) { - SHA.update(chunk); - }); + readStream.on('error', function (err) { + callback(err); + }); - readStream.on('error', function(err) { - callback(err); - }); + readStream.on('end', function () { + var hash = SHA.digest('hex'); - readStream.on('end', function() { - var hash = SHA.digest('hex'); + callback(null, hash); + }); +}; - callback(null, hash); - }); +// 简单的属性复制方法 +function extend(target, source) { + for (var method in source) { + if (!target[method]) { + target[method] = source[method]; + } + } + return target; +} + +var binaryBase64 = function (str) { + var i, len, char, arr = []; + for (i = 0, len = str.length / 2; i < len; i++) { + char = parseInt(str[i * 2] + str[i * 2 + 1], 16); + arr.push(char); + } + return new Buffer(arr).toString('base64'); }; var util = { - getAuth: getAuth, - xml2json: xml2json, - json2xml: json2xml, - md5: md5, - clearKey: clearKey, - getFileSHA : getFileSHA + getAuth: getAuth, + xml2json: xml2json, + json2xml: json2xml, + md5: md5, + clearKey: clearKey, + getFileSHA: getFileSHA, + extend: extend, + binaryBase64: binaryBase64 };