Request: Error: no puede canalizar después de que se hayan emitido datos de la respuesta.

Creado en 30 abr. 2014  ·  29Comentarios  ·  Fuente: request/request

Me encuentro con un problema en el que obtendré el mensaje "No se puede canalizar después de que se hayan emitido datos de la respuesta". Error al intentar canalizar a WriteStream. Esto solo ocurre cuando ha habido una demora entre el momento en que se realizó la llamada request() y el momento en que se realizó la llamada readStream.pipe(writeStream) .

Versión de nodo: v0.10.24
Solicitar versión: 2.34.0

Creé un repositorio de github con la fuente que reproduce el problema:
https://github.com/joe-spanning/request-error

El código también se encuentra a continuación para mayor comodidad:

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);

Es posible que deba jugar con el valor de tiempo de espera para reproducir el error. Los tiempos de espera más prolongados aumentan la probabilidad de que se produzca el error.

/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)

Comentario más útil

@ joepie91 Tomando el ejemplo original en la parte superior y canalizando inmediatamente a un PassThrough se ejecuta sin problemas. ¿Quizás estoy entendiendo mal el tema?

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);

Todos 29 comentarios

También viendo esto en circunstancias similares.

No sé qué otras implicaciones podría tener, pero puedo resolver este problema modificando request.js siguiente manera:

Agregar:

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

Y cambie la llamada dataStream.on("data" a esto:

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)
      }
})

El problema básico (creo) es que onResponse está llamando a resume() y adjuntando un oyente de "datos", los cuales inician el flujo de datos, pero es posible que no estemos listos para canalizarlo todavía. Entonces, mi solución es que si intentamos emitir datos pero aún no tenemos oyentes, pausamos el flujo de lectura subyacente hasta que se adjunte un oyente de datos.

Sería bueno si alguien más pudiera probar esta solución y verificar que funciona.

No, no funciona para mí. Debido a la falta de actividad en este tema, simplemente dejaré de usar request y volveré al módulo Node.js http .

Debo mencionar que mi solución es para la solicitud 2.37.0. No funcionaría con una versión anterior.

@aldeed He bifurcado el repositorio y apliqué su corrección: https://github.com/joepie91/request. Dependiendo del grado en que se mantenga este repositorio (original) en el futuro, podría fusionar más arreglos allí. Todavia no estoy seguro.

Había estado depurando este problema durante algunas horas, pero no pude encontrar una solución hasta que leí este hilo. A continuación, se ofrece información de antecedentes más técnica sobre el motivo de este problema. Está un poco simplificado para facilitar el seguimiento.

request ofrece una 'API de transmisión' en el sentido de que puede .pipe el objeto de solicitud original en un WritableStream . Sin embargo, dado que este objeto de solicitud no es el flujo de respuesta en sí mismo, request 'calza' el evento data : crea un oyente para el evento data en el flujo de respuesta, y luego vuelve a emitir este evento sobre sí mismo, pudiendo así hacer que el objeto de solicitud actúe como un flujo en sí mismo.

Solo hay un problema: tan pronto como se adjunta al evento data de una transmisión, comienza a fluir .

Ahora bien, esto normalmente no es un problema; Node.js funciona con 'ticks'; considérelos 'ciclos' en el intérprete. La explicación simplificada es que un bloque de código consumirá un 'tick' y no se comprobará ni activará ningún evento hasta el siguiente tick. Esto significa que el siguiente código hipotético funcionará bien:

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

Ambas líneas se ejecutan en el mismo tick, y el primer evento data no puede ocurrir hasta el siguiente tick; así es como funciona el intérprete internamente. Esto significa que tiene la garantía de que el .pipe se procesa _antes_ de que se active el primer evento data , y todo funcionará como se esperaba.

Ahora, lo que @ joe-spanning intentó hacer fue usar una construcción asincrónica, un tiempo de espera en este ejemplo en particular. La inicialización de la secuencia y .pipe ya no existen en el mismo bloque de código.

Las construcciones asincrónicas siguen la misma regla que los eventos: no es posible que se ejecuten hasta el siguiente tick. Sin embargo, para cuando se ejecuta su construcción asincrónica, el ciclo de eventos acaba de finalizar y el primer evento data se ha disparado: su primer fragmento de datos llega en el espacio entre la inicialización del flujo y su .pipe . request detecta que un evento data ya se ha disparado y le dice que ya no puede .pipe la transmisión porque ya ha comenzado a fluir.

Este mismo problema ocurre con _cualquier cosa_ que suceda al menos un tic después de la inicialización del flujo.

