Request: خطأ: لا يمكنك التمرير بعد إرسال البيانات من الاستجابة.

تم إنشاؤها على ٣٠ أبريل ٢٠١٤  ·  29تعليقات  ·  مصدر: request/request

أواجه مشكلة حيث سأحصل على رسالة "لا يمكنك تمرير البيانات بعد انبعاث البيانات من الاستجابة." خطأ عند محاولة توجيه الإخراج إلى WriteStream. يحدث هذا فقط عندما يكون هناك تأخير بين وقت إجراء المكالمة request() ووقت إجراء المكالمة readStream.pipe(writeStream) .

إصدار العقدة: v0.10.24
نسخة الطلب: 2.34.0

لقد أنشأت github repo بالمصدر الذي يعيد إنتاج المشكلة:
https://github.com/joe-spanning/request-error

الكود موجود أدناه أيضًا للراحة:

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

قد تحتاج إلى اللعب بقيمة المهلة لإعادة إنتاج الخطأ. تزيد المهلات الأطول من احتمال حدوث الخطأ.

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

التعليق الأكثر فائدة

@ joepie91 أخذ المثال الأصلي في الأعلى والأنابيب على الفور إلى مسار يمر دون مشاكل. ربما أسيء فهم المشكلة؟

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

ال 29 كومينتر

كما نرى هذا في ظل ظروف مماثلة.

لا أعرف ما هي الآثار الأخرى التي قد تترتب عليها ، لكنني قادر على حل هذه المشكلة عن طريق تغيير request.js النحو التالي:

يضيف:

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

