Upload Image Data as a File w/o File Input Control - javascript

I'm trying to upload an image to a web server .aspx page; the server side uses this mechanism to recognize the file:
HttpPostedFile file = Request.Files[0];
I can't change the server code because there are other clients.
Working Javascript/Angular code that can upload from an HTML file input control:
var fileInput = document.getElementById('the-file');
var formData = new FormData();
formData.append('file', fileInput.files[0]);
(etc)
$http.post(uploadUrl, formData, {
headers: { 'Content-Type': undefined },
transformRequest: angular.identity })
All well and good. What I need to figure out, though, is how to upload content from an image that the user has cropped on the client side (and thus the cropped image is no longer affiliated with an HTML file input control). It's in a Canvas, and I know how to get it as a DataUri or an arraybuffer or even a byte array. I just can't figure out how to upload it to this server endpoint so it will be recognized on the server side as a posted file. Is this possible? I don't know how to properly replace the following line:
formData.append('file', fileInput.files[0]);

Related

Does there exist a 'good-practice' way to send files without relying on FormData?

I'm currently working on an app with a React Native front-end and Node.js/Express backend. I am perfectly able to upload files using FormData with Content-Type multipart/form-data. The problem I have is that when using FormData, any other data that you wish to send in the body of the request is necessarily converted to a string. This isn't the case when one simply sends a JS object as the body of the request (as long you parse it on the backend of course). I wish to know if there is a good-practice way to send a file/files alongside JSON in a request, without losing the typings of said JSON?
Thanks
Add your data as JSON in a field of the FormData:
const data = {
foo: ["bar", 1]
};
const file = new File(["some content"], "myfile.txt");
const formdata = new FormData();
formdata.append("file", file);
// Send as JSON
formdata.append("data", JSON.stringify(data));
const req = new Request("./", { method: "POST", body: formdata });
// simulate server side getting the response
req.formData().then( (fd) => {
const received_file = fd.get("file");
// parse JSON
const received_data = JSON.parse(fd.get("data"));
console.log({ received_file, received_data });
});
When you insist on sending the image along with other data inside the JSON, then AFAIK your only option is to convert the image to some data type which can be transmitted via JSON.
The most obvious choice would be a string, and what is usually used here is the base64 encoding. It is, however, not very efficient and can cause lag.
What is sometimes done to stay within the JSON domain but still being able to upload images is to create two endpoints. One for the JSON data. One for the image upload in a binary format.

Simple async image upload for Express?

I'm using a button click to trigger a file input dialog. Once the file is selected, I'm displaying the thumbnail in a preview.
const uploadListener = function() {
const preview = document.getElementById('preview')
const uploadBlob = window.URL.createObjectURL(this.files[0])
preview.style.backgroundImage = `url(${ uploadBlob })`;
}
const fileUploader = document.getElementById('fileUpload')
fileUploader.addEventListener('change', uploadListener)
From here, what's the easiest way to get the file at uploadBlob asynchronously sent (via XMLHttpRequest()?) to my node.js Express server and saved to the server?
I've written this out with a base64 encoded FileReader() in the past, where you have to filter out the metadata, then decode to a binary file and figure out the name + extension on the server, but it was slow and seemed sort of obscure.
It's misleading to name the variable uploadBlob since it's not a blob any more. it's a url, you don't send that to the server.
Basically, append the blob/file to a FormData, then send the formdata in your ajax request
const fd = new FormData()
fd.append('avatar', this.files[0])
// fd.append('avatar', this.files[0], optionalFileName)
fetch(uploadUrl, {method: 'post', body: fd})
/*
or using XMLHttpRequest:
var xhr = new XMLHttpRequest
xhr.open('POST', uploadURL)
xhr.send(fd)
*/

Creating Blob object from raw file downloaded from server with $http.

On server there is url with files stored.
Url is http://website.com/abc
I do $http get on this url.
$http.get(url, { responseType: "arraybuffer" });
I want to create Blob object from this. I am sure that object is of type png, because it had this extension before upload and it shows properly.
new Blob(result.data, {type: "image/png"});
I get message:
Failed to construct 'Blob': The 1st argument is neither an array, nor does it have indexed properties.
Response from server http://website.com/abc GET in developer console looks like:
ÿØÿàJFIFÿþ;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 60
ÿÛC

 ' .)10.)-,3:J>36F7,-#WAFLNRSR2>ZaZP`JQROÿÛC&&O5-5OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOÿÂÒ"ÿÄÿÄÿÚæå2ag\Ý#úDê3[Zdfc5±Ô¢¬æ)K<`¤l2¸ÊánYR±aA`Í%RÈfbz!¤tÞÑ-µd7ZªÀ[hz¨f¥>©cAV¬{3á R³F0 W>~c³"ðÈìøÖ²ÇBÙ³±+
½ò9tµ°õ
I tried to set Blob type to application/octet-stream and also do $http.get without specified responseType.
How can I create a proper Blob file?
I need Blob File, to create File object which is an entry data to implemented logic to display files in slideshow with modals. I have implemented logic for files of type File, which were created by input forms - implementation was done for a need before uploading to server. Now it turns out that server doesn't return same files to me, but return only urls of files, which created an idea to convert from url to File in order to dont repeat in that logic.
Try
$http.get(url, { responseType: "blob" });
or
// missing `[]` at `js` at Question
new Blob([result.data], {type: "image/png"});
Note XMLHttpRequest responseType could also be set to "blob", see How to build PDF file from binary string returned from a web-service using javascript

Webcam js upload image to Amazon S3

I'm trying to upload an image taken with
Webcam js
directly to Amazon S3
var dataUri = Webcam.snap();
var raw = window.atob(dataUri.replace(/^data\:image\/\w+\;base64\,/, ''));
and after I get the policy (which is correct) I do this
$.ajax({
type: 'POST',
url: amazonUploadUrl,
data: {
file: raw,
contentType: "image/jpeg",
key: key,
AWSAccessKeyId: awsAccessKey,
acl: "public-read",
policy: policy,
signature: signature,
name: "",
Filename: filename
},
dataType: "",
success: function (r1) {
}
});
I've tried sending the encoded image, the decoded image, I've tried modifying the headers. All I keep getting is this
XMLHttpRequest cannot load 'amazon s3 bucket url'. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'my local domain' is therefore not allowed access.
I've added the CORS info on the Amazon bucket.
I'm already uploading images to that bucket using plupload.
I've also uploaded images from a standard without using ajax.
I just can't seem to get this to work.
Any ideas?
Thanks
PS: I've also tried using
jquery webcam plugin
With the same result
You'll need to use the javascript FormData object and native XMLHttpRequest methods to post the data directly to s3. I've tried to do the exact same thing this morning with jQuery and ran into that error message, but native javascript APIs work.
I have a feeling jQuery isn't using CORS by default or is sending across a the wrong header somewhere.
This answer shows how to convert to a format S3 can understand, which may not be necessary in your case.
This should get you started on the form data part:
var fd = new FormData();
fd.append("contentType", "image/jpeg");
fd.append("key", key);
fd.append("AWSAccessKeyId", awsAccessKey);
fd.append("acl", "public-read");
fd.append("policy", policy);
fd.append("signature", signature);
fd.append('filename', "");
fd.append('file', raw);
var xhr = new XMLHttpRequest();
xhr.open('POST', amazonUploadUrl);
xhr.addEventListener('load', function(e) {
console.log('uploaded!', e) // Successful upload!
});
// Optionally bind to other xhr events, like error, progress, or abort.
// Using native XHR2 is nice because the progress event works and you
// can tack on upload progress counters.
xhr.send(fd);

Google Drive resumable upload with javascript

I'm trying to upload files to Google Drive using Google APIs Client Library for JavaScript and resumable upload type.
I authenticate and get the upload URI successfully, but I ran into problems while sending the actual data. If the file contains only ASCII characters, the file is sent successfully to Drive, but in case of special characters (åäö) or binary file (such as PNG) the file gets corrupted. My guess would be that somewhere in the process the file is encoded to unicode in client side.
If I use "btoa()" to encode the raw data to base64 and add header "Content-Encoding: base64" to the data sending request, the file uploads fine. Using this method however increases the overhead for 33%, which is quite a lot when the planned upload size of files is 100MB to 1GB.
Here are some code examples:
Getting the resumable upload URI:
// Authentication is already done
var request = gapi.client.request({
"path": DRIVE_API_PATH, // "/upload/drive/v2/files"
"method": "POST",
"params": {
"uploadType": "resumable"
},
"headers": {
"X-Upload-Content-Type": self.file.type,
//"X-Upload-Content-Length": self.file.size
// If this is uncommented, the upload fails because the file size is
// different (corrupted file). Manually setting to the corrupted file
// size doesn't give 400 Bad Request.
},
"body": {
// self.file is the file object from <input type="file">
"title": self.file.name,
"mimeType": self.file.type,
"Content-Lenght": self.file.size,
}
});
Sending the whole file in one go:
// I read the file using FileReader and readAsBinaryString
// body is the reader.result (or btoa(reader.result))
// and this code is ran after the file has been read
var request = gapi.client.request({
"path": self.resumableUrl, // URI got from previous request
"method": "PUT",
"headers": {
//"Content-Encoding": "base64", // Uploading with base64 works
"Content-Type": self.file.type
},
"body": body
});
Am I missing something? Is it possible to upload file in binary stream? I am new to uploading files in HTML and Javascript and I haven't found any examples using Google Javascript library with resumable upload. There is similar question in SO with no answers.
Blob types are a hot topic for XMLHttpRequest implementations and they are not truly mature. I'd recommend you to stick with base64 encoding. Google's JavaScript client lib doesn't support resumable uploads because it's very unlikely that a client side browser app uploads very large files directly to Google Drive.
What works
To upload a binary blob, use github/googleapi's cors-upload-sample or use my gist fork, UploaderForGoogleDrive, which will grab access_token out of the gapi client for you.
Here is an ugly mixture of Promise and callback code that works for me. As a prerequisite, gapi,UploaderForGoogleDrive, JSZip need to be loaded via <script> tags. The snippet also omits gapi initialization and the API secrets, which are also necessary.
function bigCSV(){ // makes a string for a 300k row CSV file
const rows = new Array(300*1000).fill('').map((v,j)=>{
return [j,2*j,j*j,Math.random(),Math.random()].join(',');
});
return rows.join("\n");
}
function bigZip(){ // makes a ZIP file blob, about 8MB
const zip = new window.JSZip();
zip.folder("A").file("big.csv", bigCSV());
return zip.generateAsync({type:"blob", compression:"DEFLATE"});
// returns Promise<blob>
}
function upload2(zipcontent){
'use strict';
const parent = 'root';
const spaces = 'drive';
const metadata = {
name: 'testUpload2H.zip',
mimeType: 'application/zip',
parents: [parent]
};
const uploader = new window.UploaderForGoogleDrive({
file: zipcontent,
metadata: metadata,
params: {
spaces,
fields: 'id,name,mimeType,md5Checksum,size'
},
onProgress: function(x){
console.log("upload progress:",Math.floor(100*x.loaded/x.total));
},
onComplete: function(x){
if (typeof(x)==='string') x = JSON.parse(x);
// do something with the file metadata in x
console.log("upload complete: ");
},
onError: function(e){ console.log("upload error: ",e); }
});
uploader.upload();
}
function uploadZipFile(){
'use strict';
(bigZip()
.then(upload2)
);
}
What doesn't work
As of Nov 2017, uploading a binary blob with the gapi.client.request call is not going to work, because of an issue where gapi removes the PUT payload
I've also tried using base64 with gapi, which works. but deposits base64 files, not true binaries; and the fetch API in cors mode, which half-worked but produced CORS-related errors and response hiding, at least for me.

Categories

Resources