Request: Fehler: Sie können keine Pipe ausführen, nachdem Daten aus der Antwort ausgegeben wurden.

Erstellt am 30. Apr. 2014  ·  29Kommentare  ·  Quelle: request/request

Ich stoße auf ein Problem, bei dem ich die Meldung "Sie können keine Pipe nach der Ausgabe von Daten aus der Antwort" erhalte. Fehler beim Versuch, eine Pipe an einen WriteStream zu senden. Dies tritt nur auf, wenn zwischen dem Aufrufen des request() Aufrufs und dem Aufruf des readStream.pipe(writeStream) Aufrufs eine Verzögerung aufgetreten ist.

Knotenversion: v0.10.24
Version anfordern: 2.34.0

Ich habe ein Github-Repo mit der Quelle erstellt, die das Problem reproduziert:
https://github.com/joe-spanning/request-error

Code ist auch unten der Einfachheit halber:

var fs = require('fs'),
  request = require('request');

var readStream = request({
  url: 'https://gist.githubusercontent.com/joe-spanning/6902070/raw/8967b351aec744a2bb51c16cb847c44636cf53d9/pipepromise.js'
});

// wait for 5 seconds, then pipe
setTimeout(function() {

  var writeStream = readStream.pipe(fs.createWriteStream('test.js'));

  writeStream.on('finish', function() {
    console.log('all done!');
  });

  writeStream.on('error', function(err) {
    console.error(err);
  });

}, 5000);

Möglicherweise müssen Sie mit dem Timeout-Wert spielen, um den Fehler zu reproduzieren. Längere Timeouts erhöhen die Wahrscheinlichkeit, dass der Fehler auftritt.

