Multipart upload of objects
Multipart upload of objects
In addition to using the putObject() method for file uploads to BOS, BOS also supports another upload mode called Multipart Upload. This mode can be used in scenarios such as:
- When resumable uploads are required.
- When uploading files larger than 5GB.
- When the connection to the BOS server is frequently interrupted due to unstable network conditions.
- Enable streaming file uploads.
- The file size cannot be determined before uploading.
Multipart upload is slightly more complex than direct upload. Multipart upload is divided into three stages:
- Initiate upload (initiateMultipartUpload)
- Upload parts (uploadPartFromBlob)
- Complete upload (completeMultipartUpload)
Browser-side code example
Divide the file in parts
1let options = {
2 'Content-Length': <file.size>, // Add http header
3 'Content-Type': 'application/json', // Add http header
4 'Cache-Control': 'public, max-age=31536000', // Specify cache directives
5 'Content-Disposition': 'attachment; filename="example.jpg"', // Indicate how the response content should be displayed
6 'x-bce-meta-foo1': 'bar1', // Add custom meta information
7 'x-bce-meta-foo2': 'bar2', // Add custom meta information
8 'x-bce-meta-foo3': 'bar3', // Add custom meta information
9};
10 let PART_SIZE = 5 * 1024 * 1024; // Specify the part size
11function getTasks(file, uploadId, bucketName, key) {
12 let leftSize = file.size;
13 let offset = 0;
14 let partNumber = 1;
15 let tasks = [];
16 while (leftSize > 0) {
17 let partSize = Math.min(leftSize, PART_SIZE);
18 tasks.push({
19 file: file,
20 uploadId: uploadId,
21 bucketName: bucketName,
22 key: key,
23 partNumber: partNumber,
24 partSize: partSize,
25 start: offset,
26 stop: offset + partSize - 1
27 });
28 leftSize -= partSize;
29 offset += partSize;
30 partNumber += 1;
31 }
32 return tasks;
33}
Handle the upload logic for each part
1function uploadPartFile(state, client) {
2 return function(task, callback) {
3 let blob = task.file.slice(task.start, task.stop + 1);
4 client.uploadPartFromBlob(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, blob)
5 .then(function(res) {
6 ++state.loaded;
7 callback(null, res);
8 })
9 .catch(function(err) {
10 callback(err);
11 });
12 };
13}
Initialize uploadID, start uploading parts, and complete the upload
1let uploadId;
2client.initiateMultipartUpload(bucket, key, options)
3 .then(function(response) {
4 uploadId = response.body.uploadId; // Start the upload and get the server-generated uploadId
5 let deferred = sdk.Q.defer();
6 let tasks = getTasks(blob, uploadId, bucket, key);
7 let state = {
8 lengthComputable: true,
9 loaded: 0,
10 total: tasks.length
11 };
12 // To manage multipart uploads, the async library (https://github.com/caolan/async) is used for asynchronous processing
13 let THREADS = 2; // Number of parts uploaded simultaneously
14 async.mapLimit(tasks, THREADS, uploadPartFile(state, client), function(err, results) {
15 if (err) {
16 deferred.reject(err);
17 } else {
18 deferred.resolve(results);
19 }
20 });
21 return deferred.promise;
22 })
23 .then(function(allResponse) {
24 let partList = [];
25 allResponse.forEach(function(response, index) {
26 // Generate the part list
27 partList.push({
28 partNumber: index + 1,
29 eTag: response.http_headers.etag
30 });
31 });
32 return client.completeMultipartUpload(bucket, key, uploadId, partList); // Complete the upload
33 })
34 .then(function (res) {
35 // Upload completed
36 })
37 .catch(function (err) {
38 // Upload failed, add your code
39 console.error(err);
40 });
Node.js-side multipart upload
Divide the file in parts, initialize UploadID, and upload parts
1let options = {
2 'Content-Length': <file.size>, // Add http header
3 'Content-Type': 'application/json', // Add http header
4 'Cache-Control': 'public, max-age=31536000', // Specify cache directives
5 'Content-Disposition': 'attachment; filename="example.jpg"', // Indicate how the response content should be displayed
6 'x-bce-meta-foo1': 'bar1', // Add custom meta information
7 'x-bce-meta-foo2': 'bar2', // Add custom meta information
8 'x-bce-meta-foo3': 'bar3', // Add custom meta information
9};
10 let PART_SIZE = 5 * 1024 * 1024; // Specify the part size
11let uploadId;
12client.initiateMultipartUpload(bucket, key, options)
13 .then(function(response) {
14 uploadId = response.body.uploadId; // Start the upload and get the server-generated uploadId
15 let deferred = sdk.Q.defer();
16 let blob = {
17 // Use the fs file library to get the file size
18 size: fs.statSync(localFileName).size,
19 filename: localFileName
20 }
21 let tasks = getTasks(blob, uploadId, bucket, key);
22 let state = {
23 lengthComputable: true,
24 loaded: 0,
25 total: tasks.length
26 };
27 // To manage multipart uploads, the async library (https://github.com/caolan/async) is used for asynchronous processing
28 let THREADS = 2; // Number of parts uploaded simultaneously
29 async.mapLimit(tasks, THREADS, uploadPartFile(state, client), function(err, results) {
30 if (err) {
31 deferred.reject(err);
32 } else {
33 deferred.resolve(results);
34 }
35 });
36 return deferred.promise;
37 })
38 .then(function(allResponse) {
39 let partList = [];
40 allResponse.forEach(function(response, index) {
41 // Generate the part list
42 partList.push({
43 partNumber: index + 1,
44 eTag: response.http_headers.etag
45 });
46 });
47 return client.completeMultipartUpload(bucket, key, uploadId, partList); // Complete the upload
48 })
49 .then(function (res) {
50 // Upload completed
51 })
52 .catch(function (err) {
53 // Upload failed, add your code
54 console.error(err);
55 });
56function getTasks(file, uploadId, bucketName, key) {
57 let leftSize = file.size;
58 let offset = 0;
59 let partNumber = 1;
60 let tasks = [];
61 while (leftSize > 0) {
62 let partSize = Math.min(leftSize, PART_SIZE);
63 tasks.push({
64 file: file.filename,
65 uploadId: uploadId,
66 bucketName: bucketName,
67 key: key,
68 partNumber: partNumber,
69 partSize: partSize,
70 start: offset,
71 stop: offset + partSize - 1
72 });
73 leftSize -= partSize;
74 offset += partSize;
75 partNumber += 1;
76 }
77 return tasks;
78}
79function uploadPartFile(state, client) {
80 return function(task, callback) {
81 console.log("task: ", task)
82 return client.uploadPartFromFile(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, task.file , task.start)
83 .then(function(res) {
84 ++state.loaded;
85 console.log("ok")
86 callback(null, res);
87 })
88 .catch(function(err) {
89 console.log("bad")
90 callback(err);
91 });
92 };
93}
Cancel multipart upload event
Users can cancel multipart uploads by using the abortMultipartUpload method.
1client.abortMultipartUpload(<BucketName>, <Objectkey>, <UploadID>);
Retrieve unfinished multipart upload event
Users can use the listMultipartUploads method to retrieve ongoing multipart upload events within a bucket.
1client.listMultipartUploads(<bucketName>)
2 .then(function (response) {
3 // Traverse all upload events
4 for (var i = 0; i < response.body.multipartUploads.length; i++) {
5 console.log(response.body.multipartUploads[i].uploadId);
6 }
7 });
Get all uploaded part information
Users can use the listParts method to retrieve all parts uploaded during an upload event.
1client.listParts(<bucketName>, <key>, <uploadId>)
2 .then(function (response) {
3 // Traverse all upload events
4 for (var i = 0; i < response.body.parts.length; i++) {
5 console.log(response.body.parts[i].partNumber);
6 }
7 });
