Статус исполнения php скрипта на сервере в реальном времени (true hack)

Discussion in 'PHP' started by ckpunmkug, 15 Jan 2019.

  1. ckpunmkug

    ckpunmkug Member

    Joined:
    20 Mar 2017
    Messages:
    72
    Likes Received:
    72
    Reputations:
    10
    Метод использует одно соединение, созданое XMLHttpRequest, для запуска скрипта и получения статуса его исполнения: скрипт выполнил часть - послал байт, ещё выполнил - снова послал байт. Браузер, по количеству полученный байт, вычисляет процент исполнения или уменьшает счётчик.

    Обычно, нормальные программисты используют следующую модель: запускают скрипт, которому говорят - не обращай внимания если от тебя отсоединятся. Скрипт работает и пишет в файл статистику исполнения. В браузере скрипт по таймауту обновляет страничку (делает джэйкваери к серверу) и получает статистику исполнения.

    Ненормальные программисты выбрасывают прокладки из своего кода и работают на прямую. Тот скрипт который был запущен будет передавать стату текущего исполнения в то же соединение которое его запустило. А браузер получать инфу об исполнении в тот момент когда она была отправлена.

    xak первый:
    Для того что бы посылать из скрипта в браузер по байту, нужно отключить буфер вывода php скрипту, из скрипта сделать это не возможно. По этому добавим конфиг виртуальному хосту апача.
    Code:
    <Directory /var/www/html/progress>
        <IfModule mod_php7.c>
            php_admin_value output_buffering "Off"
        </IfModule>
    </Directory>
    
    В /var/www/localhost/progress нужно положить следующие файлы:
    index.html
    Code:
    <html>
        <script>
    var job = {
        prepend: "job"
        ,id: Math.round(Math.random()*100000000).toString(10)
        ,cd: null
    };
    var span = null;
    
    function main(event) {
        span = document.getElementById("stats_span");
       
        var params = "job_prepend="+job.prepend+"&job_id="+job.id;
    
        var req = new XMLHttpRequest();
        req.addEventListener("progress", reqListener);
        req.open("GET", "worker.php?"+params);
        req.send();
    }
    
    function reqListener(event) {
        if (event.loaded == 1) {
            var re = new RegExp(job.prepend+job.id+"\=([0-9]+)");
            var mat = re.exec(document.cookie);
           
            if (typeof(mat[1]) == "string") 
                job.cd = parseInt(mat[1], 10);
        }
        if (job.cd !== null) {
            job.cd -= 1;
            span.innerText = job.cd;
        }
    }
    
    window.addEventListener('load', main);
        </script>
        <body>
    <span id="stats_span">-</span>
        </body>
    </html>
    
    worker.php
    Code:
    <?php
    if (!(@ctype_alpha($_GET['job_prepend']) && @ctype_digit($_GET['job_id']))) {
        user_error("wrong GET parameters");
        die;
    }
    
    $Job = new Job($_GET['job_prepend'], $_GET['job_id']);
    
    $array = ['1', '2', '3'];
    function countdown(string $value, int $key)
    {
        sleep(1);
    }
    
    $Job->foreach("countdown", $array);
    
    class Job
    {
        private $cookie_prepend = "";
        private $job_id = 0;
       
        public function __construct(string $cookie_prepend, int $job_id)
        {
            $this->cookie_prepend = $cookie_prepend;
            $this->job_id = $job_id;
        }
       
        public function foreach(string $funcname, array $array)
        {
            $this->delete_old_job_cookies();
            $count = count($array);
            setcookie("{$this->cookie_prepend}{$this->job_id}", $count);
           
            ob_implicit_flush();
            $resource = fopen("php://output", "w");
           
            foreach ($array as $key => $value) {
                $funcname($value, $key);
                fwrite($resource, " ", 1);
            }
           
            fclose($resource);
        }
       
        private function delete_old_job_cookies()
        {
            $pattern = '/^'.$this->cookie_prepend.'[0-9]+$/';
           
            foreach ($_COOKIE as $name => $value) {
                if (preg_match($pattern, $name) == 1) {
                    setcookie($name, "");
                }
            }
        }
    }
    
    xak второй:
    Пока не завершено соединение мы не можем извлечь полученные данные, но можем отследить факт их получения отлавливая событие progress. А с помощью куки можно передать сколько ждать порций данных.

    В целом, по теме всё. Жевать не стану. Заинтересованным читать исходники, там всё понятно написано.

    PS: Можно не нулить буфер в настройках, можно отсылать блок размером в буфер, эфект будет тот же.