This is working fine now! A very special thanks to Amit at Slideshare, more to come this weekend! The slideshare response was great, parts 2 and 3 will be posted this weekend.
July 11 Weekly Code Drop Part 1 of 3 Creating an S3 library - Presentation Transcript
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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
● 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
This is part one of a three part series on creating more
This is part one of a three part series on creating an S3 LIbrary for CoCoa touch. There will be a follow on slide as well that covers using the library to store audio files gathered from the iPhone/iPod touch. less
1 comments
Comments 1 - 1 of 1 previous next Post a comment