AWSS3 iOS SDK tutorial

AWSS3 iOS SDK Tutorial

Create AWSServiceConfiguration

According to the documentation comments, there are two ways to create

Method 1: Customize the creation of ServiceConfiguration

Created according to AK/SK/token

    AWSBasicSessionCredentialsProvider *provider = [[AWSBasicSessionCredentialsProvider alloc] 
    initWithAccessKey:access_key secretKey:secret_key sessionToken:token];
    
    AWSRegionType regionType = [AWSEndpoint regionTypeFromName:config.region];
    AWSEndpoint *endpoint = [[AWSEndpoint alloc] initWithRegion:regionType
     service:AWSServiceS3 URL:[NSURL URLWithString:config.endpoint]];
    
    AWSServiceConfiguration *serviceConfig = [[AWSServiceConfiguration alloc]
     initWithRegion:regionType endpoint:endpoint credentialsProvider:provider];

Method 2: Create based on IdentityPoolId

AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] 
initWithRegionType:AWSRegionUSEast1 identityPoolId:@"YourIdentityPoolId"];

AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc]
 initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];

Get upload object

Two ways to get AWSS3TransferUtility

Method 1: Get the default service

  [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
  AWSS3TransferUtility *S3TransferUtility = [AWSS3TransferUtility
   defaultS3TransferUtility:^(NSError * _Nullable error) { }];
  // or
  AWSS3TransferUtility *S3TransferUtility = [AWSS3TransferUtility defaultS3TransferUtility];

Method 2:

[AWSS3TransferUtility registerS3TransferUtilityWithConfiguration:configuration
 forKey:@"USWest2S3TransferUtility"];
// Get registration result
 [AWSS3TransferUtility registerS3TransferUtilityWithConfiguration:configuration
  forKey:@"USWest2S3TransferUtility" completionHandler:^(NSError * _Nullable error) {

 }];
             
AWSS3TransferUtility *S3TransferUtility = [AWSS3TransferUtility
 S3TransferUtilityForKey:@"USWest2S3TransferUtility"];

Remove the upload object AWSS3TransferUtility

+ (void)removeS3TransferUtilityForKey:(NSString *)key;
After the upload task is uploaded, remove it.
After listening to AWSS3TransferUtilityURLSessionDidBecomeInvalidNotification, remove it.

Single file upload using AWSS3TransferUtility

Support NSData, NSUrl two ways to upload

    // upload progress
    AWSS3TransferUtilityUploadExpression *expression =
     [AWSS3TransferUtilityUploadExpression new];
    expression.progressBlock = ^(AWSS3TransferUtilityTask *task, NSProgress *progress) {
        !progressBlock ?: progressBlock(progress);
    };
    // AWSS3TransferUtility object
    AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility
     S3TransferUtilityForKey:key];
    // Upload NSData
    [[transferUtility uploadData:data bucket:bucket key:key contentType:contentType
     expression:expression completionHandler:^(AWSS3TransferUtilityUploadTask * _Nonnull task, NSError * _Nullable error) {
        // completion callback
        !completionHandler ?: completionHandler(error);
    }] continueWithBlock:^id(AWSTask *task) {
        if (task.error) {
            NSLog(@"AWSTask.error: %@", task.error);
        }
        if (task.result) {
            NSLog(@"AWSTask.result: %@", task.result);
        }
        return nil;
    }];
    // upload URL
    [[transferUtility uploadFile:fileUrl bucket:bucket key:key contentType:contentType
     expression:expression completionHandler:^(AWSS3TransferUtilityUploadTask * _Nonnull task, NSError * _Nullable error) {
        // completion callback
        !completionHandler ?: completionHandler(error);
    }] continueWithBlock:^id(AWSTask *task) {
        if (task.error) {
            DLog(@"AWSTask.error: %@", task.error);
        }
        if (task.result) {
            DLog(@"AWSTask.result: %@", task.result);
        }
        return nil;
    }];

Use AWSS3TransferUtility to upload large files in parts

