Request: Kesalahan: Anda tidak dapat mem-pipe setelah data dipancarkan dari respons.

Dibuat pada 30 Apr 2014  ·  29Komentar  ·  Sumber: request/request

Saya mengalami masalah di mana saya akan mendapatkan "Anda tidak dapat menyalurkan setelah data dipancarkan dari respons." kesalahan saat mencoba menyalurkan ke WriteStream. Ini hanya terjadi bila ada penundaan antara saat panggilan request() dilakukan, dan saat panggilan readStream.pipe(writeStream) dilakukan.

Versi simpul: v0.10.24
Versi permintaan: 2.34.0

Saya membuat repo github dengan sumber yang mereproduksi masalah:
https://github.com/joe-spanning/request-error

Kode juga di bawah ini untuk kenyamanan:

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

Anda mungkin perlu bermain dengan nilai batas waktu untuk mereproduksi kesalahan. Waktu tunggu yang lebih lama meningkatkan kemungkinan kesalahan akan terjadi.

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

Komentar yang paling membantu

@joepie91 Mengambil contoh asli di bagian atas dan langsung menyalurkan ke PassThrough berjalan tanpa masalah. Mungkin saya salah paham tentang masalah ini?

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

Semua 29 komentar

Juga melihat ini dalam situasi yang sama.

Saya tidak tahu implikasi lain apa yang mungkin terjadi, tetapi saya dapat menyelesaikan masalah ini dengan mengubah request.js sebagai berikut:

Menambahkan:

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

