WebSocketStream: שילוב של מקורות נתונים עם WebSocket API

כדי למנוע מצב שבו האפליקציה מוצפת בהודעות WebSocket או ששרת WebSocket מוצף בהודעות, אפשר להשתמש בלחץ חוזר.

רקע

WebSocket API מספק ממשק JavaScript לפרוטוקול WebSocket, שמאפשר לפתוח סשן תקשורת אינטראקטיבי דו-כיווני בין הדפדפן של המשתמש לבין שרת. באמצעות ה-API הזה, אתם יכולים לשלוח הודעות לשרת ולקבל תשובות מבוססות-אירועים בלי לבצע סקר בשרת כדי לקבל תשובה.

‫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 הנוכחי היא שאין דרך להפעיל backpressure. אם ההודעות מגיעות מהר יותר ממה ששיטת process() יכולה לעבד, תהליך הרינדור ינצל את הזיכרון כדי לשמור את ההודעות במאגר זמני, לא יגיב בגלל שימוש של 100% במעבד, או גם וגם.

הפעלת לחץ חוזר על הודעות שנשלחו לא נוחה

אפשר להפעיל לחץ חוזר על הודעות שנשלחו, אבל זה כרוך בביצוע סקר של המאפיין WebSocket.bufferedAmount, וזה לא יעיל ולא נוח. המאפיין הזה לקריאה בלבד מחזיר את מספר הבייטים של הנתונים שהוכנסו לתור באמצעות קריאות אל WebSocket.send(), אבל עדיין לא הועברו לרשת. הערך הזה מתאפס לאפס אחרי שכל הנתונים בתור נשלחים, אבל אם ממשיכים לקרוא לפונקציה WebSocket.send(), הערך ימשיך לעלות.

מה זה WebSocketStream API?

‫WebSocketStream API פותר את הבעיה של לחץ חוזר לא קיים או לא ארגונומי על ידי שילוב של סטרימינג עם WebSocket API. כלומר, אפשר להשתמש בלחץ חוזר בלי עלות נוספת.

תרחישי שימוש מומלצים ב-WebSocketStream API

דוגמאות לאתרים שיכולים להשתמש ב-API הזה:

  • אפליקציות WebSocket שדורשות רוחב פס גבוה כדי לשמור על אינטראקטיביות, במיוחד שיתוף מסך וסרטונים.
  • באופן דומה, אפליקציות לצילום וידאו ואפליקציות אחרות שמייצרות הרבה נתונים בדפדפן שצריך להעלות לשרת. באמצעות backpressure, הלקוח יכול להפסיק לייצר נתונים במקום לצבור נתונים בזיכרון.

הסטטוס הנוכחי

שלב סטטוס
1. יצירת הסבר הושלם
2. יצירת טיוטה ראשונית של המפרט בתהליך
3. איסוף משוב ושיפור העיצוב בתהליך
4. גרסת מקור לניסיון הושלם
5. הפעלה לא הופעל

איך משתמשים ב-WebSocketStream API

ה-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 ל-constructor של WebSocketStream. AbortController.abort() פועל רק לפני לחיצת היד, ולא אחרי.

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

אפשרות אחרת היא להשתמש בשיטה WebSocketStream.close(), אבל המטרה העיקרית שלה היא לאפשר לציין את הקוד ואת הסיבה שנשלחים לשרת.

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

שיפור הדרגתי ויכולת פעולה הדדית

נכון לעכשיו, Chrome הוא הדפדפן היחיד שמטמיע את WebSocketStream API. כדי לאפשר פעולה הדדית עם WebSocket API הקלאסי, אי אפשר להפעיל לחץ חוזר על הודעות שהתקבלו. אפשר להפעיל לחץ חוזר על הודעות שנשלחו, אבל זה כרוך בביצוע סקר של המאפיין WebSocket.bufferedAmount, וזה לא יעיל ולא נוח.

זיהוי תכונות

כדי לבדוק אם יש תמיכה ב-WebSocketStream API, משתמשים בפקודה:

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

הדגמה (דמו)

בדפדפנים תומכים, אפשר לראות את WebSocketStream API בפעולה או את ה-iframe המוטמע.

משוב

הצוות של Chrome רוצה לשמוע על החוויות שלכם עם WebSocketStream API.

מהו עיצוב ה-API?

האם יש משהו ב-API שלא פועל כמו שציפית? או שחסרות שיטות או מאפיינים שצריך להטמיע כדי לממש את הרעיון? יש לך שאלה או הערה לגבי מודל האבטחה? אפשר להגיש בעיה במפרט במאגר GitHub המתאים, או להוסיף את המחשבות שלכם לבעיה קיימת.

דיווח על בעיה בהטמעה

מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט? מדווחים על הבאג בכתובת new.crbug.com. חשוב לכלול כמה שיותר פרטים, הוראות פשוטות לשחזור הבאג ומזינים Blink>Network>WebSockets בתיבה Components (רכיבים).

תמיכה ב-API

האם אתם מתכננים להשתמש ב-WebSocketStream API? התמיכה הציבורית שלכם עוזרת לצוות Chrome לתת עדיפות לתכונות, ומראה לספקי דפדפנים אחרים עד כמה חשוב לתמוך בהן.

אתם יכולים לשלוח ציוץ אל @ChromiumDev באמצעות ההאשטאג #WebSocketStream ולספר לנו איפה ואיך אתם משתמשים בו.

קישורים שימושיים

תודות

ממשק WebSocketStream API הוטמע על ידי Adam Rice וYutaka Hirano.