OSS provides multipart upload for you to split a large object into multiple parts and upload the parts separately. After the upload is complete, you can call the CompleteMultipartUpload operation to combine these parts into a single object to implement resumable upload.

Multipart upload process

To implement multipart upload, perform the following operations:

  1. Split the object to upload into multiple parts.
  2. Call the InitiateMultipartUpload operation to initiate a multipart upload task.
  3. Call the UploadPart operation to upload the parts one by one or concurrently.
  4. Call the CompleteMultipartUpload operation to complete the upload.

Before you start a multipart upload task, note that:

  • Each part except for the last part must be at least 100 KB in size. Otherwise, you may fail to call the CompleteMultipartUpload operation.
  • After the object to upload is split into parts, they are sorted by part number specified during the upload. However, parts are not necessarily uploaded in order, and parts can be uploaded concurrently.

    Due to network conditions and the device load, the upload does not necessarily speed up when more parts are uploaded concurrently. We recommend that you increase the part size in good network conditions, and decrease the part size when network conditions are poor.

  • By default, when all parts are uploaded but you have not called the CompleteMultipartUpload operation to combine these parts, the uploaded parts are not deleted automatically. You can call the AbortMultipartUpload operation to terminate the upload task and delete the parts. For more information about how to automatically delete the uploaded parts, see Manage lifecycle rules.

Initialize a multipart upload task

The following code provides an example on how to initialize a multipart upload task:

__block NSString * uploadId = nil;
__block NSMutableArray * partInfos = [NSMutableArray new];
NSString * uploadToBucket = @"<bucketName>";
// objectKey is equivalent to objectName that indicates the complete path of the object you want to upload to OSS. The path must include the extension of the object. For example, you can set objectKey to abc/efg/123.jpg.
NSString * uploadObjectkey = @"<objectKey>";
// OSSInitMultipartUploadRequest is used to specify the name of the object you want to upload and the name of the bucket to which the object belongs.
OSSInitMultipartUploadRequest * init = [OSSInitMultipartUploadRequest new];
init.bucketName = uploadToBucket;
init.objectKey = uploadObjectkey;
// init.contentType = @"application/octet-stream";
// The response to multipartUploadInit contains the upload ID. The upload ID is the unique identifier of the multipart upload task.
OSSTask * initTask = [client multipartUploadInit:init];
[initTask waitUntilFinished];
if (! initTask.error) {
    OSSInitMultipartUploadResult * result = initTask.result;
    uploadId = result.uploadId;
} else {
    NSLog(@"multipart upload failed, error: %@", initTask.error);
    return;
}

Upload parts

The following code provides an example on how to upload parts:

NSString * filePath = [docDir stringByAppendingPathComponent:@"***"];
// Specify the object you want to upload.
int chuckCount = *;
// Specify the number of parts.
uint64_t offset = fileSie/chuckCount;
// Specify the size of each part.
for (int i = 1; i <= chuckCount; i++) {
    OSSUploadPartRequest * uploadPart = [OSSUploadPartRequest new];
    uploadPart.bucketName = uploadToBucket;
    uploadPart.objectkey = uploadObjectkey;
    uploadPart.uploadId = uploadId;
    uploadPart.partNumber = i; // part number start from 1

    NSFileHandle* readHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
        [readHandle seekToFileOffset:offset * (i -1)];

        NSData* data = [readHandle readDataOfLength:offset];
        uploadPart.uploadPartData = data;

    OSSTask * uploadPartTask = [client uploadPart:uploadPart];

    [uploadPartTask waitUntilFinished];

    if (! uploadPartTask.error) {
        OSSUploadPartResult * result = uploadPartTask.result;
        uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:uploadPart.uploadPartFileURL.absoluteString error:nil] fileSize];
        [partInfos addObject:[OSSPartInfo partInfoWithPartNum:i eTag:result.eTag size:fileSize]];
    } else {
        NSLog(@"upload part error: %@", uploadPartTask.error);
        return;
    }
}
			

The preceding code uses uploadPart to upload each part.

  • An upload ID and a part number must be specified for each multipart upload request.
  • The uploadPart operation does not check the size of each part immediately after the part is uploaded. This API operation checks the part size only when the multipart upload task is complete.
  • The part number ranges from 1 to 10000. If a part number is beyond this range, OSS returns the InvalidArgument error code.
  • When you upload each part, ensure that the stream is directed to the starting position of the part to upload.
  • After each part is uploaded, OSS returns a response that contains the ETag value of the part. If the ETag value and the MD5 hash of the part are the same, you must combine the ETag value with the part number into PartETag and save the PartETag for the subsequent upload of parts.

