Aws-sdk-js: s3 upload - uploads a file with 0 bytes length

Created on 11 Sep 2017  路  16Comments  路  Source: aws/aws-sdk-js

The response is fine, but the actual file in bucket is 0 bytes length.
it's an application/octet-stream using a nodejs createReadStream instance.

I'm currently using putObject which has other problems
such as timeout when uploading more then 500kb
and not Location property in response (so I have to create the location myself)

-- region: eu-central-1
-- bucket: EU frankfurt

documentation

Most helpful comment

I was having a similar problem when passing a http.IncomingMessage stream to s3.upload. If I tried to measure the size of the file while uploading I would always get the 0 byte object. However, if I added a pause to the stream before the upload it all worked as intended. I'm not sure this is exactly your problem, but hopefully it helps. I think in general it might be a problem with adding event listeners to a stream before passing it to s3.

  let fileSize = 0;
  req.on('data', chunk => {
    fileSize += Buffer.byteLength(chunk);
  });

  // Without this I get a 0 byte object
  req.pause();

  s3.upload({ Key: s3Key, Body: req }, {}, (err, results) => {
    ...
  });

All 16 comments

@syberkitten
Can you show how you're calling upload or putObject? The version of the SDK/node.js might be helpful too. Any request IDs you can share with us could be helpful as well.

For what it's worth, I'm able to upload objects using node streams with version 2.112.0 of the SDK and 8.4.0 of node.js. I'm also using a bucket in eu-central-1.

Here's the code I'm using. I included code to turn on request id logging for any S3 operation as well (it can be difficult to find request ids for operations that didn't error when using s3.upload otherwise)

const fs = require('fs');
const path = require('path');

const S3 = require('aws-sdk/clients/s3');
const s3 = new S3({
    region: 'eu-central-1'
});

// Add custom request handlers to log out request ids for any S3 client
S3.prototype.customizeRequests(function(request) {
    function logRequestIds(response) {
        const operation = response.request.operation;
        const requestId = response.requestId;
        const requestId2 = response.extendedRequestId;
        console.log(`${operation}, requestId: ${requestId}, requestId2: ${requestId2}`);
    }
    request.on('extractData', logRequestIds);
    request.on('extractError', logRequestIds);
});

// 3 MB text file
const filePath = path.join(process.cwd(), 'test.txt');

// upload file
s3.upload({
    Bucket: 'BUCKET',
    Key: 'test.txt',
    Body: fs.createReadStream(filePath)
}).promise().then(function(uploadData) {
    return s3.headObject({
        Bucket: 'BUCKET',
        Key: 'test.txt'
    }).promise();
}).then(function(headData) {
    console.log(headData);
    /*
        { AcceptRanges: 'bytes',
  LastModified: 2017-09-11T19:25:00.000Z,
  ContentLength: 3145728,
  ETag: '"8f9abcae94ab69f39fc6935b89dabefc"',
  ContentType: 'application/octet-stream',
  Metadata: {} }
    */
}).catch(function(err) {
    console.error(err);
});

Thanks for the lead, integrated the headObject so here is the response for upload:

  "headData": {
    "AcceptRanges": "bytes",
    "LastModified": "2017-09-11T20:08:25.000Z",
    "ContentLength": 0,
    "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
    "ContentType": "application/octet-stream",
    "Metadata": {}
  }

//and this for putobject:

{
  "AcceptRanges": "bytes",
  "LastModified": "2017-09-11T20:52:33.000Z",
  "ContentLength": 4473,
  "ETag": "\"f3e3ada967e9f95d2c9428ddb80c7ded\"",
  "ContentType": "application/octet-stream",
  "Metadata": {}
}

Using Node Version 7.10.0
AWS Sdk: 2.112.0

This is a sample of the code, I'm using TypeScript so it's a blend
and should be easy to read, works both for putObject and upload,
just upload returns 0 bytes as before.

  const params: PutObjectRequest = {
    Bucket: config.S3_OTA_UPDATES_BUCKET,
    Key: folder + '/' + payloadID,
    Body: stream,
    ACL: 'public-read',
    ContentType: 'application/octet-stream',
  }

return new Promise<UpdatePayload>(async (resolve, reject) => {

    let error
    let putResponse: UpdatePayload

    // Simply change upload to putObject, same signature, difference in results :(
    // [error, putResponse] = await to(s3.putObject(params).promise())

    [error, putResponse] = await to(s3.upload(params).promise())

    if (error) {
      reject(error)
    }

    if (putResponse) {
      const headData: any = await s3.headObject({
        Bucket: config.S3_OTA_UPDATES_BUCKET,
        Key: folder + '/' + payloadID,
      }).promise()

      const updatePayload: UpdatePayload = {
        _id: payloadID,
        timestamp: (new Date()).getTime(),
        md5: hash,
        //url: resp.Location,
        url: createBucketResourceLocation(folder, payloadID),
        headData: headData,
      }
      resolve(updatePayload)
    }
  })

Upgraded to Node 8.4.0 Stable, still same result.

The only problem with putObject for me is the timeout it seems to have
uploading big files (larger the 1mb) on slow connections.

Getting:

RequestTimeout: Your socket connection to the server was not read from or written to within the timeout period. Idle connections will be closed.

this issue:
https://github.com/aws/aws-sdk-js/issues/1633
and this:
https://github.com/aws/aws-sdk-js/issues/281
and this:
https://github.com/aws/aws-sdk-java/issues/1101

I have a version somewhere which uses multipart upload
as an express middleware, I'll try uploading something large. :)

I'm second this. It happens to us occassionally, I simply pass the ReadStream to putObject, response is fine, file is there at our end, I can check the size etc, but successful upload to S3 often results in zero bytes object.
The behaviour is also pretty inconsistent. The same file in the same requests usually fails to upload in like 30% of the time.

@vnenkpet
started using s3-upload-stream which use the S3 multipart upload API
which does a perfect job.
https://www.npmjs.com/package/s3-upload-stream

I was able to upload files up to 1gb of size with upload times of 1.5 hours
and it also supports resuming uploads and many more.

no need to re-invent the wheel, we should use battle prone software
and not shaky flacky un-tested and fragmented API calls ;)

here's a code sample:

import {S3StreamUploader, S3WriteStream} from 's3-upload-stream'

  const params: PutObjectRequest = {
    Bucket: config.S3_OTA_UPDATES_BUCKET,
    Key: folder + '/' + payloadID,
    //Body: stream,
    ACL: 'public-read',
    ContentType: 'application/octet-stream',
  }

const s3 = new AWS.S3({accessKeyId: config.S3_AK, secretAccessKey: config.S3_SK, signatureVersion: config.S3_SIGNATURE_VERSION, region: config.S3_REGION })
const s3Uploader: S3StreamUploader = s3Stream(s3)

const stream = fs.createReadStream(fileName)
const upload: S3WriteStream = s3Uploader.upload(params)
stream.pipe(upload)
upload.concurrentParts(1)

    upload.on('error', (error) => {
      console.log(error)
    })

    /* Handle progress. Example details object:
       { ETag: '"f9ef956c83756a80ad62f54ae5e7d34b"',
         PartNumber: 5,
         receivedSize: 29671068,
         uploadedSize: 29671068 }
    */
    upload.on('part', (details) => {
      logger.info(details, `Uploaded part...}`)
    })

upload.on('uploaded', async (details) => {
})

I'm running into this issue as well. Has any headway been made towards resolving it?

I'm using aws-sdk 2.176.0.

I'd love to not have to switch to a 3rd party library

I was having a similar problem when passing a http.IncomingMessage stream to s3.upload. If I tried to measure the size of the file while uploading I would always get the 0 byte object. However, if I added a pause to the stream before the upload it all worked as intended. I'm not sure this is exactly your problem, but hopefully it helps. I think in general it might be a problem with adding event listeners to a stream before passing it to s3.

  let fileSize = 0;
  req.on('data', chunk => {
    fileSize += Buffer.byteLength(chunk);
  });

  // Without this I get a 0 byte object
  req.pause();

  s3.upload({ Key: s3Key, Body: req }, {}, (err, results) => {
    ...
  });