Dan ubah panggilan dataStream.on("data" menjadi ini:

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

Masalah dasarnya (saya pikir) adalah bahwa onResponse memanggil resume() dan melampirkan pendengar "data", keduanya memulai aliran data, tetapi kita mungkin belum siap untuk menyalurkannya. Jadi solusi saya adalah jika kami mencoba memancarkan data tetapi kami belum memiliki pendengar, kami menjeda aliran baca yang mendasarinya hingga kami mendapatkan pendengar data yang terpasang.

Akan lebih baik jika orang lain dapat menguji solusi ini dan memverifikasi bahwa itu berhasil.

Nah, tidak bekerja untuk saya. Karena kurangnya aktivitas pada masalah ini, saya hanya akan berhenti menggunakan request dan kembali ke modul Node.js http .

Saya harus menyebutkan bahwa perbaikan saya adalah untuk permintaan 2.37.0. Itu tidak akan berfungsi dengan rilis yang lebih lama.

@aldeed Saya telah melakukan fork repositori dan menerapkan perbaikan Anda: https://github.com/joepie91/request. Bergantung pada sejauh mana repositori (asli) ini dipertahankan di masa mendatang, saya mungkin menggabungkan lebih banyak perbaikan ke sana. Saya belum yakin.

Saya telah men-debug masalah ini selama beberapa jam sekarang, tetapi tidak dapat menemukan solusi sampai saya membaca utas ini. Beberapa informasi latar belakang teknis lainnya tentang mengapa masalah ini terjadi berikut ini. Ini sedikit disederhanakan, untuk membuatnya lebih mudah untuk diikuti.

request menawarkan 'streaming API' dalam arti bahwa Anda dapat .pipe objek permintaan asli ke WritableStream . Namun, karena objek permintaan ini bukan aliran respons itu sendiri, request 'mengguncang' acara data - objek tersebut membuat pendengar untuk acara data pada aliran respons, dan kemudian memancarkan kembali acara ini dengan sendirinya, sehingga dapat membuat objek permintaan bertindak sebagai aliran itu sendiri.

Hanya ada satu masalah: segera setelah Anda melampirkan data acara aliran, itu mulai mengalir .

Sekarang ini biasanya tidak menjadi masalah; Node.js bekerja dengan 'kutu' - anggap mereka 'siklus' dalam penerjemah. Penjelasan yang disederhanakan adalah bahwa satu blok kode akan menggunakan 'centang', dan tidak ada peristiwa yang akan diperiksa atau diaktifkan hingga centang berikutnya. Ini berarti bahwa kode hipotetis berikut akan berfungsi dengan baik:

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

Kedua baris dieksekusi di centang yang sama, dan peristiwa data pertama tidak mungkin terjadi sampai centang berikutnya - begitulah cara kerja penerjemah secara internal. Ini berarti Anda memiliki jaminan bahwa .pipe diproses _sebelum_ peristiwa data pertama diaktifkan, dan semuanya akan berfungsi seperti yang diharapkan.

Sekarang, apa yang @joe-spanning coba lakukan adalah menggunakan konstruksi asinkron, batas waktu dalam contoh khusus ini. Inisialisasi aliran dan .pipe tidak lagi ada di blok kode yang sama.

Konstruksi asinkron mengikuti aturan yang sama dengan peristiwa - mereka tidak mungkin dijalankan hingga centang berikutnya. Namun, pada saat konstruksi asinkron Anda dijalankan, siklus peristiwa baru saja selesai dan peristiwa data pertama _sudah_ diaktifkan - potongan data pertama Anda muncul di celah antara inisialisasi aliran dan .pipe . request mendeteksi bahwa acara data telah diaktifkan, dan memberi tahu Anda bahwa Anda tidak dapat lagi .pipe aliran karena sudah mulai mengalir.

Masalah yang sama ini terjadi untuk _anything_ yang terjadi setidaknya satu centang lebih lambat dari inisialisasi aliran.

Solusi oleh @aldeed memperbaikinya dengan memeriksa apakah ada pendengar yang dilampirkan ke objek permintaan, dan jika tidak, mengembalikan potongan data ke aliran asli dan menjeda aliran asli itu - sehingga menghentikan alirannya. Setelah listener terpasang, ia melanjutkan streaming aslinya, dan kali ini akan ada event handler yang mendengarkan data .

Cara shim acara data bekerja di request sebenarnya adalah cacat desain. Itu tidak memperhitungkan kemungkinan bahwa inisialisasi aliran dan pemipaan mungkin tidak terjadi pada centang yang sama, dan bahwa Node.js adalah platform asinkron yang inheren (dengan demikian, situasi seperti ini diharapkan). Meskipun perbaikan ini berhasil, dan harus bekerja dengan andal, solusi yang lebih baik adalah menunda pengikatan ke acara data pada aliran asli, hingga pendengar pertama terpasang.

Satu catatan terakhir; jika Anda menentukan panggilan balik dalam inisialisasi permintaan Anda, perbaikan ini tidak akan berfungsi.

Secara internal, jika callback diberikan, request akan melampirkan event handler data ke dirinya sendiri, membacakan isi, dan memasok isi ke callback Anda. Cara mengatasinya, adalah dengan melampirkan ke acara response sebagai gantinya (dan tidak menentukan panggilan balik). Acara ini akan (dengan andal) diaktifkan segera setelah respons diterima dan permintaan siap untuk streaming.

@aldeed Saya telah mencoba untuk memperbaikinya selama berjam-jam, dengan semua upaya saya untuk benar-benar memperbaikinya gagal, dan sekarang saya akhirnya dapat melanjutkan pekerjaan saya. Aku berutang bir padamu :)

Catatan singkat tentang masalah ini terkait dengan aliran gaya baru dan cabang 3.0.

Dengan aliran gaya baru yang dapat dibaca, aliran tersebut dibuka dalam keadaan yang dapat dianggap sebagai status "dijeda & buffering", mereka tidak akan mengeluarkan data hingga read() berasal.

Ini berarti kita harus membuang sebagian besar peretasan nextTick(). Saya telah menangani ini sedikit di perpustakaan lain dan yang harus kita lakukan adalah menunggu read() pertama dan kemudian memicu semua kode "malas".

Salah satu konsekuensi yang jelas adalah bahwa di 3.0 jika Anda meneruskan panggilan balik, itu akan menyangga semua konten. Saat ini, jika Anda mengakses API streaming, itu menonaktifkan buffering tetapi tanpa peretasan nextTick, kami tidak dapat menyimpulkannya.

@mikael Maukah Anda menerima perbaikan ini sebagai permintaan tarik untuk cabang master saat ini?

Saya perlu melihatnya sebagai PR untuk mengevaluasinya.

Baik. Mungkin yang terbaik adalah membiarkan pembuatan permintaan tarik yang sebenarnya hingga @aldeed , mengingat bahwa ia berkontribusi pada perbaikan asli - menggabungkannya ke dalam repositori utama mungkin harus menjadi pilihannya, dari segi lisensi dan semua itu.

Terima kasih, @joepie91. @mikeal , seingat saya, cara saya membaca kode di 2.37.0, sudah menggunakan aliran gaya baru sehingga "dijeda dan disangga", seperti yang Anda katakan, pada awalnya. Masalahnya muncul ketika Anda melampirkan pendengar "data" Anda sendiri dan melanjutkan panggilan, yang Anda lakukan di tempat yang berbeda, karena ini memberi tahu readstream untuk membatalkan jeda itu sendiri. Jadi seperti yang dikatakan @ joepie91 , solusi yang benar akan membutuhkan rekayasa ulang internal sehingga Anda tidak melampirkan pendengar "data".

Dari dokumen :

Di Node v0.10, kelas Readable yang dijelaskan di bawah telah ditambahkan. Untuk kompatibilitas mundur dengan program Node yang lebih lama, aliran Readable beralih ke "mode mengalir" saat pengendali peristiwa 'data' ditambahkan, atau saat metode pause() atau resume() dipanggil. Efeknya adalah, bahkan jika Anda tidak menggunakan metode read() baru dan event 'readable', Anda tidak perlu lagi khawatir kehilangan potongan 'data'.

2.37 jelas tidak menggunakan aliran gaya baru :) Anda harus menyalurkan objek permintaan ke tujuan sebelum mulai memancarkan peristiwa data. Demikian pula, Anda harus menyalurkan ke instance permintaan di centang yang sama dengan yang Anda buat atau trik serupa akan gagal (walaupun kami memiliki beberapa kode untuk mencoba dan mengurangi ini dalam beberapa kasus, tetapi tidak semua).

Oke, saya mungkin salah membaca atau saya salah mengingat atau saya juga melihat kode 3.0, dan menjadi bingung. :)

Perbaikan tercepat mungkin adalah dengan segera menyalurkan ke objek stream.PassThrough()
Anda harus dapat menyalurkan dari objek passthrough di lain waktu tanpa masalah.
Lihat: http://nodejs.org/api/stream.html#stream_class_stream_passthrough

@ZJONSSON Saya memang mencobanya di beberapa titik, tetapi tidak berhasil juga. Sejauh yang saya mengerti, itu rentan terhadap batasan yang sama (tidak ada emisi ulang data yang dipancarkan sebelum lampiran aliran berikutnya) seperti jenis aliran lainnya, jadi Anda masih akan terjebak jika lampiran asli dengan request dan lampiran Anda sendiri ke objek request tidak terjadi di centang yang sama.

@joepie91 Mengambil contoh asli di bagian atas dan langsung menyalurkan ke PassThrough berjalan tanpa masalah. Mungkin saya salah paham tentang masalah ini?

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

Sejujurnya aku tidak tahu. Jika saya mengingatnya dengan benar, itulah yang saya coba, dan itu tidak membantu. Apakah kode yang mendasari di request mungkin berubah untuk sementara?

Saya harus menambahkan bahwa contoh yang Anda modifikasi sebenarnya adalah @joe-spanning. Saya tidak yakin apakah mungkin ada perbedaan antara itu dan kode saya yang membuatnya hanya berfungsi dalam kasusnya.

Dikirim PR #1098

Secara internal, jika panggilan balik diberikan, permintaan akan melampirkan pengendali peristiwa data ke dirinya sendiri, membacakan isi, dan memasok isi ke panggilan balik Anda. Cara mengatasinya, adalah dengan melampirkan ke acara respons sebagai gantinya > (dan tidak menentukan panggilan balik). Acara ini akan (dengan andal) diaktifkan segera setelah respons diterima dan permintaan siap untuk streaming.

