Added support for S3 storage class (eg Reduced Redundancy)
Added tests for canned ACLs
Showing
5 changed files
with
75 additions
and
11 deletions
| @@ -10,6 +10,9 @@ | @@ -10,6 +10,9 @@ | ||
| 10 | #import <Foundation/Foundation.h> | 10 | #import <Foundation/Foundation.h> |
| 11 | #import "ASIS3Request.h" | 11 | #import "ASIS3Request.h" |
| 12 | 12 | ||
| 13 | +// Constants for storage class | ||
| 14 | +extern NSString *const ASIS3StorageClassStandard; | ||
| 15 | +extern NSString *const ASIS3StorageClassReducedRedundancy; | ||
| 13 | 16 | ||
| 14 | @interface ASIS3ObjectRequest : ASIS3Request { | 17 | @interface ASIS3ObjectRequest : ASIS3Request { |
| 15 | 18 | ||
| @@ -28,7 +31,14 @@ | @@ -28,7 +31,14 @@ | ||
| 28 | // Can be autodetected when PUTing a file from disk, will default to 'application/octet-stream' when PUTing data | 31 | // Can be autodetected when PUTing a file from disk, will default to 'application/octet-stream' when PUTing data |
| 29 | NSString *mimeType; | 32 | NSString *mimeType; |
| 30 | 33 | ||
| 34 | + // Set this to specify you want to work with a particular subresource (eg an acl for that resource) | ||
| 35 | + // See requestWithBucket:key:subResource:, below. | ||
| 31 | NSString* subResource; | 36 | NSString* subResource; |
| 37 | + | ||
| 38 | + // The storage class to be used for PUT requests | ||
| 39 | + // Set this to ASIS3StorageClassReducedRedundancy to save money on storage, at (presumably) a slightly higher risk you will lose the data | ||
| 40 | + // If this is not set, no x-amz-storage-class header will be sent to S3, and their default will be used | ||
| 41 | + NSString *storageClass; | ||
| 32 | } | 42 | } |
| 33 | 43 | ||
| 34 | // Create a request, building an appropriate url | 44 | // Create a request, building an appropriate url |
| @@ -66,5 +76,5 @@ | @@ -66,5 +76,5 @@ | ||
| 66 | @property (retain, nonatomic) NSString *sourceKey; | 76 | @property (retain, nonatomic) NSString *sourceKey; |
| 67 | @property (retain, nonatomic) NSString *mimeType; | 77 | @property (retain, nonatomic) NSString *mimeType; |
| 68 | @property (retain, nonatomic) NSString *subResource; | 78 | @property (retain, nonatomic) NSString *subResource; |
| 69 | - | 79 | +@property (retain, nonatomic) NSString *storageClass; |
| 70 | @end | 80 | @end |
| @@ -8,6 +8,8 @@ | @@ -8,6 +8,8 @@ | ||
| 8 | 8 | ||
| 9 | #import "ASIS3ObjectRequest.h" | 9 | #import "ASIS3ObjectRequest.h" |
| 10 | 10 | ||
| 11 | +NSString *const ASIS3StorageClassStandard = @"STANDARD"; | ||
| 12 | +NSString *const ASIS3StorageClassReducedRedundancy = @"REDUCED_REDUNDANCY"; | ||
| 11 | 13 | ||
| 12 | @implementation ASIS3ObjectRequest | 14 | @implementation ASIS3ObjectRequest |
| 13 | 15 | ||
| @@ -96,13 +98,14 @@ | @@ -96,13 +98,14 @@ | ||
| 96 | [sourceKey release]; | 98 | [sourceKey release]; |
| 97 | [sourceBucket release]; | 99 | [sourceBucket release]; |
| 98 | [subResource release]; | 100 | [subResource release]; |
| 101 | + [storageClass release]; | ||
| 99 | [super dealloc]; | 102 | [super dealloc]; |
| 100 | } | 103 | } |
| 101 | 104 | ||
| 102 | - (void)buildURL | 105 | - (void)buildURL |
| 103 | { | 106 | { |
| 104 | if ([self subResource]) { | 107 | if ([self subResource]) { |
| 105 | - [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@?%@",[self requestScheme],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]],[self subResource]]]]; | 108 | + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@?%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]],[self subResource]]]]; |
| 106 | } else { | 109 | } else { |
| 107 | [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]]]]]; | 110 | [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@://%@.%@%@",[self requestScheme],[self bucket],[[self class] S3Host],[ASIS3Request stringByURLEncodingForS3Path:[self key]]]]]; |
| 108 | } | 111 | } |
| @@ -134,6 +137,9 @@ | @@ -134,6 +137,9 @@ | ||
| 134 | NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]]; | 137 | NSString *path = [ASIS3Request stringByURLEncodingForS3Path:[self sourceKey]]; |
| 135 | [headers setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"]; | 138 | [headers setObject:[[self sourceBucket] stringByAppendingString:path] forKey:@"x-amz-copy-source"]; |
| 136 | } | 139 | } |
| 140 | + if ([self storageClass]) { | ||
| 141 | + [headers setObject:[self storageClass] forKey:@"x-amz-storage-class"]; | ||
| 142 | + } | ||
| 137 | return headers; | 143 | return headers; |
| 138 | } | 144 | } |
| 139 | 145 | ||
| @@ -146,12 +152,11 @@ | @@ -146,12 +152,11 @@ | ||
| 146 | return [super stringToSignForHeaders:canonicalizedAmzHeaders resource:canonicalizedResource]; | 152 | return [super stringToSignForHeaders:canonicalizedAmzHeaders resource:canonicalizedResource]; |
| 147 | } | 153 | } |
| 148 | 154 | ||
| 149 | - | ||
| 150 | @synthesize bucket; | 155 | @synthesize bucket; |
| 151 | @synthesize key; | 156 | @synthesize key; |
| 152 | @synthesize sourceBucket; | 157 | @synthesize sourceBucket; |
| 153 | @synthesize sourceKey; | 158 | @synthesize sourceKey; |
| 154 | @synthesize mimeType; | 159 | @synthesize mimeType; |
| 155 | @synthesize subResource; | 160 | @synthesize subResource; |
| 156 | - | 161 | +@synthesize storageClass; |
| 157 | @end | 162 | @end |
| @@ -23,13 +23,18 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | @@ -23,13 +23,18 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; | ||
| 23 | extern NSString *const ASIS3AccessPolicyBucketOwnerRead; | 23 | extern NSString *const ASIS3AccessPolicyBucketOwnerRead; |
| 24 | extern NSString *const ASIS3AccessPolicyBucketOwnerFullControl; | 24 | extern NSString *const ASIS3AccessPolicyBucketOwnerFullControl; |
| 25 | 25 | ||
| 26 | +// Constants for requestScheme - defaults is ASIS3RequestSchemeHTTP | ||
| 27 | +extern NSString *const ASIS3RequestSchemeHTTP; | ||
| 28 | +extern NSString *const ASIS3RequestSchemeHTTPS; | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 26 | typedef enum _ASIS3ErrorType { | 32 | typedef enum _ASIS3ErrorType { |
| 27 | ASIS3ResponseParsingFailedType = 1, | 33 | ASIS3ResponseParsingFailedType = 1, |
| 28 | ASIS3ResponseErrorType = 2 | 34 | ASIS3ResponseErrorType = 2 |
| 29 | } ASIS3ErrorType; | 35 | } ASIS3ErrorType; |
| 30 | 36 | ||
| 31 | -extern NSString *const ASIS3RequestSchemeHTTP; | 37 | + |
| 32 | -extern NSString *const ASIS3RequestSchemeHTTPS; | ||
| 33 | 38 | ||
| 34 | @interface ASIS3Request : ASIHTTPRequest <NSCopying, NSXMLParserDelegate> { | 39 | @interface ASIS3Request : ASIHTTPRequest <NSCopying, NSXMLParserDelegate> { |
| 35 | 40 |
| @@ -19,7 +19,6 @@ NSString *const ASIS3AccessPolicyBucketOwnerFullControl = @"bucket-owner-full-co | @@ -19,7 +19,6 @@ NSString *const ASIS3AccessPolicyBucketOwnerFullControl = @"bucket-owner-full-co | ||
| 19 | NSString *const ASIS3RequestSchemeHTTP = @"http"; | 19 | NSString *const ASIS3RequestSchemeHTTP = @"http"; |
| 20 | NSString *const ASIS3RequestSchemeHTTPS = @"https"; | 20 | NSString *const ASIS3RequestSchemeHTTPS = @"https"; |
| 21 | 21 | ||
| 22 | - | ||
| 23 | static NSString *sharedAccessKey = nil; | 22 | static NSString *sharedAccessKey = nil; |
| 24 | static NSString *sharedSecretAccessKey = nil; | 23 | static NSString *sharedSecretAccessKey = nil; |
| 25 | 24 | ||
| @@ -119,7 +118,7 @@ static NSString *sharedSecretAccessKey = nil; | @@ -119,7 +118,7 @@ static NSString *sharedSecretAccessKey = nil; | ||
| 119 | // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private) | 118 | // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private) |
| 120 | NSMutableDictionary *amzHeaders = [self S3Headers]; | 119 | NSMutableDictionary *amzHeaders = [self S3Headers]; |
| 121 | NSString *canonicalizedAmzHeaders = @""; | 120 | NSString *canonicalizedAmzHeaders = @""; |
| 122 | - for (NSString *header in [amzHeaders keyEnumerator]) { | 121 | + for (NSString *header in [amzHeaders keysSortedByValueUsingSelector:@selector(compare:)]) { |
| 123 | canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]]; | 122 | canonicalizedAmzHeaders = [NSString stringWithFormat:@"%@%@:%@\n",canonicalizedAmzHeaders,[header lowercaseString],[amzHeaders objectForKey:header]]; |
| 124 | [self addRequestHeader:header value:[amzHeaders objectForKey:header]]; | 123 | [self addRequestHeader:header value:[amzHeaders objectForKey:header]]; |
| 125 | } | 124 | } |
| @@ -20,8 +20,6 @@ static NSString *accessKey = @""; | @@ -20,8 +20,6 @@ static NSString *accessKey = @""; | ||
| 20 | // You should run these tests on a bucket that does not yet exist | 20 | // You should run these tests on a bucket that does not yet exist |
| 21 | static NSString *bucket = @""; | 21 | static NSString *bucket = @""; |
| 22 | 22 | ||
| 23 | - | ||
| 24 | - | ||
| 25 | // Used for subclass test | 23 | // Used for subclass test |
| 26 | @interface ASIS3ObjectRequestSubclass : ASIS3ObjectRequest {} | 24 | @interface ASIS3ObjectRequestSubclass : ASIS3ObjectRequest {} |
| 27 | @end | 25 | @end |
| @@ -220,6 +218,7 @@ static NSString *bucket = @""; | @@ -220,6 +218,7 @@ static NSString *bucket = @""; | ||
| 220 | ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key]; | 218 | ASIS3ObjectRequest *request = [ASIS3ObjectRequest PUTRequestForFile:filePath withBucket:bucket key:key]; |
| 221 | [request setSecretAccessKey:secretAccessKey]; | 219 | [request setSecretAccessKey:secretAccessKey]; |
| 222 | [request setAccessKey:accessKey]; | 220 | [request setAccessKey:accessKey]; |
| 221 | + [request setStorageClass:ASIS3StorageClassReducedRedundancy]; | ||
| 223 | [request startSynchronous]; | 222 | [request startSynchronous]; |
| 224 | success = [[request responseString] isEqualToString:@""]; | 223 | success = [[request responseString] isEqualToString:@""]; |
| 225 | GHAssertTrue(success,@"Failed to PUT a file to S3"); | 224 | GHAssertTrue(success,@"Failed to PUT a file to S3"); |
| @@ -785,9 +784,55 @@ static NSString *bucket = @""; | @@ -785,9 +784,55 @@ static NSString *bucket = @""; | ||
| 785 | 784 | ||
| 786 | // DELETE it | 785 | // DELETE it |
| 787 | request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"]; | 786 | request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"]; |
| 787 | + [request setRequestScheme:ASIS3RequestSchemeHTTPS]; | ||
| 788 | [request startSynchronous]; | 788 | [request startSynchronous]; |
| 789 | success = [[request responseString] isEqualToString:@""]; | 789 | success = [[request responseString] isEqualToString:@""]; |
| 790 | - GHAssertTrue(success,@"Failed to DELETE the copy from S3"); | 790 | + GHAssertTrue(success,@"Failed to DELETE the object from S3"); |
| 791 | + | ||
| 792 | + // Delete the bucket | ||
| 793 | + request = [ASIS3BucketRequest DELETERequestWithBucket:bucket]; | ||
| 794 | + [request setRequestScheme:ASIS3RequestSchemeHTTPS]; | ||
| 795 | + [request startSynchronous]; | ||
| 796 | + GHAssertNil([request error],@"Failed to delete a bucket"); | ||
| 797 | + | ||
| 798 | + [ASIS3Request setSharedAccessKey:nil]; | ||
| 799 | + [ASIS3Request setSharedSecretAccessKey:nil]; | ||
| 800 | +} | ||
| 801 | + | ||
| 802 | +// Ideally this test would actually parse the ACL XML and check it, but for now it just makes sure S3 doesn't return an error | ||
| 803 | +- (void)testCannedACLs | ||
| 804 | +{ | ||
| 805 | + [ASIS3Request setSharedAccessKey:accessKey]; | ||
| 806 | + [ASIS3Request setSharedSecretAccessKey:secretAccessKey]; | ||
| 807 | + | ||
| 808 | + // Create a bucket | ||
| 809 | + ASIS3Request *request = [ASIS3BucketRequest PUTRequestWithBucket:bucket]; | ||
| 810 | + [request setRequestScheme:ASIS3RequestSchemeHTTPS]; | ||
| 811 | + [request startSynchronous]; | ||
| 812 | + GHAssertNil([request error],@"Failed to create a bucket"); | ||
| 813 | + | ||
| 814 | + NSArray *ACLs = [NSArray arrayWithObjects:ASIS3AccessPolicyPrivate,ASIS3AccessPolicyPublicRead,ASIS3AccessPolicyPublicReadWrite,ASIS3AccessPolicyAuthenticatedRead,ASIS3AccessPolicyBucketOwnerRead,ASIS3AccessPolicyBucketOwnerFullControl,nil]; | ||
| 815 | + | ||
| 816 | + for (NSString *cannedACL in ACLs) { | ||
| 817 | + // PUT object | ||
| 818 | + NSString *key = @"king"; | ||
| 819 | + request = [ASIS3ObjectRequest PUTRequestForData:[@"fink" dataUsingEncoding:NSUTF8StringEncoding] withBucket:bucket key:key]; | ||
| 820 | + [request setAccessPolicy:cannedACL]; | ||
| 821 | + [request startSynchronous]; | ||
| 822 | + GHAssertNil([request error],@"Failed to PUT some data into S3"); | ||
| 823 | + | ||
| 824 | + // GET object ACL | ||
| 825 | + request = [ASIS3ObjectRequest requestWithBucket:bucket key:key subResource:@"acl"]; | ||
| 826 | + [request startSynchronous]; | ||
| 827 | + GHAssertNil([request error],@"Failed to fetch the object"); | ||
| 828 | + } | ||
| 829 | + | ||
| 830 | + // DELETE it | ||
| 831 | + request = [ASIS3ObjectRequest DELETERequestWithBucket:bucket key:@"king"]; | ||
| 832 | + [request setRequestScheme:ASIS3RequestSchemeHTTPS]; | ||
| 833 | + [request startSynchronous]; | ||
| 834 | + BOOL success = [[request responseString] isEqualToString:@""]; | ||
| 835 | + GHAssertTrue(success,@"Failed to DELETE the object from S3"); | ||
| 791 | 836 | ||
| 792 | // Delete the bucket | 837 | // Delete the bucket |
| 793 | request = [ASIS3BucketRequest DELETERequestWithBucket:bucket]; | 838 | request = [ASIS3BucketRequest DELETERequestWithBucket:bucket]; |
-
Please register or login to post a comment