Complete the multipart upload task

In the following code, the partInfos field value indicates the list of PartETags saved during the multipart upload. OSS verifies the validity of each part after OSS receives the partInfos field values. When all parts are validated, OSS combines them into a complete object.

The following code provides an example on how to upload an object by using multipart upload:

OSSCompleteMultipartUploadRequest * complete = [OSSCompleteMultipartUploadRequest new];
complete.bucketName = uploadToBucket;
complete.objectKey = uploadObjectkey;
complete.uploadId = uploadId;
complete.partInfos = partInfos;

OSSTask * completeTask = [client completeMultipartUpload:complete];

[[completeTask continueWithBlock:^id(OSSTask *task) {
    if (! task.error) {
        OSSCompleteMultipartUploadResult * result = task.result;
        // ...
    } else {
        // ...
    }
    return nil;
}] waitUntilFinished];
			

You can set the servercallback parameter to complete the multipart upload request. A callback request is sent to the specified server address after the multipart upload task is complete. You can view the servercallback result in result.serverReturnJsonString of the response.

OSSCompleteMultipartUploadRequest * complete = [OSSCompleteMultipartUploadRequest new];
complete.bucketName = @"<bucketName>";
complete.objectKey = @"<objectKey>";
complete.uploadId = uploadId;
complete.partInfos = partInfos;
complete.callbackParam = @{
                          @"callbackUrl": @"<server address>",
                          @"callbackBody": @"<test>"
                          };
complete.callbackVar = @{
                        @"var1": @"value1",
                        @"var2": @"value2"
                        };
OSSTask * completeTask = [client completeMultipartUpload:complete];
[[completeTask continueWithBlock:^id(OSSTask *task) {
    if (! task.error) {
        OSSCompleteMultipartUploadResult * result = task.result;
        NSLog(@"server call back return : %@", result.serverReturnJsonString);
    } else {
        // ...
    }
    return nil;
}] waitUntilFinished];

You can attach the Content-MD5 field value to the upload request to check whether the object uploaded to OSS is same with the local file. The OSS server helps you with the MD5 verification. The upload is successful only when the MD5 hash of the object received by the OSS server is the same as the Content-MD5 field value. This method can ensure the consistency of uploaded object.

The following code provides an example on how to configure MD5 verification:

OSSUploadPartRequest * uploadPart = [OSSUploadPartRequest new];
uploadPart.bucketName = TEST_BUCKET;
uploadPart.uploadId = uploadId;
....
uploadPart.contentMd5 = [OSSUtil fileMD5String:filepath];
			

List parts

The following code provides an example on how to call the listParts method to obtain all uploaded parts of an upload task:

OSSListPartsRequest * listParts = [OSSListPartsRequest new];
listParts.bucketName = @"<bucketName>";
listParts.objectKey = @"<objectkey>";
listParts.uploadId = @"<uploadid>";

OSSTask * listPartTask = [client listParts:listParts];

[listPartTask continueWithBlock:^id(OSSTask *task) {
    if (! task.error) {
        NSLog(@"list part result success!") ;
        OSSListPartsResult * listPartResult = task.result;
        for (NSDictionary * partInfo in listPartResult.parts) {
            NSLog(@"each part: %@", partInfo);
        }
    } else {
        NSLog(@"list part result error: %@", task.error);
    }
    return nil;
}];
			
Notice By default, if a bucket contains more than 1,000 parts that are uploaded using multipart upload, OSS returns the first 1,000 parts. In the response, the IsTruncated field is set to false and NextPartNumberMarker is returned to indicate the next starting position to continue reading data.

Cancel a multipart upload task

The following code provides an example on how to cancel a multipart upload request that is mapped to the specified upload ID:

OSSAbortMultipartUploadRequest * abort = [OSSAbortMultipartUploadRequest new];
abort.bucketName = @"<bucketName>";
abort.objectKey = @"<objectKey>";
abort.uploadId = uploadId;

OSSTask * abortTask = [client abortMultipartUpload:abort];

[abortTask waitUntilFinished];

if (! abortTask.error) {
    OSSAbortMultipartUploadResult * result = abortTask.result;
    uploadId = result.uploadId;
} else {
    NSLog(@"multipart upload failed, error: %@", abortTask.error);
    return;
}