Support NSData, NSUrl two ways to upload

    // upload progress
    AWSS3TransferUtilityMultiPartUploadExpression *multipartExpression =
     [AWSS3TransferUtilityMultiPartUploadExpression new];
    multipartExpression.progressBlock = ^(AWSS3TransferUtilityMultiPartUploadTask * _Nonnull task, NSProgress * _Nonnull progress){
        !progressBlock ?: progressBlock(progress);
    };
    // upload object
    AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility
     S3TransferUtilityForKey:key];
    // NSData upload
    [[transferUtility uploadDataUsingMultiPart:data bucket:bucket key:key contentType:contentType 
    expression:multipartExpression completionHandler:^(AWSS3TransferUtilityMultiPartUploadTask * _Nonnull task, NSError * _Nullable error) {
    
        !completionHandler ?: completionHandler(error);
        
    }] continueWithBlock:^id _Nullable(AWSTask<AWSS3TransferUtilityMultiPartUploadTask *> * _Nonnull task) {
        if (task.error) {
            NSLog(@"AWSTask.error: %@", task.error);
        }
        if (task.result) {
            NSLog(@"AWSTask.result: %@", task.result);
        }
        return nil;
    }];
    // NSUrl Upload
    [[transferUtility uploadFileUsingMultiPart:fileUrl bucket:bucket key:key contentType:contentType 
    expression:multipartExpression completionHandler:^(AWSS3TransferUtilityMultiPartUploadTask * _Nonnull task, NSError * _Nullable error) {
    
        !completionHandler ?: completionHandler(error);
        
    }] continueWithBlock:^id _Nullable(AWSTask<AWSS3TransferUtilityMultiPartUploadTask *> * _Nonnull task) {
        if (task.error) {
            NSLog(@"AWSTask.error: %@", task.error);
        }
        if (task.result) {
            NSLog(@"AWSTask.result: %@", task.result);
        }
        return nil;
    }];

The above method supports: Qiniu/Tencent Cloud, does not support Alibaba Cloud

Use AWSS3

Get AWSS3

[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
AWSS3 *S3 = [AWSS3 defaultS3];

// or
[AWSS3 registerS3WithConfiguration:configuration forKey:@"USWest2S3"
AWSS3 *S3 = [AWSS3 S3ForKey:@"USWest2S3"];

Remove AWSS3

+ (void)removeS3ForKey:(NSString *)key;
After all the upload tasks are completed, they can be removed.

Using AWSS3 single file upload

    AWSS3PutObjectRequest *request = [[AWSS3PutObjectRequest alloc] init];
    request.key = key;
    request.body = data;
    request.bucket = bucket;
    // upload progress
    request.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend){
        // totalBytes: data.length total length of data
        // totalBytesSent: length completed
        
        NSProgress *progress = [[NSProgress alloc] init];
        progress.totalUnitCount = (int64_t)data.length;
        progress.completedUnitCount = totalBytesSent;
        
        !progressBlock ?: progressBlock(progress);
    };
    
    [S3 putObject:request completionHandler:^(AWSS3PutObjectOutput * _Nullable response, NSError * _Nullable error) {
        !completionHandler ?: completionHandler(error);
    }];

This method supports: Qiniu/Tencent Cloud, supports Alibaba Cloud, and needs to change the authentication method

Use AWSS3 to upload large files in parts

The bottom layer of AWSS3TransferUtility large file fragment upload is the same as this method.
System default size per piece: 5 x 1024 x 1024

Multipart upload logic
  1. Initiate multipart upload event: call the initiateMultipartUpload method to return the globally unique uploadId created by OSS
  2. Upload part: call uploadPart method to upload part data
  3. Complete multipart upload: After uploading all parts, call the completeMultipartUpload method to merge all parts into a complete file.
  4. List the shards being uploaded
  5. Cancel multipart upload: Call the abortMultipartUpload method to cancel the multipart upload event.
    When a part upload event is canceled, this uploadId can no longer be used for any operations, and the uploaded part data will be deleted.
detailed example
// AWSUploadConfig: custom object
- (void)AWSS3UploadMultipartData:(NSData *)data config:(AWSUploadConfig *)config progressBlock:(void(^)(NSProgress *progress))progressBlock completionHandler:(void (^)(NSError *error))completionHandler {
    // get-serviceConfig/register/get-AWSS3
    AWSServiceConfiguration *serviceConfig = [self createAWSServiceConfigByUploadConfig:config];
    [AWSS3 registerS3WithConfiguration:serviceConfig forKey:config.key];
    AWSS3 *S3 = [AWSS3 S3ForKey:config.key];
    
    // 1. Initialize the multipart upload event: Call the [[AWSS3CreateMultipartUploadRequest alloc] init] method to return the globally unique uploadId created by OSS
    AWSS3CreateMultipartUploadRequest *createRequest = [[AWSS3CreateMultipartUploadRequest alloc] init];
    createRequest.bucket = config.bucket;
    createRequest.key = config.key;
    
    WS(weakSelf);
    [S3 createMultipartUpload:createRequest completionHandler:^(AWSS3CreateMultipartUploadOutput * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"createMultipartUpload Creation failed: %@", error);
        } else {
            config.uploadId = response.uploadId;
            
            NSLog(@"createMultipartUpload created successfully: %@", config.uploadId);
            
            [weakSelf AWSS3:S3 uploadPartData:data config:config progressBlock:^(NSProgress *progress) {
                !progressBlock ?: progressBlock(progress);
            } complete:^(NSArray *parts) {
                if (parts.count > 0) {
                    [weakSelf AWSS3:S3 multipartUploadParts:parts config:config complete:^(NSError *error) {
                        !completionHandler ?: completionHandler(error);
                    }];
                }
            }];
        }
    }];
}