/request-error/node_modules/request/request.js:1299
      throw new Error("You cannot pipe after data has been emitted from the re
            ^
Error: You cannot pipe after data has been emitted from the response.
    at Request.pipe (/request-error/node_modules/request/request.js:1299:13)
    at null._onTimeout (/request-error/pipe-error.js:16:32)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

Hilfreichster Kommentar

@joepie91 Das ursprüngliche Beispiel oben zu nehmen und sofort zu einem PassThrough zu leiten, läuft ohne Probleme. Vielleicht verstehe ich das Thema falsch?

var fs = require('fs'),
  request = require('request');

var readStream = request({
  url: 'https://gist.githubusercontent.com/joe-spanning/6902070/raw/8967b351aec744a2bb51c16cb847c44636cf53d9/pipepromise.js'
})
.pipe(require('stream').PassThrough());  // this line is the only modification

// wait for 5 seconds, then pipe
setTimeout(function() {

  var writeStream = readStream.pipe(fs.createWriteStream('test.js'));

  writeStream.on('finish', function() {
    console.log('all done!');
  });

  writeStream.on('error', function(err) {
    console.error(err);
  });

}, 5000);

Alle 29 Kommentare

Sehen Sie dies auch unter ähnlichen Umständen.

Ich weiß nicht, welche anderen Auswirkungen es haben könnte, aber ich kann dieses Problem lösen, indem ich request.js wie folgt ändere:

Hinzufügen:

var superOn = Request.prototype.on;
Request.prototype.on = function (eventName) {
  if (eventName === "data") {
    this.resume()
  }
  superOn.apply(this, arguments)
}

Und ändern Sie den dataStream.on("data" Aufruf wie folgt:

dataStream.on("data", function (chunk) {
      var emitted = self.emit("data", chunk)
      if (emitted) {
        self._destdata = true
      } else {
        // pause URL stream until we pipe it
        dataStream.pause()
        dataStream.unshift(chunk)
      }
})

Das grundlegende Problem (glaube ich) ist, dass onResponse resume() aufruft und einen "Daten"-Listener anhängt, die beide den Datenfluss starten, aber wir sind möglicherweise noch nicht bereit, sie weiterzuleiten. Meine Lösung ist also, wenn wir versuchen, Daten auszugeben, aber noch keine Listener haben, pausieren wir den zugrunde liegenden Readstream, bis wir einen Daten-Listener angehängt bekommen.

Wäre gut, wenn jemand anderes diese Lösung testen und überprüfen könnte, ob sie funktioniert.

Nö, funktioniert bei mir nicht. Aufgrund der mangelnden Aktivität zu diesem Thema werde ich einfach aufhören, request und zum Node.js-Modul http .

Ich sollte erwähnen, dass mein Fix für die Anfrage 2.37.0 ist. Mit einer älteren Version würde es nicht funktionieren.

@aldeed Ich habe das Repository gegabelt und Ihren Fix angewendet: https://github.com/joepie91/request. Je nachdem, inwieweit dieses (ursprüngliche) Repository in Zukunft gepflegt wird, kann ich dort weitere Fixes einbinden. Ich bin noch nicht sicher.

Ich habe dieses Problem jetzt seit ein paar Stunden debuggt, konnte aber keine Lösung finden, bis ich diesen Thread gelesen habe. Es folgen einige weitere technische Hintergrundinformationen dazu, warum dieses Problem auftritt. Es ist ein bisschen vereinfacht, um es einfacher zu machen.

request bietet eine 'Streaming-API' in dem Sinne, dass Sie .pipe das ursprüngliche Anfrageobjekt an ein WritableStream . Da dieses Anfrageobjekt jedoch nicht der Antwortstream selbst ist, "shimt" request das data -Ereignis - es erstellt einen Listener für das data Ereignis im Antwortstream und sendet dieses Ereignis dann für sich selbst erneut aus, sodass das Anforderungsobjekt selbst als Stream fungieren kann.

Es gibt nur ein Problem: Sobald Sie an das Ereignis data eines Streams anhängen, beginnt es zu fließen .

Dies ist normalerweise kein Problem; Node.js arbeitet mit "Ticks" - betrachten Sie sie im Interpreter als "Zyklen". Die vereinfachte Erklärung ist, dass ein Codeblock einen "Tick" verbraucht und bis zum nächsten Tick keine Ereignisse überprüft oder ausgelöst werden. Dies bedeutet, dass der folgende hypothetische Code gut funktioniert:

stream = getSomeRequestStream();
stream.pipe(target);

Beide Zeilen werden im selben Tick ausgeführt, und das erste data Ereignis kann unmöglich bis zum nächsten Tick auftreten - so arbeitet der Interpreter intern. Das bedeutet, dass Sie eine Garantie dafür haben, dass .pipe _bevor_ das erste data Ereignis verarbeitet wird und alles wie erwartet funktioniert.

Was @joe-spanning nun versucht hat, war die Verwendung eines asynchronen Konstrukts, eines Timeouts in diesem speziellen Beispiel. Die Stream-Initialisierung und .pipe existieren nicht mehr im selben Codeblock.

Asynchrone Konstrukte folgen derselben Regel wie Ereignisse – sie können unmöglich bis zum nächsten Tick ausgeführt werden. Wenn Ihr asynchrones Konstrukt jedoch ausgeführt wird, ist der Ereigniszyklus gerade beendet und das erste data Ereignis ist _bereits_ ausgelöst - Ihr erster Datenblock kommt in der Lücke zwischen der Stream-Initialisierung und Ihrem .pipe . request erkennt, dass bereits ein data Ereignis ausgelöst wurde, und teilt Ihnen mit, dass Sie den Stream nicht mehr .pipe da er bereits zu fließen begonnen hat.

Das gleiche Problem tritt bei _anything_ auf, das mindestens einen Tick später als die Stream-Initialisierung passiert.

Die Lösung von @aldeed behebt dies, indem überprüft wird, ob dem anhält - wodurch der Fluss gestoppt wird. Sobald ein Listener angehängt ist, nimmt er den ursprünglichen Stream wieder auf, und diesmal wird ein data Ereignishandler lauschen.

Die Funktionsweise des data Event Shims in request ist eigentlich ein Konstruktionsfehler. Es berücksichtigt nicht die Möglichkeit, dass Stream-Initialisierung und Piping möglicherweise nicht im selben Tick auftreten und dass Node.js eine von Natur aus asynchrone Plattform ist (also sind solche Situationen zu erwarten). Obwohl dieser Fix funktioniert und zuverlässig funktionieren sollte, wäre eine bessere Lösung, die Bindung an das data Ereignis im ursprünglichen Stream zu verschieben, bis der erste Listener angehängt wird.

Eine letzte Anmerkung; Wenn Sie in Ihrer Anforderungsinitialisierung einen Rückruf definieren, funktioniert dieser Fix nicht.

Wenn ein Callback bereitgestellt wird, hängt request intern einen data Ereignishandler an sich selbst an, liest den Hauptteil aus und liefert den Hauptteil an Ihren Rückruf. Um dies zu umgehen, wird stattdessen das Ereignis response angehängt (und kein Rückruf angegeben). Dieses Ereignis wird (zuverlässig) ausgelöst, sobald eine Antwort eingeht und die Anfrage zum Streamen bereit ist.

@aldeed Ich habe stundenlang versucht, dies zu beheben, wobei alle meine Versuche, es tatsächlich zu beheben, fehlgeschlagen sind, und ich kann jetzt endlich weiter an meinem Ding arbeiten. Ich schulde dir ein Bier :)

Eine kurze Anmerkung zu diesem Problem im Zusammenhang mit dem Stream im neuen Stil und dem 3.0-Zweig.

Bei lesbaren Streams im neuen Stil werden sie in einem Zustand geöffnet, der als "pausiert und gepuffert" bezeichnet werden könnte. Sie geben keine Daten aus, bis sie read() entfernt sind.

Das bedeutet, dass wir die meisten nextTick()-Hacks aufgeben müssen. Ich habe mich in anderen Bibliotheken ein wenig damit beschäftigt und wir müssen auf das erste read() warten und dann den ganzen "faulen" Code auslösen.

Eine offensichtliche Konsequenz ist, dass in 3.0, wenn Sie einen Rückruf übergeben, der gesamte Inhalt gepuffert wird. Wenn Sie derzeit auf die Streaming-API zugreifen, wird die Pufferung deaktiviert, aber ohne den nextTick-Hack können wir dies nicht ableiten.

@mikael Würden Sie diesen Fix als Pull-Request für den aktuellen Master-Zweig akzeptieren?

Ich müsste es als PR sehen, um es zu bewerten.

In Ordung. Es ist wahrscheinlich am besten, die eigentliche Pull-Request-Erstellung @aldeed zu

Danke, @joepie91. @mikeal , soweit ich mich erinnere, verwendet die Art und Weise, wie ich den Code in 2.37.0 gelesen habe, bereits Streams im neuen Stil, sodass sie anfangs "angehalten und gepuffert" werden. Das Problem tritt auf, wenn Sie Ihren eigenen "Daten"-Listener anhängen und Resume aufrufen, was Sie an verschiedenen Stellen tun, da diese den Readstream anweisen, die Pause aufzuheben. Wie @joepie91 sagt, würde eine echte Lösung eine Neugestaltung der Interna erfordern, sodass Sie keinen "Daten"

Aus den Dokumenten :

In Node v0.10 wurde die unten beschriebene Readable-Klasse hinzugefügt. Aus Gründen der Abwärtskompatibilität mit älteren Node-Programmen wechseln lesbare Streams in den "Flowing-Modus", wenn ein 'data'-Ereignishandler hinzugefügt wird oder wenn die Methoden pause() oder restart() aufgerufen werden. Der Effekt ist, dass Sie sich keine Sorgen mehr um den Verlust von 'Daten'-Chunks machen müssen, selbst wenn Sie die neue Methode read() und das 'readable'-Ereignis nicht verwenden.

2.37 verwendet definitiv keine Streams im neuen Stil :) Sie müssen das Anforderungsobjekt an ein Ziel weiterleiten, bevor es mit der Ausgabe von Datenereignissen beginnt. In ähnlicher Weise müssen Sie mit demselben Tick, mit dem Sie sie erstellen, an eine Anforderungsinstanz weiterleiten, sonst schlagen ähnliche Tricks fehl (obwohl wir in einigen Fällen Code haben, um dies zu verringern, aber nicht in allen Fällen).

OK, ich habe wahrscheinlich falsch gelesen oder erinnere mich falsch oder ich habe mir auch den 3.0-Code angesehen und war verwirrt. :)

Die schnellste Lösung könnte darin bestehen, sofort an ein stream.PassThrough() Objekt weiterzuleiten
Sie sollten in der Lage sein, vom Passthrough-Objekt zu einem späteren Zeitpunkt ohne Probleme weiterzuleiten.
Siehe: http://nodejs.org/api/stream.html#stream_class_stream_passthrough

@ZJONSSON Das habe ich request und Ihr eigener Anhang zum request Objekt passieren nicht im selben Tick.

@joepie91 Das ursprüngliche Beispiel oben zu nehmen und sofort zu einem PassThrough zu leiten, läuft ohne Probleme. Vielleicht verstehe ich das Thema falsch?

var fs = require('fs'),
  request = require('request');

var readStream = request({
  url: 'https://gist.githubusercontent.com/joe-spanning/6902070/raw/8967b351aec744a2bb51c16cb847c44636cf53d9/pipepromise.js'
})
.pipe(require('stream').PassThrough());  // this line is the only modification

// wait for 5 seconds, then pipe
setTimeout(function() {

  var writeStream = readStream.pipe(fs.createWriteStream('test.js'));

  writeStream.on('finish', function() {
    console.log('all done!');
  });

  writeStream.on('error', function(err) {
    console.error(err);
  });

}, 5000);

Ich habe ehrlich gesagt keine Ahnung. Wenn ich mich richtig erinnere, habe ich genau das versucht, aber es hat nicht geholfen. Hat sich der zugrundeliegende Code in request vielleicht inzwischen geändert?

Ich sollte hinzufügen, dass das von Ihnen geänderte Beispiel tatsächlich das von @joe-spanning ist. Ich bin mir nicht sicher, ob es einen Unterschied zwischen diesem und meinem Code geben könnte, der es nur in seinem Fall funktioniert.

Eingereichte PR #1098

Wenn ein Callback bereitgestellt wird, hängt die Anforderung intern einen Datenereignishandler an sich selbst an, liest den Hauptteil aus und liefert den Hauptteil an Ihren Rückruf. Um dies zu umgehen, hängen Sie stattdessen > an das Antwortereignis an (und geben Sie keinen Rückruf an). Dieses Ereignis wird (zuverlässig) ausgelöst, sobald eine Antwort eingeht und die Anfrage zum Streamen bereit ist.

Ich wurde gerade von request(url, mycb).pipe(mywstream) gebissen
manchmal wird mycb aufgerufen, manchmal nicht.
Vielleicht sollte die Anfrage warnen, dass die Verwendung von pipe() mit einem bereits festgelegten Rückruf vermieden werden sollte?

Ich habe das gleiche Problem, außer dass ich diesen Fehler erhalte, wenn ich versuche, .pipe in .on('response') :

req = request(options)
req.on 'response', (response) ->
  # get info from headers here
  stream = createWriteStream file
  req.pipe stream

Update von meiner Seite, ich bin mir nicht sicher, warum ich das nicht schon früher gepostet habe.

Da dieses Problem nicht rechtzeitig behoben wurde und auf eine Reihe anderer frustrierender Fehler in request stößt, habe ich vor einiger Zeit meine eigene HTTP-Client-Bibliothek geschrieben, die diesen Anwendungsfall korrekt behandelt; bhttp . Es löst auch die Streams2-Supportprobleme.

Gibt es dazu ein Update?

Ich weiß, dass dieses Problem alt ist, aber in meinem Fall (ähnlich wie @dogancelik , eine Antwort bedingt basierend auf den

const req = request.get(URL);
req.on('response', (response) => {
    response.pause();
    // Do stuff with response.statusCode etc.
    setTimeout(() => {
        response
        .pipe(process.stdout)
        ;
    }, 2000);
});

dh den Antwortstream anhalten ( request setzt den Stream anscheinend automatisch in den Fließmodus?) und das Antwortobjekt selbst (nicht das req )

Ich würde mich über Feedback von Benutzern freuen, die diesbezüglich besser informiert sind als ich.

Komme gerade auch darauf... Ich habe es von meiner Seite gelöst, da es in meinem Fall ein logischer Fehler war.

Ich habe ein globales Array, in dem ich jede Anfrage über eine eindeutige ID speichere. Ich führe mehrere Anfragen gleichzeitig aus. Ich habe eine Abbruchoption für die Anfragen. Dies kann Ihnen weitere Informationen geben;

unzipper = function() {
    var self = this;

    /**
     * Properties
     */
    this.req = [];
    this.aborted = [];
    /* */

    /**
     * Methods
     */
    this.download = function(id, onProgress, onSuccess, onError, onAbort){

        var newinstanceAbort = {
          id: id,
          abort: false
        };

        this.aborted.push(newinstanceAbort);

        var url = '...';
        var target = '...';

        var request = require('request');
        var j = request.jar();
        var progress = require('request-progress');
        var cookie = request.cookie(app.session.sessionName + '=' + app.session.sessionId);
        j.setCookie(cookie, url);

        var downloadRequest = {
          id: id,
          request: request({url: url, jar: j})
        };

        this.req.push(downloadRequest);

        var instanceRequest = app.findObject(this.req, id);
        var instanceAbort = app.findObject(this.aborted, id);

        progress(instanceRequest.request, {
            throttle: 10,
        })
        .on('progress', function (state) {
            var progress = Math.round(state.percent * 100);
            onProgress(progress);
        })
        .on('error', function (err) {
            onError(err);
        })
        .on('end', function (e) {
            if(instanceAbort.abort) {
                onAbort();
            } else {
                onSuccess();
            }

            /**
             * Remove the unique request object from the req to complete the request
             *
             * If this is not done, the request would throw "You cannot pipe after data has been emitted from the response" as it is constantly re-emitting the same request
             */
            self.complete(id);

        })
        .pipe(app.fs.createWriteStream(target));
    };



    this.abort = function(id){
        var instanceAbort = app.findObject(this.aborted, id);
        var instanceRequest = app.findObject(this.req, id);

        instanceRequest.request.abort();
        instanceAbort.abort = true;
    };

    this.complete = function(id){
        var index = this.req.map(function(x){ return x.id; }).indexOf(id);

        this.req.splice(index, 1);
    };
    /* */

};

@julien-c Guter Aufruf, dies scheint tatsächlich daran zu liegen, dass das Binden eines response Listeners dazu führt, dass er automatisch in den "fließenden Modus" wechselt. Ich kann bestätigen, dass es sowohl in [email protected] als auch in [email protected] passiert.

Ich bin mir nicht sicher, ob dies beabsichtigt ist oder nicht (mein Verständnis war, dass dies nicht so funktionieren sollte). Im Moment plane ich, den Workaround zu verwenden, den Stream zu pausieren (ich werde versuchen und daran denken, hier zu posten, wenn ich unterwegs etwas Neues herausfinde).

Ich erlebe zufällig dasselbe wie dogancelik im obigen Kommentar : Der Fehler tritt auf, wenn eine Pipe innerhalb des Antworthandlers erfolgt. Aber wie kann das möglich sein? Wenn Sie sich request.js ansehen, wird das Ereignis "response" ausgegeben, BEVOR der eigene "data"-Handler der Anfrage (der die Probleme verursacht) überhaupt angehängt wird:

self.emit('response', response)

self.dests.forEach(function (dest) {
  self.pipeDest(dest)
})

responseContent.on('data', function (chunk) {
  if (self.timing && !self.responseStarted) {
    self.responseStartTime = (new Date()).getTime()

    // NOTE: responseStartTime is deprecated in favor of .timings
    response.responseStartTime = self.responseStartTime
  }
  self._destdata = true
  self.emit('data', chunk)
})

Da die Ereignishandler synchron aufgerufen werden, erwarte ich, dass die Pipe angehängt wird, bevor Daten verbraucht werden.

Ich bekomme den gleichen Fehler mit folgendem Code:

let rp = require('request-promise');
let options = {uri: 'http://a-url-pointing-online-pdf-link'};

koaRouter.get('/api/url/download', async(ctx, next) => {
        try {
            ctx.set('Content-disposition', 'attachment;filename=a.pdf');
            ctx.body = rp(options);  
        } catch (e) {
            ctx.body = e;
        }
    })   

Und ich arbeite um die ID herum, indem ich PassThrough , aber ich weiß nicht, warum der Fehler aufgetreten ist, und ich weiß auch nicht, warum passthrough gelöst wird. Mein gesamter Code soll im selben 'Tick' ausgeführt werden.

let rp = require('request-promise');
let stream = require('stream');
let options = {uri: 'http://a-url-pointing-online-pdf-link'};

koaRouter.get('/api/url/download', async(ctx, next) => {
        try {
            ctx.set('Content-disposition', 'attachment;filename=a.pdf');
            let pass = stream.PassThrough();
            rp(options).pipe(pass);
            ctx.body = pass;
        } catch (e) {
            ctx.body = e;
        }
    })   

Der ähnliche Fehler existiert auch für das Request-Objekt. Wenn Sie write dazu gemacht haben, können Sie nicht pipe tun.

Ich habe auch auf Kern-http/https-Module für Streams umgestellt. Es ist einfach und zuverlässig. request wird also für Streams nicht benötigt.

Keine telefonische Antwort passiertdas funktioniert bei mir gut
` var request = require('request');
var fs = erfordern('fs');
var-Pfad = require('Pfad');

downloadFile(file_url , targetPath){
// Variable speichern, um den Fortschritt zu erfahren

var _self = this;
var received_bytes = 0;
var total_bytes = 0;

var req = request({
    method: 'GET',
    uri: file_url
});

var out = fs.createWriteStream(targetPath);
**///////////////MOVE THIS LINE  FROM HERE TO ///////////////////

/////////////// BE INSIDE req.on('Antwort', Funktion ( Daten )///////
//////////////////////////erf.pipe(out);/////// ////////// ///////////////**

req.on('response', function ( data ) {
    **### req.pipe(out);**
    // Change the total bytes value to get progress later.
    total_bytes = parseInt(data.headers['content-length' ]);
});

req.on('data', function(chunk) {
    // Update the received bytes
    received_bytes += chunk.length;

    _self.showProgress(received_bytes, total_bytes);
    var percentage = (received_bytes * 100) / total_bytes;
    console.log(percentage + "% | " + received_bytes + " bytes out of " + total_bytes + " bytes.");

});

req.on('end', function() {
    alert("File succesfully downloaded");
});

}`

Request befindet sich jetzt im Wartungsmodus und führt keine neuen Funktionen zusammen. Weitere Informationen finden Sie unter #3142.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen