מה קורה בפועל כשחיבור הופך ל-WebSocket¶
הפוסט על WebSocket הסביר מתי כדאי חיבור דו-כיווני מתמשך במקום HTTP רגיל, והזכיר בקצרה ש"החיבור מתחיל כבקשת HTTP רגילה ואז משתדרג". השאלה המעניינת יותר היא איך בדיוק חיבור, שהתחיל בפרוטוקול טקסטואלי בקשה-תשובה, הופך ברגע אחד לחיבור שני-הכיוונים לגמרי, בלי לפתוח חיבור TCP חדש בכלל.
הבקשה שמבקשת לשנות את כללי המשחק¶
חיבור WebSocket מתחיל בדיוק כמו כל בקשת HTTP רגילה - אותו חיבור TCP, אותו פרוטוקול. אבל הבקשה עצמה מכילה כותרות מיוחדות שמבקשות שדרוג:
GET /chat HTTP/1.1
Host: amittech.dev
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
הכותרת Upgrade היא בעצם בקשה מפורשת - "אני רוצה להפסיק לדבר HTTP רגיל, ולעבור לפרוטוקול WebSocket, על גבי אותו חיבור בדיוק שכבר פתוח בינינו". זה מנגנון כללי של HTTP, לא ייחודי ל-WebSocket, שמאפשר לשני צדדים "להסכים" לשנות את שפת התקשורת שלהם אחרי שהחיבור כבר נפתח.
Sec-WebSocket-Key - למה זה קיים בכלל¶
אחד הפרטים שנראים מוזרים בהתחלה הוא הכותרת Sec-WebSocket-Key, ערך אקראי שהלקוח שולח. השרת אמור לקחת את הערך הזה, לצרף אליו מחרוזת קבועה מוסכמת מראש (magic string) שמוגדרת בתקן עצמו, לחשב hash של השילוב, ולהחזיר את התוצאה בכותרת תשובה בשם Sec-WebSocket-Accept.
המטרה של המנגנון הזה היא לא אבטחה במובן ההצפנתי - היא הוכחה שהשרת בצד השני מבין באמת את פרוטוקול WebSocket, ולא איזה שרת HTTP ישן או פרוקסי שרק "מעביר הלאה" בקשות בלי להבין אותן. אם שרת שלא תומך ב-WebSocket בכלל מקבל את הבקשה הזאת, הוא פשוט יתעלם מהכותרות המוזרות ויגיב כמו לבקשת GET רגילה, ולא יחזיר Sec-WebSocket-Accept תקין, כך שהלקוח יידע מיד שהשדרוג נכשל.
תשובת השרת - 101, לא 200¶
אם השרת תומך ותוכן להשדרג, הוא לא מחזיר תשובת HTTP רגילה עם קוד 200. הוא מחזיר קוד סטטוס מיוחד, 101 Switching Protocols, יחד עם ה-Sec-WebSocket-Accept המחושב. הקוד 101 הוא בעצם הצהרה מפורשת - "אני מסכים לשדרוג, ומהרגע הזה, מה שעובר על החיבור הזה כבר לא HTTP רגיל".
מסגור - איך הודעות עוברות אחרי השדרוג¶
אחרי שהשדרוג הושלם, החיבור נשאר פתוח, אבל הפורמט שבו מידע עובר עליו משתנה לגמרי. במקום בקשות ותשובות בפורמט HTTP, כל הודעה שנשלחת עוטפת עצמה במסגרת (frame) קצרה - כותרת בינארית קטנה שמציינת את סוג ההודעה (טקסט, בינארי, או הודעת בקרה כמו ping/pong לבדיקת חיות), ואת האורך שלה, ואחריה התוכן עצמו. הודעות ping ו-pong מאפשרות לשני הצדדים לוודא מדי פעם שהחיבור עדיין חי, גם אם אין מידע ממשי לשלוח כרגע - חשוב במיוחד בחיבור שיכול להישאר פתוח שעות.
הודעות ששולחים מהלקוח לשרת חייבות להיות ממוסכרות (masked) עם מפתח אקראי קצר, דרישה שנועדה למנוע מצב שבו תוקף מנצל חיבור WebSocket כדי להסתיר תוכן שנראה כמו בקשת HTTP רגילה בעיני ציוד רשת ביניים שלא מבין את הפרוטוקול.
למה כדאי להבין את שכבת הפרוטוקול הזאת¶
הבנת מנגנון השדרוג מסבירה למה פרוקסי או מאזן עומסים שלא מוגדר נכון לתמוך ב-WebSocket חוסם או שובר את החיבור, למה WebSocket דורש תמיכה מפורשת בתשתית ולא פשוט "עובד לבד", ולמה ping/pong מופיעים בקוד כמעט כל ספרייה שמטפלת בחיבורים כאלה.
בקורס צד-שרת בונים תכונת זמן אמת עם WebSocket מהיסודות, כולל ההבנה של שכבת הפרוטוקול שמאחוריו.
יש לכם חיבור WebSocket שנשבר מאחורי פרוקסי או מאזן עומסים? שאלו בדיסקורד.
לסיכום¶
חיבור WebSocket לא נפתח בפרוטוקול נפרד מההתחלה - הוא מתחיל כבקשת HTTP רגילה עם כותרת Upgrade, עובר אימות הדדי קצר עם Sec-WebSocket-Key, ומקבל תשובת 101 שמסמנת שמעכשיו החיבור מדבר בשפה אחרת לגמרי - מסגרות בינאריות קצרות במקום בקשות ותשובות. כל זה קורה על אותו חיבור TCP בדיוק שכבר היה פתוח מההתחלה.