La solución de @aldeed corrige esto verificando si hay oyentes adjuntos al objeto de solicitud y, si no es así, volviendo a colocar el fragmento de datos en el flujo original y pausando ese flujo original, evitando así que fluya. Una vez que se adjunta un oyente, luego reanuda la transmisión original, y esta vez habrá un controlador de eventos data escuchando.

La forma en que funciona la corrección de evento data en request es en realidad un defecto de diseño. No tiene en cuenta la posibilidad de que la inicialización del flujo y la canalización no ocurran en el mismo tick, y que Node.js sea una plataforma inherentemente asincrónica (por lo tanto, este tipo de situaciones son de esperar). Si bien esta solución funciona y debería funcionar de manera confiable, una mejor solución sería diferir el enlace al evento data en la transmisión original, hasta que se adjunte el primer oyente.

Una última nota; si define una devolución de llamada en la inicialización de su solicitud, esta solución no funcionará.

Internamente, si se proporciona una devolución de llamada, request adjuntará un controlador de eventos data a sí mismo, leerá el cuerpo y proporcionará el cuerpo a su devolución de llamada. La forma de evitar esto es adjuntar al evento response lugar (y no especificar una devolución de llamada). Este evento se activará (de manera confiable) tan pronto como se reciba una respuesta y la solicitud esté lista para la transmisión.

@aldeed He estado tratando de arreglar esto durante horas, con todos mis intentos de solucionarlo fallando, y ahora finalmente puedo continuar trabajando en lo mío. Te debo una cerveza :)

Una nota rápida sobre este problema en lo que respecta a la transmisión de estilo nuevo y la rama 3.0.

Con flujos legibles de nuevo estilo, se abren en lo que podría considerarse un estado de "pausa y almacenamiento en búfer", no dejarán salir datos hasta que estén read() de.

Esto significa que tendremos que deshacernos de la mayoría de los hacks de nextTick (). Me he ocupado de esto un poco en otras bibliotecas y lo que tendremos que hacer es esperar la primera lectura () y luego activar todo el código "perezoso".

Una consecuencia obvia es que en 3.0, si pasa una devolución de llamada, se almacenará en búfer todo el contenido. Actualmente, si accede a la API de transmisión, deshabilita el almacenamiento en búfer, pero sin el truco de nextTick no podemos inferir eso.

@mikael ¿Aceptarías esta corrección como una solicitud de extracción para la rama maestra actual?

Necesitaría verlo como un PR para poder evaluarlo.

Bien. Probablemente sea mejor dejar la creación de la solicitud de extracción real en manos de

Gracias, @ joepie91. @mikeal , según recuerdo, de la forma en que leí el código en 2.37.0, ya está usando flujos de nuevo estilo, por lo que están "pausados ​​y almacenados en búfer", como usted dice, inicialmente. El problema surge cuando adjunta su propio oyente de "datos" y llama a resume, lo que hace en diferentes lugares, porque estos le dicen a la secuencia de lectura que se reanude. Entonces, como dice @ joepie91 , una verdadera solución requeriría rediseñar los

De los documentos :

En Node v0.10, se agregó la clase Readable que se describe a continuación. Para compatibilidad con versiones anteriores de los programas de Node más antiguos, los flujos legibles cambian al "modo de flujo" cuando se agrega un controlador de eventos de 'datos' o cuando se llaman a los métodos pause () o resume (). El efecto es que, incluso si no está utilizando el nuevo método read () y el evento 'legible', ya no tiene que preocuparse por perder fragmentos de 'datos'.

2.37 definitivamente no está usando flujos de nuevo estilo :) Debe canalizar el objeto de solicitud a un destino antes de que comience a emitir eventos de datos. Del mismo modo, debe canalizar a una instancia de solicitud en el mismo tick que la crea o, de lo contrario, fallarán trucos similares (aunque tenemos algún código para intentar mitigar esto en algunos casos, pero no en todos).

De acuerdo, probablemente leí mal o no recuerdo bien o miré el código 3.0 también y me confundí. :)

La solución más rápida podría ser canalizar inmediatamente a un objeto stream.PassThrough()
Debería poder canalizar desde el objeto de paso a través en cualquier momento posterior sin problemas.
Ver: http://nodejs.org/api/stream.html#stream_class_stream_passthrough

@ZJONSSON Lo intenté en algún momento, pero tampoco funcionó. Por lo que tengo entendido, es propenso a la misma limitación (sin reemisión de datos emitidos antes de un archivo adjunto de transmisión posterior) que cualquier otro tipo de transmisión, por lo que aún estaría atascado si el archivo adjunto original por request y su propio adjunto al objeto request no ocurren en el mismo tick.

@ joepie91 Tomando el ejemplo original en la parte superior y canalizando inmediatamente a un PassThrough se ejecuta sin problemas. ¿Quizás estoy entendiendo mal el tema?

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);

Honestamente, no tengo idea. Si mal no recuerdo, eso es exactamente lo que intenté y no ayudó. ¿Ha cambiado el código subyacente en request mientras tanto?

Debo agregar que el ejemplo que modificó es en realidad el de @ joe-spanning. No estoy seguro de si puede haber una diferencia entre eso y mi código que hace que solo funcione en su caso.

Presentado PR # 1098

Internamente, si se proporciona una devolución de llamada, la solicitud adjuntará un controlador de eventos de datos a sí misma, leerá el cuerpo y proporcionará el cuerpo a su devolución de llamada. La forma de evitar esto es adjuntar al evento de respuesta en su lugar> (y no especificar una devolución de llamada). Este evento se activará (de manera confiable) tan pronto como se reciba una respuesta y la solicitud esté lista para la transmisión.

Me acaba de morder request(url, mycb).pipe(mywstream)
a veces se llama mycb, a veces no.
¿Quizás la solicitud debería advertir que se debe evitar el uso de pipe() con una devolución de llamada ya configurada?

Tengo el mismo problema, excepto que obtengo este error cuando intento .pipe en .on('response') :

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

Actualización de mi lado, no estoy seguro de por qué no publiqué esto antes.

Debido a que este problema no se resuelve a tiempo y se encuentra con una serie de errores frustrantes en request , escribí mi propia biblioteca de cliente HTTP hace un tiempo que se ocupa correctamente de este caso de uso; bhttp . También resuelve los problemas de soporte de Streams2.

¿Hay alguna actualización sobre esto?

Sé que este problema es antiguo, pero en mi caso (similar a @dogancelik , canalizando una respuesta condicionalmente basada en los encabezados de respuesta), pude hacer lo siguiente:

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

es decir, pausar el flujo de respuesta ( request aparentemente establece el flujo en modo de flujo automáticamente?) y canalizar el objeto de respuesta en sí (no el req ).

Agradecería los comentarios de los usuarios con más conocimientos que yo sobre esto.

Simplemente encontré esto también ... Lo resolví desde mi final, ya que era un error lógico en mi caso.

Tengo una matriz global donde almaceno cada solicitud a través de una identificación única. Estoy ejecutando varias solicitudes simultáneamente. Tengo una opción de cancelación para las solicitudes. Esto puede brindarle más información;

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 Buena llamada, esto de hecho parece deberse a que vincular un oyente response hace que cambie al "modo de flujo" automáticamente. Puedo verificar que está sucediendo tanto en [email protected] como en [email protected] .

No estoy seguro de si esto es lo que se pretendía o no (tenía entendido que no era así como se suponía que debía funcionar). Por ahora, planeo usar la solución alternativa de pausar la transmisión (intentaré y recordar publicar aquí si descubro algo nuevo en el camino)

Estoy experimentando aleatoriamente lo mismo que dogancelik en el comentario anterior: el error ocurre cuando se canaliza dentro del controlador de respuesta. Pero, ¿cómo puede ser esto posible? Si observa request.js, el evento "response" se emite ANTES de que se adjunte el propio controlador de "datos" de la solicitud (que causa el problema):

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)
})

Como los controladores de eventos se llaman sincrónicamente, espero que la tubería se adjunte antes de que se consuman los datos.

Obtengo el mismo error con el siguiente código:

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;
        }
    })   

Y trabajo un ID de rount usando PassThrough , pero no sé por qué aparece el error, y tampoco sé por qué se resuelve el passthrough . Se supone que todo mi código se ejecuta en el mismo 'tick'.

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;
        }
    })   

El error similar también existe para el objeto de solicitud. Si le hiciste write , no puedes pipe hacerlo.

También cambié a los módulos centrales http / https para las transmisiones. Es fácil y confiable. Por lo tanto, request no es necesario para las transmisiones.

No se produjo una respuesta telefónica por canalizaciónesto funciona bien para mi
`var request = require ('solicitud');
var fs = require ('fs');
var ruta = require ('ruta');

downloadFile (file_url, targetPath) {
// Guardar variable para conocer el progreso

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

/////////////// ESTAR DENTRO req.on ('respuesta', función (datos) ///////
//////////////////////////req.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");
});

} `

La solicitud está ahora en modo de mantenimiento y no fusionará ninguna función nueva. Consulte el número 3142 para obtener más información.

¿Fue útil esta página
0 / 5 - 0 calificaciones