WebSocketStream: دمج مصادر البيانات مع واجهة برمجة التطبيقات WebSocket API

يمكنك منع تطبيقك من الغرق في رسائل WebSocket أو إغراق خادم WebSocket بالرسائل من خلال تطبيق الضغط الخلفي.

الخلفية

توفّر WebSocket API واجهة JavaScript لبروتوكول WebSocket، مما يتيح فتح جلسة تواصل تفاعلية ثنائية بين متصفّح المستخدم والخادم. باستخدام واجهة برمجة التطبيقات هذه، يمكنك إرسال رسائل إلى خادم وتلقّي ردود مستندة إلى الأحداث بدون استطلاع الخادم للحصول على ردّ.

واجهة برمجة التطبيقات Streams API

تتيح Streams API للغة JavaScript الوصول آليًا إلى مصادر أجزاء البيانات التي يتم تلقيها عبر الشبكة ومعالجتها على النحو المطلوب. من المفاهيم المهمة في سياق أحداث البث هو الضغط الخلفي. هذه هي العملية التي ينظم بها بث واحد أو سلسلة أنابيب سرعة القراءة أو الكتابة. عندما يكون البث نفسه أو بث لاحق في سلسلة المسار مشغولًا ولا يزال غير جاهز لقبول المزيد من الأجزاء، يُرسِل إشارة إلى الوراء عبر السلسلة لإبطاء عملية التسليم حسب الاقتضاء.

المشكلة في واجهة برمجة التطبيقات WebSocket API الحالية

من المستحيل تطبيق الضغط الخلفي على الرسائل المستلَمة.

باستخدام واجهة برمجة التطبيقات WebSocket API الحالية، يحدث التفاعل مع الرسالة في WebSocket.onmessage، EventHandler الذي يتم استدعاؤه عند تلقّي رسالة من الخادم.

لنفترض أنّ لديك تطبيقًا يحتاج إلى تنفيذ عمليات معالجة بيانات مكثفة عند تلقّي رسالة جديدة. من المحتمل أن يتم إعداد المسار مشابهًا للرمز البرمجي أدناه، وبما أنّك await نتيجة طلب process()، من المفترض أن تكون الأمور على ما يرام، أليس كذلك؟

// A heavy data crunching operation.
const process = async (data) => {
  return new Promise((resolve) => {
    window.setTimeout(() => {
      console.log('WebSocket message processed:', data);
      return resolve('done');
    }, 1000);
  });
};

webSocket.onmessage = async (event) => {
  const data = event.data;
  // Await the result of the processing step in the message handler.
  await process(data);
};

خطأ. تكمن المشكلة في واجهة برمجة التطبيقات WebSocket API الحالية في عدم توفّر طريقة لتطبيق الضغط الخلفي. عندما تصل الرسائل بشكل أسرع من قدرة طريقة process() على معالجتها، ستؤدي عملية العرض إلى إما ملء الذاكرة من خلال تخزين هذه الرسائل مؤقتًا، أو إلى عدم الاستجابة بسبب استخدام وحدة المعالجة المركزية بنسبة% 100، أو كليهما.

إنّ تطبيق الضغط الخلفي على الرسائل المُرسَلة غير ملائم للاستخدام.

من الممكن تطبيق الضغط الخلفي على الرسائل المُرسَلة، ولكنّ ذلك يتطلّب الاستعلام عن السمة WebSocket.bufferedAmount ، وهي طريقة غير فعّالة وغير ملائمة. تعرض هذه السمة للقراءة فقط عدد وحدات البايت من البيانات التي تم وضعها في قائمة الانتظار باستخدام طلبات إلى WebSocket.send()، ولكن لم يتم إرسالها إلى الشبكة بعد. تتم إعادة ضبط هذه القيمة على الصفر بعد إرسال جميع البيانات في "قائمة الانتظار"، ولكن إذا واصلت طلب WebSocket.send()، ستواصل الزيادة.

ما هي واجهة برمجة التطبيقات WebSocketStream API؟

تعالج واجهة برمجة التطبيقات WebSocketStream API مشكلة الضغط الخلفي غير المتوفّر أو غير الملائم من خلال دمج أحداث البث مع واجهة برمجة التطبيقات WebSocket API. وهذا يعني أنّه يمكن تطبيق الضغط الخلفي "مجانًا"، بدون أي تكلفة إضافية.

حالات الاستخدام المقترَحة لواجهة برمجة التطبيقات WebSocketStream API

تشمل أمثلة المواقع الإلكترونية التي يمكنها استخدام واجهة برمجة التطبيقات هذه ما يلي:

  • تطبيقات WebSocket ذات النطاق الترددي العالي التي تحتاج إلى الاحتفاظ بالتفاعل، لا سيما تطبيقات مشاركة الفيديو والشاشة
  • وبالمثل، فإنّ تطبيقات تسجيل الفيديوهات والتطبيقات الأخرى التي تُنشئ الكثير من البيانات في المتصفّح تحتاج إلى تحميلها إلى الخادم. باستخدام الضغط الخلفي، يمكن للعميل إيقاف إنتاج البيانات بدلاً من تجميعها في الذاكرة.

الوضع الحالي

الخطوة الحالة
1. إنشاء فيديو توضيحي مكتمل
2. إنشاء مسودة أولية للمواصفة قيد التقدّم
3- جمع الملاحظات وتحسين التصميم قيد التقدّم
4. مرحلة التجربة والتقييم مكتمل
5- إطلاق لم تبدأ عملية المراجعة

كيفية استخدام واجهة برمجة التطبيقات WebSocketStream API

تستند واجهة برمجة التطبيقات WebSocketStream إلى الوعود، ما يجعل التعامل معها سهلًا في عالم JavaScript الحديث. تبدأ بإنشاء WebSocketStream جديد وإرسال عنوان URL لخادم WebSocket إليه. بعد ذلك، عليك الانتظار إلى أن يصبح الاتصال opened، مما يؤدي إلى ظهور رمز ReadableStream و/أو رمز WritableStream.

من خلال استدعاء الطريقة ReadableStream.getReader() ، يمكنك الحصول أخيرًا على ملف ReadableStreamDefaultReader، يمكنك بعد ذلك read() الحصول على البيانات منه إلى أن ينتهي مصدر البيانات، أي إلى أن يعرض ملفًا من النوع {value: undefined, done: true}.

وفقًا لذلك، من خلال استدعاء الأسلوب WritableStream.getWriter() ، يمكنك الحصول أخيرًا على WritableStreamDefaultWriter، يمكنك بعد ذلك write() البيانات إليه.

  const wss = new WebSocketStream(WSS_URL);
  const {readable, writable} = await wss.opened;
  const reader = readable.getReader();
  const writer = writable.getWriter();

  while (true) {
    const {value, done} = await reader.read();
    if (done) {
      break;
    }
    const result = await process(value);
    await writer.write(result);
  }

الضغط الخلفي

ماذا عن ميزة الضغط الخلفي التي تم الإعلان عنها؟ يمكنك الحصول عليه "مجانًا"، ولا يلزم اتّخاذ أي خطوات إضافية. إذا استغرقت process() وقتًا إضافيًا، لن يتم استخدام الرسالة التالية إلا بعد أن تصبح قناة الإرسال جاهزة. وبالمثل، لا تتم معالجة الخطوة WritableStreamDefaultWriter.write() إلا إذا كان ذلك آمنًا.

أمثلة متقدّمة

الوسيطة الثانية لواجهة WebSocketStream هي حزمة خيارات للسماح بإضافة ميزات في المستقبل. الخيار الوحيد هو protocols، الذي يعمل بالطريقة نفسها التي تعمل بها الوسيطة الثانية لدالة إنشاء WebSocket:

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;

إنّ protocol المحدّد وextensions المحتمل هما جزء من القاموس المتاح من خلال WebSocketStream.opened. يتم تقديم جميع المعلومات عن الاتصال المباشر من خلال هذا الوعد، لأنّه ليس من الملائم تقديمها في حال تعذّر الاتصال.

const {readable, writable, protocol, extensions} = await chatWSS.opened;

معلومات عن اتصال WebSocketStream المغلق

