יום שלישי, 19 במרץ 2013

אבני הבניין של האינטרנט: URL


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

במשך שנים אני עובד בטכנולוגיות ווב ונתקל לא-פעם בחוסר הבנה בטכנולוגיות שהן אבני הבניין של האינטרנט. לדוגמה: מחשבה שההבדל העיקרי בין get ל post הוא מספר התוים שניתן להעביר בכל קריאה. זו טעות.

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

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

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


פוסט זה הוא חלק מהסדרה: אבני הבניין של האינטרנט.







מבנה ה Universal Resource Locator

כשאנו מאמתים קלט של משתמש, אנו לרוב מסתפקים בבדיקה של תחילית http בכדי לאמת שהערך שהוקלד הוא URL. לעתים מוסיפים בדיקה שיש "//" או את הנקודתיים של הפורט. כל זה טוב ויפה עבור בדיקה מהירה, אבל זה רחוק מאוד מהמציאות.

להלן מבנה ה URL הרשמי:

(1) סכמה
לרוב זהו שם הפרוטוקול כגון http או https[א], או אפילו ftp, git, gg או אחד מני רבים אחרים.
הסכמה יכולה לכלול Psuedo-Protocol שאלו בעצם שמות שנשמרו ע"י הדפדפן בכדי לגשת לפונקציונליות פנימית מבלי לבצע קריאת HTTP. לדוגמה:
  • about:blank טוען דף HTML ריק. משמש לבדיקות ולאיתור זליגות זיכרון ע"י מתכנתי ג'אווהסקריפט. רשימת פקודות about בדפדפנים השונים.
  • :javascript - בכדי להריץ פקודת ג'אווהסקריפט
  • :data - בכדי לקודד מסמך (קצר) inline בתוך ה URL כגון:
data:text/plain, hello%20world!


השימוש העקרי של סכמה זו היא הטמעה של תמונות קטנות בתוך המסמך בכדי לחסוך roundtrips לשרת.

(2) //
המבטאים שמדובר ב URL בעל חלק היררכי.
למשל: mailto:name@example.com?a=b הוא URL תקני ללא חלק היררכי.

(3) פרטי החיבור
משמש לפרוטוקולים ספציפיים כגון ftp או Https.

(4) כתובת
הכתובת יכולה להיות מתוארת באחת מ 3 סכמות:

Hostname
כתובת ה hostname היא כתובת טקסטואלית שמשמשת כ alias ל IP Address. כתובות IP הן דינמיות ועשויות להשתנות לאורך הזמן. תהליך ההפיכה של הכתובת הטקסטואלית (hostname) לכתובת IP היא תהליך שנקרא DNS Lookup. שרת ה (Domain Name Servers (DNS שאליו פנינו כעת יקרא לשורה של DNS אחרים בכדי ללמוד על הכתובות, כל שרת DNS אחד בתורו יודע לפרש חלק מהכתובת.
לדוגמה, עבור הכתובת http://ynet.co.il תחילה קוראים לשרת ה ROOT כדי ללמוד על כתובת ה IP של שרת ה "il" (מפרשים את כתובת ה hostname מהסוף להתחלה). בשלב הבא תהיה פנייה ל DNS של "il" וללמוד ממנו את כתובת ה IP של שרת ה "co" ורק אז מקבלים את הכתובת ה IP של השרת של YNET בכבודו ובעצמו. כמובן ששרתי DNS שומרים cache, כך שסביר ששמות נפוצים יחזרו מה cache ולא יהיה צורך לפנות לשרתי DNS אחרים.

אתר יכול להוסיף לעצמו תחיליות ל hostname לצורך חלוקה פנימית. לדוגמה, לאחר שהדומיין ynet.co.il נמצא, ynet יכולים להפנות ל"תתי אתרים / אפליקציה" שלהם בשם mail.ynet.co.il או en.ynet.co.il.

התחילית www היא תחילית לכל דבר ממש כמו mail או en שכל שרת יכול לבחור אם להשתמש בה או לא. www הוא קיצור של world-wide-web ובשנות ה-90 זו הייתה קונבנציה מאוד מקובלת. אם אתם נגשים לאתר ללא תחילית www הוא לרוב פשוט יפנה אתכם לכתובת האתר עם התחילית. או שלא.
יש דיון ארוך שנים העוסק בשאלה האם נכון להמשיך בקונבנציה דיון1 דיון2, ללא שום מסקנה ברורה באופק.

כתובת IPv4
כתובת ה IP שמוזנת ידנית ע"י המשתמש, כך שניתן לדלג על ה DNS Lookup (בהנחה שכתובת ה IP לא השתנתה).
כתובת ה IPv4 מורכבת בעיקרה מ4 octets של 0-255, כגון 10.3.0.163. URL הכולל כתובת IPv4 יכולה להראות כך: http://10.3.0.163/yo#hey.

כתובת IPv6
פרוטוקול IP גרסה 6 נוצר בכדי להתמודד עם המחסור שנוצר בכתובות ה IPv4 הזמינות. בערך לפני כחצי שנה היו אמורות להיגמר כל כתובת ה IPv4 בעולם וכדי למנוע זאת היה מעבר מסיבי לכתובות ארוכות יותר שיספיקו לעוד מספר רב של שנים.
כתובת IPv6 מכילה 8 קבוצות של מספרים hexdecimal ארבע-ספרתיים כגון 2001:0db8:85a3:0000:0000:8a2e:0370:7334. על מנת לחסוך באורך הכתובת ניתן לכתוב את אותה הכתובת כ 2001:db8:85a3:0:0:8a2e:370:7334 או אפילו כ 2001:db8:85a3::8a2e:370:7334.

לכתובות ה IPv6 יש עוד מספר חוקים שונים ומשונים. ניתן לקרוא עליהם כאן.

(5) Port
מספר בין 1-65536. הטווח 0-1023 שמור לרוב עבור מערכת ההפעלה ואין להשתמש בו כשאתם פותחים server socket. אם לא הוקלד ערך ב http - הערך יקבע על 80, וב https - יקבע על 443. כאשר יש שירות שנענה גם ל http וגם ל https, מקובל שמספרי הפורט יהיו עוקבים, למשל 5000 ו 5001.

(6) רשימה היררכית של Resources.
רוב הדפדפנים יקבלו גם "\" בתור ה Separator.
סטייה מהתקן על מנת "להקל" על המשתמש  - היא התנהגות נפוצה בקרב דפדפנים.

(7) Query String
אנשים נוטים להניח שהפורמט של Query Sting נדרש להיות key1=value1&key2=value2 – אבל זו סתם קונבנציה שהשתרשה במשך השנים והיא איננה מחייבת. לדוגמה: מותר להשתמש ב ? בתוך ה Query String בכדי לבצע separation בין ערכים.
פעם כתבתי קוד שהניח שיש רק ? אחד ב URL - וכך נכשל. שיחה קצרה עם המפתח שכתב את הקוד שגרם לכשלון למדה אותי על החוקיות של מבנה ה Query String.

בפועל Query String הוא BLOB ש:
  • מיוצג בתווי ASCII. אם אתם רוצים באמת להעביר מידע בינארי (מותר) עליכם להשתמש ב Encoding שנקרא Base64.
  • נתחם ע"י סימן ה # הראשון שמופיע.
Query String משמש לרוב על מנת להעביר פרמטרים לשרת. Query String הוא חלק לכל דבר ועניין מה URL כך ששינוי בתו יחיד בתוך ה Query String משמעו - URL אחר.

(8) Fragment ID
בניגוד ל Query String שמיועד לצד-השרת, ה FID מיועד לצריכה ע"י קוד צד-לקוח. קונבנציה מקובלת היא שה Fragment מתאר מיקום במסמך. לדוגמה: אם תבצעו inspect למסמך ותמצאו id של אלמנט ב DOM, תוכלו להוסיף אותו ל URL כ FID כך שמעתה הדפדפן יציג את הדף כאשר בראש המסך הוא יציג את האלמנט עם ה ID שצוין.

באפליקציות מודרניות משתמשים ב FID על מנת לתאר את ה State של האפליקציה, לדוגמה: איזה טאב נבחר ואיזה Panels הם פתוחים. אין מניעה לקודד state מורכב כ BLOB על ה FID. שינוי ה FID-בלבד ב URL לא יגרום לטעינה מחודשת של הדף אלא יזרוק event מסוג hashChange. ה FID לא אמור להישלח לשרת.

ע"פ התקן, אגב, לא אמורה להיות מגבלה על אורך ה URL אולם IE8 ו IE9 מוגבלים ל 2048 תווים, ודפדפנים אחרים מתירים פי 2 או 3 מכך (בזה אני מעודכן כמה שנים אחורה, אולי דברים השתנו...).


Relative URL

עוד נקודה אחרונה ורלוונטית היא היכולת להתייחס ל "URL יחסי", כלומר URL יחסית ל URL של ה Document (דף ה HTML) הנוכחי. URL יחסי אמור לעבוד מכל הבחינות בדיוק כמו URL מלא.

נניח שאני כרגע בכתובת
http://amazon.com/product/ref=fs_cl/index.html?w=1

URL יחסי שאינו מתחיל ב "/", למשל "gogo/a/b" שקול ל:

http://amazon.com/product/ref=fs_cl/gogo/a/b 


URL יחסי שמתחיל ב "/" למשל "gogo/a/b/" שקול ל:

http://amazon.com/gogo/a/b


כללי ה path הבסיסיים של unix תקפים כך שה URL היחסי "a/b/.." שקול ל

http://amazon.com/product/a/b


בהגדרה, URL יחסי הוא כזה שלא מתחיל בפרוטוקול (למשל http) וגם לא מכיל את האינדיקטור ל URL היררכי "//".

URL יחסי תקני הוא "www.walla.co.il//" - שמצביע לאתר וואלה עם הפרוטוקול שכרגע בשימוש בדפדפן (למשל ftp).
דוגמה אחרת היא ה URL היחסי "details#" - שהוא URL יחסי לכתובת הדפדפן הנוכחית עם FID בשם details. שימוש ב details# לא תגרום לדף להיטען מחדש (מכיוון שה FID הוא חלק ה URL הרלוונטי לצד-הלקוח בלבד).

אם תקלידו בדפדפן מודרני URL יחסי בכתובת, רוב הפעמים הוא יפנה אתכם לחיפוש (גוגל). URL יחסיים כן יעבדו אם יהיו לינק בתוך ה HTML markup.


שיהיה בהצלחה!



---

[א] Https הוא Http שמוצפן על גבי פרוטוקול שנקרא SSL (או בגרסה היותר חדשה: TLS). השימוש ב Http יעשה באתרים שמכילים מידע פרטי או מסווג (למשל קופת חולים או בנק). במערכות ארגוניות נהוג להשתמש בעיקר ב https משיקולי אבטחה.

פוסט רלוונטי (כולל כמה נקודות על Encoding של URL): http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding

9 תגובות:

  1. ההסבר שלך על address resolution לא נכון.
    הדפדפן לא צריך לקרוא לסדרה של שרתי DNS. הוא שולח שאילתה בודדת לשרת ה DNS. שרת ה DNS הוא זה שפונה בצורה היררכית לקבלת המידע.

    השבמחק
  2. אתה צודק.

    תודה רבה, למדתי משהו :)

    השבמחק
  3. סעיף 3 קיים גם ב-HTTP ן-HTTPS
    http://en.wikipedia.org/wiki/Basic_access_authentication

    השבמחק
    תשובות
    1. תודה מנחם!

      אציין שאני תמיד ראיתי basic authentication מתבצע כאשר הפרמטרים מועברים ב Query String.

      מחק
    2. כלומר: כנראה ששימוש ב Query String היא גם דרך נפוצה (אם כי אולי לא הייתה כוונת המשורר)

      מחק
  4. רק לשם השלמה,
    URL יחסי לא חייב להתייחס לכתובת שמופיעה בדפדפן. קיים ב-html תג base שמאפשר להגדיר מחדש את הכתובת אליה יתייחסו שאר הקישורים בדף, הוא לא בשימוש מאוד נפוץ אבל בהחלט מופיע פה ושם.

    השבמחק
  5. תודה ארז.

    אני שמח מאוד לראות את מפגן הידע, שמבחינתי משלים ומשפר את הפוסט.

    תודה לכולם!!

    השבמחק
  6. לגבי כתובות יחסיות, צריך לזכור לקורא את הכתובת כמו שצריך.

    ה־URL אליו התייחסת הוא http://amazon.com/product/ref=fs_cl?w=1 כאשר הנתיב למשאב הוא /products/, והמשאב עצמו ("שם הקובץ") הוא ref=fs_c1.

    כאשר אתה פונה למשאב אחר שנמצא באותו נתיב (דוגמה 1) יוחלף המשאב במשאב החדש, כך ש־gogo/a/b יוביל ל־http://amazon.com/product/gogo/a/b, ללא ref=fs_c1. לשם השוואה, דמיין שאתה טוען תמונה כלשהי שנמצאת באותה תיקייה של דף HTML. הדפדפן יטען את הקובץ image.png ולא את index.html/image.png.

    מאותה סיבה גם הדוגמה השלישית בסעיף הדן בכתובות יחסיות שגוי. שתי הנקודות מורות לדפדפן לטפס תיקייה אחת מעלה, כלומר לצאת מ־products ולכן הדוגמה השנייה והשלישית יצביעו למעשה על http://amazon.com/a/b.

    חשוב לזכור: Query Strings לא עוברים מכתובת לכתובת בקישורים יחסיים, כך ששלושת הדוגמאות שגויות כי בכתובת היחסית לא ציינת את ה־Query String בכתובת החדשה. כמו־כן, צריך לזכור שבניגוד למערכות קבצים רגילות, ב־URL ישנו מושג של "קובץ בררת מחדל" שיטען כאשר כתובת המשאב לא מציינת שם קובץ; לרוב ייטען קובץ בשם index.html או קובץ עם שם דומה, בהתאם להגדרות השרת.

    השבמחק
    תשובות
    1. היי תומר,

      אתה בהחלט צודק - תודה רבה!
      הוספתי את שם ה resource ב URL הבסיס בכדי לתקן את הדוגמאות.

      אתה כמובן גם צודק לגבי ה query string ולגבי שם הקובץ ברירת-המחדל.

      שוב תודה,

      ליאור

      מחק