aws / aws-sdk-js

AWS SDK for JavaScript in the browser and Node.js
https://aws.amazon.com/developer/language/javascript/
Apache License 2.0
7.59k stars 1.55k forks source link

S3 modifies presignedUrl uploaded file Content-Type (and Elastic Transcoder then fails) #911

Closed miguelcalderon closed 8 years ago

miguelcalderon commented 8 years ago

My app (Node 4.3) requests an upload signed URL:

var params = {
  Bucket: 'mybucket',
  Key: 'mimedia.mov',
  ContentType: 'video/quicktime'
};
new AWS.S3().getSignedUrl('putObject', params, function (err, presigned_url) {
  if (err) {
    console.log(err);
  } else {
    console.log(presigned_url);
  }
});

This always works and shows a presigned upload URL like this one:

https://mybucket.s3-eu-west-1.amazonaws.com/mimedia.mov?AWSAccessKeyId=[redacted]&Content-Type=video%2Fquicktime&Expires=1456324952&Signature=[redacted]

This URL is sent to a client browser like Chrome, which then executes the following code:

//presigned_url is the resulting URL from the app call
//videoFile is a blob object, a quicktime video file
var xhr = new XMLHttpRequest();
xhr.open('PUT', presigned_url);
xhr.setRequestHeader('Content-Type', 'video/quicktime');
xhr.send(videoFile);

The file is uploaded to the S3 bucket, but it holds the Content-Type "application/octet-stream", which is what is apparently making Elastic Transcoder fail to read it with the following code:

var eltr = new appConfig.AWS.ElasticTranscoder();
var params = {
    PipelineId: 'mypipelineid',
    Input: {
        Key: 'mymedia.mov',
        FrameRate: 'auto',
        Resolution: 'auto',
        AspectRatio: 'auto',
        Interlaced: 'auto',
        Container: 'auto'
    },
    Output: {
        Key: 'mymedia.mp4',
        ThumbnailPattern: '',
        PresetId: 'mypreset',
        Rotate: 'auto'
    }
};
eltr.createJob(params, function (err, data) {
    if (err) {
        console.log('Failed to send new video ' + params.Input.Key + ' to Elastic Transcoder');
        console.log(err);
        console.log(err.stack)
    } else {
        console.log(data);
    }
});

The resulting job's status is Error: Amazon Elastic Transcoder could not interpret the media file.

If I download that uploaded video file from S3 Browser, it seems to be corrupted (not playable).

If I upload the original video file with S3 browser, the uploaded file shows the correct ContentType (vdieo/quicktime) and Elastic Transcoder is able to do the conversion job.

Could you guide me in pointing out any possible mistake in this process that might be the reason for the error (eithr if it's the ContentType or some other Elastic Transcoder problem).

Thank you in advance,

LiuJoyceC commented 8 years ago

Hi @miguelcalderon When you try to put the video file to the signed url via the request module in Node, do you get the same error? When I upload a video to S3 through a presigned url and specify 'video/quicktime' as the content type in the header, the uploaded file still holds the correct content type and is not corrupted. Try something like this in Node as a test:

var request = require('request');
var fs = require('fs');
request({
  method: 'PUT',
  uri: presigned_url,
  body: fs.readFileSync('path_and_name_of_video_file.mp4'),
  headers: {
    'Content-Type': 'video/quicktime'
  }
},
function(error, response, body) {
  if (error) {
    console.error(error);
  } else {
    console.log('upload successful:', body);
  }
});

After a successful upload, if the downloaded video can play and it still holds the content type 'video/quicktime', then the issue does not seem to be related to the SDK or the presigned url produced by it. It seems the issue is occurring when the XMLHttpRequest is made. Please let us know if you still experience this same issue with this test and you believe it is related to the SDK.

miguelcalderon commented 8 years ago

Hi again,

I did as suggested (test data omitted):

var AWS = require('aws-sdk');
var request = require('request');
var fs = require('fs');
AWS.config.update(testData.AWSConfig);

var params = {
  Bucket: testData.Bucket,
  Key: 'uploadtest.mov',
  ContentType: 'video/quicktime'
};
return new AWS.S3().getSignedUrl('putObject', params, function (err, presigned_url) {
  if (err) {
    console.log('AWS.S3().getSignedUrl error' + err);
  } else {
    request({
      method: 'PUT',
      uri: presigned_url,
      body: fs.readFileSync(testData.filePath),
      headers: {
        'Content-Type': 'video/quicktime'
      }
    },
    function(error, response, body) {
      if (error) {
        console.error(error);
      } else {
        console.log('upload successful:', body);
      }
    });
  }
});

The file was uploaded and the mime type preserved. It was also playable once downloaded from S3 Browser.

So there seems to be a problem with my XMLHttprequest code as you suggest, but I'm not sure which could it be, since the PUT request carries the correct Content-Type header.

Do you have any ideas that could help me solve this problem?

Thank you in any case for helping me narrow it.

LiuJoyceC commented 8 years ago

Hi @miguelcalderon If you are looking to make a put request to S3 from the browser, you could use the browser SDK (https://aws.amazon.com/sdk-for-browser/), which will allow you to directly perform a putObject operation in your browser code and you will no longer need to generate a presigned url in Node to send to the browser. In the browser, you could have code similar to: s3.putObject(params, callback); where the params has a Body property containing the video file, in addition to the Bucket, Key, and ContentType.

I am closing this issue regarding the presigned url upload. If you have questions regarding uploading through the browser SDK, feel free to open a separate issue for it. Thanks!

lock[bot] commented 5 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.