I am writing some React.js that will upload multiple photos at a time. I am currently trying to send a batch of photos to the server but I cannot seem to get the files to append to the formData.
I call this function on the onChange event of the input field:
batchUpload(e) {
e.preventDefault();
let files = e.target.files;
for (let i = 0; i < files.length; i++) {
let file = files[i],
reader = new FileReader();
reader.onload = (e) => {
let images = this.state.images.slice();
if (file.type.match('image')) {
images.push({file, previewURL: e.target.result});
this.formData.append('files', file); //THIS IS NOT APPENDING THE FILE CORRECTLY
this.setState({images});
}
};
reader.readAsDataURL(file);
}
this.props.setFormWasEdited(true);
}
Then once the save button is pressed I run this function:
saveClick(goBack, peopleIdArray) {
if (this.state.images.length > 0) {
let formData = this.formData;
formData.append('token', Tokens.findOne().token);
formData.append('action', 'insertPhotoBatch');
formData.append('tags', peopleIdArray);
formData.append('date', dateString());
for (var pair of formData.entries()) {
console.log(pair[0] + ', ' + JSON.stringify(pair[1]));
}
let xhr = new XMLHttpRequest();
xhr.open('POST', Meteor.settings.public.api, true);
xhr.onload = (e) => {
if (xhr.status === 200) {
// upload success
console.log('XHR success');
} else {
console.log('An error occurred!');
}
};
xhr.send(formData);
} else {
//signifies error
return true;
}
}
Everything seems to be fine until I append the files to the formData. What am I doing wrong?
If I'm not mistaken you problem is with this.formData.append('files', file);
Running this line in a for loop will get you 1 field with all the file appended to each other resulting in an invalid file.
Instead you must file the file "array" syntax used informs like so:
this.formData.append('files[]', file);
This way you get the files on server side as $_FILES['files']['name'][0], $_FILES['files']['name'][1], ... and like wise for other properties of the files array.
I hope you have solved your issues already. I am still stuck not understanding why it would seem that my formData is not bringing anything to my server, but I did find an issue with your code.
When you use
JSON.stringify(pair[1])
the result looks like an empty array. If you instead try
pair[1].name
you'd see that append actually did attach your file.
const config = {
headers: { 'content-type': 'multipart/form-data' }
}
const formData = new FormData();
Object.keys(payload).forEach(item => {
formData.append([item], payload[item])
})
pass this formData to your API.
Related
I am trying to compress images on the client side using Compressor JS before making the request because it takes a lot of time to upload them in original size.
The problem i ran into:
I have a FormData() object declared as 'fd' in my code and when i try appending an image to it via the success hook function in Compressor JS just doesn't do anything (the object remains as it was). Here is the code
var fd = new FormData();
const fileList = document.querySelector('[type=file]').files;
for (file of fileList) {
new Compressor(file, {
quality: 0.4,
maxWidth: 800,
success: function (result) {
fd.append('compressedImage', result, result.name);
},
});
}
Basically I am looping through the array of files and compressing each image one by one.
What I have noticed:
By making a new empty array before the loop and append each image to it should give me all the compressed images in an array. This is what i mean
const fileList = document.querySelector('[type=file]').files;
compressedImgs = [];
for (file of fileList) {
new Compressor(file, {
quality: 0.4,
maxWidth: 800,
success: function (result) {
compressedImgs.push(result);
},
});
}
HOWEVER, console.log(compressedImgs) outputs this (nothing unusual because i added 2 images to the input):
Click here to see the output
But when i try to log an element of the array (for example compressedImages[0]), outputs undefined.
I need a way to put my compressed files into the FormData() to be able to send them to the server.
in the wetransfer link I passed you there is a complete example using the CompressorJS library and saving in PHP. It is working and maybe it will help you with your issue in case you are overlooking something.
Also, if it is helpful and you can fix the problem it would be helpful if you comment on how you solved it.
Below is the code that works normally for me:
index.html
<input type="file" accept="image/*" multiple>
<script>
const formData = new FormData()
window.addEventListener('load', function () {
const fileInput = document.querySelector('[type=file]')
fileInput.addEventListener('change', function (e) {
const files = e.target.files
const promises = []
for (let file of files) {
promises.push(new Promise(function (resolve, reject) {
new Compressor(file, {
quality: 0.6,
success(result) {
formData.append('files[]', result, result.name)
console.log(result)
resolve()
},
error(err) {
console.log(err.message)
reject()
},
})
}))
}
Promise.all(promises).then(function () {
console.log(Array.from(formData.values()))
const request = new XMLHttpRequest()
request.open("POST", "./files-handler.php")
request.send(formData)
})
})
})
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/compressorjs/1.0.7/compressor.min.js"></script>
files-handler.php
$data = [];
$data['_FILES'] = $_FILES;
$data['_POST'] = $_POST;
$data['uploadedFiles'] = [];
$uploadDir = __DIR__ . '/uploads/';
$data['uploadDir'] = $uploadDir;
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0777);
}
$filesInput = $_FILES['files'];
$filesName = $filesInput['name'];
$filesTmpName = $filesInput['tmp_name'];
$filesError = $filesInput['error'];
foreach ($filesName as $index => $name) {
if ($filesError[$index] == \UPLOAD_ERR_OK) {
$toPath = $uploadDir . uniqid() . '_' . $name;
$uploaded = move_uploaded_file($filesTmpName[$index], $toPath);
if ($uploaded) {
$data['uploadedFiles'][] = $toPath;
}
}
}
header('Content-Type: application/json');
echo json_encode($data);
Try changing compressedImage to compressedImage[].
Something like:
let formData = new FormData()
let fileInput = document.querySelector('[type=file]')
fileInput.addEventListener('change', function(e){
let files = e.target.files
for(let file of files){
formData.append('files[]', file)
}
console.log(Array.from(formData.values()))
})
<input type="file" multiple/>
EDIT 1:
It would be useful to see the form submission code and also some of the server code.
However, one other thing occurs to me are you submitting the files with Content-Type multipart/form-data?
The above is important for submitting files to the server.
I am sending attached files/images with formData to back end along with content of the mail and then inject them into nodemailer, I am using multer as a middleware if it helps
component.ts
let fileList: FileList = event.target.files;
if(fileList.length > 0) {
this.formData = new FormData();
for(let i = 0 ; i < fileList.length; i++){
let file: File = fileList[i];
this.formData.append('uploadFile', file, file.name);
}
}
service.ts(this works and I can fetch files from req.files in the node)
sendData(formData){
let headers = new HttpHeaders();
return this.http.post('http://localhost:3000/mail',formData)
}
service.ts(doesn't work, need to do this)
sendEmail(formData,email){
let data = { data : email , formData : formData}
return this.http.post('http://localhost:3000/mail',data)
}
both req.files and req.body.formData comes undefined in this case,
I tried appending the formdata to the header but I don't know the proper way to append/fetch or maybe it's not possible to.
Node.js Part
var storage = multer.diskStorage({
destination : (req,file, callback)=>{
req.body.path = [];
callback(null, './storage');
},
filename : (req, file ,callback)=>{
let filename = Date.now() + '-' +file.originalname;
req.body.path.push(filename);
callback(null, filename);
app.post('/mail',upload.any(),nodemailer);
nodemailer.js
module.exports = (req,res)={
console.log(req.files); X
//Code Irrelevant for this question
}
For this you have to do it like this :
sendEmail(formData,email){
let headers = new HttpHeaders();
formData.append('email', email);
return this.http.post('http://localhost:3000/mail',formData);
}
Reason, you have to pass the form as multipart and you are trying to
send data as JSON , so it will not receive anything until you pass the
whole thing as above.
sendEmail(formData,email){
let data = { data : email , formData : formData}
return this.http.post('http://localhost:3000/mail',data)
}
as you can see in your function you are sending the JSON.
I have set up in an SPA application the ability to send files to Azure Blob Storage.
To do this I used XMLHttpRequest and FormData (my users are on computers managed by my company and all have access to HTML5).
In order to manage security, the sending of each file is preceded by a call to a method of a Web Api to obtain the shared access signature.
I forward the Content-Type of the file as well as other information to headers.
Everything happens for the better, the files are correctly sent and saved in Azure Blob Storage, but during the transfer, the image files seem to be "altered".
They are well present, I can download them and read them after the download, but I can not open them directly from an img tag.
On the other hand if I send the same image file via Microsoft Azure Storage Explorer, there is no problem, the image is well recognized in the img tag.
However, in one case as in the other, the content-type is marked as "image / jpeg". The only noticeable difference is that the MD5 is not the same between these 2 mailings while it is the same file of origin.
From my findings it seems that there is text added to the beginning and the end of the file when sending via XMLHttpRequest.
I explain my code so that you can guide me:
Note 1 : I use typescript (but a javascript solution will suit me) and Promise.
Note 2 : I have resolve all the CORS problems.
Note 3 : I'm using Azure Storage Emulator, but i try with the normal Azure service and the problem is the same.
Here is the text added in the image in Chrome:
------WebKitFormBoundaryKj5cK88faAwJd4av
Content-Disposition: form-data; name="file1"; filename="test.jpg"
Content-Type: image/jpeg
[image content]
------WebKitFormBoundaryKj5cK88faAwJd4av--
My Web Api :
[Route(#"api/Storage/FileSas/Customers/{id:int}")]
public async Task<IHttpActionResult> GetFileSas(int id, string fileName, long? fileSize = 0, string contentType = null)
{
if (string.IsNullOrWhiteSpace(fileName))
this.ModelState.AddModelError("fileName", "File name i");
if (!fileSize.HasValue || fileSize.Value > maxFileSize)
this.ModelState.AddModelError("fileSize", "File size exceeded");
if (!this.ModelState.IsValid)
return BadRequest(this.ModelState);
var serverUrl = ConfigurationManager.AppSettings[SERVER_URL];
var container = ConfigurationManager.AppSettings[CONTAINER_NAME];
SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.Write,
SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-60),
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(60),
};
CloudBlockBlob blobFile = blobContainer.GetBlockBlobReference(Path.Combine("customers", id.ToString(), fileName));
var exists = await blobFile.ExistsAsync();
if (exists)
{
await blobFile.SnapshotAsync();
}
var signature = blobFile.GetSharedAccessSignature(policy);
return Content<string>(HttpStatusCode.Created, Path.Combine(serverUrl, container, blobFile.Name + signature));
}
My TypeScript file :
context.Storage.getFileSas(customerId, file)
.then((response: Interfaces.Result<string>) => {
let sasUrl = response.Data;
let formData = new FormData();
formData.append("file1", file, file.name);
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = (event) => {
if (event.total > 0)
this.Progress(event.loaded * 100 / event.total);
};
xhr.onloadstart = function (e) {
}
xhr.onloadend = (e) => {
this.Progress(0);
}
xhr.open("PUT", sasUrl, true);
xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('x-ms-blob-content-type', file.type);
xhr.setRequestHeader('x-ms-version', "2016-05-31");
xhr.setRequestHeader('x-ms-meta-CustomerId', customerId);
xhr.setRequestHeader('x-ms-meta-UserId', context.User.User.Id.toString());
xhr.setRequestHeader('x-ms-meta-UserName', context.User.User.Name);
xhr.send(formData);
})
})).catch((error) => {
console.log(error);
});
File come from here :
let fileInputElement1: HTMLInputElement = <HTMLInputElement>document.getElementById("file1");
let file = fileInputElement1.files[0];
My HTML part : (i'm using knockout)
<form method="put" target="_blank" enctype="multipart/form-data">
<input type="file" name="name" value="" id="file1" />
<button data-bind="click:send"> Send</button>
</form>
If someone have an idea ? ...
Thank's in advance.
PS : sasUrl is like this : http://127.0.0.1:10000/devstoreaccount1/customers/65143/test.jpg?sv=2016-05-31&sr=b&sig=s0671%2BLvCZTqyNfhlCthZW8KftjKyIMAlOT1nbsnlng%3D&st=2017-03-05T11%3A38%3A22Z&se=2017-03-06T12%3A38%3A22Z&sp=r&rsct=image%2Fjpeg
Thank's to Gaurav Mantri, he point me to the right, here my modifications (only because i use typescript) :
context.Storage.getFileSas(customerId, file)
.then((response: Interfaces.Result<string>) => {
let sasUrl = response.Data;
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = (event) => {
if (event.total > 0)
this.Progress(event.loaded * 100 / event.total);
};
xhr.onloadstart = function (e) {
}
xhr.onloadend = (e) => {
this.Progress(0);
}
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = (event) => {
let target = <FileReader>event.target;
if (target.readyState == reader.DONE) {
var requestData = new Uint8Array(target.result);
xhr.open("PUT", sasUrl, true);
xhr.responseType = "blob";
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('X-File-Name', file.name);
xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
xhr.setRequestHeader('x-ms-blob-content-type', file.type || 'application/octet-stream');
xhr.setRequestHeader('x-ms-version', "2016-05-31");
xhr.setRequestHeader('x-ms-meta-CustomerId', customerId);
xhr.setRequestHeader('x-ms-meta-UserId', context.User.Id.toString());
xhr.setRequestHeader('x-ms-meta-UserName', context.User.Name);
xhr.send(requestData);
}
}
})
})).catch((error) => {
console.log(error);
});
Now i'll start to write a Promise to embedded this fonctionality.
PS : i didn't find the way to mark the Gaurav Mantri as answer so i create mine.
PS 2 : I'll like to put some +1 to Gaurav Mantri for the help... but i can't :/
I am trying to :
Send a zip file via xmlhttp to the client
then read the file using zip.js and render its contents
I successfully receive the binary of the file i.e. the success callback is called but I get and error when I try to do getEntries. I think the error is with the way of sending stream , please help.
Error msg :
Error in reading zip file
My client side code (using angular) :
$http.get(window.location.origin + '/book/'+bookName,{responseType:"Blob"}).
success(function (data , error) {
var a = new Uint8Array(data);
//var dataView = new DataView(data);
//var blob = new Blob(dataView.buffer);
zip.useWebWorkers = true;
zip.workerScriptsPath = '/js/app/';
zip.createReader(new zip.BlobReader(data), function(reader) {
// get all entries from the zip
reader.getEntries(function(entries) { //HERE I GET THE ERROR
if (entries.length) {
// get first entry content as text
entries[0].getData(new zip.TextWriter(), function(text) {
// text contains the entry data as a String
console.log(text);
// close the zip reader
reader.close(function() {
// onclose callback
var a = 0;
});
}, function(current, total) {
// onprogress callback
var a = 0;
});
}
});
},
function(error) {
// onerror callback
var a = 0;
});
})
.error( function (data , error) {
var a = 0;
});
My Server side code on Node:
router.get('/book/:bookName',function (req , res ) {
console.log('Inside book reading block : ' + req.params.bookName);
req.params.bookName += '.zip';
var filePath = path.join(__dirname,'/../\\public\\books\\' ,req.params.bookName );
var stat = fileSystem.statSync(filePath);
res.writeHead(200, {
//'Content-Type': 'application/zip',
'Content-Type': 'blob',
'Content-Length': stat.size
});
var readStream = fileSystem.createReadStream(filePath);
// replace all the event handlers with a simple call to readStream.pipe()
readStream.pipe(res);
});
It is probable that you might have already found a solution. I faced the same problem today and this is how I solved it in plain javascript:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'assets/object/sample.zip', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
// response is unsigned 8 bit integer
var responseArray = new Uint8Array(this.response);
var blobData = new Blob([responseArray], {
type: 'application/zip'
});
zip.createReader(new zip.BlobReader(blobData), function(zipReader) {
zipReader.getEntries(displayEntries);
}, onerror);
};
xhr.send();
The problem I see in your code is that you are changing the value to Uint8Array and assigning it to var a, but still use the raw data in blobreader. Also the blob reader required blob and not an array. So you should have converted var a into blob and then used it for reading.
I am trying to upload a file to amazon S3 from a phonegap app on android. I already have the code working for iOS. But I've got a trouble making it work on android. The problem is that I do not know how to create the Blob object properly. On iOS I just do this:
blob = new Blob([evt.target.result], {type: "image/png"});
It is uploaded just fine. On android one can not use the Blob constructor (see here), but I could not manage to get the file data correctly into the Blob object using WebKitBlobBuilder.
Here is how I retrieve the file data, there are two approaches and both pass without errors, but the resulting file on the S3 is empty:
window.resolveLocalFileSystemURI(url, function(e){
e.file(function(f){
var reader = new FileReader();
reader.onloadend = function(evt) {
// This way also did not work:
// var builder = new WebKitBlobBuilder();
// builder.append(evt.target.result);
// blob = builder.getBlob("image/png");
var blob = null;
var builder = new WebKitBlobBuilder();
for(var i = 0; i < evt.target.result.length; i++){
builder.append(evt.target.result[i]);
}
blob = builder.getBlob("image/png");
uploadToS3(filename, s3url, blob);
};
reader.readAsArrayBuffer(f);
});
}, function(e){
console.log("error getting file");
error();
});
also, here is the uploadToS3 function:
var uploadToS3 = function(filename, s3url, fileData) {
var xhr = createCORSRequest('PUT', s3url);
if(!xhr) {
console.log('CORS not supported');
error();
return;
}
xhr.onload = function () {
if(xhr.status == 200) {
console.log('100% - Upload completed.');
callback(filename); //callback defined in outer context
}
else {
console.log('0% - Upload error: ' + xhr.status);
console.log(xhr.responseText);
error();
}
};
xhr.onerror = function () {
console.log(0, 'XHR error.');
error();
};
xhr.upload.onprogress = function (e) {
if(e.lengthComputable) {
var percentLoaded = Math.round((e.loaded / e.total) * 100);
var label = (percentLoaded == 100) ? 'Finalizing.' : 'Uploading.';
console.log(percentLoaded + "% - " + label);
}
};
xhr.setRequestHeader('Content-Type', "image/png");
//xhr.setRequestHeader('x-amz-acl', 'public-read');
xhr.send(fileData);
};
EDIT:
I checked the filesize by logging evt.target.result.byteLength and it was ok, so evt.target.result contains image data. Still there is a problem with the upload - I checked the s3 storage and file size is 0, so I am not constructing the Blob correctly.
So this is an android bug after all, which was not fixed since at least Nov 2012.
Here is an article I found which is directly related to my problem: https://ghinda.net/article/jpeg-blob-ajax-android/
It also provides a workaround for the bug, which is to send ArrayBuffer directly, without creating a Blob. In my case I had to send evt.target.result directly.