XHR file upload
XMLHttpRequest a fost creat de catre, atentie, Microsoft. Da, stiu, pare contraintuitiv ca Microsoft, care a dat nastere pagubei pe capetele web developers numite Internet Explorer 6 si care refuza sa moara, a creat unul din stalpii de baza a tehnologiilor pe care se bazeaza internetul de azi. Apoi a fost preluata si de Mozilla in Gecko Engine v. 0.6, iar implementarea lor a devenit standardul de facto urmand sa fie preluat de celelalte browsere si apoi si de W3C.
Pana acuma XMLHttpRequest avea cateva limitari majore. Nu se puteau face cereri catre alte siteuri, mai ales din motive de securitate, nu se putea transmite fluxuri de biti si nu erau disponibile informatii despre cat s-a transmis.
In 25 februarie 2008 W3C a publicat un nou Working Draft pentru un obiect nou numit "XMLHttpRequest Level 2", care ar completa deficientele existente in standardul actual. Deocamdata este tot la stadiul de Working Draft (din pacate tehnologiile legate de web progreseaza extreeeem de incet), dar a fost implementat deja in Firefox 3.6+, Google Chrome si Safari 4+.
Ieri a trebuit sa fac ceva schimbari la partea de upload a siteului bisericii la care apartin si am vrut sa fac uploadul cu XHR, fara iframes. Pe net in schimb am gasit doar 2 siteuri relevante despre aceasta: Mozilla Hackssi siteul lui Andrew Valums care a facut un plugin pentru uploadat fisiere prin XHR.
Pluginul lui Andrew Valums ar fi trebuit sa il hackuiesc prea mult ca sa il fac sa mearga asa cum vroiam eu, iar postul de pe Mozilla Hacks era prea sumar si a trebuit sa mai scriu inca vreo 40 de linii pana sa il fac sa mearga.
Sa incepem prin a construi partea HTML a paginii.
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="index.js" type="text/javascript"></script>
<style>
#progres {
height:20px;
background-color:blue;
width:0px;
}
</style>
</head>
<body>
<div id='uploader'>
<input type='file' name='input' id='input'>
<div id="progres"></div>
</div>
</body
</html>
Este doar o pagina simpla, care contine un file input
si un div
in care vom afisa progresul incarcatului. Nu am facut un fisier separat pentru CSS, deoarece contine doar 4 randuri, dar pe siteuri adevarate e bine sa folositi fisiere separate pentru CSS.
window.onload= function() {
document.getElementById('input').onchange=ajaxupload;
}
function ajaxupload(element) {
if (element.srcElement)
file=element.srcElement.files[0];
else if (element.currentTarget)
file=element.currentTarget.files[0];
else
alert('S-or inventat si alte browsere');
var reader = new FileReader();
name=file.name;
reader.onloadend = function() {
var xhr = new XMLHttpRequest();
xhr.open("POST", 'php.php?nume='+name, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
xhr.setRequestHeader("Content-Type", "application/octet-stream");
bara = document.getElementById('progres')
xhr.upload.addEventListener("progress",function(e) {
bara.style.width=e.loaded/e.total*200+'px';
bara.innerHTML=e.loaded/e.total*100+'%';
}, false);
xhr.upload.addEventListener("load", function(e){
bara.style.width=200+'px';
bara.innerHTML=100+'%';
}, false);
xhr.send(file);
}
reader.readAsDataURL(file);
}
Prima functie este doar pentru a seta event handling
pe file input cand se incarca pagina. Apoi incepe magia.
Primele 6 randuri din functia ajaxupload
sunt doar pentru a obtine corect fisierul. Webkit si Gecko au fiecare proprietati diferite pentru originea evenimentelor.
FileReader
este noul API W3C care permite manipularea de catre Javascript a fisierelor. Pentru proprietatile si metodele sale cititi pe MDC.
Cand s-a incarcat fisierul in FileReader, deschidem un XMLHttpRequest nou. Trimitem numele fisierului prin variabile GET nume
, dar fisierul in sine il trimitem prin POST ca octet-stream
. Elementul de noutate este proprietatea upload
a XHR. Aceasta contine eventurile progress
si load
(si multe altele, dar pe care nu le prezint aici), la care atasam functii care modifica latimea si textul din div
la cat la suta s-a incarcat deja. Primirea fisierului nu se face poate face cu $_FILES in PHP, ci trebuie citit direct fluxul de date care vine de la browser.
<?php
$file='../tutorials/';
$nume=$_GET['nume'];
if (isset($_SERVER["CONTENT_LENGTH"])){
$size=(int)$_SERVER["CONTENT_LENGTH"];
} else {
throw new Exception('Getting content length is not supported.');
}
$file.=$nume;
$input = fopen("php://input", "r");
$fp = fopen($file, "w");
echo $file;
while ($data = fread($input, 1024)){
fwrite($fp,$data);
}
fclose($fp);
fclose($input);
echo "Fisierul ".$nume." a fost incarcat pe server";
?>
Marimea fisierului o obtinem din headerul CONTENT_LENGTH
trimis de browser. Citirea se va face in sectiuni de cate 1024 de biti, pentru a nu consuma prea multa memorie pe server. Acesta este desigur un cod foarte sumar, fara nici un fel de verificare. De asemenea, functioneaza doar in browsere moderne, gen Firefox 3.6+, Chrome 6+ (in astea doua am testat) si cred ca si in Safari 4+. Suportul Opera este cam limitat deocamdata, din cate stiu.
Toate cele trei fisiere se pot descarca de aici.