MTurk how to upload file to S3 when task is submitted - javascript

I am trying to create a task on Amazon MTurk, where the workers would collect some data and upload a single file when they are ready & submit the task. When the task is submitted, I want to upload the file to my linked S3 bucket - which is mostly based on this tutorial.
However, the file is sometimes uploaded successfully, and sometimes not. Since the S3.upload function is asynchronous, it looks like the task submission is sometimes completed before the file upload is completed. I am a javascript newbie: I tried to make this happen synchronously, but it still doesn't work properly. Here is my javascript code:
<script>
let config = {
region: 'xxx',
pool: 'xxx',
bucket: 'xxx'
}
AWS.config.region = config.region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: config.pool,
});
var s3 = new AWS.S3({
apiVersion: '2006-03-01',
params: {Bucket: config.bucket},
});
start_upload = function (event) {
$("#status").text("Uploading...");
let file = $("#file").prop('files')[0];
if (file === null || file === undefined) {
alert("You must upload a file before submitting.");
$("#status").text("");
return false;
}
console.log('Filename: ' + file.name);
let workerId = turkGetParam('workerId');
let fileKey = '${food_name}' + '/' + workerId + '-' + file.name;
return upload_to_s3(file, fileKey);
};
upload_to_s3 = async (file, fileKey) => {
const params = {
Key: fileKey,
Body: file,
ContentType: file.type,
ACL: 'bucket-owner-full-control'
};
try {
console.log("Starting upload...");
const data = await s3.upload(params).promise();
console.log("Done uploading file");
$("#status").text("Success.");
return true;
} catch (err) {
console.log("Error uploading data. ", err);
alert("Failed to upload, please try again. If the problem persists, contact the Requester.");
$("#status").text("");
return false;
}
}
// Validate and upload file on submit
window.onload = function() {document.getElementById('submitButton').setAttribute('onclick', 'return start_upload()'); }
</script>
Here is the relevant part of the layout of this task (HIT):
How can I make sure that the file upload is completed before the task is completed? I saw that I can overwrite the default submit button added by MTurk, but I would prefer not doing that if possible.

I've found the problem: S3#upload returns a ManagedUpload object, but it doesn't mean that the file upload is completed. I am now using promises and in the callback I submit the form manually. Note that the form is provided by MTurk by default. I just find it by its ID and invoke the submit function manually.
For reference, here is the working code:
<script>
let config = {
region: 'xxx',
pool: 'xxx',
bucket: 'xxx'
}
AWS.config.region = config.region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: config.pool,
});
var s3 = new AWS.S3({
apiVersion: '2006-03-01',
params: {Bucket: config.bucket},
});
start_upload = function (event) {
$("#status").text("Uploading, please wait...");
let file = $("#file").prop('files')[0];
if (file === null || file === undefined) {
alert("You must choose a file before submitting.");
$("#status").text("");
return false;
}
let workerId = turkGetParam('workerId');
let fileKey = '${food_name}' + '/' + workerId + '-' + file.name;
upload_to_s3(file, fileKey);
return false;
};
upload_to_s3 = (file, fileKey) => {
const params = {
Key: fileKey,
Body: file,
ContentType: file.type,
ACL: 'bucket-owner-full-control'
};
let promise = s3.upload(params).promise();
promise.then( (data) => {
console.log("Upload completed");
$("#status").text("Success.");
const form = document.getElementById('mturk_form');
form.submit();
}, (err) => {
console.log("Upload failed!!!", err);
alert("Failed to upload, please try again. If the problem persists, contact the Requester.");
$("#status").text("");
} );
}
// Validate and upload file on submit
window.onload = function() {document.getElementById('submitButton').setAttribute('onclick', 'return start_upload()'); }
</script>

Related

AWS S3 file upload with Node.js: Unsupported body payload error

I am trying to get my node.js backend to upload a file to AWS S3, which it got in a post request from my front-end. This is what my function looks like:
async function uploadFile(file){
var uploadParams = {Bucket: '<bucket-name>', Key: file.name, Body: file};
s3.upload (uploadParams, function (err, data) {
if (err) {
console.log("Error", err);
} if (data) {
console.log("Upload Success", data.Location);
}
});
}
When I try uploading the file this way, I get an Unsupported Body Payload Error...
I used fileStream.createReadStream() in the past to upload files saves in a directory on the server, but creating a fileStream did not work for me, since there is no path parameter to pass here.
EDIT:
The file object is created in the angular frontend of my web application. This it the relevant html code where the file is uploaded by a user:
<div class="form-group">
<label for="file">Choose File</label>
<input type="file" id="file"(change)="handleFileInput($event.target.files)">
</div>
If the event occurs, the handleFileInput(files: FileList) method in the corresponding component is called:
handleFileInput(files: FileList) {
// should result in array in case multiple files are uploaded
this.fileToUpload = files.item(0);
// actually upload the file
this.uploadFileToActivity();
// used to check whether we really received the file
console.log(this.fileToUpload);
console.log(typeof this.fileToUpload)
}
uploadFileToActivity() {
this.fileUploadService.postFile(this.fileToUpload).subscribe(data => {
// do something, if upload success
}, error => {
console.log(error);
});
}
the postFile(fileToUpload: File) method of the file-upload service is used to make the post request:
postFile(fileToUpload: File): Observable<Boolean> {
console.log(fileToUpload.name);
const endpoint = '/api/fileupload/single';
const formData: FormData = new FormData();
formData.append('fileKey', fileToUpload, fileToUpload.name);
return this.httpClient
.post(endpoint, formData/*, { headers: yourHeadersConfig }*/)
.pipe(
map(() => { return true; }),
catchError((e) => this.handleError(e)),
);
}
Here is the the server-side code that receives the file and then calls the uploadFile(file) function:
app.post('/api/fileupload/single', async (req, res) => {
try {
if(!req.files) {
res.send({
status: false,
message: 'No file uploaded'
});
} else {
let file = req.files.fileKey;
uploadFile(file);
//send response
res.send({
status: true,
message: 'File is uploaded',
data: {
name: file.name,
mimetype: file.mimetype,
size: file.size
}
});
}
} catch (err) {
res.status(500).send(err);
}
});
Thank you very much for your help in solving this!
Best regards, Samuel
Best way is stream the file. Assuming you are. reading it from disk. You could do this
const fs = require("fs");
const aws = require("aws-sdk");
const s3Client = new aws.S3();
const Bucket = 'somebucket';
const stream = fs.createReadStream("file.pdf");
const Key = stream.path;
const response = await s3Client.upload({Bucket, Key, Body: stream}).promise();
console.log(response);

How to get file properties and upload a file from ionic 4?