/// 2. Upload part: call uploadPart method to upload part data
- (void)AWSS3:(AWSS3 *)S3 uploadPartData:(NSData *)data config:(AWSUploadConfig *)config progressBlock:(void(^)(NSProgress *progress))progressBlock complete:(void(^)(NSArray *parts))complete {
    
    // The default is 5M per piece
    NSInteger partSize = 5 * 1024 * 1024;
    NSUInteger chunkSize = ceil((float)data.length /(unsigned long) partSize);
    
    __block NSInteger location = 0;
    __block NSMutableArray<AWSS3CompletedPart *> *parts = [NSMutableArray array];
    
    int64_t totalBytes = data.length;
    __block int64_t completeBytes = 0;
    
    for (int i = 1; location < data.length; i++) {
        partSize = MIN(partSize, data.length - location);
        NSData *partData = [data subdataWithRange:NSMakeRange(location, partSize)];
        
        AWSS3UploadPartRequest *uploadRequest = [[AWSS3UploadPartRequest alloc] init];
        uploadRequest.bucket = config.bucket;
        uploadRequest.key = config.key;
        uploadRequest.uploadId = config.uploadId;
        uploadRequest.partNumber = @(i);
        uploadRequest.body = partData;
        
        uploadRequest.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
            completeBytes += bytesSent;

            NSProgress *progress = [[NSProgress alloc] init];
            progress.totalUnitCount = totalBytes;
            progress.completedUnitCount = completeBytes;
            
            !progressBlock ?: progressBlock(progress);
        };
        
        [S3 uploadPart:uploadRequest completionHandler:^(AWSS3UploadPartOutput * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"uploadPart fail: %@", error);
            } else {
                AWSS3CompletedPart *part = [[AWSS3CompletedPart alloc] init];
                part.ETag = response.ETag;
                part.partNumber = @(i);
                [parts addObject:part];
                
                if (parts.count == chunkSize) {
                    // Fragment blocks require ascending order
                    [parts sortUsingComparator:^NSComparisonResult(AWSS3CompletedPart *obj1, AWSS3CompletedPart *obj2) {
                        if ([obj1.partNumber integerValue] < [obj2.partNumber integerValue]) {
                            return NSOrderedAscending;
                        } else {
                            return NSOrderedDescending;
                        }
                    }];
                    !complete ?: complete(parts);
                }
            }
        }];
        
        location += partSize;
    }
}

/// 3. Complete multipart upload: After uploading all parts, call the completeMultipartUpload method to merge all parts into a complete file
- (void)AWSS3:(AWSS3 *)S3 multipartUploadParts:(NSArray *)parts config:(AWSUploadConfig *)config complete:(void (^)(NSError *error))complete {
    
    AWSS3CompletedMultipartUpload *multipartUpload = [[AWSS3CompletedMultipartUpload alloc] init];
    multipartUpload.parts = parts;
    
    AWSS3CompleteMultipartUploadRequest *completeRequest = [[AWSS3CompleteMultipartUploadRequest alloc] init];
    completeRequest.bucket = config.bucket;
    completeRequest.key = config.key;
    completeRequest.uploadId = config.uploadId;
    completeRequest.multipartUpload = multipartUpload;
    
    [S3 completeMultipartUpload:completeRequest completionHandler:^(AWSS3CompleteMultipartUploadOutput * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"completeMultipartUpload fail: %@", error);
        } else {
            // Remove AWSS3
            [S3 removeS3ForKey:config.key];
            
            NSLog(@"completeMultipartUpload success: %@", response.location);
            !complete ?: complete(error);
        }
    }];
}

/// 4. List the shards being uploaded
- (void)AWSS3:(AWSS3 *)S3 abortMultipartUploadConfig:(AWSUploadConfig *)config {
    
    AWSS3ListMultipartUploadsRequest *listRequest = [[AWSS3ListMultipartUploadsRequest alloc] init];
    listRequest.bucket = config.bucket;

    [S3 listMultipartUploads:listRequest completionHandler:^(AWSS3ListMultipartUploadsOutput * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"listMultipartUploads upload failed: %@", error);
        } else {
            NSInteger count = response.uploads.count;
            
            NSLog(@"listMultipartUploads: %ld", count);
            
            /// 5. Cancel multipart upload
            for (AWSS3MultipartUpload *upload in response.uploads) {
                AWSS3AbortMultipartUploadRequest *abortRequest = [[AWSS3AbortMultipartUploadRequest alloc] init];
                abortRequest.bucket = config.bucket;
                abortRequest.key = config.key;
                abortRequest.uploadId = upload.uploadId;
                
                [S3 abortMultipartUpload:abortRequest completionHandler:^(AWSS3AbortMultipartUploadOutput * _Nullable response, NSError * _Nullable error) {
                    if (error) {
                        NSLog(@"abortMultipartUpload fail: %@", error);
                    } else {
                        NSLog(@"abortMultipartUpload success");
                    }
                }];
            }
        }
    }];
}

This method supports Qiniu and Alibaba Cloud (need to change the authentication method), but does not support Tencent Cloud

download file

/// download file
- (void)AWSS3:(AWSS3 *)S3 getObjectWithFilePath:(NSString *)filePath config:(AWSUploadConfig *)config {

    AWSS3GetObjectRequest *request = [[AWSS3GetObjectRequest alloc] init];
    request.key = config.key;
    request.bucket = config.bucket;
    request.downloadingFileURL = [NSURL fileURLWithPath:filePath];
    
    [S3 getObject:request completionHandler:^(AWSS3GetObjectOutput * _Nullable response, NSError * _Nullable error) {
        
    }];
}

Change AWSS3 signature authentication to support Alibaba Cloud upload

In the AWSSignature.m file,
Change - (NSString *)signS3RequestV4:(NSMutableURLRequest *)urlRequest credentials:(AWSCredentials *)credentials method in the generated authorization

  1. Change contentSha256, set to contentSha256 = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:[AWSSignatureSignerUtility hashData:[urlRequest HTTPBody]] encoding:NSASCIIStringEncoding]];
  2. Change the setting HTTPBodyStream: [urlRequest setHTTPBodyStream:stream];

details as follows:

