Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

s3 upload - uploads a file with 0 bytes length #1713

Closed
syberkitten opened this issue Sep 11, 2017 · 18 comments
Closed

s3 upload - uploads a file with 0 bytes length #1713

syberkitten opened this issue Sep 11, 2017 · 18 comments
Labels
closed-for-staleness documentation This is a problem with documentation.

Comments

@syberkitten
Copy link

syberkitten commented Sep 11, 2017

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

@chrisradek
Copy link
Contributor

@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);
});

@syberkitten
Copy link
Author

syberkitten commented Sep 11, 2017

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)
    }
  })

@syberkitten
Copy link
Author

syberkitten commented Sep 11, 2017

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:
#1633
and this:
#281
and this:
aws/aws-sdk-java#1101

@syberkitten
Copy link
Author

syberkitten commented Sep 11, 2017

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

@vnenkpet
Copy link

vnenkpet commented Nov 3, 2017

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.

@syberkitten
Copy link
Author

@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) => {
})

@nadrane
Copy link

nadrane commented Jan 5, 2018

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

@notoriaga
Copy link

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) => {
    ...
  });

@cpttripzz
Copy link

cpttripzz commented Jun 10, 2018

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

@ngrilly
Copy link

ngrilly commented Nov 21, 2018

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?

@srchase srchase added the documentation This is a problem with documentation. label Nov 21, 2018
@TanveerDayan
Copy link

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

@pavvell
Copy link

pavvell commented Dec 17, 2018

I use PassThrough solution that works just fine.

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

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

@srchase srchase added guidance Question that needs advice or information. and removed Question labels Jan 4, 2019
@rpstreef
Copy link

rpstreef commented Jul 1, 2019

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

@bkarv
Copy link

bkarv commented Aug 18, 2019

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:

angular/angular#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:
angular/angular#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?

@bkarv
Copy link

bkarv commented Aug 22, 2019

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.

@ajredniwja ajredniwja removed the guidance Question that needs advice or information. label Nov 20, 2019
@alfredo-celso
Copy link

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;
});
}

@github-actions
Copy link

Greetings! We’re closing this issue because it has been open a long time and hasn’t been updated in a while and may not be getting the attention it deserves. We encourage you to check if this is still an issue in the latest release and if you find that this is still a problem, please feel free to comment or open a new issue.

@github-actions github-actions bot added closing-soon This issue will automatically close in 4 days unless further comments are made. closed-for-staleness and removed closing-soon This issue will automatically close in 4 days unless further comments are made. labels Mar 15, 2021
@sotosoul
Copy link

In case somebody is looking for a Python solution, you need to seek. Here's an example:

data = open('local_file.txt', "rb")
size = len(data.read())
data.seek(0)
boto3_client.upload_fileobj(data, bucket_name, destination_key)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-for-staleness documentation This is a problem with documentation.
Projects
None yet
Development

No branches or pull requests

16 participants