Upload de gros fichier avec HTML5 et Javascript

Upload de gros fichier avec HTML5 et Javascript

18/05/2013

N​‌‍​‌​‌‍​‍’avez vous jamais eu envie ou besoin d’uploader des fichiers d’une taille supérieure à la limite maximum imposée par votre serveur web? Je me suis aussi retrouvé face à ce problème et n’ayant pas toujours la main sur la config du serveur, j’ai dû trouver des solutions. La plus simple est de passé par un service FTP, ce qui est très bien pour une personne initiée mais pour la partie publique d’un site, nettement moins. Il y a aussi des plugins flash comme uploadify qui propose de l’upload “facile” mais la limite de taille maximum est toujours là. Il faut dire aussi que flash tend à disparaître.

C’est le logiciel XtremSplit qui m’a donné l’idée de découper les fichers avant de les envoyer au serveur pour qu’ils les reconstituent. Oui mais comment? C’est HTML5 qui m’a apporté la réponse (je passe la solution flash…) avec son API javascript FILE qui permet de découper des fichier blobs très facilement.

Le fonctionnement est le suivant :

Html5 Upload

  • L’utilisateur sélectionne un fichier
  • Le fichier est découpé
  • Les parties sont envoyées séparément au serveur
  • Le serveur récupère et réassemble toutes les parties

La librairie est développée en PHP 5 pour les systèmes APACHE. Il est libre à vous de l’adapter pour d’autre système serveur.

Si vous souhaitez le mettre en place il vous faudra:

  • Des clients HTML5
  • Proposer une solution alternative pour les vieux clients
  • Quelques connaissances en JavaScript pour intégrer le script dans vos pages
  • Quelques connaissances en php pour comprendre le fonctionnement côté serveur

Le code est disponible ci-dessous et la librairie est disponible en téléchargement en pied de page. Ce contenu est sous licence creative common.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* Aderesse des web services appelés
*/
var uploadVars = {
openUpload : ‘openupload.php’, // Ouverture d’une session d’upload
uploadManager : ‘uploadmanager.php’, // Envoi d’un bout de fichier
assembleParts : ‘assembleparts.php’, // Assembler les parties envoyés
assembleState : ‘assemblestate.php’ // Etat de l’assemblage
}
/**
* Récupère un objet XMLHttp
*/
function getXmlHttp()
{
if (window.XMLHttpRequest)
return new XMLHttpRequest();
else
return new ActiveXObject(“Microsoft.XMLHTTP”);
}
/**
* Permet de réaliser un post en AJAX
*/
function post(url, params, callbackdone, callbackfail)
{
var xmlhttp = getXmlHttp();
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200)
callbackdone(xmlhttp);
else if(xmlhttp.readyState==4 && xmlhttp.status!=200)
callbackfail(xmlhttp);
};
xmlhttp.open(“POST”,url,true);
xmlhttp.setRequestHeader(“Content-type”,”application/x-www-form-urlencoded”);
xmlhttp.send(params);
}
/**
* Réaliser un post avec des datas
*/
function postdatas(url, params, callbackdone, callbackfail, callbackprogress)
{
var xmlhttp = getXmlHttp();
if(callbackprogress!=null)xmlhttp.addEventListener(“progress”, callbackprogress, false);
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200)
callbackdone(xmlhttp);
else if(xmlhttp.readyState==4 && xmlhttp.status!=200)
callbackfail(xmlhttp);
};
xmlhttp.open(“POST”,url,true);
xmlhttp.send(params);
}

/**
* Classe d’upload
*/
function Upload(file){
var uploadedFile, aborted, started, currentpart, reader, that, jsonStart, assembleInterval, miniprogress;
this.start = function()
{
this.started = true;
post(uploadVars.openUpload,
‘&filesize=’+this.uploadedFile.size+’&filename=’+this.uploadedFile.name,
function(x){
var json = JSON.parse(x.responseText);
that.startDone(json);
},
function(x){
that.startFailed(x);
}
);
}
this.startFailed = function(xmlhttp)
{
}
this.startDone = function(json)
{
this.jsonStart = json;
this.idSet(this.jsonStart.id);
this.uploadPart(1);
}
this.uploadPart = function(numpart)
{
this.currentpart = numpart;
var begin = (numpart-1)this.jsonStart.partsize;
var end = begin + this.jsonStart.partsize;
if(end > this.uploadedFile.size) end = this.uploadedFile.size;
var blob = this.uploadedFile.slice(begin, end);

var formPost = new FormData();
formPost.append(“uploadid”, this.jsonStart.id);
formPost.append(“part”, numpart);
formPost.append(“blob”, blob);

that.progressUpdateChanged(this.currentpart + that.miniprogress,this.jsonStart.parts,that.jsonStart.id);
postdatas(uploadVars.uploadManager,
formPost,
function(x){
that.miniprogress = 0;
if(numpart < that.jsonStart.parts)
that.uploadPart(numpart+1);
else
{
//the process is over assemble the file
post(uploadVars.assembleParts,
‘&uploadid=’+that.jsonStart.id,
function(x){
that.uploadOver(that.jsonStart.id);
},
function(x){

}
);
that.getAssemblageState();
}
},
function(x){
that.miniprogress = 0;
that.uploadPart(numpart);
},
function(evt){
that.miniprogress = evt.loaded / evt.total;
that.progressUpdateChanged(that.currentpart + that.miniprogress,that.jsonStart.parts,that.jsonStart.id);
}
);

this.reader.readAsBinaryString(blob);
}
this.getAssemblageState = function()
{
post(uploadVars.assembleState,
‘&uploadid=’+that.jsonStart.id,
function(x){
var percent = parseInt(x.responseText);
that.assemblageStateChanged(that.jsonStart.id, percent);
if(that.assembleInterval!=null)window.clearInterval(that.assembleInterval);
if(percent != 100)
that.assembleInterval = setInterval(function(){that.getAssemblageState();},250);
},
function(x){

}
);
}
this.progressUpdateChanged=function(currentpart, all, id)
{
console.log(this.currentpart/this.jsonStart.parts);
}
this.uploadOver=function(id)
{
}
this.idSet = function(id)
{
}
this.assemblageStateChanged = function(id, percent)
{
}

that = this;
this.uploadedFile = file;
this.started = false;
this.aborted = false;
this.state = 0;
this.reader = new FileReader();
this.assembleInterval=null;
this.miniprogress = 0;
}