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