I just found the HTML5 File API the other day so I had to see what I could do with the APEX Listener's RESTful services. There's a bunch of blogs on what can be done such as on HTML5rocks .
The end result is that the new File api let's javascript get details of the file and slice it up into parts. Then I made a pretty simple REST end point to receive the chunks and put them back together again.
The actual sending part of the javascript is here
function sendChunk(chunkNumber){ var reader = new FileReader(); var start = chunkSize * (chunkNumber-1); var end = start + chunkSize -1; // create the slice of the file var fileContent = selectedFile.slice(start, end); // grab the length var length = fileContent.size; // read the slice of the file reader.readAsArrayBuffer(fileContent); $.ajax({ url: uri, type: "POST", data: fileContent, processData: false, beforeSend: function(xhr) { // pass in the chunk size,offset,name // as headers xhr.setRequestHeader('x-chunknumber', chunkNumber); xhr.setRequestHeader('x-filename', selectedFile.name); xhr.setRequestHeader('x-offset', start ); xhr.setRequestHeader('x-chunksize', length ); xhr.setRequestHeader('x-content-type', selectedFile.type ); }, success: function (data, status) { console.log(data); console.log(status); bytesUploaded += length; // set the percent complete var percentComplete = ((bytesUploaded / selectedFile.size) * 100).toFixed(2); $("#fileUploadProgress").text(percentComplete + " %"); // make a link to the REST that can deliver the file $("#downloadLink").html("New File"); // if there's more chunks send them over if ( chunkNumber < chunks ) { sendChunk(chunkNumber+1); } }, error: function(xhr, desc, err) { console.log(desc); console.log(err); } }); }
The next step is to make the HTTP Headers into bind variable so the plsql block will be able to use them.
declare p_b blob; p_body blob; p_offset number; p_filename varchar2(4000); p_raw long raw; p_chunksize varchar2(200); p_status varchar2(200); begin -- pull the binds into locals p_offset := :OFFSET + 1; p_body := :body; p_filename := :filename; p_chunksize := :chunksize; -- NOT FOR PRODUCTION OR REAL APPS -- If there is a file already with this name nuke it since this is chunk number one. if ( :chunkNumber = 1 ) then p_status := 'DELETING'; delete from chunked_upload where filename = p_filename; end if; -- grab the blob storing the first chunks select blob_data into p_b from chunked_upload where filename = p_filename for update of blob_data; p_status :=' WRITING'; -- append it dbms_lob.append(p_b, p_body); commit; exception -- if no blob found above do the first insert when no_data_found then p_status :=' INSERTING'; insert into CHUNKED_UPLOAD(filename,blob_data,offset,content_type) values ( p_filename,p_body,p_offset,:contenttype); commit; when others then -- when something blows out print the error message to the client htp.p(p_status); htp.p(SQLERRM); end;
A very simple html page for testing it all out.
Here's a quick video of how it all works.
Here's the complete Javascript/html for this sample.
JS Bin