- (NSString *)aliyun_signS3RequestV4:(NSMutableURLRequest *)urlRequest
                  credentials:(AWSCredentials *)credentials {
    if ([urlRequest valueForHTTPHeaderField:@"Content-Type"] == nil) {
        [urlRequest addValue:@"binary/octet-stream" forHTTPHeaderField:@"Content-Type"];
    }

    NSDate *date = [NSDate aws_clockSkewFixedDate];
    NSString *dateStamp = [date aws_stringValue:AWSDateShortDateFormat1];

    NSString *scope = [NSString stringWithFormat:@"%@/%@/%@/%@", dateStamp, self.endpoint.regionName, self.endpoint.serviceName, AWSSignatureV4Terminator];
    NSString *signingCredentials = [NSString stringWithFormat:@"%@/%@", credentials.accessKey, scope];

    // compute canonical request
    NSString *httpMethod = urlRequest.HTTPMethod;
    // URL.path returns unescaped path
    // For S3,  url-encoded URI need to be decoded before generate  CanonicalURI, otherwise, signature doesn't match occurs. (I.e. CanonicalURI for "/ios-v2-test-445901470/name%3A" will still be  "/ios-v2-test-445901470/name%3A".  "%3A" -> ":" -> "%3A")
    NSString *cfPath = (NSString*)CFBridgingRelease(CFURLCopyPath((CFURLRef)urlRequest.URL));
    NSString *path = [cfPath aws_stringWithURLEncodingPath];
    
    if (path.length == 0) {
        path = [NSString stringWithFormat:@"/"];
    }
    
    NSString *query = urlRequest.URL.query;
    if (query == nil) {
        query = [NSString stringWithFormat:@""];
    }

    NSUInteger contentLength = [[urlRequest allHTTPHeaderFields][@"Content-Length"] integerValue];
    if (contentLength == 0) {
        [urlRequest setValue:nil forHTTPHeaderField:@"Content-Length"];
    } else {
        [urlRequest setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[[urlRequest HTTPBody] length]] forHTTPHeaderField:@"Content-Length"];
    }

    // aliyun upload changes
    // Compute contentSha256
    NSString *contentSha256 = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:[AWSSignatureSignerUtility hashData:[urlRequest HTTPBody]] encoding:NSASCIIStringEncoding]];
    
    //[request.urlRequest setValue:dateTime forHTTPHeaderField:@"X-Amz-Date"];
    [urlRequest setValue:contentSha256 forHTTPHeaderField:@"x-amz-content-sha256"];

    //Set Content-MD5 header field if required by server.
    if (([ urlRequest.HTTPMethod isEqualToString:@"PUT"] && ([[[urlRequest URL] query] hasPrefix:@"tagging"] ||
                                                             [[[urlRequest URL] query] hasPrefix:@"lifecycle"] ||
                                                             [[[urlRequest URL] query] hasPrefix:@"cors"]))
        || ([urlRequest.HTTPMethod isEqualToString:@"POST"] && [[[urlRequest URL] query] hasPrefix:@"delete"])
        ) {
        if (![urlRequest valueForHTTPHeaderField:@"Content-MD5"]) {
            [urlRequest setValue:[NSString aws_base64md5FromData:urlRequest.HTTPBody] forHTTPHeaderField:@"Content-MD5"];
        }
        
    }
    
    NSMutableDictionary *headers = [[urlRequest allHTTPHeaderFields] mutableCopy];

    NSString *canonicalRequest = [AWSSignatureV4Signer getCanonicalizedRequest:httpMethod
                                                                          path:path
                                                                         query:query
                                                                       headers:headers
                                                                 contentSha256:contentSha256];
    AWSDDLogVerbose(@"Canonical request: [%@]", canonicalRequest);

    NSString *stringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",
                              AWSSignatureV4Algorithm,
                              [urlRequest valueForHTTPHeaderField:@"X-Amz-Date"],
                              scope,
                              [AWSSignatureSignerUtility hexEncode:[AWSSignatureSignerUtility hashString:canonicalRequest]]];
    AWSDDLogVerbose(@"AWS4 String to Sign: [%@]", stringToSign);

    NSData *kSigning  = [AWSSignatureV4Signer getV4DerivedKey:credentials.secretKey
                                                         date:dateStamp
                                                       region:self.endpoint.regionName
                                                      service:self.endpoint.serviceName];

    NSData *signature = [AWSSignatureSignerUtility sha256HMacWithData:[stringToSign dataUsingEncoding:NSUTF8StringEncoding] withKey:kSigning];
    NSString *signatureString = [AWSSignatureSignerUtility hexEncode:[[NSString alloc] initWithData:signature encoding:NSASCIIStringEncoding]];

    NSString *authorization = [NSString stringWithFormat:@"%@ Credential=%@, SignedHeaders=%@, Signature=%@",
                               AWSSignatureV4Algorithm,
                               signingCredentials,
                               [AWSSignatureV4Signer getSignedHeadersString:headers],
                               signatureString];

    NSInputStream *stream = [urlRequest HTTPBodyStream];
    if (nil != stream) {
        // Aliyun changes: cancel multi-part upload
        [urlRequest setHTTPBodyStream:stream];
    }

    return authorization;
}

Tags: iOS xcode objective-c

Posted by spxmgb on Wed, 18 Jan 2023 11:22:27 +0300