פרוטוקול HTTP – מדריך למתחילים

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

פרוטוקול HTTP – מה זה ולמה צריך את זה?

Hypertext transfer protocol, ובקיצור HTTP, הינו פרוטוקול שרת-לקוח מבוסס טקסט. במילים פשוטות, HTTP מגדיר שפת טקסט פשוטה המאפשרת לצרכן שירות כלשהו – הלקוח – לבקש משאבים מנותן שירות כלשהו – השרת.

לקוח ה-HTTP הנפוץ ביותר הוא הדפדפן, המבצע בקשות אל מול שרת אינטרנט להורדת דפי אינטרנט, תמונות וכדומה. אך זהו לא השימוש היחיד בפרוטוקול: HTTP משמש גם תוכנות אחרות מבוססות שרת לקוח כגון אפליקציות מובייל, מערכות החושפות REST API, שרתי source control, תקשורת בין רכיבים במערכות מורכבות ועוד אינספור דוגמאות אחרות.

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

במודל 4 השכבות (וגם בזה המכיל 7), HTTP שייך לשכבה הגבוהה ביותר – שכבת היישום (האפליקציה). כלומר כדי להשתמש בפרוטוקול HTTP אנחנו צריכים לדאוג קודם כל לחיבור אמין בין השרת ללקוח, שיאפשר לנו העברת טקסט בין שתי הישויות. אחרי שייצרנו חיבור כזה, שכמעט תמיד יהיה מסוג TCP, פרוטוקול HTTP למעשה מגדיר איך יראה המידע שיעבור בין השרת ללקוח, כלומר איך תיראה בקשה למידע, ואיך תיראה תגובה אליה.

מבנה בקשת HTTP

השלב הראשון בשיחת HTTP הוא שליחת בקשה מהלקוח לשרת. הבקשה נשלחת ע"ג חיבור TCP שנפתח בין הלקוח לשרת, ובפרט לפורט עליו מאזין השרת. באינטרנט יהיה זה בד"כ פורט 80, אך ניתן להשתמש בכל פורט.

פרוטוקול HTTP - בקשה
שליחת בקשת HTTP מהלקוח לשרת

כל בקשת HTTP היא למעשה טקסט רגיל המורכב משלושה חלקים המופרדים ביניהם בשורת רווח (התווים \r\n):

  • שורת הפתיחה
  • שורות כותרת (headers)
  • גוף הבקשה (לא חובה)

שורת הפתיחה

שורת הפתיחה מורכבת גם היא משלושה חלקים, המופרדים ביניהם בתו רווח:

  • שיטת הבקשה (method) היא מילה אחת המתארת מה מבקש הלקוח מהשרת לעשות. בפרוטוקול HTTP 1.1 קיימות 8 שיטות, שהנפוצות ביניהן הן GET המבקשת לקבל משהו מהשרת, ו-POST השולחת מידע לשרת. מבחינת הפרוטוקול עצמו, ההבדל בין השיטות הוא כמעט סמנטי בלבד (אין כמעט הבדל בין GET ל-POST), אך בפועל השרתים עצמם הם אלו שיוצרים את ההבדל כאשר הם מתייחסים בצורה שונה לגמרי לכל שיטה (GET מחזיר מידע, POST מעלה מידע).
  • כתובת המשאב המבוקש המציינת לשרת לאיזה אובייקט נרצה לגשת. הכתובת יכולה להיות אבסולוטית, לדוגמה http://icode.co.il/file.html או יחסית: /file1
  • גרסת הפרוטוקול לפיה נבנתה הבקשה, לרוב HTTP 1.1

הנה דוגמה לשורת פתיחה מלאה:

GET /index.html HTTP/1.1

כדי להמחיש כמה פשוט לקרוא פרוטוקול HTTP בקוד, להלן דוגמה לשורת פייתון המחלצת את שורת הפתיחה בבקשת HTTP, ומפרקת אותה (בהנחה שמבנה הבקשה תקין) למרכיביה:

method, url, version = HTTP_REQUEST.split('\r\n')[0].strip().split(' ')

שורות הכותרת

החלק השני בכל בקשת HTTP הוא אוסף של שורות כותרת (header lines) המכילות מידע נוסף על הבקשה (metadata) הדרוש לשרת כדי להשלים אותה. כל כותרת מורכבת משם וערך המופרדים בנקודותיים ורווח. לדוגמה:

Accept-Language: he

שורת כותרת זאת, ששמה Accept-Language וערכה he (קיצור של Hebrew) מציינת לשרת שהתשובה לבקשה יכולה להכיל מידע בעברית.

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

החל מגרסה 1.1 קיימת כותרת, Host, אותה חובה להעביר בכל בקשה. כותרת זאת היא כתובת האתר אליו מפנים את הבקשה, והיא נדרשת כדי שהשרת יוכל לתמוך בכמה אתרים במקביל (דוגמה בהמשך).

גוף הבקשה

חלק מהבקשות יכולות להכיל מידע אותו מבקש הלקוח להעביר לשרת. מידע זה נקרא גוף הבקשה והוא החלק היחיד בבקשת HTTP שלא חייב להיות טקסטואלי (כלומר הוא יכול להיות גם מידע בינארי כמו קובץ תמונה).

לדוגמה בבקשת POST גוף הבקשה עשוי להכיל את המידע אותו נרצה לשמור בשרת (פרטי טופס, קובץ וכד'). בקשות GET לרוב לא מכילות גוף.

כאשר מעבירים את גוף הבקשה, יש להוסיף את הכותרת Content-Length המציינת את אורך גוף הבקשה, בבתים (דוגמה בהמשך).

דוגמאות לבקשת HTTP

להלן דוגמה לבקשת GET המכילה שתי כותרות:

GET /index.html HTTP/1.1
Host: icode.co.il
Accept-Encoding: gzip, deflate

בדוגמה זאת אנו מבקשים מהשרת להחזיר את העמוד index.html מהאתר icode.co.il, ומוסיפים שאנו תומכים בקבלת מידע דחוס מסוג gzip.

והנה דוגמה לבקשת POST המכילה גוף. שימו לב לשורת הרווח בין הכותרות לגוף הבקשה:

POST /submit.html HTTP/1.1
Host: icode.co.il
Content-Length: 10

name=tzach

בדוגמה האחרונה, הכותרת Content-Length מכילה את האורך של גוף הבקשה בבתים, במקרה שלנו זהו האורך של הטקסט name=tzach, שהוא בדיוק 10 בתים.

בניית בקשת HTTP בקוד

בדומה לקריאת בקשת HTTP, גם הבנייה שלה היא קלה מאוד לקידוד. הנה דוגמה לבניית בקשה, בהינתן כל החלקים שלה:

request = "{method} {url} HTTP/1.1\r\n{headers}\r\n\r\n{body}".format(
    method = METHOD,
    url = URL,
    headers = '\r\n'.join('%s: %s' % item for item in HEADERS_DICT),
    body = BODY)

מבנה תגובת HTTP

השלב השני והאחרון בכל שיחת HTTP הוא התגובה (response) אותה שולח השרת ללקוח עבור הבקשה. התגובה, כשמה כן היא, מכילה את התשובה של השרת לבקשה. למשל אם הלקוח ביקש קובץ מסויים מהשרת, התגובה עשויה להכיל את תוכן הקובץ, או הודעת שגיאה (למשל במקרה שהקובץ לא קיים).

פרוטוקול HTTP - תגובה
לאחר קבלת הבקשה, השרת שולח תגובה בחזרה ללקוח

בדומה לבקשה, גם התגובה מורכבת משלושת החלקים:

  • שורת מצב המכילה את גרסת הפרוטוקול בה השתמש השרת וקוד מצב.
  • שורות כותרת באותו מבנה כמו בבקשה (ולאחריהן שורת רווח)
  • גוף התגובה (לא חובה)

קוד המצב

נסתכל על דוגמה לשורת מצב החוזרת מהשרת:

HTTP/1.1 200 OK

בדוגמה זאת הערך 200 (בתוספת המחרוזת OK) נקרא קוד המצב (status code) והוא מציין את התוצאה של נסיון השרת לטפל בבקשה שהתקבלה, לדוגמה: הצלחה (200), משאב לא נמצא (404) או שגיאה כללית (500).

קוד המצב מכיל תמיד 3 ספרות, ולרוב יהיה אחד מקודי המצב המוכרים שנקבעו מראש בפרוטוקול (למרות שאין חובה כזאת). קודי המצב מתחלקים ל-5 קבוצות, בהתאם לספרה הראשונה שלהם:

  • 1XX – בקשה התקבלה אך אין עדיין תגובה (מאוד נדיר וכמעט לא בשימוש).
  • 2XX – הצלחה. הערך הנפוץ ביותר בקבוצה זאת הוא 200 OK, המציין תגובה תקינה לבקשה, אך קיימים גם ערכים אחרים כגון 201 (נוצר).
  • 3XX – המשאב נמצא אבל הועבר. לרוב התגובה תהיה 301 (הועבר באופן קבוע) או 302 (הועבר זמנית).
  • 4XX – קיימת שגיאה בבקשה עצמה, למשל התבקש משאב לא קיים (404), אין הרשאות (403), יש צורך לבצע אימות (401) ועוד.
  • 5XX – התרחשה שגיאה בשרת. שני הערכים הנפוצים הם 500 (שגיאה כללית) ו-503 (שגיאה זמנית).

שורות הכותרת

בדומה לבקשת HTTP, גם התגובה מכילה שורות כותרת (header lines), אשר כל אחת מהן מורכבת משם וערך. לרוב, כל כותרת תהיה אחת מהכותרות הסטנדרטיות בפרוטוקול HTTP, אך למעשה ניתן להחזיר כל כותרת שבא לנו, או אפילו לא להחזיר כותרות כלל.

חלק גדול מכותרות התגובה מתחלקות לקבוצות הבאות:

  • כותרות המתארות את תוכן התגובה: סוג המידע שחזר, אורכו, תאריכי היצירה והשינוי, מזהה ייחודי, עוגיות שהשרת מעוניין לשמור בצד הלקוח וכד'.
  • כותרות המתארות את השרת: סוג השרת, תכונות הנתמכות ע"י השרת, וכד'.
  • כותרות המשפיעות על החיבור בין השרת ללקוח: מידע על אבטחה, או למשל, השרת יכול להודיע ללקוח שהוא סגר את חיבור ה-TCP.

להלן דוגמה לתגובה:

HTTP/1.1 200 OK
Date: Fri, 8 Jan 2016 11:29:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 31

<html><body>Hello</body></html>

תגובה זו מכילה שלוש שורות כותרת:

  • Date – שערכה הוא הזמן בשרת בעת יצירת התגובה.
  • Content-Type – שמציינת את סוג המידע אותו מכיל גוף התגובה, במקרה שלנו מדובר בקוד HTML בקידוד UTF-8.
  • Content-Length – אורך גוף התגובה בבתים.

גוף התגובה

לעתים קרובות השרת ירצה להחזיר ללקוח מידע כלשהו בתגובה לבקשה. מידע זה נקרא גוף התגובה, ויכול להיות מכל סוג. דוגמאות נפוצות לגוף התגובה הן קוד HTML, CSS או Javascript, מידע כלשהו בקידוד JSON או XML, קובץ בינארי כגון תמונה, gzip או מסמך.

במידה והתגובה מכילה גוף, היא חייבת להכיל גם את הכותרת Content-Length, שתציין את אורך הגוף בבתים. במידה והכותרת לא קיימת, הלקוח עלול להחשיב את התגובה כלא חוקית ולהתעלם ממנה.

נבחן דוגמה נוספת לתגובה:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Length: 23

{"error": "unexpected"}

בדוגמה זאת השרת החזיר קוד מצב 500 המציין שגיאה בשרת. בנוסף, גוף התגובה מטיפוס JSON מכיל מידע נוסף על השגיאה. שימו לב לכותרת Content-Type המכילה את סוג המידע החוזר (הנקרא לפעמים גם MIME type או Media type).

אז מה עושים עם כל המידע הזה?

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

לדוגמה, ניתן להשתמש בידע שצברנו על הפרוטוקול כדי לנתח את תעבורת הרשת של אתר אינטרנט מסויים. יש הרבה כלים המאפשרים לעשות זאת כמו Wireshark, Fiddler, אך האהוב עליי הוא Chrome DevTools המובנה בדפדפן כרום (גם דפדפנים אחרים מכילים מודול כזה, אך לטעמי כרום הוא הטוב ביותר).

מתוך דפדפן כרום, לחצו F12 לפתיחת תפריט המפתחים, ולאחר מכן לחצו על לשונית Network וטענו מחדש את הדף:

Chrome Dev Tools

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

מה הלאה?

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