10

How can I retrieve $_FILES in PHP when I upload through a Web Worker? When I tried to use FormData, I got the following error:

Error: FormData is not defined

This is my code:

function uploadFile(blobFile, fileName, filePart, totalChunks) {
    //if I try to put this
    //var formData = new FormData(); //it does not work
    var xhr = new XMLHttpRequest();                
    xhr.open("POST", "upload.php"+"?"+"file="+fileName + filePart, true);
    xhr.onload = function(e) {};
    xhr.send(blobFile);
}   

So in upload.php how am I supposed to get the tmp path from $_FILES ? Just in case, I will also show the page referencing the Web worker:

<form id="fileuploader" enctype="multipart/form-data" method="post" action="upload.php">
    <label for="fileToUpload">Select Files to Upload</label><br />
    <input type="file" name="fileToUpload[]" multiple="" id="fileToUpload" onchange="fileList();"/><br />
    <input type="button" onclick="sendRequest();" value="Upload" />
    <!-- a place for File Listing -->
    <div id="fileList"></div>           
</form>
<script type="text/javascript">
function sendRequest() {
    var worker = new Worker("fileupload.js");
    worker.onmessage = function(e) {
        alert(e.data);
    }
    var file = document.getElementById('fileToUpload');
    for(var i = 0; i < file.files.length; i++) { 
        worker.postMessage(file.files[i]);  
    }
}
vhs
  • 9,316
  • 3
  • 66
  • 70
Harts
  • 4,023
  • 9
  • 54
  • 93
  • 1
    The formData object needs access to the DOM to get the form, and webworkers does not have access to the DOM, so those two won't work toghether. This is'nt really what webworkers are intended for. – adeneo Dec 14 '12 at 00:34
  • @adeneo so you mean, there's no way to make this work? is there any other way without using formdata? because based on this article to use webworker for upload file http://kongaraju.blogspot.com/2012/07/large-file-upload-more-than-1gb-using.html seems like a good one, but he does not show the server part – Harts Dec 14 '12 at 00:38
  • Not sure, all I know is that a webworker can never have access to the DOM, and FormData builds an object pretty much based on a form, and without access to the DOM, it would be hard to get FormData to work as it does'nt have access to the form at all. – adeneo Dec 14 '12 at 00:46
  • @adeneo The OP does not attempt to use the `
    ` object (that's not possible). Instead, he posts `File` objects, which are correctly received because of the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm). As I demonstrate below, absence of DOM does not mean that the `FormData` API cannot be used - see the answer below.
    – Rob W Dec 20 '12 at 10:36

1 Answers1

14

I have written the following polyfill to emulate the FormData method in Web Workers. Since Web workers do not support DOM, the new FormData(<HTMLFormElement>); constructor call is not supported either.
File and Blob objects, typed arrays and strings are supported by the polyfill though.

It was originally posted as a part of the answer to Upload a File in a Google Chrome Extension. To see an example of how it can be used, have a look at the other answer.

/*
 * FormData for XMLHttpRequest 2  -  Polyfill for Web Worker  (c) 2012 Rob W
 * License: Creative Commons BY - http://creativecommons.org/licenses/by/3.0/
 * - append(name, value[, filename])
 * - toString: Returns an ArrayBuffer object
 * 
 * Specification: http://www.w3.org/TR/XMLHttpRequest/#formdata
 *                http://www.w3.org/TR/XMLHttpRequest/#the-send-method
 * The .append() implementation also accepts Uint8Array and ArrayBuffer objects
 * Web Workers do not natively support FormData:
 *                http://dev.w3.org/html5/workers/#apis-available-to-workers
 **/
(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);
    };
})();
Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678