Hi, I have searched the forum for how to make S3 uploads work from Android but most of the threads are quite old and actually not very helpful (I tried many suggestions but with no success).
I'm using Titanium mobile version 2.1.0 (Studio build: 2.1.0.201206251749). Generally I used emulator but it behaves the same on a device. I run OS X Lion.
POST
I got S3 uploads working very simply for iOS using the POST method like this:
var xhr = Ti.Network.createHTTPClient(); xhr.setTimeout(3 * 60 * 1000); // 3 min xhr.onload = function() { callback(true, null); }; xhr.onerror = function(e) { callback(false, 'Something has gone wrong'); }; xhr.open('POST', 'http://our-bucket.s3.amazonaws.com/', true); fields['file'] = blob; xhr.send(fields);(Where fields is a server-generated JSON object with all the HTML fields needed for a browser-based S3 upload using POST and blob is a Ti.Blob with a photo)
Unfortunately, on Android Amazon's server returns the following error:
InvalidArgument: Bucket POST must contain a field named 'key'. If it is specified, please check the order of the fields.
I fixed this by manually creating a string with parameters to send as a part of the request (the order is fine in our server's response but not actually preserved in the JS object). Last 2 lines of the previous code snippet get replaced with:
var urlParams = ''; for (var i in fields) { if (urlParams !== '') { urlParams += '&'; } if (i !== 'file') { urlParams += i + '=' + fields[i]; } else { urlParams += i + '=' + Ti.Utils.base64encode(blob); } } xhr.send(urlParams);
This failed again with:
InvalidArgument: Conflicting query string parameters: acl, policy(Just for the record acl is set to 'public-read' and policy includes just all it needs to. Again, the code works on iOS and uploads are handled in the same way on our server running Django.)
Here I got stuck because I was not able to figure out what was wrong with it after googling for a bit. There are actually not too many people having this kind of problem as it turns out.
PUT
So I switched to using the PUT method as seen on this forum.
Ti.include('sha-aws.js'); // file comes from this URL's project: http://aws.amazon.com/code/Amazon-S3/3236824658053653 Ti.include('utf8.js'); // code for this file from this URL: http://www.webtoolkit.info/javascript-utf8.html Ti.include('date.js'); // file comes from this URL: http://www.mattkruse.com/javascript/date/source.html var xhr = Ti.Network.createHTTPClient(); xhr.setTimeout(3 * 60 * 1000); // 3 min xhr.onload = function() { callback(true, null); }; xhr.onerror = function(e) { callback(false, 'Something has gone wrong'); }; var AWSAccessKeyID = 'AWS ' + 'accesskey' + ':'; var AWSSecretAccessKey = 'secretkey'; // STEP 2: Change your bucket name to a bucket you have on AWS S3. var AWSBucketName = 'our-bucket'; // STEP 3: Change to a file that exists in your resources directory of your titanium app. // STEP 4: Change your GMT offset (-0700 is PST) var currentDateTime = formatDate(new Date(),'E, d MMM dd yyyy HH:mm:ss') + ' +0000'; //Expected date format for AWS: 'Mon, 30 May 2011 17:00:00 -0700'; xhr.setTimeout(99000); // true or false if it's asynchronous or not (last parameter below for xhr.open) xhr.open('PUT', 'https://' + AWSBucketName + '.s3.amazonaws.com/' + '/' + fields['key'], false); var StringToSign = 'PUT\n\n\n'+blob.mimeType+'\n' + currentDateTime + '\n/'+AWSBucketName+'/' + fields['key']; var AWSSignature = b64_hmac_sha1(AWSSecretAccessKey, Utf8.encode(StringToSign)); var AuthorizationHeader = AWSAccessKeyID.concat(AWSSignature); // On Android, base64encode() returns a TiBlob, so we must convert this toString in order to work. xhr.setRequestHeader('Authorization', Ti.Utils.base64encode(AuthorizationHeader).toString()); xhr.setRequestHeader('Content-Type', blob.mimeType); // The Content-Length header works fine for iOS, but on Android raises an error: "Content-Length header already present" //xhr.setRequestHeader('Content-Length', uploadFile.size); xhr.setRequestHeader('Host', AWSBucketName + '.s3.amazonaws.com'); xhr.setRequestHeader('Date', currentDateTime); xhr.send(blob);This is where I got this strange error (like in one of the threads I saw here):
InvalidArgument: Authorization header is invalid -- one and only one ' ' (space) required
Other
I also tried using this code snippet: https://gist.github.com/1012632/21d665acaf4b674a19f04ee4fb3b8e37a4b4cd28 but it didn't work. Maybe I was using it in a wrong way but it looked like I had to change the snippet a little to fix some errors but further ones appeared.
So...
I will be grateful for any help regarding S3 uploads from Android. Hopefully there are more people who have experience with this now. Thanks.