I am using the awd-sdk for nodejs and have a working upload.
const checksum = await this.getChecksum(path);
const payload: S3.PutObjectRequest = {
Bucket: bucket,
Key: key,
Body: fs.createReadStream(path),
ContentMD5: checksum,
};
return this.s3.upload(payload).promise();
This piece of code works great for small files and takes advantage of ContentMD5 which automatically verifies the file integrity.
Content-MD5
The base64-encoded 128-bit MD5 digest of the message (without the headers) according to RFC 1864. This header can be used as a message integrity check to verify that the data is the same data that was originally sent. Although it is optional, we recommend using the Content-MD5 mechanism as an end-to-end integrity check. For more information about REST request authentication, see REST Authentication.
https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
However it doesn't work for multipart uploads.
The Content-MD5 you specified is invalid for multi-part uploads.
That makes sense because we send the file chunk by chunk but then, I am wondering how am I supposed to use this feature with multipart uploads?
I too faced this and did multiple testing. Finally found the answer and verified it in my own way. If you guys know a much better way please let me know here.
Being said that, here's how I solved the issue.
When you create the S3 client you have to create it as below.
const s3 = new AWS.S3({ computeChecksums: true });
Then you can define the s3 upload parameters like below
var fileData = Buffer.from(fileContentString, 'binary');
var s3params = {
Bucket: bucketName,
Key: folderName + "/" + fileName,
ContentType: 'binary',
Body: fileData
};
Then do the upload as below.
await s3.upload(s3params).promise()
.then(data => {
// use this log to verify whether the md5 checksum was verified
console.log(`File uploadedd successfully ${JSON.stringify(data)}`);
// handle success upload
})
.catch(error => {
//handle the error
});
After upload is successful here's how I verified its working.
Check the item in the S3. In the document details, Object Overview section check the E-tag. You should see something like this 7e35f58f134d8914604c9fc6c35b2db7-9. This number after the - means how many parts were uploaded. For bigger files there should be a number bigger than 1. In this case it is 9.
Check the log in the console. (The log with the comment in above code.) You'll see something like below.
{
"Location": "https://bucketname.s3.region.amazonaws.com/folderName/fileName",
"Bucket": "bucketName",
"Key": "folderName/fileName",
"ETag": "\"7e35f58f134d8914604c9fc6c35b2db7-9\""
}
If you are debugging you can further test it by printing the s3 upload request.
var uploadRequest = s3.upload(s3params);
console.log(`S3 Request ${JSON.stringify(uploadRequest)}`);
This will print the s3 client configurations. Check whether the 'computeChecksums' is set to true.
I tried to verify with the s3.putObject as well. But when I print the request it didn't show me the md5Checksum config in header as it is intended to be. Also it gave me cyclic JSON stringifying error when I tried to log the whole object in the same way as in the third point for upload. So I printed httpRequest only.
var uploadRequest = s3.putObject(s3params);
console.log(`S3 Request ${JSON.stringify(uploadRequest.httpRequest)}`
//Following gives the JSON stringifying cyclic issue
//console.log(`S3 Request ${JSON.stringify(uploadRequest)}`);
Appreciate if someone can tell how to do this and verify with putObject as well.
Related
I have a trouble, i'm using aws-sdk on the browser to upload videos from my web app to Amazon S3, it works fine with shortest files (less than 100MB) but with large files (500MB for example) amazon retries the upload.
For example, when the upload is in 28% it return back to 1%, i don't know why, i put an event listener of the upload file but it don't give any error, just return back to 1%
Literally this is my code:
const params = {
Bucket: process.env.VUE_APP_AWS_BUCKET_NAME, // It comes from my dotenv file
Key: videoPath, // It comes from a external var
ACL: 'public-read',
ContentType: this.type, // It comes from my class where i have the video type
Body: VideoObject // <- It comes from the input file
};
s3.putObject(params, function (err) {
if (err)
console.log(err);
}).on('httpUploadProgress', progress => {
console.log(progress);
console.log(progress.loaded + " - " + progress.total);
this.progress = parseInt((progress.loaded * 100) / progress.total);
});
I really would like to give more info but that's all i have, i don't know why s3 retry the upload without any error (Also i don't know how to catch s3 errors...)
My internet connection is fine, i'm using my business conection and it works fine, this issue happens with all my computers
For large object you may try upload function that supports progress tracking and multipart uploading to upload parts in parallel. Also I didn't see content length set in your example, actually uplaod accepts a stream without a content length necessarily defined.
An example: https://aws.amazon.com/blogs/developer/announcing-the-amazon-s3-managed-uploader-in-the-aws-sdk-for-javascript/
You need to use a multipart upload for large files.
I need to create a signed url to upload a file to an s3 bucket.
The s3 file key should be its sha256 hash.
The question then is: how can I make sure the client sends a valid hash? I'm creating the signed url at my lambda function and avoid passing the file through it, so the lambda of course cannot calculate the hash.
I'm thinking I can achieve this using 2 steps:
Force the client to send its calculated sha256 with the upload. Based on spec I am assuming this will be auto-checked when providing it in a x-amz-content-sha256 header.
Force client to send the same hash to the lambda so I can force it to be the key.
First, I tried this:
s3.getSignedUrl('putObject', { Key: userProvidedSha256 }, callback)
I tried adding a condition like { header: { 'X-Amz-Content-Sha256': userProvidedSha256 } }.
But I found no way of adding such a definition so that it actually forces the client to send a X-Amz-Content-Sha256 header.
Also, I would have taken the same approach to enforce a fixed required Content-Length header (client sends desired length to back-end, there we sign it), but not sure that would work because of this issue.
Because I found out that s3.createPresignedPost also lets me limit max attachment size and appears more flexible, I went down that route:
const signPostFile = () => {
const params = {
Fields: {
key: userProvidedSha256
},
Expires: 86400,
Conditions: [
['content-length-range', 0, 10000000],
{ 'X-Amz-Content-Sha256': userProvidedSha256]
]
}
s3.createPresignedPost(params, callback)
}
But while that works (it forces the client to send the enforced sha256 header, and the header gets passed, see request log below), it looks like the client now has to add the x-amz-content-sha256 into the form fields rather than the header. This seems to be as intended, but it clearly appears that s3 won't check the submitted file against the provided sha256: any file I append to the form is successfully uploaded even if the sha256 is a mismatch.
Any suggestion what's wrong, or how else I can enforce the sha256 condition, while also limiting content length?
Update: I'm using signature v4, and I've tried a S3 policy Deny for this condition:
Condition:
StringEquals:
s3:x-amz-content-sha256: UNSIGNED-PAYLOAD
Relevant request log for submitting a file containing the string "hello world":
----------------------------986452911605138616518063
Content-Disposition: form-data; name="X-Amz-Content-Sha256"
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
----------------------------986452911605138616518063
Content-Disposition: form-data; name="key"
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
To my knowledge S3 does not provide sha256 by default. However, by listening to S3 events you can implement a Lambda function that does this automatically for you. Here is a suggestion that comes to mind:
The client requests a S3 signed url based on the user provided sha256
The client uploads the file using the signed url
A Lambda function is configured to listen to s3:ObjectCreated:* events from the upload bucket
When the upload is completed, the Lambda function is triggered by a S3 Message Event. A part of the event is the S3 object key
The Lambda function downloads the uploaded file and re-calculate the sha256
The Lambda function deletes the file if the calculated sha256 value differs from the sha256 value that was provided by the client (either as the object key or available from the objects metadata)
Alternatively, if the main objective is to verify the file integrity of uploaded files, S3 provides another option to use sha256 when calculating checksums.
Configure a bucket policy to only accept requests that have been signed
Configure the client AWS S3 sdk to use AWS signature version 4, e.g.
const s3 = new AWS.S3({apiVersion: '2006-03-01', signatureVersion: 'v4'});
The S3.putObject() function will sign the request before uploading the file
S3 will not store an object if the signature is wrong as described in the AWS CLI S3 FAQ
I have a specific fetch request in my node app which just pulls a json file from my S3 bucket and stores the content within the state of my app.
The fetch request works 99% of the time but for some reason about every 4 or 5 days I get a notification saying the app has crashed and when I investigate the reason is always because of this ssl handshake failure.
I am trying to figure out why this is happening as well as a fix to prevent this in future cases.
The fetch request looks like the following and is called everything someone new visits the site, Once the request has been made and the json is now in the app's state, the request is no longer called.
function grabPreParsedContentFromS3 (prefix, callback) {
fetch(`https://s3-ap-southeast-2.amazonaws.com/my-bucket/${prefix}.json`)
.then(res => res.json())
.then(res => callback(res[prefix]))
.catch(e => console.log('Error fetching data from s3: ', e))
}
When this error happens the .catch method with get thrown and returns the following error message:
Error fetching data from s3: {
FetchError: request to https://s3-ap-southeast-2.amazonaws.com/my-bucket/services.json failed,
reason: write EPROTO 139797093521280:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:../deps/openssl/openssl/ssl/s3_pkt.c:659:
...
}
Has anyone encountered this kind of issue before or has any idea why this might be happening? Currently I am wondering if maybe there is a limit to the amount of S3 request i can make at one time which is causing it to fail? But the site isn't super popular either to be request a huge portion of fetches.
I'm trying to return a 1px gif from an AWS API Gateway method.
Since binary data is now supported, I return an image/gif using the following 'Integration Response' mapping:
$util.base64Decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
However, when I look at this in Chrome, I see the following binary being returned:
Instead of:
Could anyone help me understand why this is garbled and the wrong length? Or what I could do to return the correct binary? Is there some other what I could always return this 1px gif without using the base64Decode function?
Many thanks in advance, this has being causing me a lot of pain!
EDIT
This one gets stranger. It looks like the issue is not with base64Decode, but with the general handling of binary. I added a Lambda backend (previously I was using Firehose) following this blog post and this Stack Overflow question. I set images as binaryMediaType as per this documentation page.
This has let me pass the following image/bmp pixel from Lambda through the Gateway API, and it works correctly:
exports.handler = function(event, context) {
var imageHex = "\x42\x4d\x3c\x00\x00\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x18\x00\x00\x00\x00\x00\x06\x00\x00\x00\x27\x00\x00\x00\x27\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00";
context.done(null, { "body":imageHex });
};
However the following images representing an image/png or a image/gif get garbled when passed through:
exports.handler = function(event, context) {
//var imageHex = "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b";
//var imageHex = "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b";
var imageHex = "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b\x0a"
context.done(null, { "body":imageHex });
};
This seems to be the same issue as another Stack Overflow question, but I was hoping this would be fixed with the Gateway API binary support. Unfortunately image/bmp doesn't work for my use case as it can't be transparent...
In case it helps anyone, this has been a good tool for converting between base64 and hex.
To anyone else having problems with this: I was also banging my head against the wall trying to retrieve a binary image over API Gateway proxy integration from lambda, but then I noticed that it says right there in the Binary Support section of Lambda Console:
API Gateway will look at the Content-Type and Accept HTTP headers to decide how to handle the body.
So I added Accept: image/png to the request headers and it worked. Oh the joy, and joyness!
No need to manually change content handling to CONVERT_TO_BINARY or muck about with the cli. Of course this rules out using, for example, <img src= directly (can't set headers).
So, in order to get a binary file over API Gateway from lambda with proxy integration:
List all supported binary content types in the lambda console (and deploy)
The request Accept header must include the Content-Type header returned from the lambda expression
The returned body must be base64 encoded
The result object must also have the isBase64Encoded property set to true
Code:
callback(null, {
statusCode: 200,
headers: { 'Content-Type': 'image/png' },
body: buffer.toString('base64'),
isBase64Encoded: true
}
It looks like this was a known issue previously:
https://forums.aws.amazon.com/thread.jspa?messageID=668306򣊒
But it should be possible now that they've added support for binary data:
http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html
It looks like this is the bit we need: "Set the contentHandling property of the IntegrationResponse resource to CONVERT_TO_BINARY to have the response payload converted from a Base64-encoded string to its binary blob". Then we shouldn't need the base64Decode() function.
Working on a test now to see if this works.
EDIT: I was finally able to get this working. You can see the binary image here:
https://chtskiuz10.execute-api.us-east-1.amazonaws.com/prod/rest/image
I updated the method response as follows:
I updated the integration response to include a hard-coded image/png header:
The last step was tricky: setting the contentHandling property to "CONVERT_TO_BINARY". I couldn't figure out how to do in the AWS console. I had to use the CLI API to accomplish this:
aws apigateway update-integration-response \
--profile davemaple \
--rest-api-id chtskiuzxx \
--resource-id ki1lxx \
--http-method GET \
--status-code 200 \
--patch-operations '[{"op" : "replace", "path" : "/contentHandling", "value" : "CONVERT_TO_BINARY"}]'
I hope this helps.
Check out this answer. It helped me with exposing PDF file for download through GET request without any additional headers.
I have a lambda function which fetches a file from s3 using the input key in event and needs to send the same to client. I am using the following function to get the file from s3
function getObject(key){
var params = {
Bucket: "my_bucket",
Key: key
}
return new Promise(function (resolve, reject){
s3.getObject(params, function (err, data){
if(err){
reject(err);
}
resolve(data.Body)
})
})
}
If I send the response of this promise (buffer) to context.succeed, it is displayed as a JSON array on front end. How can I send it as a file ? The files can be either ZIP or HTTP Archive (HAR) files. The s3 keys contain the appropriate extension. I am guessing it has got something to do with the "Integration Response" in API Gateway. But not able to figure out where to change
Good news, you can now handle binary input and output for API Gateway (announcement and documentation).
Basically, nothing changes in your Lambda Function, but you can now set the contentHandling API Gateway Integration property to CONVERT_TO_BINARY.
Unfortunately, the official AWS examples showcase only the HTTP API Gateway backend, as the AWS Lambda support seems not complete yet. For example, I haven't managed to return gzipped content from AWS Lambda yet, although it should be possible thanks to the new binary support and the $util.base64Decode() mapping utility.