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

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

רקע

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

ממשק ה-API של Streams

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

החלת הלחץ האחורי על הודעות שנשלחות היא לא ארגונומית

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

מהו WebSocketStream API?

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

תרחישים לדוגמה של הצעות ל-WebSocketStream API

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

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

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

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

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

WebSocketStream API מבוסס על הבטחה, ולכן קל לעבוד איתו בעולם JavaScript מודרני. השלב הראשון הוא יצירה של WebSocketStream חדש והעברת כתובת ה-URL של שרת ה-WebSocket. לאחר מכן, ממתינים שהחיבור יהיה opened, כתוצאה מכך מופיעה ההודעה ReadableStream ו/או ההודעה WritableStream.

באמצעות קריאה ל-method ReadableStream.getReader(), בסופו של דבר מקבלים ReadableStreamDefaultReader, שאותו אפשר לקבל read() נתונים עד לסיום השידור, כלומר עד שהוא מחזיר אובייקט בצורת{value: undefined, done: true}.

לכן, כשקוראים ל-method‏ 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, שמתנהגת כמו הארגומנט השני ל-constructor של 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.

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

לחלופין, אפשר גם להשתמש ב-method 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.

תיאור של עיצוב ה-API

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

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

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

הצגת תמיכה ב-API

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

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

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

אישורים

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