وقم بتغيير استدعاء dataStream.on("data" إلى هذا:

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

المشكلة الأساسية (على ما أعتقد) هي أن onResponse يتصل بـ resume() ويرفق مستمع "بيانات" ، وكلاهما يبدأ تدفق البيانات ، لكننا قد لا نكون مستعدين لتوجيهها بعد. لذا فإن الحل الذي أقدمه هو إذا حاولنا إصدار بيانات ولكن ليس لدينا أي مستمعين حتى الآن ، فإننا نوقف تيار القراءة الأساسي مؤقتًا حتى نحصل على مستمع بيانات مرفق.

سيكون جيدًا إذا تمكن شخص آخر من اختبار هذا الحل والتحقق من أنه يعمل.

ناه ، لا يعمل معي. نظرًا لقلة النشاط بشأن هذه المشكلة ، سأتوقف عن استخدام request وأعود إلى الوحدة النمطية Node.js http .

يجب أن أذكر أن الإصلاح الخاص بي هو للطلب 2.37.0. لن يعمل مع إصدار أقدم.

aldeed لقد https://github.com/joepie91/request. اعتمادًا على درجة الاحتفاظ بهذا المستودع (الأصلي) في المستقبل ، قد أقوم بدمج المزيد من الإصلاحات فيه. لست متأكدا بعد.

لقد كنت أقوم بتصحيح هذه المشكلة لبضع ساعات الآن ، لكن لم أتمكن من العثور على حل حتى قرأت هذا الموضوع. فيما يلي بعض المعلومات الأساسية التقنية حول سبب حدوث هذه المشكلة. إنها مبسطة قليلاً ، لتسهيل المتابعة.

request "واجهة برمجة تطبيقات متدفقة" بمعنى أنه يمكنك .pipe كائن الطلب الأصلي إلى WritableStream . ومع ذلك ، نظرًا لأن كائن الطلب هذا ليس تدفق الاستجابة نفسه ، request 'shims' الحدث data - فإنه ينشئ مستمعًا لحدث data في تدفق الاستجابة ، و ثم يعيد إرسال هذا الحدث على نفسه ، وبالتالي يكون قادرًا على جعل كائن الطلب يعمل بمثابة دفق بحد ذاته.

هناك مشكلة واحدة فقط: بمجرد إرفاق الحدث data بالحدث ، يبدأ التدفق .

الآن هذه ليست مشكلة في العادة ؛ تعمل Node.js مع "ticks" - اعتبرها "دورات" في المترجم. التفسير المبسط هو أن كتلة واحدة من التعليمات البرمجية سوف تستهلك "علامة" ، ولن يتم التحقق من أي أحداث أو إطلاقها حتى العلامة التالية. هذا يعني أن الكود الافتراضي التالي سيعمل بشكل جيد:

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

يتم تنفيذ كلا الخطين في نفس العلامة ، ولا يمكن أن يحدث الحدث الأول data حتى العلامة التالية - هذه هي الطريقة التي يعمل بها المترجم داخليًا. هذا يعني أن لديك ضمانًا بأن .pipe تمت معالجته _ قبل_ أول حدث data ، وسيعمل كل شيء كما هو متوقع.

الآن ، ما حاول @ joe-spanning فعله هو استخدام بنية غير متزامنة ، مهلة في هذا المثال بالذات. لم يعد بدء البث و .pipe موجودين في نفس كتلة الكود.

تتبع التركيبات غير المتزامنة نفس قاعدة الأحداث - لا يمكن تنفيذها حتى العلامة التالية. ومع ذلك ، بحلول الوقت الذي يتم فيه تنفيذ البناء غير المتزامن ، تكون دورة الحدث قد انتهت للتو ويتم تشغيل الحدث الأول data _فعلًا - يأتي الجزء الأول من البيانات الخاص بك من خلال الفجوة بين تهيئة الدفق و .pipe . يكتشف request أن حدثًا data قد تم إطلاقه بالفعل ، ويخبرك أنه لم يعد بإمكانك .pipe التدفق لأنه قد بدأ بالفعل في التدفق.

تحدث هذه المشكلة نفسها لأي شيء يحدث بعد علامة واحدة على الأقل بعد تهيئة الدفق.

يعمل الحل بواسطة aldeed على إصلاح ذلك عن طريق التحقق مما إذا كان هناك أي مستمعين مرتبطين بكائن الطلب ، وإذا لم يكن الأمر كذلك ، فقم بإعادة جزء البيانات مرة أخرى في الدفق الأصلي وإيقاف هذا التدفق الأصلي مؤقتًا - وبالتالي منع تدفقه. بمجرد إرفاق المستمع ، فإنه يستأنف البث الأصلي ، وهذه المرة سيكون هناك معالج الحدث data يستمع.

الطريقة التي يعمل بها رقاقة الحدث data في request هي في الواقع عيب في التصميم. لا يأخذ في الاعتبار احتمال عدم حدوث تهيئة التدفق والأنابيب في نفس العلامة ، وأن Node.js عبارة عن منصة غير متزامنة بطبيعتها (وبالتالي ، من المتوقع حدوث هذا النوع من المواقف). بينما يعمل هذا الإصلاح ، ويجب أن يعمل بشكل موثوق ، فإن الحل الأفضل هو تأجيل الربط إلى الحدث data على الدفق الأصلي ، حتى يتم إرفاق المستمع الأول.

ملاحظة أخيرة ؛ إذا قمت بتعريف رد اتصال في تهيئة الطلب الخاص بك ، فلن يعمل هذا الإصلاح.

داخليًا ، إذا تم توفير رد اتصال ، فسيقوم request بإرفاق معالج الحدث data بنفسه ، وقراءة النص ، وإمداد الجسم برد الاتصال الخاص بك. الطريقة للتغلب على هذا ، هي إرفاق الحدث response بدلاً من ذلك (وعدم تحديد رد اتصال). سيتم إطلاق هذا الحدث (بشكل موثوق) بمجرد تلقي استجابة ويكون الطلب جاهزًا للبث.

aldeed لقد كنت أحاول إصلاح هذا لساعات ، مع فشل كل محاولاتي لإصلاحه بالفعل ، ويمكنني الآن أخيرًا مواصلة العمل على الشيء الخاص بي. أنا مدين لك البيرة :)

ملاحظة سريعة حول هذه المشكلة من حيث صلتها بالدفق ذي النمط الجديد والفرع 3.0.

من خلال التدفقات القابلة للقراءة ذات النمط الجديد ، يتم فتحها في ما يمكن اعتباره حالة "متوقفة مؤقتًا ومخزن مؤقتًا" ، ولن يسمحوا بإخراج البيانات حتى يصلوا إلى read() من.

هذا يعني أنه سيتعين علينا التخلص من معظم الاختراقات التالية. لقد تعاملت مع هذا قليلاً في مكتبات أخرى وما يتعين علينا القيام به هو انتظار القراءة الأولى () ثم تشغيل جميع الشفرات "البطيئة".

إحدى النتائج الواضحة هي أنه في الإصدار 3.0 إذا قمت بتمرير رد اتصال ، فسيؤدي ذلك إلى تخزين كل المحتوى مؤقتًا. حاليًا ، إذا قمت بالوصول إلى واجهة برمجة التطبيقات المتدفقة ، فإنها تعطل التخزين المؤقت ولكن بدون الاختراق التالي ، لا يمكننا استنتاج ذلك.

mikael هل تقبل هذا الإصلاح

سأحتاج إلى رؤيتها على أنها علاقات عامة من أجل تقييمها.

على ما يرام. ربما يكون من الأفضل ترك إنشاء طلب السحب الفعلي حتى aldeed ، نظرًا لأنه ساهم في الإصلاح الأصلي - ربما يكون دمجها في المستودع الرئيسي هو اختياره ومنح الترخيص وكل ذلك.

شكرا @ joepie91. mikeal ، على ما أذكر ، بالطريقة التي قرأت بها الشفرة في 2.37.0 ، إنها تستخدم بالفعل تدفقات ذات نمط جديد لذا فهي "متوقفة مؤقتًا ومخزنة مؤقتًا" ، كما قلت ، في البداية. تأتي المشكلة عندما تقوم بإرفاق مستمع "البيانات" الخاص بك واستئناف المكالمة ، وهو ما تفعله في أماكن مختلفة ، لأنهما يخبران تيار القراءة بإلغاء الإيقاف المؤقت نفسه. لذا ، كما يقول @ joepie91 ، فإن الحل الحقيقي يتطلب إعادة هندسة

من المستندات :

في Node v0.10 ، تمت إضافة فئة Readable الموضحة أدناه. للتوافق مع برامج Node القديمة ، تتحول التدفقات المقروءة إلى "وضع التدفق" عند إضافة معالج حدث "البيانات" ، أو عند استدعاء أساليب الإيقاف المؤقت () أو الاستئناف (). التأثير هو أنه ، حتى إذا كنت لا تستخدم طريقة read () الجديدة والحدث "المقروء" ، فلا داعي للقلق بعد الآن بشأن فقدان أجزاء "البيانات".

2.37 لا يستخدم بالتأكيد تدفقات النمط الجديد :) يجب توجيه كائن الطلب إلى وجهة قبل أن يبدأ في إصدار أحداث البيانات. وبالمثل ، يجب أن تقوم بالتمرير إلى مثيل طلب في نفس العلامة التي قمت بإنشائها وإلا ستفشل الحيل المماثلة (على الرغم من أن لدينا بعض التعليمات البرمجية لمحاولة التخفيف من ذلك في بعض الحالات ، ولكن ليس كلها).

حسنًا ، ربما أخطأت في القراءة أو أخطأت في التذكر أو نظرت إلى كود 3.0 أيضًا ، وشعرت بالارتباك. :)

قد يكون الحل الأسرع هو التوجيه الفوري إلى كائن stream.PassThrough()
يجب أن تكون قادرًا على توجيه من كائن العبور في أي وقت لاحق دون مشاكل.
راجع: http://nodejs.org/api/stream.html#stream_class_stream_passthrough

ZJONSSON لقد جربت ذلك في وقت ما ، لكنه لم ينجح أيضًا. بقدر ما أفهم ، فهو عرضة لنفس القيد (عدم إعادة إرسال البيانات قبل إرفاق دفق لاحق) مثل أي نوع آخر من الدفق ، لذلك ستظل عالقًا إذا كان المرفق الأصلي بمقدار request والمرفق الخاص بك إلى الكائن request لا يحدثان في نفس العلامة.

@ joepie91 أخذ المثال الأصلي في الأعلى والأنابيب على الفور إلى مسار يمر دون مشاكل. ربما أسيء فهم المشكلة؟

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

بصراحة ليس لدي فكرة. إذا كنت أتذكر بشكل صحيح ، فهذا بالضبط ما جربته ، ولم يساعد. هل تم تغيير الكود الأساسي في request في هذه الأثناء؟

يجب أن أضيف أن المثال الذي قمت بتعديله هو في الواقع مثال @ joe-spanning. لست متأكدًا مما إذا كان هناك فرق بين ذلك وبين الكود الخاص بي الذي يجعله يعمل فقط في حالته.

تم إرسال رقم PR # 1098

داخليًا ، إذا تم توفير رد اتصال ، فسيقوم الطلب بإرفاق معالج حدث بيانات بنفسه ، ويقرأ النص ، ويزود الجسم برد الاتصال الخاص بك. الطريقة للتغلب على هذا ، هي إرفاق حدث الاستجابة بدلاً من ذلك> (وعدم تحديد رد اتصال). سيتم إطلاق هذا الحدث (بشكل موثوق) بمجرد تلقي استجابة ويكون الطلب جاهزًا للبث.

لقد تعرضت للتو للعض من قبل request(url, mycb).pipe(mywstream)
في بعض الأحيان يتم استدعاء mycb ، وأحيانًا لا.
ربما يجب أن يحذر الطلب من أنه يجب تجنب استخدام pipe() مع رد اتصال تم تعيينه بالفعل؟

لدي نفس المشكلة باستثناء ظهور هذا الخطأ عندما أحاول .pipe في .on('response') :

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

تحديث من جانبي ، لست متأكدًا من سبب عدم نشر هذا من قبل.

نظرًا لعدم حل هذه المشكلة في الوقت المناسب وتشغيلها عبر عدد من الأخطاء المحبطة الأخرى في request ، فقد قمت بكتابة مكتبة عميل HTTP الخاصة بي منذ فترة والتي تتعامل بشكل صحيح مع حالة الاستخدام هذه ؛ bhttp . كما أنه يحل مشكلات دعم Streams2.

هل هناك تحديث لهذا؟

أعلم أن هذه المشكلة قديمة ، ولكن في حالتي (على غرار dogancelik ، إرسال استجابة مشروطة بناءً على رؤوس الاستجابة) ، تمكنت من القيام بما يلي:

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

على سبيل المثال ، إيقاف تدفق الاستجابة مؤقتًا (من الواضح أن request يعيّن التدفق إلى وضع التدفق تلقائيًا؟) وتوجيه كائن الاستجابة نفسه (وليس req ).

أرحب بتعليقات المستخدمين الأكثر دراية مني حول هذا الموضوع.

فقط صادف هذا أيضًا ... لقد حللت الأمر من نهايتي لأنه كان خطأ منطقيًا في حالتي.

لدي مصفوفة عالمية حيث أقوم بتخزين كل طلب عبر معرف فريد. أنا أقوم بتشغيل طلبات متعددة في وقت واحد. لدي خيار إجهاض للطلبات. قد يمنحك هذا مزيدًا من المعلومات ؛

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 مكالمة جيدة ، يبدو أن هذا يرجع بالفعل إلى أن ربط مستمع response يتسبب في تحوله إلى "وضع التدفق" تلقائيًا. يمكنني التحقق من حدوث ذلك في كل من [email protected] و [email protected] .

لست متأكدًا مما إذا كان هذا مقصودًا أم لا (ما فهمته هو أن هذه ليست الطريقة التي من المفترض أن تعمل بها). في الوقت الحالي ، سأخطط لاستخدام الحل البديل لإيقاف البث مؤقتًا (سأحاول وأتذكر إعادة النشر هنا إذا اكتشفت أي شيء جديد على طول الطريق)

أعاني بشكل عشوائي نفس الشيء مثل dogancelik في التعليق أعلاه: يحدث الخطأ عند الأنابيب داخل معالج الاستجابة. لكن كيف يمكن أن يكون هذا ممكنا؟ إذا نظرت إلى request.js ، فسيتم إصدار حدث "الاستجابة" قبل إرفاق معالج "البيانات" الخاص بالطلب (الذي يسبب المشكلة) على الإطلاق:

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

نظرًا لأنه يتم استدعاء معالجات الأحداث بشكل متزامن ، أتوقع توصيل الأنبوب قبل استهلاك البيانات.

أحصل على نفس الخطأ مع الكود التالي:

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

وأنا أعمل معرّف arount باستخدام PassThrough ، لكنني لا أعرف سبب ظهور الخطأ ، كما أنني لا أعرف سبب حل passthrough . من المفترض أن يتم تنفيذ كل شفراتي بنفس "التجزئة".

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

يوجد الخطأ المماثل لكائن الطلب أيضًا. إذا فعلت ذلك write ، فلا يمكنك pipe .

لقد تحولت أيضًا إلى وحدات http / https الأساسية للتدفقات. إنه سهل وموثوق. لذلك لا يلزم استخدام request للتدفقات.

لا تحدث استجابة هاتفية عبر الأنابيبهذا يعمل بشكل جيد معي
"طلب فار = يتطلب ('طلب') ؛
var fs = تتطلب ('fs') ؛
var path = يتطلب ('path') ؛

downloadFile (file_url ، targetPath) {
// حفظ متغير لمعرفة التقدم

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 ('response' ، الوظيفة (البيانات) ///////
//////////////////////////req.pipe (خارج) ؛ ///////// ////////// ////////////// **

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

} `

الطلب الآن في وضع الصيانة ولن يتم دمج أي ميزات جديدة. الرجاء مراجعة # 3142 لمزيد من المعلومات.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات