This document provides a summary of a 4-part presentation on connecting to and making requests to Amazon S3 for cloud storage. It discusses formulating the S3 request by adding authentication headers to a NSMutableURLRequest object. Specifically, it covers generating the StringToSign, adding date, host, canonicalized resource and headers. The presentation provides code examples for each step to help developers interface with S3 directly without a library.
The Codex of Business Writing Software for Real-World Solutions 2.pptx
Connecting to Amazon S3 Weekly Code Drop
1. Weekly Code Drop:
Connecting to Amazon S3
Part 1 of 3
Formulating the S3 Request
Jason Hayes Christensen
jasonc411.com
July 11 Weekly Code Drop
2. ● Amazon S3 is a cloud-storage technology from
Amazon Web Services, LLC.
● Cloud storage can help overcome the storage
limitations of mobile architectures.
● There is a cost to using S3, so the applications
that use S3 to augment storage should have
a business model that accounts for these
costs.
● Pricing Information:
http://aws.amazon.com/s3/#pricing
July 11 Weekly Code Drop
3. ● Detailed S3 documentation can be accessed
at:
http://developer.amazonwebservices.com/connect/entry.jspaexternalID=123&categoryID=48
● We are not using any of the existing S3 libs.
● This is a 4-part set of presentations that show
how to develop for S3 from the ground up.
● The focus of this particular presentation is how
to augment the headers of the HTTP REST
request for authentication in virtual hosted
mode.
– Using NSURL and NSMutableURLRequest.
July 11 Weekly Code Drop
4. ● First, we will be using the ehmacauth
functionality from the July 4 code drop.
– Note that a memory leak was fixed and the
download site is updated.
● We create the “StringToSign” in this example.
● The “StringToSign” is defined on page 13 of the
S3 Developers Guide 20060301 as:
StringToSign = HTTP-Verb +
"n" +
Content-MD5 + "n" +
Content-Type + "n" +
Date + "n" +
CanonicalizedAmzHeaders +
CanonicalizedResource;
July 11 Weekly Code Drop
5. ● Remember, we are using virtual hosted mode,
this means that we address a bucket as:
<bucketName>.s3.amazonaws.com
● The “resource” is the file name, it is specified
as the path on the server when using virtual
hosted mode:
mybucket.s3.amazonaws.com/myresource.wav
● This model is nice because we do not have to
determine what part of the file path is the
bucket vs. the resource.
July 11 Weekly Code Drop
6. ● Let's define a new class that will modify the
headers of the outbound URL Request.
● This class adds S3 headers to an existing
NSMutableURLRequest.
● This class is named S3HTTPRequestHeaders.
● The one initializer for this class is:
- (id)initForBucket:(NSString*) bucket
andResource:(NSString*) resource;
● To avoid any specific coupling, the bucket and
resource names are broken out.
July 11 Weekly Code Drop
7. ● This initializer simply sets up the member
variables, including the start of the auth token:
- (id) initForBucket:(NSString*) bucket
andResource:(NSString*) resource
{
_bucket = bucket;
_resource = resource;
_authToken = [NSString stringWithFormat:@"AWS %@:", kPublicKey];
_amzDate = NO;
return self;
}
July 11 Weekly Code Drop
8. ● Dates are key to the request process.
● First of all, a request's date can differ by no
more than 15 minutes from the time it is
received by the Amazon servers.
● Second, if the date in the StringToSign is
different than the date Amazon receives in
the request header it will not authenticate.
● Watch for issues that affect the date header;
for instance some proxies can modify Date
header slightly.
– In this case use X-Amz-Date header.
July 11 Weekly Code Drop
9. ● Date header formatting must meet the RFC
1123 standard( http://ietf.org/rfc/rfc1123.txt )
● To do this we use NSDateFormatter.
– This allows us to both read and stream string
representations of the Date header.
- (NSString*)currentRFC1123DateTime:(NSDate*) date
{
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setFormatterBehavior: NSDateFormatterBehavior10_4];
[dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss ZZ"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
return [dateFormatter stringFromDate:date];
}
July 11 Weekly Code Drop
10. ● Modifying a header is simple, here we update
the HTTP Date header by calling:
– NSMutableURLRequest setValue:
forHTTPHeaderField:.
- (void) appendDateHeader:(NSMutableURLRequest*) request
date:(NSDate*) date
useAmzDate:(Boolean) amzDate;
{
[request setValue:[self currentRFC1123DateTime:date] forHTTPHeaderField:@"Date"];
if(amzDate){
[request setValue:[self currentRFC1123DateTime:date] forHTTPHeaderField:@"X-Amz-Date"];
_amzDate = amzDate;}
}
July 11 Weekly Code Drop
11. ● Since we are using virtualized host mode, we
have to modify the host header, this again is
very simple using the NSHttpURLRequest
setValue:forHTTPHeaderField:.
- (void) appendHostHeader:(NSMutableURLRequest*) request
{
[request setValue:[NSString stringWithFormat:@"%@.%@", _bucket, kHost]
forHTTPHeaderField:@"Host"];
}
July 11 Weekly Code Drop
12. ● Next we will add the canonicalizedResource.
● Directly from page 14 of the developers guide:
1 Start with the empty string ("").
2 If the request specif es a bucket using the HTTP Host header (virtual hosted-
i
style), append the bucket name preceded by a "/" (e.g., "/bucketname").
For path-style requests and requests that don't address a bucket, do
nothing. For more information on virtual hosted-style requests, see Virtual
Hosting of Buckets (p. 25).
3 Append the path part of the un-decoded HTTP Request-URI, up-to but not
including the query string.
4 If the request addresses a sub-resource, like ?location, ?acl, or ?
torrent, append the sub-resource including question mark.
July 11 Weekly Code Drop
13. ● Because we break out the resource and bucket
separately in the initializer for
S3HTTPRequestHeaders, this ends up being
very simple.
- (NSString*)canonicalizedResource:(NSURLRequest*) request
{
NSMutableString* canonResource = [[[NSMutableString alloc] initWithString:@"/"]
autorelease];
if(_bucket != nil)
[canonResource appendFormat:@"%@", _bucket];
if(_resource != nil)
[canonResource appendFormat:@"%@", _resource];
return canonResource;
}
July 11 Weekly Code Drop
14. ● The most obscure part of the “StringToSign” for
new devs are the canonicalizedAmzHeaders.
● These headers all have the prefix X-Amz-
● They are used to communicate application
specific information in the REST request.
● One caveat: multiple headers of the same
name are not supported by
NSHTTPUrlRequest.
● Therefore our demo code just concatenates
the headers after sorting them.
July 11 Weekly Code Drop
15. ● Directly from pp 14-15 of the Developers
Guide:
1 Convert each HTTP header name to lower-case. For example, 'X-Amz-Date'
becomes 'x- amz-date'.
2 Sort the collection of headers lexicographically by header name.
3 Combine header f elds with the same name into one "header-name:comma-
i
separated-value-list" pair as prescribed by RFC 2616, section 4.2, without
any white-space between values. For example, the two metadata headers
'x-amz-meta-username: fred' and 'x- amz-meta-username:
barney' would be combined into the single header 'x-amz-meta-
username: fred,barney'.
4 "Un-fold" long headers that span multiple lines (as allowed by RFC 2616,
section 4.2) by replacing the folding white-space (including new-line) by a
single space.
July 11 Weekly Code Drop
16. ● CanonicalizedAmzHeaders Process Cont.
5 Trim any white-space around the colon in the header. For example, the
header 'x-amz- meta-username: fred,barney' would become 'x-
amz-meta-username:fred,barney'
6 Finally, append a new-line (U+000A) to each canonicalized header in the
resulting list. Construct the CanonicalizedResource element by
concatenating all headers in this list into a single string
● Since our utility class NSHTTPUrlRequest
does not support multiple entries of the same
header, and removes whitespace, we have
simplified the code for the demo.
July 11 Weekly Code Drop
17. ● First, we have to sort the headers
– to do this we grab the array of keys and sort
them using NSString's
caseInsensitiveCompare.
- (NSString*)canonicalizedAmzHeaders:(NSURLRequest*) request
{
NSMutableString* ret = [[[NSMutableString alloc] init] autorelease];
NSDictionary* dict = [request allHTTPHeaderFields];
NSArray* keys = [[dict allKeys]
sortedArrayUsingSelector:@selector(caseInsensitiveCompare:) ];
July 11 Weekly Code Drop
18. ● Next we iterate over the array of keys looking
for keys with the header X-Amz
● When we find one we append it and its value to
the return string.
for(id header in keys)
{
NSString* str = [(NSString*)header lowercaseString];
if([str hasPrefix:@"x-amz"])
[ret appendFormat:@"%@:%@n", str, (NSString*)[dict objectForKey:header]];
}
return ret;
}
July 11 Weekly Code Drop
19. ● Finally we have all the information we need for
the StringToSign so we add the authorization
header.
● We sign the string with our private key, then
pass the public key, and the signature to S3
in the Authorization header.
● The string has the format:
“AWS <PublicKey>:<Signature>”
July 11 Weekly Code Drop
20. ● OK, a final look at setting up the StringToSign
● Let's setup a mutable string and grab the
standard headers that make up the string.
NSMutableString* s2s = [[[NSMutableString alloc]
initWithFormat:@"%@n", [request HTTPMethod]] autorelease];
NSString* contentMD5 = [request valueForHTTPHeaderField:@"content-md5"];
NSString* contentType = [request valueForHTTPHeaderField:@"content-type"];
NSString* date = [request valueForHTTPHeaderField:@"date"];
//We can't append null strings, so we do the following check on each header
if(contentMD5 != nil)
[s2s appendFormat:@"%@n", contentMD5];
else
[s2s appendString:@"n"];
July 11 Weekly Code Drop
21. ● Next we append the canonicalized headers,
push out a debug string, and return the
string.
[s2s appendString:[self canonicalizedAmzHeaders:request]];
[s2s appendString:[self canonicalizedResource:request]];
NSLog(@"************ String to Sign follows *******************");
NSLog(s2s);
NSLog(@"************ End String To Sign ***********************");
return s2s;
}
July 11 Weekly Code Drop
22. ● Finally we sign the string, this should look
familiar from last weeks code drop.
- (void) appendAuthorizationHeader:(NSMutableURLRequest*) request
{
NSString* stringToSign = [self createStringToSign:request];
NSString* authToken = [NSString stringWithFormat:@"%@:%@",
_authToken,
[EncodedHMACToken createEncodedHMACToken:kPrivateKey
message:stringToSign
signatureType:kSHA1
forURI:NO]];
[request setValue:authToken
forHTTPHeaderField:@"Authorization"];
}
July 11 Weekly Code Drop
23. ● At this point, we should be good to go. The
NSMutableURLRequest has the appropriate
headers to make the invocation.
● From this point, to make a request, you will
have to setup your own S3 Account.
● See page 9 of the Getting Started Guide for S3
accessible at:
http://developer.amazonwebservices.com/connect/entry.jspaexternalID=123&categoryID=48
July 11 Weekly Code Drop
24. ● In summary
– Unit test are included
– To this point, we are able to use the outlined
tests in the developers guide on pp. 14-19
– The first three lines of each test are just staged
for right now, in the actual requests they will
be more formalized.
– As always, code is accessible at
jasonc411.com's downloads pages.
– July 18 WCD should go out mid-week.
July 11 Weekly Code Drop
25. ● Upcoming Code Drops:
– Part 2 Processing a Response
– Part 3 Creating the libS3 Functional interface
– Part 4 Using S3 with the iAudioNotebook.
Hope all is well,
jason h christensen (on Twitter: jasonc411)
founder
jasonc411.com
software architecture
technical research
technical thought leadership
July 11 Weekly Code Drop