Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Web Workers File Upload

I am trying to make a html uploader for very large files using HTML5 and Web Workers. Atm it uploads slowly but consumes alot of memory. I think it is transferring the whole file to the memory when it adds it to the form. Heres the code: jswebworker.js:

 /*importScripts('webworkerFormData.js');*/

(function() {
// Export variable to the global scope
(this == undefined ? self : this)['FormData'] = FormData;

var ___send$rw = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype['send'] = function(data) {
    if (data instanceof FormData) {
        if (!data.__endedMultipart) data.__append('--' + data.boundary + '--\r\n');
        data.__endedMultipart = true;
        this.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + data.boundary);
        data = new Uint8Array(data.data).buffer;
    }
    // Invoke original XHR.send
    return ___send$rw.call(this, data);
};

function FormData() {
    // Force a Constructor
    if (!(this instanceof FormData)) return new FormData();
    // Generate a random boundary - This must be unique with respect to the form's contents.
    this.boundary = '------RWWorkerFormDataBoundary' + Math.random().toString(36);
    var internal_data = this.data = [];
    /**
    * Internal method.
    * @param inp String | ArrayBuffer | Uint8Array  Input
    */
    this.__append = function(inp) {
        var i=0, len;
        if (typeof inp === 'string') {
            for (len=inp.length; i<len; i++)
                internal_data.push(inp.charCodeAt(i) & 0xff);
        } else if (inp && inp.byteLength) {/*If ArrayBuffer or typed array */
            if (!('byteOffset' in inp))   /* If ArrayBuffer, wrap in view */ 
                inp = new Uint8Array(inp);
            for (len=inp.byteLength; i<len; i++)
                internal_data.push(inp[i] & 0xff);
        }
    };
}
/**
* @param name     String                                  Key name
* @param value    String|Blob|File|Uint8Array|ArrayBuffer Value
* @param filename String                                  Optional File name (when value is not a string).
**/
FormData.prototype['append'] = function(name, value, filename) {
    if (this.__endedMultipart) {
        // Truncate the closing boundary
        this.data.length -= this.boundary.length + 6;
        this.__endedMultipart = false;
    }
    var valueType = Object.prototype.toString.call(value),
        part = '--' + this.boundary + '\r\n' + 
            'Content-Disposition: form-data; name="' + name + '"';

    if (/^\[object (?:Blob|File)(?:Constructor)?\]$/.test(valueType)) {

        return this.append(name,
                        new Uint8Array(new FileReaderSync().readAsArrayBuffer(value)),
                        filename || value.name);
    } else if (/^\[object (?:Uint8Array|ArrayBuffer)(?:Constructor)?\]$/.test(valueType)) {
        part += '; filename="'+ (filename || 'blob').replace(/"/g,'%22') +'"\r\n';
        part += 'Content-Type: application/octet-stream\r\n\r\n';
        this.__append(part);
        this.__append(value);
        part = '\r\n';
    } else {
        part += '\r\n\r\n' + value + '\r\n';
    }
    this.__append(part);
};
})();

movies = [];
var timeStarted = 0;
uploadingVar = false;
const BYTES_PER_CHUNK = 64 * 1024 * 1024; 

function toTitleCase(str)
{
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1);});
}

function newUpload(blobOrFile, moviename, filename, i, fileType, sizeFile) {
var xhr = new XMLHttpRequest();
path = '/moviehtml/newmupload.php?        moviename='+escape(moviename)+'&filename='+escape(filename)+'&num='+escape(i);
xhr.open('POST', path, false);
self.postMessage(blobOrFile.size);
var fd = new FormData();
//xhr.setRequestHeader('Content-Type', fileType)
//blobOrFile2 = FileReaderSync.readAsArrayBuffer(blobOrFile);
fd.append("files1", blobOrFile);
//fd.append("moviename", moviename);
//fd.append("filename", filename);
//fd.append("num",i);

seconds = new Date() / 1000;
xhr.send(fd);
self.postMessage(xhr.responseText)
self.postMessage({'type':'partial','done':i*BYTES_PER_CHUNK, 'started':timeStarted, 'total':sizeFile});
var finish = Date()/1000;
if (finish >= (seconds+100)){
    return false;
}
return true;

}
function newFileUpload(file, movieName, fileType, filename, exten){
if (movieName == movieName.match(/[a-zA-Z0-9\. \:]+/)){
    timeStarted = new Date().getTime();

var blob = file;// this.files[0];
//var filename = blob.name;
var moviename = toTitleCase(movieName);
 // 1MB chunk sizes.
const SIZE = blob.size;
//alert(SIZE + ' '+document.getElementById('fileToUpload').files[0].size)
var start = 0;
var end = BYTES_PER_CHUNK;
//alert(SIZE/BYTES_PER_CHUNK)
var i = 1;
while(start < SIZE) {
    wow = newUpload(blob.slice(start, end), moviename, filename, i, fileType, SIZE);

    start = end;
    end = start + BYTES_PER_CHUNK;
    i++;
}
var xhr = new XMLHttpRequest();
var fd2 = new FormData();
typeFile = filename.split('.').pop()
fd2.append("type", blob.type);
fd2.append("exten", typeFile);
fd2.append("moviename", moviename);
xhr.open('POST', '/moviehtml/finishedupload.php', false);
xhr.send(fd2);
}
}
function process(){
uploadingVar = true;
while(movies.length > 0) {

    upMovie = movies.pop();
    var file = upMovie[0];
    var movieName = upMovie[1];
    var fileType = upMovie[2];
    var filename = upMovie[3];
    var exten = upMovie[4];
    self.postMessage({'type':'start','size':file.size, 'name':movieName});

    newFileUpload(file, movieName, fileType, filename, exten);
    self.postMessage({'type':'finish','name':movieName})
    self.postMessage(movieName + " Uploaded Succesfully");
 }
 uploadingVar = false;
}

self.addEventListener('message', function(e) {
movies.push(e.data);
if (!uploadingVar){
process();

}
}, false);

My function that calls it:

var worker = new Worker('jswebworker.js');
function testUpload(){
//newFileUpload();
var file = document.getElementById('fileToUpload').files[0];
worker.postMessage([file,toTitleCase(document.getElementById('movieName').value),     file.type, file.name, file.name.split('.').pop()]);

}

This is the web page for a media server for my flat. I am hoping that there is a way to create a blob without loading all of the original into memory. Thanks for any help, Nick

like image 909
Hoodlum Avatar asked Sep 27 '12 06:09

Hoodlum


People also ask

Can we upload file using JavaScript?

html file through a browser, the client will be able to upload a file to the server using Ajax and pure JavaScript. A pure JavaScript file uploader simplifies Ajax based interactions with the server.

Can web worker access local storage?

Because it is tab specific, it is not accessible from web workers or service workers. LocalStorage should be avoided because it is synchronous and will block the main thread.

How do JavaScript web workers work?

A web worker is a JavaScript that runs in the background, independently of other scripts, without affecting the performance of the page. You can continue to do whatever you want: clicking, selecting things, etc., while the web worker runs in the background.

How many web workers can run concurrently JavaScript?

How many web workers can run concurrently JavaScript? A web worker is a JavaScript program running on a different thread, in parallel with main thread. The browser creates one thread per tab. The main thread can spawn an unlimited number of web workers, until the user's system resources are fully consumed.


1 Answers

I think that this library (here is the github page for resumable.js as well) may already be doing what you are trying too accomplish. Since I don't know how big the files are that you are tying to upload I can't provide any insight as far as benchmarks go. I hope this helps you out.

like image 129
coderrick Avatar answered Oct 12 '22 11:10

coderrick