XHR file upload

    XML­HttpRe­quest a fost creat de catre, atentie, Microsoft. Da, stiu, pare con­train­tu­itiv 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 tehnologi­ilor pe care se bazeaza internetul de azi. Apoi a fost preluata si de Mozilla in Gecko Engine v. 0.6, iar im­ple­mentarea lor a devenit standardul de facto urmand sa fie preluat de celelalte browsere si apoi si de W3C.

    Pana acuma XML­HttpRe­quest 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 disponi­bile informatii despre cat s-a transmis.

    In 25 februarie 2008 W3C a publicat un nou Working Draft pentru un obiect nou numit "XML­HttpRe­quest Level 2", care ar completa de­fi­cien­tele existente in standardul actual. Deocamdata este tot la stadiul de Working Draft (din pacate tehnologi­ile legate de web pro­gre­seaza extreeeem de incet), dar a fost im­ple­men­tat 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 in­car­cat­u­lui. 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 pro­pri­etati diferite pentru originea eveni­mentelor.

    FileReader este noul API W3C care permite ma­nip­u­larea de catre Javascript a fisierelor. Pentru pro­pri­etatile si metodele sale cititi pe MDC.

    Cand s-a incarcat fisierul in FileReader, deschidem un XML­HttpRe­quest nou. Trimitem numele fisierului prin variabile GET nume, dar fisierul in sine il trimitem prin POST ca octet-stream. Elementul de noutate este pro­pri­etatea 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, func­tioneaza 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.