إنّ المعلومات التي كانت متاحة من حدثَي WebSocket.onclose و WebSocket.onerror في WebSocket API متاحة الآن من خلال وعد WebSocketStream.closed. يتم رفض الوعد في حال إغلاق غير نظيف، وإلا يتم حلّه إلى الرمز والسبب الذي أرسله الخادم.

يمكنك الاطّلاع على جميع رموز الحالة المحتمَلة وتفسيرها في قائمة رموز حالة CloseEvent.

const {code, reason} = await chatWSS.closed;

إغلاق اتصال WebSocketStream

يمكن إغلاق WebSocketStream باستخدام AbortController. لذلك، مرِّر AbortSignal إلى الدالة الإنشائية WebSocketStream.

const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);

كبديل، يمكنك أيضًا استخدام الطريقة WebSocketStream.close()، ولكن الغرض الرئيسي منها هو السماح بتحديد الرمز والسبب الذي يتم إرساله إلى الخادم.

wss.close({code: 4000, reason: 'Game over'});

التحسين التدريجي والتشغيل التفاعلي

Chrome هو المتصفّح الوحيد الذي ينفِّذ واجهة برمجة التطبيقات WebSocketStream API حاليًا. لضمان إمكانية التشغيل التفاعلي مع واجهة برمجة التطبيقات الكلاسيكية WebSocket API، لا يمكن تطبيق الضغط الخلفي على الرسائل المستلَمة. من الممكن تطبيق الضغط الخلفي على الرسائل المُرسَلة، ولكنّ ذلك يتطلّب الاستعلام عن السمة WebSocket.bufferedAmount ، وهي طريقة غير فعّالة وغير ملائمة.

رصد الميزات

للتحقّق من توفّر واجهة برمجة التطبيقات WebSocketStream API، استخدِم:

if ('WebSocketStream' in window) {
  // `WebSocketStream` is supported!
}

عرض توضيحي

في المتصفحات المتوافقة، يمكنك الاطّلاع على واجهة برمجة التطبيقات WebSocketStream API أثناء عملها في إطار iframe المضمّن، أو على Glitch مباشرةً.

ملاحظات

يريد فريق Chrome معرفة تجاربك مع واجهة برمجة التطبيقات WebSocketStream API.

أخبِرنا عن تصميم واجهة برمجة التطبيقات.

هل هناك مشكلة في واجهة برمجة التطبيقات لا تعمل على النحو المتوقّع؟ هل هناك طرق أو سمات غير متوفّرة تحتاجها لتنفيذ فكرتك؟ هل لديك سؤال أو تعليق بشأن نموذج الأمان؟ يمكنك الإبلاغ عن مشكلة في المواصفات في مستودع GitHub المقابل، أو إضافة ملاحظاتك إلى مشكلة حالية.

الإبلاغ عن مشكلة في التنفيذ

هل رصدت خطأ في عملية تنفيذ Chrome؟ أم أنّ عملية التنفيذ مختلفة عن المواصفات؟ يمكنك إرسال بلاغ عن خلل على الرابط new.crbug.com. احرص على تضمين أكبر قدر ممكن من التفاصيل وتعليمات بسيطة لإعادة إنتاج الخلل، وأدخِل Blink>Network>WebSockets في مربّع المكوّنات. تُعدّ أداة Glitch مثالية لمشاركة حالات إعادة الإنتاج بسرعة وسهولة.

إظهار الدعم لواجهة برمجة التطبيقات

هل تخطّط لاستخدام واجهة برمجة التطبيقات WebSocketStream API؟ يساعد دعمك العلني فريق Chrome في تحديد أولويات الميزات، ويُظهر لموفّري المتصفّحات الآخرين مدى أهمية توفير هذه الميزات.

أرسِل تغريدة إلى ‎@ChromiumDev باستخدام الهاشتاغ #WebSocketStream وأطلِعنا على مكان استخدامك للميزة وطريقة استخدامك لها.

روابط مفيدة

الشكر والتقدير

نفَّذ آدم رايس و يوتاكا هيرانو واجهة برمجة التطبيقات WebSocketStream API.