jQuery AJAX-Stream inkrementell lesen?


78

Ich habe diese Frage gelesen , aber sie beantwortet meine Frage nicht genau. Leider sieht es so aus, als hätten sich die Dinge im XHR-Objekt geändert, seit ich AJAX das letzte Mal angesehen habe. Daher ist es nicht mehr möglich, direkt darauf zuzugreifen, responseTextbevor das Auffüllen abgeschlossen ist.

Ich muss eine Seite schreiben, die AJAX verwendet (vorzugsweise jQuery, aber ich bin offen für Vorschläge), um CSV-Daten über HTTP von einem Server abzurufen, über den ich keine Kontrolle habe. Die Antwortdaten können sehr groß sein. Ein Megabyte Text ist keine Seltenheit.

Der Server ist streamfreundlich. Gibt es noch eine Möglichkeit, direkt aus JavaScript auf einen Datenstrom zuzugreifen, wenn dieser zurückgegeben wird?

Ich habe die Möglichkeit, PHP-Code zu schreiben, der in der Mitte lebt und eine Art "Comet" -Technologie verwendet (Long-Polling, EventSource usw.), aber ich würde es vorziehen, dies nach Möglichkeit zu vermeiden.

Nehmen Sie für diese Frage an, dass Benutzer über die neueste Version von Firefox / Chrome / Opera verfügen und die Kompatibilität mit alten Browsern kein Problem darstellt.


Ich weiß, dass dies beantwortet wurde, ich habe so etwas schon einmal gemacht, schau es dir an, reiß
MrJD

Antworten:


21

Sie werden dafür direkt Javascript verwenden wollen. Der Grund dafür ist, dass Sie kontinuierlich abfragen und nicht darauf warten möchten, dass die Rückrufe ausgelöst werden. Sie brauchen dafür kein jQuery, es ist ziemlich einfach. Sie haben einen schönen Quellcode dafür auf der Ajax Patterns-Website .

Im Wesentlichen möchten Sie nur Ihre letzte Position in der Antwort verfolgen und regelmäßig nach mehr Text über diesen Speicherort hinaus abfragen. Der Unterschied in Ihrem Fall besteht darin, dass Sie das gesamte Ereignis abonnieren und Ihre Umfrage beenden können.


3
Können Sie mich auf ein funktionierendes Beispiel verweisen? Der von Ihnen angegebene Link lautet: "Die responseText-Eigenschaft von XMLHttpRequest enthält immer den Inhalt, der aus dem Server gelöscht wurde, auch wenn die Verbindung noch offen ist." .. und nach dem, was ich gelesen habe, ist dies in neueren Browsern nicht mehr der Fall.
Josh

Ist das nicht nur im IE? Ich dachte, readyState 3 enthält es in anderen Browsern.
Scottheckel

1
In erster Linie habe ich mich an den HINWEIS in diesem jquery-Plugin gehalten: plugins.jquery.com/project/ajax-http-stream ' HINWEIS : Es ist mir aufgefallen , dass dies ab Firefox 3.0.11 nicht mehr funktioniert (funktioniert in 3.0. 8 unter Linux), IE8 oder der neuesten Version von Chrome. Anscheinend besteht der Trend darin, den Zugriff auf xmlhttprequest.responseText zu verbieten, bevor die Anforderung abgeschlossen ist (dumm imo). Tut mir leid, ich kann nichts tun, um das zu beheben '
Josh

Es stellt sich heraus, dass dies tatsächlich mit direktem Javascript funktioniert, nachdem Sie es nur ausprobiert haben (zumindest mit Browsern, die sich richtig verhalten). Ich hoffe immer noch, eine jquery-Version zu finden, damit sie in allen Browsern korrekt funktioniert, aber im Moment ist dies doch die beste Antwort.
Josh

tote Links machen mich traurig
captncraig

77

Dies ist bei der Ausgabe von Text oder HTML recht einfach . Unten ist ein Beispiel.

(Sie werden jedoch auf Probleme stoßen, wenn Sie versuchen, JSON auszugeben , die ich weiter unten angehen werde.)

PHP-DATEI

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-DATEI

<!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>

Was ist, wenn ich dies mit JSON tun muss?

Es ist nicht möglich, ein einzelnes JSON-Objekt inkrementell zu laden (bevor es vollständig geladen ist), da die Syntax immer ungültig ist, bis Sie das vollständige Objekt haben.

Wenn Ihre Antwort jedoch mehrere JSON-Objekte nacheinander enthält, können Sie eines nach dem anderen laden, sobald sie die Pipe durchlaufen.

Also habe ich meinen Code oben um ...

  1. Ändern von PHP FILE Zeile 4 von echo $val;nach echo '{"name":"'.$val.'"};'. Dies gibt eine Reihe von JSON-Objekten aus.

  2. Ändern der HTML-DATEI-Zeile 24 von console.log(this_response);nach

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

    Beachten Sie, dass dieser rudimentäre Code davon ausgeht, dass jeder "Brocken", der in den Browser gelangt, ein gültiges JSON-Objekt ist. Dies ist nicht immer der Fall, da Sie nicht vorhersagen können, wie Pakete ankommen werden. Möglicherweise müssen Sie die Zeichenfolge anhand von Semikolons aufteilen (oder ein anderes Trennzeichen erstellen).

Nicht benutzen application/json

Do NOT Für Änderung Ihrer Header application/json- Ich tat dies , und es hatte mich für 3 Tage googeln. Wenn der Antworttyp lautet application/json, wartet der Browser, bis die Antwort vollständig ist. Die vollständige Antwort wird dann analysiert, um zu überprüfen, ob es sich um infaktes JSON handelt. Unsere vollständige Antwort lautet {...};{...};{...};jedoch NICHT JSON. Die jqXHR.doneMethode geht davon aus, dass ein Fehler aufgetreten ist, da die vollständige Antwort nicht als JSON analysiert werden kann.

Wie in den Kommentaren erwähnt, können Sie diese Prüfung auf der Clientseite deaktivieren, indem Sie Folgendes verwenden:

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

Hoffe, einige Leute finden das nützlich.


1
Wow, danke, Sir, genau das habe ich gesucht! Sehr gutes Beispiel für die Verwendung dieser Technik mit JSON.
Aaron

3
Vielen Dank, ich habe 1 Minute gebraucht, um erfolgreich zu implementieren. Tolles Zeug.
Pål Thingbø

1
Rufen Sie $ .ajax mit {dataType: "text"} auf, dies verhindert das intelligente Erraten (siehe api.jquery.com/jquery.ajax dataType )
Christophe Quintard

1
Ja, Sie können JSON mithilfe eines Streaming-JSON-Parsers wie oboe ( oboejs.com ) schrittweise lesen . Sie müssen Ihre JSON-Antwort nicht ändern, um mehrere JSON-Objekte zu haben, und es ist wahrscheinlich besser, dies nicht aus gestalterischer Sicht zu
tun

1
Ein Hinweis zu PHP: Es ist im Allgemeinen eine schlechte Praxis, einen JSON manuell auf PHP-Seite zu erstellen, indem Zeichenfolgen (z. B. echo '{"name":"'.$val.'"};') verkettet werden . Ein besserer Code könnte sein echo json_encode(["name"=>$val]).";";.
Laef

34

Verwenden Sie XMLHttpRequest.js

https://github.com/ilinsky/xmlhttprequest

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

  • Bietet eine unauffällige standardkonforme (W3C) Cross-Browser-Implementierung des XMLHttpRequest 1.0-Objekts
  • Behebt ALLE Browser-Macken, die in ihren nativen XMLHttpRequest-Objektimplementierungen festgestellt wurden
  • Aktiviert die transparente Protokollierung der XMLHttpRequest-Objektaktivität

So verwenden Sie lange Abfragen mit 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>

Dies sollte Folgendes ausgeben:

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 

Für IE müssen Sie in XDomainRequest schauen

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


Dies scheint Readystate 3 nicht zu unterstützen, auch nicht in Chrome :(
Josh

1
@ Josh, ja, das tut es. Aber es gibt verschiedene Macken mit langen Umfragen. Sie müssen 2 KB Daten senden, bevor sich der Lesestatus ändert, und den Inhaltstyp auf festlegen application/octet-stream. In meinem aktualisierten Beitrag finden Sie ein PHP-Beispiel.
Petah

Ich werde sehen, was ich damit machen kann. Es scheint unvermeidlich, dass ich PHP in der Mitte haben muss, da ich den Inhaltstyp der ursprünglichen Antwort nicht kontrollieren kann. Aber ich möchte auch wirklich IE6 / 7 unterstützen können (leider) ...
Josh

1
@xorinzor pastebin.com/3Dbt2mhQ Je nach Ihnen allerdings braucht, könnte man ein eigenes Protokoll implementieren müssen. So lesen Sie alle Daten bis a ;.
Petah

3
@ Bakalash, da einige Browser das Streaming erst zulassen, wenn 2 KB Ausgabe gesendet wurden.
Petah

16

Haben Sie das jQuery Stream Plugin überprüft, da Sie sagen, Ihr Server ist streamfreundlich (asynchron) und nach einer jquery-Lösung gesucht hat ?

Es ist wirklich einfach zu bedienen und ermöglicht es Ihnen, sich über nichts wirklich Sorgen zu machen. Es hat ziemlich gut Dokumentation .


Ich kann mir das auf jeden Fall ansehen. Auf einem kurzen Blick auf die API-Seite sehe ich keine Möglichkeit, HTTP-POST- und Basisauthentifizierungsinformationen an den Server zu senden, aber ich bin sicher, dass sie irgendwo vorhanden sein müssen. Vielleicht war "Stream-freundlich" auch die falsche Wahl des Begriffs. Ich meine nicht asynchron oder bidirektional. Ich meinte, dass es im Laufe der Zeit eine große Datenmenge in einem Stream zurücksendet, wie eine gigantische HTTP-Antwort. In der Zwischenzeit habe ich auch eine Non-JQuery-Lösung gefunden, die für meine ursprünglichen Zwecke "gut genug" sein sollte.
Josh

Gut für http-Post und grundlegende Authentifizierung, würden Sie sowieso gerade jquery verwenden.
g19fanatic

Und wie integriere ich "Straight JQuery Anyways" in das JQuery Stream Plugin? Die Dokumente sind in diesem Punkt unklar. Hast du ein Beispiel?
Josh

5
+1 Es ist jetzt ein Portal und sieht wirklich fantastisch aus, einschließlich WebSockets und allem. github.com/flowersinthesand/portal
Marsbard

1
@marsbard Portal hat sein Lebensende erreicht und wird nicht mehr gewartet! Verwenden Sie Vibe .
Donghwan Kim

0

Hier ist eine einfache Möglichkeit, dies mit JQuery zu erreichen (wie vom OP angefordert):

Erweitern Sie zunächst das Ajax-Objekt, um onreadystatechange zu unterstützen, indem Sie den folgenden Code unter https://gist.github.com/chrishow/3023092 ausführen (am Ende dieser Antwort angehängt). Rufen Sie dann einfach Ajax mit einer Funktion onreadystatechange auf, die xhr.responseText auf neuen Text überprüft.

Wenn Sie noch schicker werden möchten, können Sie die responseText-Daten jedes Mal löschen, wenn Sie sie lesen (wie hier beschrieben ).

Siehe beispielsweise https://jsfiddle.net/g1jmwcmw/1/ , in dem die Antwort von https://code.jquery.com/jquery-1.5.js heruntergeladen und in Blöcken in Ihrem Konsolenfenster ausgegeben wird Code unten (den Sie einfach in eine HTML-Seite kopieren und dann in Ihrem Browser öffnen können):

<!-- 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>

OP hier. Die Frage wurde vor 6 Jahren gestellt. Ist das etwas, das 2011/2012 funktioniert hätte? Ich arbeite nicht mehr an diesem Projekt, daher kann ich Ihre Antwort nicht testen.
Josh

Ja, es funktioniert gut mit jquery 1.5 (Januar 2011, code.jquery.com/jquery-1.5.min.js ). Als Beispiel können Sie einfach den obigen Code ausschneiden / einfügen.
Mwag

Ich muss dir nur vertrauen. Ich habe Ihren genauen Code in mehreren Browsern ausgeführt und die gesamte Antwort war in einem "Block", sodass nichts wirklich bewiesen wurde. Ich habe keine Zeit, weiter damit herumzuspielen.
Josh

Sie sollten es sehen können. Ich habe das oben Gesagte wörtlich in einer test.html-Datei gespeichert und in Chrome geöffnet. Das Konsolenfenster zeigte die empfangene Antwort in zwei Abschnitten an.
Mwag

0

Ich musste ein Grid mit einer großen JSON-Nutzlast versorgen, die immer wieder die maximal zulässige Größenbeschränkung erreichte. Ich habe MVC und jquery verwendet und daher die obige Lösung von AlexMorley-Finch angepasst.

Der Servercode stammt aus "Streaming-Daten mit Web-API" . Auch https://github.com/DblV/StreamingWebApi .

public class StreamingController : ApiController
{

    [HttpGet]
    [ActionName("GetGridDataStream")]
    public HttpResponseMessage GetGridDataStream(string id)
    {
        var response = Request.CreateResponse();
        DynamicData newData = new DynamicData();
        var res = newData.GetDataRows(id);
        response.Content = new PushStreamContent((stream, content, context) =>
        { 
            foreach (var record in res)
            {
                var serializer = new JsonSerializer();
                using (var writer = new StreamWriter(stream))
                {
                    serializer.Serialize(writer, record);
                    stream.Flush();
                }

               // Thread.Sleep(100);
            }

            stream.Close();
        });

        return response;
    }
}

Dadurch wurde ein Stream von {json object} {json object} {json object} erstellt, bei dem Kommas und umgebende [] begrenzt werden mussten, um erfolgreich als json analysiert zu werden.

Dem Client-Code wurden die fehlenden Zeichen folgendermaßen geliefert:

 var jsonData = {}; 

 $.ajax("api/Streaming/GetGridDataStream/" + viewName, {
    xhrFields: {
            onprogress: function (e) { 
                // console.log(this_response);
            }
        }
    }, { dataType: "text" }) //<== this is important for JSON data
    .done(function (data) { 

        data = "[" + data.replace(/\}\{/gi, "},{") + "]";

        jsonData["DataList"] = JSON.parse(data);
        //more code follows to create grid
    })
    .fail(function (data) {
        console.log('Error: ', data);
    });

Ich hoffe, dies hilft jemandem, der .Net MVC und jQuery verwendet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.