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 Hacks si 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.

Anunțuri

6 gânduri despre „XHR file upload

  1. in loc de:

    while ($data = fread ($input, 1024)){
    fwrite ($fp,$data);
    }

    ai putea sa pui:

    while ($data = fread($input, (($size < 1024) ? $size : 1024))) {
    fwrite ($fp,$data);
    $size -= 1024;
    }

    asa la sfarsit nu incerci sa citesti octeti inexistenti de la final

      • in PHP nu sunt sigur…
        in C stiu ca daca citesti in mod text, atunci cu iti da o eroare ca a trecut peste sfarsitul fisierului (eroarea o detectezi numai cu ferror()).
        Daca citesti in mod binar, atunci o sa ai bytes un plus la sfarsit.
        Cum stdlib-ul din PHP e aproape identic cu cel din C, presupun ca si in PHP face la fel

        • ferror() nu exista in PHP. Un cod similar am folosit pentru incarcarea la un site si s-au incarcat cateva sute de mp3-uri care apoi au fost redate fara probleme.
          Asa ca in PHP nu e bai prea mare ca citeste cativa octeti in plus.

Lasă un răspuns

Completează mai jos detaliile tale sau dă clic pe un icon pentru a te autentifica:

Logo WordPress.com

Comentezi folosind contul tău WordPress.com. Dezautentificare / Schimbă )

Poză Twitter

Comentezi folosind contul tău Twitter. Dezautentificare / Schimbă )

Fotografie Facebook

Comentezi folosind contul tău Facebook. Dezautentificare / Schimbă )

Fotografie Google+

Comentezi folosind contul tău Google+. Dezautentificare / Schimbă )

Conectare la %s