I am trying to upload a file from mobile to google bucket using ionic 4. Although a file can upload into the could. I am struggling to get the file properties out of file object.
Here is my method,
async selectAFile() {
const uploadFileDetails = {
name: '',
contentLength: '',
size: '',
type: '',
path: '',
};
this.fileChooser.open().then(uri => {
this.file.resolveLocalFilesystemUrl(uri).then(newUrl => {
let dirPath = newUrl.nativeURL;
const dirPathSegments = dirPath.split('/');
dirPathSegments.pop();
dirPath = dirPathSegments.join('/');
(<any>window).resolveLocalFileSystemURL(
newUrl.nativeURL,
function(fileEntry) {
uploadFileDetails.path = newUrl.nativeURL;
const file: any = getFileFromFileEntry(fileEntry);
//log 01
console.log({ file });
uploadFileDetails.size = file.size;
uploadFileDetails.name = `${newUrl.name
.split(':')
.pop()}.${file.type.split('/').pop()}`;
uploadFileDetails.type = file.type;
async function getFileFromFileEntry(fileEntry) {
try {
return await new Promise((resolve, reject) =>
fileEntry.file(resolve, reject)
);
} catch (err) {
console.log(err);
}
}
},
function(e) {
console.error(e);
}
);
});
});
// here uploadFileDetails is simller to what I declared at the top ;)
// I wan't this to be populated with file properties
// console.log(uploadFileDetails.name) --> //''
const uploadUrl = await this.getUploadUrl(uploadFileDetails);
const response: any = this.uploadFile(
uploadFileDetails,
uploadUrl
);
response
.then(function(success) {
console.log({ success });
this.presentToast('File uploaded successfully.');
this.loadFiles();
})
.catch(function(error) {
console.log({ error });
});
}
even though I can console.log the file in log 01. I am unable to get file properties like, size, name, type out of the resolveLocalFileSystemURL function. basically, I am unable to populate uploadFileDetails object. What am I doing wrong? Thank you in advance.
you actually need 4 Ionic Cordova plugins to upload a file after getting all the metadata of a file.
FileChooser
Opens the file picker on Android for the user to select a file, returns a file URI.
FilePath
This plugin allows you to resolve the native filesystem path for Android content URIs and is based on code in the aFileChooser library.
File
This plugin implements a File API allowing read/write access to files residing on the device.
File Trnafer
This plugin allows you to upload and download files.
getting the file's metadata.
file.resolveLocalFilesystemUrl with fileEntry.file give you all the metadata you need, except the file name. There is a property called name in the metadata but it always contains value content.
To get the human readable file name you need filePath. But remember you can't use returning file path to retrieve metadata. For that, you need the original url from fileChooser.
filePathUrl.substring(filePathUrl.lastIndexOf('/') + 1) is used to get only file name from filePath.
You need nativeURL of the file in order to upload it. Using file path returning from filePath is not going to work.
getFileInfo(): Promise<any> {
return this.fileChooser.open().then(fileURI => {
return this.filePath.resolveNativePath(fileURI).then(filePathUrl => {
return this.file
.resolveLocalFilesystemUrl(fileURI)
.then((fileEntry: any) => {
return new Promise((resolve, reject) => {
fileEntry.file(
meta =>
resolve({
nativeURL: fileEntry.nativeURL,
fileNameFromPath: filePathUrl.substring(filePathUrl.lastIndexOf('/') + 1),
...meta,
}),
error => reject(error)
);
});
});
});
});
}
select a file from the file system of the mobile.
async selectAFile() {
this.getFileInfo()
.then(async fileMeta => {
//get the upload
const uploadUrl = await this.getUploadUrl(fileMeta);
const response: Promise < any > = this.uploadFile(
fileMeta,
uploadUrl
);
response
.then(function(success) {
//upload success message
})
.catch(function(error) {
//upload error message
});
})
.catch(error => {
//something wrong with getting file infomation
});
}
uploading selected file.
This depends on your backend implementation. This is how to use File Transfer to upload a file.
uploadFile(fileMeta, uploadUrl) {
const options: FileUploadOptions = {
fileKey: 'file',
fileName: fileMeta.fileNameFromPath,
headers: {
'Content-Length': fileMeta.size,
'Content-Type': fileMeta.type,
},
httpMethod: 'PUT',
mimeType: fileMeta.type,
};
const fileTransfer: FileTransferObject = this.transfer.create();
return fileTransfer.upload(file.path, uploadUrl, options);
}
hope it helps. :)

AWS S3 Upload after GET Request to Image, Not Uploading Correctly

I'm trying to upload an image to my AWS S3 bucket after downloading the image from another URL using Node (using request-promise-native & aws-sdk):
'use strict';
const config = require('../../../configs');
const AWS = require('aws-sdk');
const request = require('request-promise-native');
AWS.config.update(config.aws);
let s3 = new AWS.S3();
function uploadFile(req, res) {
function getContentTypeByFile(fileName) {
var rc = 'application/octet-stream';
var fn = fileName.toLowerCase();
if (fn.indexOf('.png') >= 0) rc = 'image/png';
else if (fn.indexOf('.jpg') >= 0) rc = 'image/jpg';
return rc;
}
let body = req.body,
params = {
"ACL": "bucket-owner-full-control",
"Bucket": 'testing-bucket',
"Content-Type": null,
"Key": null, // Name of the file
"Body": null // File body
};
// Grabs the filename from a URL
params.Key = body.url.substring(body.url.lastIndexOf('/') + 1);
// Setting the content type
params.ContentType = getContentTypeByFile(params.Key);
request.get(body.url)
.then(response => {
params.Body = response;
s3.putObject(params, (err, data) => {
if (err) { console.log(`Error uploading to S3 - ${err}`); }
if (data) { console.log("Success - Uploaded to S3: " + data.toString()); }
});
})
.catch(err => { console.log(`Error encountered: ${err}`); });
}
The upload succeeds when I test it out, however after trying to redownload it from my bucket the image is unable to display. Additionally, I notice after uploading the file with my function, the file listed in the bucket is much larger in filesize than the originally uploaded image. I'm trying to figure out where I've been going wrong but cannot find where. Any help is appreciated.
Try to open the faulty file with a text editor, you will see some errors written in it.
You can try using s3.upload instead of putObject, it works better with streams.

How to upload files from ReactJS to Express endpoint

In the application I'm currently working on, there are a couple of file forms that are submitted via superagent to an Express API endpoint. For example, image data is posted like so:
handleSubmit: function(evt) {
var imageData = new FormData();
if ( this.state.image ) {
imageData.append('image', this.state.image);
AwsAPI.uploadImage(imageData, 'user', user.id).then(function(uploadedImage) {
console.log('image uploaded:', uploadedImage);
}).catch(function(err) {
this.setState({ error: err });
}.bind(this));
}
}
and this.state.image is set like this from a file input:
updateImage: function(evt) {
this.setState({
image: evt.target.files[0]
}, function() {
console.log('image:', this.state.image);
});
},
AWSAPI.uploadImage looks like this:
uploadImage: function(imageData, type, id) {
var deferred = when.defer();
request.put(APIUtils.API_ROOT + 'upload/' + type + '/' + id)
.type('form')
.send(imageData)
.end(function(res) {
if ( !res.ok ) {
deferred.reject(res.text);
} else {
deferred.resolve(APIUtils.normalizeResponse(res));
}
});
return deferred.promise;
}
And lastly, the file receiving endpoint looks like this:
exports.upload = function(req, res) {
req.pipe(req.busboy);
req.busboy.on('file', function(fieldname, file) {
console.log('file:', fieldname, file);
res.status(200).send('Got a file!');
});
};
Currently, the receiving endpoint's on('file') function never gets called and so nothing happens. Previously, I've tried similar approaches with multer instead of Busboy with no more success (req.body contained the decoded image file, req.files was empty).
Am I missing something here? What is the best approach to upload files from a (ReactJS) Javascript app to an Express API endpoint?
I think superAgent is setting the wrong content-type of application/x-form-www-encoded instead of multipart/form-data you can fix this by using the attach method like so:
request.put(APIUtils.API_ROOT + 'upload/' + type + '/' + id)
.attach("image-file", this.state.image, this.state.image.name)
.end(function(res){
console.log(res);
});
for more information about the attach method, read the documentation here: http://visionmedia.github.io/superagent/#multipart-requests
since this involves a nodejs server script I decided to make a GitHub repo instead of a fiddle: https://github.com/furqanZafar/reactjs-image-upload
From experience, uploading a file using ajax works when you use FormData, however the file must be the only form field / data. If you try and combine it with other data (like username, password or pretty much anything at all) it does not work. (Possibly there are work arounds to get around that issue, but I am not aware of any)
If you need to send the username/password you should be sending those as headers if you can instead.
Another approach I took was first do the user registration with the normal data, then on success I upload the file with the FormData separately as an update.
The react file upload iamges component:
class ImageUpload extends React.Component {
constructor(props) {
super(props);
this.state = {file: '',imagePreviewUrl: ''};
}
_handleSubmit(e) {
e.preventDefault();
// this.uploadImage()
// TODO: do something with -> this.state.file
console.log('handle uploading-', this.state.file); }
_handleImageChange(e) {
e.preventDefault();
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
this.setState({
file: file,
imagePreviewUrl: reader.result
});
}
reader.readAsDataURL(file) }
// XHR/Ajax file upload uploadImage(imageFile) {
return new Promise((resolve, reject) => {
let imageFormData = new FormData();
imageFormData.append('imageFile', imageFile);
var xhr = new XMLHttpRequest();
xhr.open('post', '/upload', true);
xhr.onload = function () {
if (this.status == 200) {
resolve(this.response);
} else {
reject(this.statusText);
}
};
xhr.send(imageFormData);
}); }
render() {
let {imagePreviewUrl} = this.state;
let $imagePreview = null;
if (imagePreviewUrl) {
$imagePreview = (<img src={imagePreviewUrl} />);
} else {
$imagePreview = (<div className="previewText">Please select an Image for Preview</div>);
}
return (
<div className="previewComponent">
<form onSubmit={(e)=>this._handleSubmit(e)}>
<input className="fileInput" type="file" onChange={(e)=>this._handleImageChange(e)} />
<button className="submitButton" type="submit" onClick={(e)=>this._handleSubmit(e)}>Upload Image</button>
</form>
<div className="imgPreview">
{$imagePreview}
</div>
</div>
) } } React.render(<ImageUpload/>, document.getElementById("mainApp"));
The Server Side Image Save and Copy:
Along with express You needed to npm install 'multiparty'. This example uses multiparty to parse the form data and extract the image file information. Then 'fs' to copy the temporarily upload image to a more permanent location.
let multiparty = require('multiparty');
let fs = require('fs');
function saveImage(req, res) {
let form = new multiparty.Form();
form.parse(req, (err, fields, files) => {
let {path: tempPath, originalFilename} = files.imageFile[0];
let newPath = "./images/" + originalFilename;
fs.readFile(tempPath, (err, data) => {
// make copy of image to new location
fs.writeFile(newPath, data, (err) => {
// delete temp image
fs.unlink(tempPath, () => {
res.send("File uploaded to: " + newPath);
});
});
});
})
}

