Request: Erro: você não pode enviar um pipe após os dados terem sido emitidos da resposta.

Criado em 30 abr. 2014  ·  29Comentários  ·  Fonte: request/request

Estou encontrando um problema em que obtenho a mensagem "Você não pode canalizar depois que os dados foram emitidos da resposta". erro ao tentar canalizar para um WriteStream. Isso só ocorre quando há um atraso entre o momento em que a chamada request() foi feita e quando a chamada readStream.pipe(writeStream) foi feita.

Versão do nó: v0.10.24
Versão do pedido: 2.34.0

Criei um repositório github com a fonte que reproduz o problema:
https://github.com/joe-spanning/request-error

O código também está abaixo para sua conveniência:

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

Pode ser necessário brincar com o valor do tempo limite para reproduzir o erro. Tempos limites mais longos aumentam a probabilidade de ocorrência do erro.

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

Comentários muito úteis

@ joepie91 Pegando o exemplo original no topo e canalizando imediatamente para um PassThrough funciona sem problemas. Talvez eu esteja entendendo mal o problema?

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 comentários

Também vendo isso em circunstâncias semelhantes.

Não sei que outras implicações isso pode ter, mas posso resolver esse problema alterando request.js seguinte maneira:

Adicionar:

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

E mude a chamada dataStream.on("data" para este:

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

O problema básico (eu acho) é que onResponse está chamando resume() e anexando um ouvinte de "dados", ambos os quais iniciam o fluxo de dados, mas podemos não estar prontos para canalizá-lo ainda. Portanto, minha solução é se tentarmos emitir dados, mas ainda não tivermos nenhum ouvinte, pausaremos o fluxo de leitura subjacente até obter um ouvinte de dados anexado.

Seria bom se outra pessoa pudesse testar esta solução e verificar se ela funciona.

Nah, não funciona para mim. Devido à falta de atividade neste assunto, vou simplesmente parar de usar request e voltar ao módulo Node.js http .

Devo mencionar que minha correção é para a solicitação 2.37.0. Não funcionaria com uma versão mais antiga.

@aldeed Eu https://github.com/joepie91/request. Dependendo do grau em que este repositório (original) é mantido no futuro, posso mesclar mais correções nele. Eu ainda não tenho certeza.

Eu estava depurando esse problema por algumas horas, mas não consegui encontrar uma solução até ler este tópico. Seguem mais algumas informações técnicas básicas sobre por que esse problema está ocorrendo. É um pouco simplificado para facilitar o acompanhamento.

request oferece uma 'API de streaming' no sentido de que você pode .pipe o objeto de solicitação original para um WritableStream . No entanto, uma vez que este objeto de solicitação não é o próprio fluxo de resposta, request 'corrige' o evento data - ele cria um ouvinte para o evento data no fluxo de resposta, e em seguida, reemite esse evento para si mesmo, podendo, assim, fazer com que o objeto de solicitação atue como um fluxo em si.

Só há um problema: assim que você se anexa ao evento data de um stream, ele começa a fluir .

Agora, isso normalmente não é um problema; Node.js funciona com 'ticks' - considere-os como 'ciclos' no interpretador. A explicação simplificada é que um bloco de código consumirá um 'tique' e nenhum evento será verificado ou disparado até o próximo tique. Isso significa que o seguinte código hipotético funcionará bem:

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

Ambas as linhas são executadas no mesmo tique, e o primeiro evento data não pode ocorrer até o próximo tique - é assim que o interpretador funciona internamente. Isso significa que você tem a garantia de que .pipe será processado _antes_ do primeiro data evento ser acionado e que tudo funcionará conforme o esperado.

Agora, o que @joe-spanning tentou fazer foi usar uma construção assíncrona, um tempo limite neste exemplo específico. A inicialização do stream e .pipe não existem mais no mesmo bloco de código.

Construções assíncronas seguem a mesma regra dos eventos - elas não podem ser executadas até o próximo tique. No entanto, no momento em que sua construção assíncrona é executada, o ciclo do evento acaba de terminar e o primeiro data evento é _already_ disparado - seu primeiro pedaço de dados vem na lacuna entre a inicialização do stream e seu .pipe . request detecta que um evento data já foi disparado e informa que você não pode mais .pipe o fluxo, pois ele já começou a fluir.

Este mesmo problema ocorre para _qualquer coisa_ que acontece pelo menos um tique depois da inicialização do fluxo.

A solução de @aldeed corrige isso verificando se há ouvintes anexados ao objeto de solicitação e, se não, colocando o bloco de dados de volta no fluxo original e pausando esse fluxo original - impedindo-o de fluir. Depois que um ouvinte é anexado, ele então retoma o fluxo original e, desta vez, haverá um manipulador de eventos data escutando.

A maneira como o shim de evento data funciona em request é na verdade uma falha de design. Ele não leva em consideração a possibilidade de que a inicialização do stream e o piping não ocorram no mesmo tick e que o Node.js seja uma plataforma inerentemente assíncrona (portanto, esse tipo de situação é esperado). Embora essa correção funcione e deva funcionar de maneira confiável, uma solução melhor seria adiar a vinculação ao evento data no fluxo original, até que o primeiro ouvinte seja anexado.

Uma última nota; se você definir um retorno de chamada na inicialização da solicitação, essa correção não funcionará.

Internamente, se um retorno de chamada for fornecido, request anexará um manipulador de eventos data a si mesmo, lerá o corpo e fornecerá o corpo ao seu retorno de chamada. A maneira de contornar isso é anexar ao evento response (e não especificar um retorno de chamada). Este evento será disparado (de forma confiável) assim que uma resposta for recebida e a solicitação estiver pronta para streaming.

@aldeed Estou tentando consertar isso há horas, com todas as minhas tentativas de consertar falhando, e agora posso finalmente continuar trabalhando no meu projeto. Eu lhe devo uma cerveja :)

Uma nota rápida sobre esse problema no que se refere ao fluxo de novo estilo e ao branch 3.0.

Com fluxos legíveis de novo estilo, eles são abertos no que poderia ser considerado um estado de "pausa e armazenamento em buffer", eles não deixarão os dados saírem até que sejam read() de.

Isso significa que teremos que abandonar a maioria dos hacks nextTick (). Lidei um pouco com isso em outras bibliotecas e o que teremos que fazer é esperar pelo primeiro read () e então acionar todo o código "preguiçoso".

Uma consequência óbvia é que no 3.0, se você passar um callback, todo o conteúdo será armazenado em buffer. Atualmente, se você acessar a API de streaming, ela desativa o armazenamento em buffer, mas sem o hack nextTick não podemos inferir isso.

@mikael Você aceitaria essa correção como uma solicitação de pull para o branch master atual?

Eu preciso ver isso como um PR para poder avaliá-lo.

Tudo bem. Provavelmente, é melhor deixar a criação da solicitação de pull real para @aldeed , visto que ele contribuiu com a correção original - fazer a fusão com o repositório principal provavelmente deve ser sua escolha, em termos de licenciamento e tudo mais.

Obrigado, @ joepie91. @mikeal , se bem me lembro, da maneira como li o código em 2.37.0, ele já está usando streams de novo estilo, então eles são "pausados ​​e armazenados em buffer", como você disse, inicialmente. O problema surge quando você anexa seu próprio ouvinte de "dados" e o currículo de chamada, o que você faz em lugares diferentes, porque eles dizem ao fluxo de leitura para retomar a si mesmo. Portanto, como diz @ joepie91 , uma solução verdadeira exigiria a reengenharia dos internos de modo que você não conecte um ouvinte de "dados".

Dos documentos :

No Node v0.10, a classe Legível descrita abaixo foi adicionada. Para compatibilidade com programas Node mais antigos, os fluxos legíveis mudam para o "modo de fluxo" quando um manipulador de eventos de 'dados' é adicionado ou quando os métodos pause () ou resume () são chamados. O efeito é que, mesmo que você não esteja usando o novo método read () e o evento 'legível', não precisa mais se preocupar em perder pedaços de 'dados'.

2.37 definitivamente não está usando fluxos de novo estilo :) Você deve canalizar o objeto de solicitação para um destino antes que ele comece a emitir eventos de dados. Da mesma forma, você deve canalizar para uma instância de solicitação no mesmo tick em que a criou, ou então truques semelhantes falharão (embora tenhamos algum código para tentar mitigar isso em alguns casos, mas não todos).

OK, provavelmente eu li errado ou estou me lembrando mal ou também olhei o código 3.0 e fiquei confuso. :)

A solução mais rápida pode ser canalizar imediatamente para um objeto stream.PassThrough()
Você deve ser capaz de canalizar a partir do objeto de passagem a qualquer momento posterior sem problemas.
Veja: http://nodejs.org/api/stream.html#stream_class_stream_passthrough

@ZJONSSON Eu tentei isso em algum momento, mas também não funcionou. Pelo que eu entendi, está sujeito à mesma limitação (sem reemissão de dados emitidos antes de um anexo de fluxo posterior) como qualquer outro tipo de fluxo, então você ainda estaria preso se o anexo original por request e seu próprio anexo ao objeto request não acontecem no mesmo tick.

@ joepie91 Pegando o exemplo original no topo e canalizando imediatamente para um PassThrough funciona sem problemas. Talvez eu esteja entendendo mal o problema?

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

Eu honestamente não tenho idéia. Se bem me lembro, foi exatamente o que tentei, e não adiantou. O código subjacente em request talvez tenha mudado nesse meio tempo?

Devo acrescentar que o exemplo que você modificou é, na verdade, @joe-spanning. Não tenho certeza se pode haver uma diferença entre isso e meu código que faz com que ele funcione apenas no caso dele.

PR enviado # 1098

Internamente, se um retorno de chamada for fornecido, a solicitação anexará um manipulador de eventos de dados a si mesma, lerá o corpo e fornecerá o corpo ao seu retorno de chamada. A maneira de contornar isso é anexar ao evento de resposta em vez de> (e não especificar um retorno de chamada). Este evento será disparado (de forma confiável) assim que uma resposta for recebida e a solicitação estiver pronta para streaming.

Acabei de ser mordido por request(url, mycb).pipe(mywstream)
às vezes mycb é chamado, às vezes não.
Talvez a solicitação deva alertar que o uso de pipe() com um retorno de chamada já definido deve ser evitado?

Tenho o mesmo problema, exceto que recebo este erro quando tento .pipe em .on('response') :

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

Atualização do meu lado, não sei por que não postei isso antes.

Como esse problema não foi resolvido em tempo hábil e passou por vários outros bugs frustrantes em request , escrevi minha própria biblioteca de cliente HTTP há algum tempo que lida corretamente com este caso de uso; bhttp . Ele também resolve os problemas de suporte do Streams2.

Existe uma atualização sobre isso?

Sei que esse problema é antigo, mas no meu caso (semelhante a @dogancelik , canalizando uma resposta condicionalmente com base nos cabeçalhos de resposta), consegui fazer o seguinte:

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

ou seja, pausar o fluxo de resposta ( request aparentemente define o fluxo para o modo de fluxo automaticamente?) e canalizar o próprio objeto de resposta (não o req ).

Eu gostaria de receber comentários de usuários com mais conhecimento do que eu sobre isso.

Acabei de descobrir isso também ... Resolvi do meu lado, pois era um erro lógico no meu caso.

Eu tenho uma matriz global onde armazeno cada solicitação por meio de um id único. Estou executando várias solicitações simultaneamente. Eu tenho uma opção de abortar para as solicitações. Isso pode lhe dar mais informações;

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 Boa chamada, realmente parece ser porque vincular um ouvinte response faz com que ele mude para o "modo contínuo" automaticamente. Posso verificar que isso está acontecendo tanto em [email protected] quanto em [email protected] .

Não tenho certeza se isso é intencional ou não (meu entendimento era que não era assim que deveria funcionar). Por enquanto, planejarei usar a solução alternativa de pausar o stream (tentarei e lembrarei de postar novamente aqui se descobrir alguma coisa nova ao longo do caminho)

Estou experimentando aleatoriamente o mesmo, como dogancelik no comentário acima: O erro ocorre ao canalizar dentro do manipulador de resposta. Mas como isso pode ser possível? Se você olhar para request.js, o evento "response" é emitido ANTES do próprio manipulador de "dados" da solicitação (que causa o problema) ser anexado:

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 os manipuladores de eventos são chamados de forma síncrona, espero que o pipe seja conectado antes que os dados sejam consumidos.

Eu recebo o mesmo erro com o seguinte 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;
        }
    })   

E eu trabalho uma id totalmente usando PassThrough , mas não sei por que o erro surge, e também não sei por que o passthrough resolve. Todo o meu código deve ser executado no mesmo 'tique'.

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

O bug semelhante também existe para o objeto de solicitação. Se você fez write com ele, não pode pipe com ele.

Eu também mudei para módulos http / https centrais para streams. É fácil e confiável. Portanto, request não é necessário para streams.

Não canalize a resposta do tel aconteceuisso funciona bem comigo
`var request = require ('request');
var fs = requer ('fs');
var path = require ('path');

downloadFile (file_url, targetPath) {
// Salve a variável para saber o progresso

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 DENTRO req.on ('resposta', função (dados) ///////
////////////////////////////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");
});

} `

O Request está agora em modo de manutenção e não combinará nenhum novo recurso. Consulte # 3142 para obter mais informações.

Esta página foi útil?
0 / 5 - 0 avaliações