4

I'm using AWS Lambda to create a simple upload service for my mobile product.

On the server, I generate a presigned URL using the following code

var params = {
    Bucket: targetS3Bucket,
    Key: key,
    Body: '',
    ContentType: event.contentType,
    Expires: 60
};

s3.getSignedUrl('putObject', params, function (err, url){
    context.done(null, {
        'oneTimeUploadUrl': url,
        'resultUrl': urlPrefix + key
    });
});

Where targetS3Bucket is a path to a folder on S3, key is the name of the file itself and urlPrefix is the root of the HTTP location of the file on S3 (ie: s3.amazonaws.com/some-folder/)

Using this code with a built-in HTTP library (That is to say, NOT using any aws SDK) works without failure on PC and iOS, but not on Android.

The most recent version of the Android client code looks like this:

uri = new URL(oneTimeUploadUrl);

// Setup Connection
HttpsURLConnection http = (HttpsURLConnection) uri.openConnection();
http.setDoOutput(true);
http.setRequestMethod("PUT");
​
// Write Data
OutputStream os = http.getOutputStream();
os.write(_bytes);
os.flush();
os.close(); // request gets sent off to the server

This consistently fails with code 400. I've tried several things like changing the encoding, using the non-https version of HttpsURLConnection and a few other things, but to no avail.

I'd prefer to avoid bringing in the AWS SDK since I only need this single function to work, and using this lambada-side solution has made that possible on all platforms except android.

Here is the XML returned from AWS. The message that is returned is confusing because the client never alters the token, and the same process succeeds on other devices.

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>InvalidToken</Code>
    <Message>The provided token is malformed or otherwise invalid.</Message>
    <Token-0>{Token-0}</Token-0>
    <RequestId>{RequestId}</RequestId>
    <HostId>{HostId}</HostId>
</Error>
Mervill
  • 61
  • 5
  • Can you capture the response body that accompanies that `400 Bad Request` response? There should be some human-readable XML with a hint in there to help us figure out what, specifically, is going on. Otherwise, it's just guesswork. – Michael - sqlbot Apr 05 '16 at 02:45
  • Updated the main comment with this information – Mervill Apr 05 '16 at 18:21
  • Thanks for the update. We'll need to see things with a little less redaction, I suspect. Nothing in a pre-signed URL or the error response is genuinely sensitive -- the RequestId and HostId are just values that AWS support can use to research errors in their logs, so those don't need to be shown (but aren't sensitive), and if you want to remove the accesskeyid (usually starts with A*IA), you could remove that from the URL... but we probably need to see the rest of the URL and response. – Michael - sqlbot Apr 06 '16 at 12:08

1 Answers1

2

The issue is that HttpURLConnection silently adds Content-Type: application/x-www-form-urlencoded to the request. This is annoying since it's not easy to determine what headers are in the request of a HttpURLConnection object.

Anyway. Here is the correct code

uri = new URL(oneTimeUploadUrl);

// Setup Connection
HttpsURLConnection http = (HttpsURLConnection) uri.openConnection();
http.setDoOutput(true);
http.setRequestMethod("PUT");
http.setRequestProperty("Content-Type"," "); // remove Content-Type header

// Write Data
OutputStream os = http.getOutputStream();
os.write(_bytes);
os.flush();
os.close(); // request gets sent off to the server

See also: HttpURLConnection PUT to Google Cloud Storage giving error 403

Community
  • 1
  • 1
Mervill
  • 61
  • 5