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

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

רקע

ממשק API של WebSocket

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

ממשק ה-API של Streams

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

הבעיה ב-WebSocket API הנוכחי

אי אפשר להחיל לחץ לאחור על הודעות שהתקבלו

באמצעות ממשק ה-API הנוכחי של WebSocket, התגובה להודעות מתרחשת 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% במעבד (CPU) או גם וגם.

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

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

מה זה WebSocketStream API?

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

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

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

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

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

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

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

דוגמה למתחילים

ממשק ה-API של WebSocketStream API מבוסס על הבטחות, והטיפול בו מרגיש טבעי בעולם 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 constructor:

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. כדי לפעול בשילוב עם ה-API הקלאסי של WebSocket, לא ניתן להחיל לחץ לאחור על הודעות שהתקבלו. ניתן להפעיל לחץ לאחור על הודעות שנשלחות, אבל היא כוללת דגימה של 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 בעזרת hashtag #WebSocketStream וספר לנו איפה אתה משתמש בו ובאיזה אופן.

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

אישורים

WebSocketStream API הוטמע על ידי Adam Rice יוטקה היראנו. התמונה הראשית (Hero) של Daan Mooij מופעלת ביטול הפתיחה.