לדלג לתוכן

מה קורה בפועל כששרת מקבל בקשת HTTP - מבייטים גולמיים לתשובה שחוזרת

כשכותבים @app.get("/users") בפריימוורק כמו FastAPI או Express, קל לשכוח שמאחורי השורה הנוחה הזאת מסתתר תהליך שלם - מהרגע שבייטים גולמיים נוחתים על שקע רשת (socket), ועד שהם הופכים לקריאה לפונקציה שלכם, ואז חוזרים בדרך ההפוכה כתשובה. בואו נפרק את זה.

שלב 1 - הבייטים מגיעים לשקע פתוח

השרת שלכם, ברגע שהוא עולה, פותח שקע רשת ומאזין על פורט מסוים (למשל 8080), ומחכה שמישהו יתחבר. כשדפדפן פותח חיבור TCP לשרת ומתחיל לשלוח מידע, מה שמגיע בפועל הוא סתם זרם של בייטים - אין עדיין שום "מושג" של בקשת HTTP, יש רק רצף בייטים גולמי שצריך לפרש.

שלב 2 - פענוח הבקשה מטקסט גולמי לאובייקט מובנה

HTTP הוא בעיקרו פרוטוקול טקסטואלי, מה שאומר שאפשר "לקרוא" בקשה כמו טקסט רגיל. השרת מפרק אותה לשלושה חלקים עיקריים - שורת הבקשה, שמכילה את הפועל (כמו GET או POST) ואת הכתובת שביקשו; כותרות (headers), רשימת שדות מפתח-ערך שנותנים מידע נוסף כמו סוג התוכן או אורך הגוף; וגוף הבקשה (body), שמכיל את המידע הממשי, אם יש, למשל JSON שנשלח בבקשת POST.

בפועל זה נראה בערך ככה על החוט:

GET /users/42 HTTP/1.1
Host: amittech.dev
Accept: application/json

השרת, או ליתר דיוק שכבה שנקראית שרת HTTP (כמו nginx או uvicorn), קוראת את הבייטים האלה שורה אחר שורה, ובונה מהם אובייקט מובנה בזיכרון - מה שהקוד שלכם רואה בסופו של דבר כ-request.

שלב 3 - ניתוב לפונקציה הנכונה

ברגע שיש אובייקט בקשה מובנה, הפריימוורק שלכם צריך להחליט איזו פונקציה בקוד אמורה לטפל בה. זו בדיוק העבודה של שכבת הניתוב (routing) - היא בודקת את הכתובת ואת הפועל שביקשו, מוצאת התאמה מול טבלת הכללים שהגדרתם (למשל "כל GET ל-/users/{id} הולך לפונקציה get_user"), ומחלצת מהכתובת פרמטרים כמו המזהה 42.

שלב 4 - הרצת הלוגיקה שלכם

עכשיו, ורק עכשיו, מגיע התור לקוד שאתם כתבתם בפועל - הפונקציה שמריצה שאילתה למסד הנתונים, מחשבת משהו, או קוראת לשירות אחר. מבחינת השרת, כל מה שקרה עד עכשיו היה תשתית שתפקידה היחיד הוא להביא לכם, בצורה נוחה, בדיוק את המידע שאתם צריכים כדי להריץ את הלוגיקה העסקית שלכם.

שלב 5 - הפיכת התשובה חזרה לבייטים

כשהפונקציה שלכם מחזירה ערך, למשל אובייקט פייתון או JavaScript, הפריימוורק צריך להפוך אותו בחזרה לטקסט - תהליך שנקרא serialization - ולבנות תשובת HTTP תקנית, עם שורת סטטוס (כמו HTTP/1.1 200 OK), כותרות מתאימות (כמו Content-Type: application/json), וגוף התשובה. כל זה נשלח בחזרה דרך אותו שקע רשת, כזרם בייטים, שהדפדפן בצד השני יודע לפענח בחזרה לתשובה שהוא מציג לכם.

למה שווה להבין את השכבות האלה

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

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

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

יש לכם endpoint שמתנהג מוזר ולא ברור לכם למה? שאלו בדיסקורד.

הצטרפו לקהילה בדיסקורד

לסיכום

בקשת HTTP לא "מגיעה" לפונקציה שלכם בקסם - היא עוברת שרשרת ברורה, מבייטים גולמיים על שקע רשת, דרך פענוח לטקסט מובנה, ניתוב לפונקציה הנכונה, הרצת הלוגיקה שלכם, וחזרה לבייטים שנשלחים ברשת. כל שכבה בשרשרת הזאת קיימת בשביל סיבה ברורה, וברגע שמבינים אותה, כל endpoint שכתבתם פתאום נראה אחרת.