it would be nice to add this to the docs to save other devs from wasting hours / days

req.pause();

kudos to @notoriaga for the fix

I'm second this. It happens to us occassionally, I simply pass the ReadStream to putObject, response is fine, file is there at our end, I can check the size etc, but successful upload to S3 often results in zero bytes object.

@vnenkpet Have you solved the issue?

I have this same issue. Response is fine. But successful upload to s3 causes a 0 byte file. And the behaviour is very inconsistent.

I use PassThrough solution that works just fine.

const sourceFile: ReadableStream;
const streamCopy = new PassThrough();
sourceFile.pipe(streamCopy);

aws.getInstanse().upload({ Body: streamCopy,... });

it would be nice to add this to the docs to save other devs from wasting hours / days

req.pause();

kudos to @notoriaga for the fix

this does not fix the issue, I still get 0 byte uploads with filestream pause.

Even if it did, this is still a hack where the actual lib needs fixing. Reliability is key

It is an angular service worker issue. When enabled it causes uploading 0kb in safari. People using firebase uploads also are having the same issue when angular service worker is enabled and in safari. See below:

https://github.com/angular/angular/issues/23244

Seems like the workaround is to bypass the service worker when doing the S3 call. There is a feature here below to bypass:

https://angular.io/guide/service-worker-devops

To bypass the service worker you can set ngsw-bypass as a request header, or as a query parameter. (The value of the header or query parameter is ignored and can be empty or omitted.)

Implementation of bypass here in angular:
https://github.com/angular/angular/pull/30010

I tried doing the following in aws call:

this.bucket
          .on('build', (req) =>{ req.httpRequest.headers['ngsw-bypass']})
          .on('httpUploadProgress', (progress) => {
                 // progress code    
          })
          .send((err, data) => {
          //success response
        },
        err => {
        })

Still getting the issue of uploading okb ie not being bypassed. Any suggestion on how I can have implement the bypass feature for AWS s3 upload calls?

Ok found the solution. As my previous comment angular service worker is causing the issue. There is an option to bypass service by adding 'ngsw-bypass'. However I did not find a way to add custom headers to S3.manageUploads ('build' method only applies to putObject).

Therefore only work around is to manually add the following code AFTER build to the ngsw-worker.js file.

Under onFetch(event) function add the following after const declarations:

if (req.method==="PUT" && requestUrlObj.origin.indexOf('amazonaws.com')>-1){return;}

It should look like this:

    /**
     * The handler for fetch events.
     *
     * This is the transition point between the synchronous event handler and the
     * asynchronous execution that eventually resolves for respondWith() and waitUntil().
     */
    onFetch(event) {
        const req = event.request;
        const scopeUrl = this.scope.registration.scope;
        const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
        if (req.method==="PUT" && requestUrlObj.origin.indexOf('amazonaws.com')>-1){return;}
        if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {
            return;
        }
        ....

Alternatively you can also add the following INSTEAD:
if (req.method==="PUT" || !!req.headers.get('range')) {return;}

For my project there is no need for PUT methods to go through service workers hence I am using the latter code as this also should avoid other issues such as video/audio not properly playing in Safari. More information on this see below:
angular/angular#25865 (comment)
https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/

If anyone knows how to automate this during the build please let me know. but at least it is a solution now.

Hello people;

I have a similar issue when trying to post a file into JIRA instance.
The file is uploaded with 0 bytes and so far, no solution yet.

Here is my code:
module.exports = function(fileName, jiraUrl) {
var request = require('request');
var fs = require('fs');

var options = {
'method': 'POST',
'url': jiraUrl,
'headers': {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'xx',
'X-Atlassian-Token': 'no-check',
'Authorization': 'Basic XXXX',
'Accept': 'application/json',
},
formData: {
'file': {
'value': fs.createReadStream(fileName),
'options': {
'filename': fileName,
'contentType': null
}
}
}
};
//console.log(options);

request(options, function (error, response) {
if (error) {console.log(error)};
return;
});
}

Was this page helpful?
0 / 5 - 0 ratings