Uploading base64 encoded Image to Amazon S3 via Node.js

Yesterday I did a deep night coding session and created a small node.js/JS (well actually CoffeeScript, but CoffeeScript is just JavaScript so lets say JS) app.
what's the goal:
client sends a canvas datauri (png) to server (via socket.io)
server uploads image to amazon s3
step 1 is done.
the server now has a string a la
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACt...
my question is: what are my next steps to "stream"/upload this data to Amazon S3 and create an actual image there?
knox https://github.com/LearnBoost/knox seems like an awesome lib to PUT something to S3, but what I'm missing is the glue between the base64-encoded-image-string and actual upload action?
Any ideas, pointers and feedback welcome.
For people who are still struggling with this issue. Here is the approach I used with native aws-sdk :
var AWS = require('aws-sdk');
AWS.config.loadFromPath('./s3_config.json');
var s3Bucket = new AWS.S3( { params: {Bucket: 'myBucket'} } );
Inside your router method (ContentType should be set to the content type of the image file):
var buf = Buffer.from(req.body.imageBinary.replace(/^data:image\/\w+;base64,/, ""),'base64')
var data = {
Key: req.body.userId,
Body: buf,
ContentEncoding: 'base64',
ContentType: 'image/jpeg'
};
s3Bucket.putObject(data, function(err, data){
if (err) {
console.log(err);
console.log('Error uploading data: ', data);
} else {
console.log('successfully uploaded the image!');
}
});
s3_config.json file :
{
"accessKeyId":"xxxxxxxxxxxxxxxx",
"secretAccessKey":"xxxxxxxxxxxxxx",
"region":"us-east-1"
}
Here's the code from one article I came across, posting below:
const imageUpload = async (base64) => {
const AWS = require('aws-sdk');
const { ACCESS_KEY_ID, SECRET_ACCESS_KEY, AWS_REGION, S3_BUCKET } = process.env;
AWS.config.setPromisesDependency(require('bluebird'));
AWS.config.update({ accessKeyId: ACCESS_KEY_ID, secretAccessKey: SECRET_ACCESS_KEY, region: AWS_REGION });
const s3 = new AWS.S3();
const base64Data = new Buffer.from(base64.replace(/^data:image\/\w+;base64,/, ""), 'base64');
const type = base64.split(';')[0].split('/')[1];
const userId = 1;
const params = {
Bucket: S3_BUCKET,
Key: `${userId}.${type}`, // type is not required
Body: base64Data,
ACL: 'public-read',
ContentEncoding: 'base64', // required
ContentType: `image/${type}` // required. Notice the back ticks
}
let location = '';
let key = '';
try {
const { Location, Key } = await s3.upload(params).promise();
location = Location;
key = Key;
} catch (error) {
}
console.log(location, key);
return location;
}
module.exports = imageUpload;
Read more: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
Credits: https://medium.com/#mayneweb/upload-a-base64-image-data-from-nodejs-to-aws-s3-bucket-6c1bd945420f
ok, this one is the answer how to save canvas data to file
basically it loos like this in my code
buf = new Buffer(data.dataurl.replace(/^data:image\/\w+;base64,/, ""),'base64')
req = knoxClient.put('/images/'+filename, {
'Content-Length': buf.length,
'Content-Type':'image/png'
})
req.on('response', (res) ->
if res.statusCode is 200
console.log('saved to %s', req.url)
socket.emit('upload success', imgurl: req.url)
else
console.log('error %d', req.statusCode)
)
req.end(buf)
The accepted answer works great but if someone needs to accept any file instead of just images this regexp works great:
/^data:.+;base64,/
For laravel developers this should work
/* upload the file */
$path = Storage::putFileAs($uploadfolder, $uploadFile, $fileName, "s3");
make sure to set your .env file property before calling this method

Categories