Skip to content

Instantly share code, notes, and snippets.

@piatra
Created June 27, 2012 12:14
Show Gist options
  • Save piatra/3003691 to your computer and use it in GitHub Desktop.
Save piatra/3003691 to your computer and use it in GitHub Desktop.
xhr2 + nodejs + filereader = resumable uploads
var http = require('http')
, formidable = require('formidable')
, fs = require('fs')
, qs = require('querystring')
, util = require('util')
, uploads = {};
http.createServer(function(req, res){
if(req.method == 'GET') {
if(req.url != '/favicon.ico') {
res.writeHead(200, {'Content-Type' : resolveType(req)});
var url = __dirname;
if(req.url != '/') url += '/public' + req.url;
else url += '/views/index.html';
var readStream = fs.createReadStream(url)
.pipe(res)
.on('error', function(o_O){
console.log(o_O);
});
} else {
res.end();
}
}
if(req.method == 'POST') {
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files){
if (err) {
console.error(err.message);
return;
}
res.writeHead(200, {'content-type': 'text/plain'});
if(fields.name) {
if(!uploads[fields.name]) { // new upload
console.log('new upload ', fields.name);
uploads[fields.name] = {
chunk: 0,
size: fields.size,
content: '',
total: fields.size / 272144
};
} else {
console.log('got chunk ', uploads[fields.name].chunk);
uploads[fields.name].chunk++;
console.log(fields);
uploads[fields.name].content += fields.data;
}
if(uploads[fields.name].chunk < uploads[fields.name].total) {
console.log('requesting ', uploads[fields.name].chunk, ' out of ', uploads[fields.name].total);
res.end('chunk' + uploads[fields.name].chunk);
}
else {
console.log('complete');
res.end('upload complete');
}
}
});
}
}).listen(3000, function(){
console.log("Express server listening on port 3000");
});
var resolveType = function(req) {
var extension = req.url.split('.');
extension = extension[extension.length-1];
if(extension == 'js') return 'text/javascript';
else if(extension == 'css') return 'text/css';
else return 'text/html';
};
<!DOCTYPE html>
<html>
<head>
<title>Resumable Upload</title>
<link rel="stylesheet" href="/stylesheets/style.css"/>
<link rel="stylesheet" href="/stylesheets/bootstrap.min.css"/>
</head>
<body>
<div class="container">
<div class="row">
<div class="span8">
<h1>Resumable Upload</h1>
</div>
<div class="span4">
<div id="message" class="alert alert-success">
<strong>All requirements met !</strong>
</div>
</div>
</div>
<div class="row">
<div class="span4">
<label for="file">
<input type="file" id="file" required="required"/>
</label>
</div>
<div class="span4">
<input type="submit" id="submit" class="btn btn-primary"/>
</div>
</div>
<p>
<div class="progress progress-striped active">
<div style="width: 0%;" class="bar">
</div>
</div>
</p>
<ul id="output">
</ul>
</div>
</body>
<script src="/javascripts/script.js"></script>
</html>
(function () {
//"use strict";
var chunk = 272144
, file
, output = document.querySelector('#output')
, fileReader = new FileReader();
var fileChosen = function(evnt) {
file = evnt.target.files[0];
};
fileReader.onload = function(evnt) {
var formData = new FormData();
formData.append('name', file.name);
formData.append('data', evnt.target.result);
sendData(formData);
};
var startUpload = function() {
var formData = new FormData();
formData.append('name', file.name);
formData.append('size', file.size);
sendData(formData);
};
var sendChunk = function(offset) {
console.log('sending chunk ', offset);
var place = offset * chunk; //The Next Blocks Starting Position
var blob = new Blob([file], {"type" : file.type});
var nFile = blob.slice(place, place + Math.min(chunk, (file.size-place)));
fileReader.readAsBinaryString(nFile);
};
var sendData = function(formData) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.onload = function(e) {
if (this.status == 200) {
if(this.response.slice(0,5) === 'chunk') {
var part = parseInt(this.response.slice(5), 10);
sendChunk(part);
} else {
console.log(this.response);
}
}
};
xhr.send(formData);
};
// var place = data.offset * chunk; //The Next Blocks Starting Position
// var blob = new Blob([file], {"type" : file.type});
// var nFile = blob.slice(place, place + Math.min(chunk, (file.size-place)));
// fileReader.readAsBinaryString(nFile);
var progress = function(p) {
var bar = document.querySelector('.bar');
bar.style.width = p*100 + '%';
bar.innerText = p*100 + '%';
if(p==1) {
bar.parentElement.className = 'progress progress-success progress-striped active';
bar.innerText = 'Done !';
}
};
if(window.File && window.FileReader){
document.querySelector('#submit').addEventListener('click', startUpload);
document.querySelector('#file').addEventListener('change', fileChosen);
} else {
document.querySelector('#message').innerHTML = "Your Browser Doesn't Support The File API Please Update Your Browser";
}
}());
@Elanouette
Copy link

Hi,

I'm trying to get your code working but I have an issue. Are you getting some file chunk data in your node app.js ?

Output from "console.log(fields);" on line 50 always show the name but data has no value :

new upload  Bars.mp4
requesting  0  out of  29.493099241577987
got chunk  0
{ name: 'Bars.mp4', data: '' }
requesting  1  out of  29.493099241577987
got chunk  1
{ name: 'Bars.mp4', data: '' }
requesting  2  out of  29.493099241577987
got chunk  2
{ name: 'Bars.mp4', data: '' }
requesting  3 ...

@Elanouette
Copy link

The issue was related to my browser...

I'm using the unreleased Safari v7.0 that will come with the next version of OSX. I did not have time to tests older versions of Safari but my guess is that it cannot send a blob, because body payload is empty.

Sending an ArrayBuffer in this case should fix it.

@pforprasad
Copy link

May I know how to get this full project ? I'm able to get 3 files only, where is full project source code ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment