JQuery читать поток AJAX постепенно?

Я прочитал этот вопрос, но он точно не отвечает на мой вопрос. К сожалению, похоже, что все изменилось в объекте XHR, так как я в последний раз смотрел на AJAX, поэтому больше не возможно напрямую обращаться к responseText до того, как он будет заполнен.

Мне нужно написать страницу, которая использует AJAX (желательно jQuery, но я открыт для предложений) для извлечения CSV-данных через HTTP с сервера, на котором я не контролирую. Данные ответа могут быть довольно большими; мегабайт текста не является чем-то необычным.

Сервер удобен для потока. Есть ли способ получить доступ к потоку данных, поскольку он возвращается, непосредственно из JavaScript?

У меня есть возможность написать некоторый PHP-код, который живет посередине, и использует какую-то технику "Comet" (long-poll, EventSource и т.д.), но я предпочел бы избежать этого, если это возможно.

В случае, если это имеет значение, предположите для этого вопроса, что пользователи имеют последнюю версию Firefox/Chrome/Opera и старую совместимость с браузером, не является проблемой.

Ответ 1

Для этого вам понадобится использовать javascript. Причина в том, что вы захотите провести постоянный опрос и не дожидаться срабатывания обратных вызовов. Для этого вам не нужен jQuery, это довольно просто. У них есть хороший исходный код для этого на веб-сайте Ajax Patterns.

По сути, вы просто хотите отслеживать свою последнюю позицию в ответе и периодически проводить опрос для получения большего количества текста из этого местоположения. Разница в вашем случае заключается в том, что вы можете подписаться на полное мероприятие и прекратить опрос.

Ответ 2

Это довольно просто при выводе текста или HTML. Ниже приведен пример.

(Если вы попытаетесь вывести JSON, вы столкнетесь с проблемами, которые я рассмотрю дальше.)

ФАЙЛ FILE

header('Content-type: text/html; charset=utf-8');
function output($val)
{
    echo $val;
    flush();
    ob_flush();
    usleep(500000);
}
output('Begin... (counting to 10)');
for( $i = 0 ; $i < 10 ; $i++ )
{
    output($i+1);
}
output('End...');

ФАЙЛ HTML

<!DOCTYPE>
<html>
    <head>
        <title>Flushed ajax test</title>
        <meta charset="UTF-8" />
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    </head>
    <body>
        <script type="text/javascript">
        var last_response_len = false;
        $.ajax('./flushed-ajax.php', {
            xhrFields: {
                onprogress: function(e)
                {
                    var this_response, response = e.currentTarget.response;
                    if(last_response_len === false)
                    {
                        this_response = response;
                        last_response_len = response.length;
                    }
                    else
                    {
                        this_response = response.substring(last_response_len);
                        last_response_len = response.length;
                    }
                    console.log(this_response);
                }
            }
        })
        .done(function(data)
        {
            console.log('Complete response = ' + data);
        })
        .fail(function(data)
        {
            console.log('Error: ', data);
        });
        console.log('Request Sent');
        </script>
    </body>
</html>

Что делать, если мне нужно сделать это с помощью JSON?

На самом деле невозможно загрузить один объект JSON инкрементально (до его полной загрузки), потому что до тех пор, пока у вас не будет полного объекта, синтаксис всегда будет недействительным.

Но если ваш ответ имеет несколько объектов JSON, один за другим, тогда можно загружать по одному, когда они спускаются по трубе.

Итак, я изменил свой код выше...

  • Изменение строки PHP FILE 4 от echo $val; до echo '{"name":"'.$val.'"};'. Это выводит ряд объектов JSON.

  • Изменение строки HTML FILE 24 от console.log(this_response); до

    this_response = JSON.parse(this_response);
    console.log(this_response.name);
    

    Обратите внимание, что этот рудиментарный код предполагает, что каждый "кусок", поступающий в браузер, является допустимым объектом JSON. Это не всегда так, потому что вы не можете предсказать, как будут поступать пакеты - вам может понадобиться разбить строку на основе полуколоней (или создать другой разделительный символ).

Не используйте application/json

НЕ ЗАМЕНЯЙТЕ заголовки application/json - я сделал это, и у меня был Google Going в течение 3 дней. Когда тип ответа application/json, браузер ждет, пока ответ не будет завершен, как в полном объеме. Затем полный ответ анализируется, чтобы проверить, не является ли он интактным JSON. Однако наш FULL-ответ {...};{...};{...};, который НЕ является допустимым JSON. Метод jqXHR.done предполагает, что произошла ошибка, поскольку полный ответ не может быть проанализирован как JSON.

Как указано в комментариях, вы можете отключить эту проверку на стороне клиента, используя:

$.ajax(..., {dataType: "text"})

Надеюсь, что некоторые люди посчитают это полезным.

Ответ 3

Использовать XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

http://code.google.com/p/xmlhttprequest

  • Предоставляет ненавязчивую стандартную (W3C) кросс-браузерную реализацию объекта XMLHttpRequest 1.0
  • Устраняет все особенности браузера, наблюдаемые в их собственных реализациях объектов XMLHttpRequest.
  • Включает прозрачную регистрацию активности объекта XMLHttpRequest.

Использовать длительный опрос с PHP:

output.php:

<?php
header('Content-type: application/octet-stream');

// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0) {
    // Get the curent level
    $level = ob_get_level();
    // End the buffering
    ob_end_clean();
    // If the current level has not changed, abort
    if (ob_get_level() == $level) break;
}
// Disable apache output buffering/compression
if (function_exists('apache_setenv')) {
    apache_setenv('no-gzip', '1');
    apache_setenv('dont-vary', '1');
}

// Count to 20, outputting each second
for ($i = 0;$i < 20; $i++) {
    echo $i.str_repeat(' ', 2048).PHP_EOL;
    flush();
    sleep(1);
}

run.php:

<script src="http://code.jquery.com/jquery-1.6.4.js"></script>
<script src="https://raw.github.com/ilinsky/xmlhttprequest/master/XMLHttpRequest.js"></script>

<script>
$(function() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/longpoll/', true);
    xhr.send(null);
    var timer;
    timer = window.setInterval(function() {
        if (xhr.readyState == XMLHttpRequest.DONE) {
            window.clearTimeout(timer);
            $('body').append('done <br />');
        }
        $('body').append('state: ' + xhr.readyState + '<br />');
        console.log(xhr.responseText);
        $('body').append('data: ' + xhr.responseText + '<br />');
    }, 1000);
});
</script>

Это должно выводить:

state: 3
data: 0
state: 3
data: 0 1
state: 3
data: 0 1 2
state: 3
data: 0 1 2 3
state: 3
data: 0 1 2 3 4
...
...
...
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
state: 3
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
done
state: 4
data: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 

Для IE вам нужно посмотреть в XDomainRequest

http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx

http://msdn.microsoft.com/en-us/library/cc288060(VS.85).aspx

Ответ 4

Поскольку вы говорите, что ваш сервер дружественен потоку (асинхронный) и ищет решение jquery, вы проверили плагин jQuery Stream?

Он очень прост в использовании и позволяет вам не беспокоиться о многом. Он имеет неплохую документацию.

Ответ 5

Вот простой способ добиться этого с помощью JQuery (по запросу OP):

Во-первых, расширьте объект ajax для поддержки onreadystatechange, выполнив приведенный ниже код из https://gist.github.com/chrishow/3023092 (добавляется в нижней части этого ответа), Затем просто вызовите ajax, используя функцию onreadystatechange, которая проверит xhr.responseText для нового текста.

Если вы хотите стать еще более привлекательным, вы можете очистить данные responseText каждый раз, когда вы его читаете, например, здесь).

Например, см. https://jsfiddle.net/g1jmwcmw/1/, который будет загружать ответ от https://code.jquery.com/jquery-1.5.js и вывести его в виде кусков внутри вашего окна консоли, используя приведенный ниже код (который вы можете просто скопировать на html-страницу, а затем открыть в своем браузере):

<!-- jquery >= 1.5. maybe earlier too but not sure -->
<script src=https://code.jquery.com/jquery-1.5.min.js></script>
<script>
/* One-time setup (run once before other code)
 *   adds onreadystatechange to $.ajax options
 *   from https://gist.github.com/chrishow/3023092)
 *   success etc will still fire if provided
 */
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
    if ( options.onreadystatechange ) {
        var xhrFactory = options.xhr;
        options.xhr = function() {
            var xhr = xhrFactory.apply( this, arguments );
            function handler() {
                options.onreadystatechange( xhr, jqXHR );
            }
            if ( xhr.addEventListener ) {
                xhr.addEventListener( "readystatechange", handler, false );
            } else {
                setTimeout( function() {
                    var internal = xhr.onreadystatechange;
                    if ( internal ) {
                        xhr.onreadystatechange = function() {
                            handler();
                            internal.apply( this, arguments ); 
                        };
                    }
                }, 0 );
            }
            return xhr;
        };
    }
});

// ----- myReadyStateChange(): this will do my incremental processing -----
var last_start = 0; // using global var for over-simplified example
function myReadyStateChange(xhr /*, jqxhr */) {
    if(xhr.readyState >= 3 && xhr.responseText.length > last_start) {
        var chunk = xhr.responseText.slice(last_start);
        alert('Got chunk: ' + chunk);
        console.log('Got chunk: ', chunk);
        last_start += chunk.length;
    }
}

// ----- call my url and process response incrementally -----
last_start = 0;
$.ajax({
  url: "https://code.jquery.com/jquery-1.5.js", // whatever your target url is goes here
  onreadystatechange: myReadyStateChange
});

</script>