Alternative PHP Cache(APC) - Upload progress

Alternative PHP Cache, często zwany APC jest mechanizmem buforowania skompilowanego kodu php. Dostępny jest za darmoz repozytoriów PEAR i PECL. Pierwsze wersje pakietu była przygotowane przez Daniela Cowgilla oraz Gorge'a Schlossnagle'a i wydane przez firmę Community Connect. Po pojawieniu się PHP 5, oryginalny pakiet został zmodyfikowany przez Yahoo!, dostowując go do nowych mechanizmów Zend'a.

Działanie pakietu polega na pobieraniu ze środowiska wykonawczego skompilowanego kodu PHP i zapisywaniu go w pamięci. Przy każdym rządaniu skryptu APC sprawdza najpierw czy nie jest dostępna jego skompilowana postać, a powtórna kompilacja i wykonanie skryptu z dysku nastepuje tylko wtedy gdy w pamięci nie ma potrzebnych danych. Pozwala to na wzrost wydajności nawet czterokrotnie. My głównie wykorzystamy pakiet APC do śledzenia procesu przesyłania plików z przeglądarki do serwera WWW. Pomine więc instalację i konfiguracje samego rozszerzenia, które możemy uruchomić jako dynamiczna bibliotekę lub mieć pakiet na stałe wkompilowany w php. Najwygodniejszym sposobem jest oczywiście pierwszy, który pozwala łatwo i sprawnie w razie kłopotów wyłączyć moduł. Musimy pamietać że sam moduł zadanie ma inne niż kontrolowanie przesyłanych plików, więc cudów nie należy się spodziewać. Działający przykład z artykułu, możemy zobaczyć pod adresem: http://mephir.net.pl/upload_progress-apc/(tymczasowo nie działa, powodem jest przeniesienie serwisu na nowy serwer, potrzeba ksonfigurowania apc, co nie jest potrzebą pierwszej konieczności,za utrudnienia przepraszam).

Instalacja i konfiguracja

Jak już pisałem nie będe tu mówił o pełnej procedurze instalacji i konfiguracji, ponieważ jest to zbędne. Pakiet jest dostępny dla systemu Linux oraz Widnows. Ważną kwestią jest to, że zdziebko się gryzie z mod_security serwera Apache, jak i również mod_evasive, ale tu głównie chodzi o liczbę żądań wysyłanych do serwera poprzez Ajax dla sprawdzenia postępu wysyłania pliku, więc jedyne co wystarczy zrobić to po prostu ustawić go mniej resrtykcyjnie. Najważniejszą sprawą jest abyśmy mieli włączoną obsługe rfc1867, można to zrobić dopisując na koniec php.ini, bądź jak to jest u mnie na serwerach w oddzielnym pliku apc.ini, linię:

apc.rfc1867 = on

Bardzo ważną rzeczą jest to, że apc nie potrafi kontrolować przesyłania wielu plików naraz, kontroluje jedynie pojedyńczy plik lub całą kolejkę(wszystkie pliki z formularza). Można to rozwiązać poprzez wysyłanie pliku w oddzielnych iframe'ach, tworzonych za pomocą JS.

Formularz

Niestety sam formualrz z możliwością wysyłania pliku, też mus przejść mały tuning abyśmy mogli wiedzieć ile ów danych zostało już przesłane do serwera. Do samego formularza musimy dodać ukryte pole, które musi mieć odpowiednią nazwe. Domyślnie jest to APC_UPLOAD_PROGRESS. Pole te musi mieć unikalna wartość dla każdego z transferów. Możemy wykorzystac do tego zmienną sesyjną, ale również z powodzeniem wartość funckji uniqid(), która zwróci nam unikalny ciąg znaków. W przykładzie będzie wykorzystana ta druga opcja. Ja w formularzu umieściłem cztery pola input typu file i ma on postać:

<form action="upload.php" method="POST" enctype="multipart/form-data" id="pliczki">
        <input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?=uniqid()?>" />
        <input type="file" name="plik_1" /><br />
        <input type="file" name="plik_2" /><br />
        <input type="file" name="plik_3" /><br />
        <input type="file" name="plik_4" /><br />
        <input type="submit" value="Ślij" />
</form>

Kilk osób na pewno zauważyło, że dla formularza oraz pola ukrytego nadałem identyfikatory, nie jest to puki co konieczne, jednakże bardzo ułatwi pracę z przykładową aplikacją. Nastepnym krokiem będzie przygotowanie skryptu php, który będzie nam zwracał ilość przesłanych danych. Zwrócimy, również sobie takie informacje jak czas trwania, pozostały czas oraz kilka innych informacji.

Kontrola uploadu - ilości przesłanych danych

Jeżeli chcemy dowiedzieć się na jakim etapie stoji nasz upload, musimy "zadać pytanie" modułowi APC o ilość otrzymanych danych. Dokonamy tego korzystając z funckji apc_fetch(), gdzie jako argument podamy identyfikator uploadu, który ma postać upload_naszunikalnyciagznakow. Wynik działania tej funckji, po paru obliczeniach zwrócimy sobie w postaci ciągu znaków JSON abyśmy dane te mogli wygodnie używać w JS. Funckja apc_fetch zwraca tablice z wartościami, jak w przykładzie:

array
  'total' => int 37825474
  'current' => int 41230
  'filename' => string 'tw4win_pro.exe' (length=14)
  'name' => string 'plik_1' (length=6)
  'done' => int 0
  'start_time' => float 1222799074.4788

Plik upload_progress.php będzie miał postać:

<?php
/**
 * Funckja przelicza ilośc bajtów na kilo bajty oraz na megabajty w zalezności od rozmiaru pliku
 *
 * @param integer $bytes
 * @return string
 */
function przelicz($bytes){
    
$kb=$bytes/1024;
    
$mb=$kb/1024;
    if(
$mb<1){
        return 
round($kb2).'KB';
    } else {
        return 
round($mb2).'MB';
    }
}

/**
 * Funckja przelicza ilość sekund na minuty
 *
 * @param integer $sek
 * @return string
 */
function czas($sek){
    
$s=$sek 60;
    
$m=(int)($sek/60);
    return 
$m.':'.(strlen($s)==1?'0'.$s:$s);
}

if(isset(
$_GET['progress_key'])) {
    
/* Wysłanie odpowiednich nagłówków do przeglądarki, aby nie zcachowała zawartości pliku */
     
header("Cache-Control: no-cache, must-revalidate");  
    
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
    
    
/* Pobranie stanu odebrania pliku */
      
$status apc_fetch('upload_'.$_GET['progress_key']);
      
      
/* Pobranie aktualnego czasu w formacie unixtimestamp, typ danych float */
     
$czas=microtime(true);
     
/* wyliczenie czasu trwania uploadu, predkości, postałego czasu oraz stosunku procentowego wysłanych danych */ 
     
$roznica=(int)($czas-$status['start_time']);
     
$predkosc=$status['current']/$roznica/1024;
     
$pozostalo=(int)(($status['total']-$status['current'])/$predkosc/1024);
     
$procentowo=round((100*$status['current']/$status['total']),2);
     
     
/* utworzenie ciągu znaków json oraz wyświetlenie go */
     
echo json_encode(array(
         
'nazwapliku'=>$status['filename'], //nazwa aktualnie wysyłanego pliku
         
'calkowity'=>przelicz($status['total']), //całkowity rozmiar kolejki w kb lub mb
         
'wyslano'=>przelicz($status['current']), //ilośc przesłanych danych przeliczona na kb lub mb
         
'czas'=>czas($roznica), //czas trwania tranzakcji
         
'pozostalo'=>czas($pozostalo), //przypuszczalny czas do konca
         
'predkosc'=>round($predkosc,2), //predkosc transferu wyrazona w KB/s
         
'procent'=>$procentowo//procent ilosci wyslanych danych
     
));
}
?>

Następnym krokiem będzie utworzenie warstwy o identyfikatorze progress, na której będzie wyświetlać postęp wysyłania plików. Przed wysłaniem naszego formularza warstwa ta będzie niewidoczna(będzie posiadać tabelkę do wyświetlania informacji o uploadzie), więc nasz plik index.php w całości będzie wyglądał mniej więcej tak:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="pl">
<head>
        <title>APC upload</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript" src="functions.js"></script>
</head>
<body>
<form action="upload.php" method="POST" enctype="multipart/form-data" id="pliczki">
        <input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?=uniqid()?>" />
        <input type="file" name="plik_1" /><br />
        <input type="file" name="plik_2" /><br />
        <input type="file" name="plik_3" /><br />
        <input type="file" name="plik_4" /><br />
        <input type="submit" value="Wyślij" />
</form>
        <div id="progress" style="display:none;">
                <table border="0">
                        <tr><th>Całkowity rozmiar kolejki</th><td id="cala">0</td></tr>
                        <tr><th>Ilość wysłanych danych</td><td id="wyslanych">0</td></tr>
                        <tr><th>Nazwa wysyłanego pliku</th><td id="nazwapliku">...</td></tr>
                        <tr><th>Prędkość wysyłania</th><td id="predkosc">0</td></tr>
                        <tr><th>Czas trwania</th><td id="trwa">0</td></tr>
                        <tr><th>Pozostały czas</th><td id="pozostaly">0</td></tr>
                </table>
        </div>
</body>
</html>

Jak widać do obsługi uploadu wykorzystamy biblioteke javascript - jquery, która jest załączona w przykładzie do tego artykułu. Drugi załączony plik to functions.js, w którym znajda się wszystkie potrzebne funckje związane z obsługa uploadu.

JavaScript

Pierwszą rzeczą jaka musimy zrobić to pobrać status poczatkowy transferu oraz wyświetlic warstwe progress. Zrobimy to w wyjątku submit formularza pliczki.

$("#pliczki").submit(function () {
        $("#pliczki").fadeOut("slow",function (){ $("#progress").fadeIn("slow"); } );
        setTimeout("checkprogress()", 700);
});

Teraz wystarczy napisac funckje, która będzie uaktualniać tabelkę z stanem wysyłania, czyli checkprogress(), która będziemy cyklicznie wywoływac za pomoca funkcji setTimeout(), jak widać w funkcji inicjującej również jej użyliśmy, ponieważ często-gęsto infomarcje o uploadzie nie są dostępne od razu po wciśnięciu przycisku wyślij. Chcemy uniknąć wyświetlania zafałszowanego wyniku. Tak będzie wyglądać nasz funkcja checkprogress():
function checkprogress(){
        //pobranie unikalnego idenfikatora
        var key=$("#progress_key").val();
       
        //pobranie i dekodowanie ciagu JSON
        $.getJSON("upload_progress.php?progress_key="+key, function (data) {
                //przepisanie wartosci
                $("td#cala").text(data.calkowity);
                $("td#wyslanych").text(data.wyslano+" ("+data.procent+"%)");
                $("td#nazwapliku").text(data.nazwapliku);
                $("td#predkosc").text(data.predkosc+" KB/s");
                $("td#trwa").text(data.czas);
                $("td#pozostaly").text(data.pozostalo);
        });
        setTimeout("checkprogress()", 500);
}

Jak widać w kodzie na samym końcu funckji mamy odwołanie o półsekundy samej do siebie, aby odswieżyć wartości. Czas ten można zmniejszyć lub zwiekszyć w zalezności od dysponowanego serwera. Na sam koniec zostało nam odebranie plików, będą one na nas czekac w pliku, do którego kieruje formularz, w moim wypadku jest to upload.php. Plik ten nic nie robi po za wyświetleniem podziękowań za upload plików i wyświetleniem ich nazw.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="pl">
<head>
        <title>APC upload</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript" src="functions.js"></script>
</head>
<body>
        <h2>Dziękuje za wgranie plików:</h2>
        <ul>
        <?php foreach($_FILES as $plik): ?>
                <li><?=$plik['name']?></li>    
        <?php endforeach; ?>
        </ul>
</body>
</html>

Podsumowanie

Alternatywą dla tego rozwiązania jest użycie niewidzialnego pliku swf, choćby korzystając z darmowego swfupload, którym też się zajmiemy. Nasze rozwiązanie, posiada swoje plusy i minusy. Na pewno dużym powodem, który przepowiada za, jest uniezalżnienie od Flash'a, jednak w serwisach, które mają dużą odwiedzalność, a nie mają za wydajnych serwerów, może to powodować problemy wydajnościowe, z powodu dużej ilości żądań wysyłanych do serwera. Innym minusem jest to, że większość infomacji o pliku i tak otrzymujemy po jego wysłaniu, więc nie bardzo możemy zablokować np. rozmiar jego, na co dla odmiany pozwala nam użycie flasha. Przykład mój można uzupełnic o takie rzeczy jak pasek postępu choćby, do czego serdecznie zachęcam.

ZałącznikWielkość
upload_progress-apc.zip19.43 KB
Your rating: Brak Ocena: 4.3 (3 votes)

hmm

Chciałem zaznaczyć że niestety nie działa :( zmienna 'upload_uid' zostaje zwrócona dopiero po zakończeniu przesyłania... nie udało mi się jej odczytać w trakcie przesyłania pliku. Live demo na http://mephir.net.pl/upload_progress-apc/ też nie działa...

Sprawdzałem działa

Sprawdzałem działa, z konfiguracją APC czasami bywają problemy, czesto lubi sie wykrzaczać, może masz js'a wyłączonego dlatego nie odświerzyło, ciężko mi powiedzieć, projekt z wykorzystaniem tego robiłem dla swojego partnera jednego, o tyle mi konfiguracja APC poszła gładko, o tyle oni na serwerach produkcyjnych nieźle się namęczyli by działało zawsze.

ort!wybacz, ale !ortografia

ort!
wybacz, ale !ortografia daje po oczach:
'żądanie', 'stoi'
polecam słownik!

z drugiej strony dobry artykuł.

pzdr.

Dodaj nową odpowiedź

Zawartość pola nie będzie udostępniana publicznie.
  • Adresy internetowe są automatycznie zamieniane w odnośniki, które można kliknąć.
  • Dozwolone znaczniki HTML: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Znaki końca linii i akapitu dodawane są automatycznie.
  • You can use the <go> tags just like the <a> for nicer urls.

Więcej informacji na temat formatowania