Saya baru saja digigit request(url, mycb).pipe(mywstream)
terkadang mycb dipanggil, terkadang tidak.
Mungkin permintaan harus memperingatkan bahwa menggunakan pipe() dengan panggilan balik yang sudah disetel harus dihindari?

Saya memiliki masalah yang sama kecuali, saya mendapatkan kesalahan ini ketika saya mencoba .pipe di .on('response') :

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

Perbarui dari pihak saya, tidak yakin mengapa saya tidak memposting ini sebelumnya.

Karena masalah ini tidak diselesaikan tepat waktu dan menjalankan sejumlah bug frustasi lainnya di request , saya telah menulis pustaka klien HTTP saya sendiri beberapa waktu lalu yang menangani kasus penggunaan ini dengan benar; bhttp . Ini juga memecahkan masalah dukungan Streams2.

Apakah ada pembaruan tentang ini?

Saya tahu masalah ini sudah lama, tetapi dalam kasus saya (mirip dengan @dogancelik , menyalurkan respons secara kondisional berdasarkan header respons), saya dapat melakukan hal berikut:

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

yaitu, menjeda aliran respons ( request tampaknya menyetel aliran ke mode mengalir secara otomatis?) dan menyalurkan objek respons itu sendiri (bukan req ).

Saya akan menyambut umpan balik dari pengguna yang lebih berpengetahuan dari saya tentang ini.

Temukan ini juga ... Saya menyelesaikannya dari pihak saya karena itu adalah kesalahan logis dalam kasus saya.

Saya memiliki array global tempat saya menyimpan setiap permintaan melalui id unik. Saya menjalankan beberapa permintaan secara bersamaan. Saya memiliki opsi batalkan untuk permintaan. Ini mungkin memberi Anda lebih banyak info;

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 Panggilan bagus, ini memang tampaknya karena mengikat pendengar response menyebabkannya beralih ke "mode mengalir" secara otomatis. Saya dapat memverifikasi bahwa itu terjadi di [email protected] dan [email protected] .

Tidak yakin apakah ini dimaksudkan atau tidak (pemahaman saya adalah bahwa ini bukan cara kerjanya). Untuk saat ini, saya akan berencana menggunakan solusi untuk menjeda streaming (saya akan mencoba & ingat untuk memposting kembali ke sini jika saya menemukan sesuatu yang baru di sepanjang jalan)

Saya secara acak mengalami hal yang sama seperti dogancelik di komentar di atas: Kesalahan terjadi saat melakukan pemipaan di dalam penangan respons. Tapi bagaimana ini mungkin? Jika Anda melihat request.js, acara "respons" dikeluarkan SEBELUM penangan "data" permintaan sendiri (yang menyebabkan masalah) dilampirkan sama sekali:

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

Karena event handler dipanggil secara serempak, saya berharap pipa dilampirkan sebelum data dikonsumsi.

Saya mendapatkan kesalahan yang sama dengan kode berikut:

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

Dan saya mengerjakan id dengan menggunakan PassThrough , tetapi saya tidak tahu mengapa kesalahan itu muncul, dan saya juga tidak tahu mengapa passthrough dipecahkan. Semua kode saya seharusnya dieksekusi dalam 'centang' yang sama.

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

Bug serupa juga ada untuk objek permintaan. Jika Anda melakukan write untuk itu, Anda tidak dapat pipe untuk itu.

Saya juga beralih ke modul inti http/https untuk streaming. Hal ini mudah dan dapat diandalkan. Jadi request tidak diperlukan untuk streaming.

Jangan pipa respon tel terjadiini bekerja dengan baik dengan saya
`var permintaan = membutuhkan('permintaan');
var fs = membutuhkan('fs');
var jalur = membutuhkan('jalur');

downloadFile(file_url , targetPath){
// Simpan variabel untuk mengetahui kemajuan

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('respons', function ( data )///////
//////////////////////////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");
});

}`

Permintaan sekarang dalam mode pemeliharaan dan tidak akan menggabungkan fitur baru apa pun. Silakan lihat #3142 untuk informasi lebih lanjut.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat