1

I have read a lot of already existing questions on uploading a file to azure storage directly from client-side/browser. The one I am implementing is from this blog by gaurav mantri. In this blog he splits the SAS url into path and query and then append file name to the path and then stiched the query again to it. I have SAS url which doesn't have any query. I just have url like this

This SAS url has the file name also appended in it. In the blog he appends blockId and blockList etc. Do it really need to do that? If not how should I make PUT request? Just using my SAS url would work?

Update: I have included query parameter (SAS token) as "URL?SAS-TOKEN". Now I am getting error like this

Error

  dll.vendor.js:44368 PUT https://triggerbackendnormal.blob.core.windows.net/backend-media/a07d312c-6…Vhgoxw/NmD2AeSo4qVhBntrI04xJo1tsqfKJA/7bmQ%3D&comp=block&blockid=undefined 400 (Value for one of the query parameters specified in the request URI is invalid.)CORS

CORS Rules Setup in portal: image of CORS Rules Setup in portal

JS code:

 handleFileSelect(e) {
        var that = this
        maxBlockSize = 256 * 1024;
        currentFilePointer = 0;
        totalBytesRemaining = 0;
        files = e.target.files;
        selectedFile = files[0];
        console.log(selectedFile.name)
        console.log(selectedFile.size)
        console.log(selectedFile.type)
        var fileSize = selectedFile.size;
        if (fileSize < maxBlockSize) {
            maxBlockSize = fileSize;
            console.log("max block size = " + maxBlockSize);
        }
        totalBytesRemaining = fileSize;
        if (fileSize % maxBlockSize == 0) {
            numberOfBlocks = fileSize / maxBlockSize;
        } else {
            numberOfBlocks = parseInt(fileSize / maxBlockSize, 10) + 1;
        }
        console.log("total blocks = " + numberOfBlocks);
        // $("#fileName").text(selectedFile.name);
        // $("#fileSize").text(selectedFile.size);
        // $("#fileType").text(selectedFile.type);

        var baseUrl = 'https://example.blob.core.windows.net/backend-m/a07d312c-6e7a-4281-9e4f-050f5afc4609.mp4?sr=b&se=2017-05-04T15%3A07%3A30Z&sp=w&sv=2016-05-31&sig=SVhgoxw/NmD2AeSo4qVhBntrI04xJo1qfKJA/7bmQ%3D'
        submitUri = baseUrl
        console.log(submitUri);

        this.uploadFileInBlocks();

    }
        //var fileContent = selectedFile.slice(currentFilePointer, currentFilePointer + maxBlockSize);
        //currentFilePointer =+ maxBlockSize;





    uploadFileInBlocks() {
        if (totalBytesRemaining > 0) {
            console.log("current file pointer = " + currentFilePointer + " bytes read = " + maxBlockSize);
            var fileContent = selectedFile.slice(currentFilePointer, currentFilePointer + maxBlockSize);
            var blockId = blockIdPrefix + this.pad(blockIds.length, 6);
            console.log("block id = " + blockId);
            blockIds.push(btoa(blockId));
            reader.readAsArrayBuffer(fileContent);
            currentFilePointer += maxBlockSize;
            totalBytesRemaining -= maxBlockSize;
            if (totalBytesRemaining < maxBlockSize) {
                maxBlockSize = totalBytesRemaining;
            }
        } else {
            this.commitBlockList();
        }
    }

    commitBlockList() {
        var uri = submitUri + '&comp=blocklist';
        console.log(uri);
        var requestBody = '<?xml version="1.0" encoding="utf-8"?><BlockList>';
        for (var i = 0; i < blockIds.length; i++) {
            requestBody += '<Latest>' + blockIds[i] + '</Latest>';
        }
        requestBody += '</BlockList>';
        console.log(requestBody);
        $.ajax({
            url: uri,
            type: "PUT",
            data: requestBody,
            beforeSend: function (xhr) {
                //xhr.setRequestHeader('x-ms-blob-content-type', selectedFile.type);
                //xhr.setRequestHeader('Content-Length', requestBody.length);
            },
            success: function (data, status) {
                console.log(data);
                console.log(status);
            },
            error: function (xhr, desc, err) {
                console.log(desc);
                console.log(err);
            }
        });

    }
    pad(number, length) {
        var str = '' + number;
        while (str.length < length) {
            str = '0' + str;
        }
        return str;
    }

    render(){

        reader.onloadend = function (evt) {
            if (evt.target.readyState == FileReader.DONE) { // DONE == 2
                var uri = submitUri + '&comp=block&blockid=' + blockIds[blockIds.length - 1];
                var requestData = new Uint8Array(evt.target.result);
                $.ajax({
                    url: uri,
                    type: "PUT",
                    data: requestData,
                    processData: false,
                    beforeSend: function(xhr) {
                        xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
                        // xhr.setRequestHeader('Content-Length', requestData.length);
                    },
                    success: function (data, status) {
                        console.log(data);
                        console.log(status);
                        bytesUploaded += requestData.length;
                        var percentComplete = ((parseFloat(bytesUploaded) / parseFloat(selectedFile.size)) * 100).toFixed(2);
                        console.log(percentComplete)
                        this.uploadFileInBlocks();
                    },
                    error: function(xhr, desc, err) {
                        console.log(desc);
                        console.log(err);
                    }
                });
            }
        };

        return (
            <label htmlFor='myInput'>
                <input id="myInput" type="file" ref={(ref) => this.upload = ref} style={{visibility: 'hidden'}} onChange={this.handleFileSelect.bind(this)}/>
                <FloatingActionButton
                    className="floatingButton"
                    backgroundColor='#fb802a'
                    onClick={(e) => this.upload.click() }>
                    <ContentAdd />
                </FloatingActionButton>
            </label>
        )
    }
ArbitB
  • 113
  • 3
  • 12
  • Remove this line `xhr.setRequestHeader('Content-Length', requestData.length);` XMLHttpRequest isn't allowed to set this header, it is being set automatically by the browser. See [AJAX post error : Refused to set unsafe header “Connection”](http://stackoverflow.com/questions/7210507/ajax-post-error-refused-to-set-unsafe-header-connection/7210840) – Aaron Chen May 04 '17 at 09:45
  • What about the second error "Value for one of the query parameters specified in the request URI is invalid"? – ArbitB May 04 '17 at 09:49

2 Answers2

1

I have SAS url which doesn't have any query. I just have url like this 'https://exapmplename.blob.core.windows.net/backend/a074281-9e4f-050f5afc4609.mp4'

This is not a SAS URL. It is simply a URL for the blob. A SAS URL has Shared Access Signature parameters like sig, se, sv etc. appended to the URL as querystring parameters. I would suggest you create a SAS URL and use that. In order to upload a blob, the SAS URL must have Write permission.

In the blog he appends blockId and blockList etc. Do it really need to do that?

It depends! If you're uploading blob without splitting the file in blocks using Put Blob REST API, then you need not do that. However if you need to split the file into blocks and use Put Block and Put Block List REST API, then you have to do that.

If not how should I make PUT request?

If your file is small and have good Internet speed, then you really need not split the file in smaller chunks and upload a file in one go using Put Blob REST API.

Gaurav Mantri
  • 128,066
  • 12
  • 206
  • 241
  • I got it. I had to append SAS token with this URL like URL?SAS-TOKEN. I corrected this mistake. But now I am facing few errors in console. Please look at the updates in my question. I have include CORS rules, my code and error. And about the file size, I have file of size over 500mb, therefore I will need to do it in blocks. Please help me out with my updates in question. – ArbitB May 04 '17 at 09:31
  • When are you getting the error in your code? When you upload the first block or when you're committing the block list? – Gaurav Mantri May 04 '17 at 11:05
  • I rectified that thing. I am placing reader.onloadend in render{} . And that is not correct I guess (look at the update in question). Where should I put reader.onloadend? And I am calling uploadFileInBlocks() from onChange method of input for the first time. – ArbitB May 04 '17 at 11:10
  • What should I do? – ArbitB May 04 '17 at 11:16
  • I'm confused now :). So are you getting an error? If so what's that error and where are you getting it? – Gaurav Mantri May 04 '17 at 11:17
  • Sorry for confusing you. I am trying to do a simple thing. I have a button (file input) which when clicked open a file explorer. When I select any file, and click open it should upload the file to azure. For that I am calling uploadFileInBlocks() method from onChange method of this input button only. But as we have reader.onloadend function also, I am confused where to place it my code. I currently placed it in render method (I am using ReactJS) which is not the correct place I guess. Please ignore the earlier errors. Have a look at the code in my update. Sorry for asking a lot. Thanks again – ArbitB May 04 '17 at 11:38
  • Aah I see. We've also been using React a lot lately. So this method should not be in `Render` method. This should be outside of render block. `reader.onloadend` is essentially an event that gets fired when the file or chunk is read so it will automatically gets called. You don't call it explicitly. – Gaurav Mantri May 04 '17 at 11:51
  • Yeah. But where outside. It gives error when I place it outside the render. I know it has to be called repeatedly, that's why I was sure that this is not right. Can you make an edit in my code and show me that? – ArbitB May 04 '17 at 11:55
  • What's the error you're getting when place this outside? – Gaurav Mantri May 04 '17 at 11:59
  • Will it be possible for you to edit your question and put the complete code of the component? – Gaurav Mantri May 04 '17 at 12:07
  • I solved it. Thanks a lot for helping me out. I have mailed you a query regarding multiple file upload. Your blog taught about handling single file upload. Would be really helpful if you could add something in that area. Thanks again Gaurav. – ArbitB May 04 '17 at 12:15
  • Awesome! Would you mind posting your solution as an answer. It would certainly help others who are facing the same problem. Regarding multiple uploads, you would just have to repeat the same process for multiple files. – Gaurav Mantri May 04 '17 at 12:30
  • I did. @Gaurav It is taking too much time for a 1 GB file to upload. It took around 1 hr to upload it on good internet. – ArbitB May 05 '17 at 06:35
  • Please try to change the max block size. Currently it is set as 256KB. With this size you're making a too many network requests for 1GB file. If you have good Internet speed, you could possibly try with 1MB or more (max 4MB). This will reduce the number of network requests and it should speed up the process. HTH. – Gaurav Mantri May 05 '17 at 07:28
  • I made it 3 mb. But it still takes a very long time.? Is there any way for parallel processing of blocks or something like that? – ArbitB May 05 '17 at 08:33
0

For ReactJS, this is how it should be done

handleFileSelect(e) {
        var that = this
        maxBlockSize = 256 * 1024;
        currentFilePointer = 0;
        totalBytesRemaining = 0;
        files = e.target.files;
        selectedFile = files[0];
        console.log(selectedFile.name)
        console.log(selectedFile.size)
        console.log(selectedFile.type)
        var fileSize = selectedFile.size;
        if (fileSize < maxBlockSize) {
            maxBlockSize = fileSize;
            console.log("max block size = " + maxBlockSize);
        }
        totalBytesRemaining = fileSize;
        if (fileSize % maxBlockSize == 0) {
            numberOfBlocks = fileSize / maxBlockSize;
        } else {
            numberOfBlocks = parseInt(fileSize / maxBlockSize, 10) + 1;
        }
        console.log("total blocks = " + numberOfBlocks);
        // $("#fileName").text(selectedFile.name);
        // $("#fileSize").text(selectedFile.size);
        // $("#fileType").text(selectedFile.type);
    var baseUrl = 'https://example.blob.core.windows.net/backend-media/e7581d7b-a59d-47eb-b8aa-6b6799179b36.mp4?sv=2016-05-31&sr=b&se=2017-05-09T18%3A26%3A07Z&sp=w&sig=TlS/a9RgVT/j7BHztjFZSF2L2skno3Sko%3D'
    submitUri = baseUrl
    console.log(submitUri);

    this.uploadFileInBlocks();

}


loadEnd(evt){
    var that = this;
    if (evt.target.readyState == FileReader.DONE) { // DONE == 2
        var uri = submitUri + '&comp=block&blockid=' + blockIds[blockIds.length - 1];
        var requestData = new Uint8Array(evt.target.result);
        $.ajax({
            url: uri,
            type: "PUT",
            data: requestData,
            processData: false,
            beforeSend: function(xhr) {
                xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
                // xhr.setRequestHeader('Content-Length', requestData.length);
            },
            success: function (data, status) {
                console.log(data);
                console.log("hi" + status);
                bytesUploaded += requestData.length;
                var percentComplete = ((parseFloat(bytesUploaded) / parseFloat(selectedFile.size)) * 100).toFixed(2);
                console.log(percentComplete)
                that.uploadFileInBlocks();
            },
            error: function(xhr, desc, err) {
                console.log(desc);
                console.log(err);
            }
        });
    }
}

uploadFileInBlocks() {
    if (totalBytesRemaining > 0) {
        console.log("current file pointer = " + currentFilePointer + " bytes read = " + maxBlockSize);
        var fileContent = selectedFile.slice(currentFilePointer, currentFilePointer + maxBlockSize);
        var blockId = blockIdPrefix + this.pad(blockIds.length, 6);
        console.log("block id = " + blockId);
        blockIds.push(btoa(blockId));
        reader.readAsArrayBuffer(fileContent);
        reader.onloadend = this.loadEnd.bind(this);
        currentFilePointer += maxBlockSize;
        totalBytesRemaining -= maxBlockSize;
        if (totalBytesRemaining < maxBlockSize) {
            maxBlockSize = totalBytesRemaining;
        }
    } else {
        this.commitBlockList();
    }
}

commitBlockList() {
    var uri = submitUri + '&comp=blocklist';
    console.log(uri);
    var requestBody = '<?xml version="1.0" encoding="utf-8"?><BlockList>';
    for (var i = 0; i < blockIds.length; i++) {
        requestBody += '<Latest>' + blockIds[i] + '</Latest>';
    }
    requestBody += '</BlockList>';
    console.log(requestBody);
    $.ajax({
        url: uri,
        type: "PUT",
        data: requestBody,
        beforeSend: function (xhr) {
            //xhr.setRequestHeader('x-ms-blob-content-type', selectedFile.type);
            //xhr.setRequestHeader('Content-Length', requestBody.length);
        },
        success: function (data, status) {
            console.log(data);
            console.log("hi" + status);
        },
        error: function (xhr, desc, err) {
            console.log(desc);
            console.log(err);
        }
    });

}
pad(number, length) {
    var str = '' + number;
    while (str.length < length) {
        str = '0' + str;
    }
    return str;
}

render(){

    reader.onloadend = function (evt) {
        if (evt.target.readyState == FileReader.DONE) { // DONE == 2
            var uri = submitUri + '&comp=block&blockid=' + blockIds[blockIds.length - 1];
            var requestData = new Uint8Array(evt.target.result);
            $.ajax({
                url: uri,
                type: "PUT",
                data: requestData,
                processData: false,
                beforeSend: function(xhr) {
                    xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
                    // xhr.setRequestHeader('Content-Length', requestData.length);
                },
                success: function (data, status) {
                    console.log(data);
                    console.log(status);
                    bytesUploaded += requestData.length;
                    var percentComplete = ((parseFloat(bytesUploaded) / parseFloat(selectedFile.size)) * 100).toFixed(2);
                    console.log(percentComplete)
                    this.uploadFileInBlocks();
                },
                error: function(xhr, desc, err) {
                    console.log(desc);
                    console.log(err);
                }
            });
        }
    };

    return (
ArbitB
  • 113
  • 3
  • 12