Table of Contents

Some insight

Plupload has a built-in support for chunking, although it is disabled by default. What happens when it is activated, is that instead of sending a complete file, Plupload splits it into chunks of predefined size and uploads them one by one to be reassembled back to a complete file on a server-side. Obviously such procedure requires some additional care from upload handler (see below).

Check: "When to use chunking and when not?" from our FAQ for a bit of history and theory behind the chunking feature in Plupload.

Configuration

We will build up on a code from Getting Started, all we need to add though is only a chunk_size option:

var uploader = new plupload.Uploader({
    browse_button: 'browse', // this can be an id of a DOM element or the DOM element itself
    url: 'upload.php',
    chunk_size: '200kb',
    max_retries: 3
});

chunk_size accepts either a size in bytes or a formatted string, e.g: 204800 or "204800b"`` or "200kb".

Events

Each uploaded chunk triggers ChunkUploaded event if upload was successful and Error (plupload.HTTP_ERROR) if upload failed for some reason. Additionally if max_retries option is set and is bigger than 0, which is default, chunk will be re-uploaded that many times in case of failure, before it actually results in Error event.

ChunkUploaded is similar to FileUploaded event. The only real difference is that the former relates to a part of the file and contains some additional properties in a third argument:

uploader.bind('ChunkUploaded', function(up, file, info) {
     // do some chunk related stuff
});

info contains the following properties:

  • offset - chunk offset in bytes from the beginning of the file
  • total - full file size
  • status - HTTP status code (e.g. 200)
  • response - full server response in text form
  • responseHeaders - HTTP response headers (in some cases might be empty, for example in html4 runtime)

Server-side handling

Each chunk is sent either as multipart/form-data (default) or as binary stream, depending on the value of multipart option. Additionally three arguments are sent with each chunk of data:

  • chunks - the total number of chunks in the file
  • chunk - the ordinal number of the current chunk in the set (starts with zero)
  • name - the name of file (usually used to associate the chunk with the actual file)

These arguments can then be used to reassemble the original file on server-side. In our example of upload handler we simply append the chunks as they arrive to a temporary file. This is how you handle it in PHP for example:

<?php

if (empty($*FILES) || $*FILES['file']['error']) {
  die('{"OK": 0, "info": "Failed to move uploaded file."}');
}

$chunk = isset($*REQUEST["chunk"]) ? intval($*REQUEST["chunk"]) : 0;
$chunks = isset($*REQUEST["chunks"]) ? intval($*REQUEST["chunks"]) : 0;

$fileName = isset($*REQUEST["name"]) ? $*REQUEST["name"] : $*FILES["file"]["name"];
$filePath = "uploads/$fileName";


// Open temp file
$out = @fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab");
if ($out) {
  // Read binary input stream and append it to temp file
  $in = @fopen($*FILES['file']['tmp_name'], "rb");

  if ($in) {
    while ($buff = fread($in, 4096))
      fwrite($out, $buff);
  } else
    die('{"OK": 0, "info": "Failed to open input stream."}');

  @fclose($in);
  @fclose($out);

  @unlink($*FILES['file']['tmp_name']);
} else
  die('{"OK": 0, "info": "Failed to open output stream."}');


// Check if file has been uploaded
if (!$chunks || $chunk == $chunks - 1) {
  // Strip the temp .part suffix off 
  rename("{$filePath}.part", $filePath);
}

die('{"OK": 1, "info": "Upload successful."}');

Notice that this code is written so that it can successfully handle upload with or without chunking.

Gist for the client-side code, and gist for - server-side.

In real world scenario you might want to store the chunks as separate files and combine them only after all of them are uploaded. This way you will be able to monitor the state of each chunk, request reuploading of the failed one, resume paused or failed uploads, etc.

There is an effort currently to build a convenient server-side handler class, that will handle it all. We are going to translate it into various server-side languages once it's ready. You can contribute if you want and are proficient in - some :)

Fork me on GitHub