שיטות מומלצות לשימוש בשירותי אינטרנט של Directions API (גרסה קודמת)

שירותי האינטרנט של Google Maps Platform הם אוסף של ממשקי HTTP לשירותי Google, שמספקים נתונים גיאוגרפיים לאפליקציות המפות שלכם.

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

מהו שירות אינטרנט?

שירותי האינטרנט של Google Maps Platform הם ממשק לשליחת בקשות לנתוני Maps API משירותים חיצוניים ולשימוש בנתונים באפליקציות של מפות Google. השירותים האלה מיועדים לשימוש בשילוב עם מפה, בהתאם להגבלות הרישיון שמופיעות בתנאים ובהגבלות של Google Maps Platform.

שירותי האינטרנט של Maps APIs משתמשים בבקשות HTTP(S) לכתובות URL ספציפיות, ומעבירים פרמטרים של כתובות URL ו/או נתוני POST בפורמט JSON כארגומנטים לשירותים. בדרך כלל, השירותים האלה מחזירים נתונים בגוף התגובה בפורמט JSON או XML לצורך ניתוח או עיבוד על ידי האפליקציה.

בקשה אופיינית ל-Directions API (מאגר מידע מדור קודם) היא בדרך כלל מהצורה הבאה:

https://maps.googleapis.com/maps/api/directions/output?parameters

כאשר output מציין את פורמט התשובה (בדרך כלל json או xml).

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

גישה ל-SSL/TLS

חובה להשתמש ב-HTTPS בכל הבקשות ל-Google Maps Platform שמשתמשות במפתחות API או שמכילות נתוני משתמשים. בקשות שמתבצעות באמצעות HTTP ומכילות מידע אישי רגיש עשויות להידחות.

יצירת כתובת URL תקינה

יכול להיות שאתם חושבים שכתובת URL 'תקינה' היא דבר מובן מאליו, אבל זה לא בדיוק המצב. לדוגמה, כתובת URL שמוזנת בסרגל הכתובות בדפדפן עשויה להכיל תווים מיוחדים (למשל, "上海+中國"). הדפדפן צריך לתרגם את התווים האלה באופן פנימי לקידוד אחר לפני השידור. באופן דומה, כל קוד שמייצר או מקבל קלט UTF-8 עשוי להתייחס לכתובות URL עם תווים בקידוד UTF-8 כאל כתובות 'תקינות', אבל הוא גם יצטרך לתרגם את התווים האלה לפני שהוא שולח אותם לשרת אינטרנט. התהליך הזה נקרא קידוד כתובות URL או קידוד באחוזים.

תווים מיוחדים

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

סיכום של תווים חוקיים בכתובת URL
הגדרהתוויםשימוש בכתובת URL
אלפאנומרי a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 מחרוזות טקסט, שימוש בסכימה (http), יציאה (8080) וכו'.
לא שמור - _ . ~ מחרוזות טקסט
בוצעה הזמנה ! * ' ( ) ; : @ & = + $ , / ? % # [ ] תווי בקרה או מחרוזות טקסט

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

  • יש תווים שרוצים לטפל בהם מחוץ לקבוצה שלמעלה. לדוגמה, תווים בשפות זרות כמו 上海+中國 צריכים להיות מקודדים באמצעות התווים שלמעלה. לפי המוסכמה המקובלת, רווחים (שלא מותרים בכתובות URL) מיוצגים לעיתים קרובות גם באמצעות התו פלוס '+'.
  • התווים האלה קיימים בקבוצה שלמעלה כתווים שמורים, אבל צריך להשתמש בהם באופן מילולי. לדוגמה, התו ? משמש בכתובות URL כדי לציין את תחילת מחרוזת השאילתה. אם רוצים להשתמש במחרוזת '? and the Mysterions', צריך לקודד את התו '?'.

כל התווים שמקודדים בכתובת URL מקודדים באמצעות התו '%' וערך הקסדצימלי בן שני התווים שמתאים לתווים ב-UTF-8. לדוגמה, 上海+中國 ב-UTF-8 יותאם לקידודי התווים שמתאימים לכתובות URL באופן הבא: %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B. המחרוזת ? and the Mysterians תעבור קידוד כתובות URL כ-%3F+and+the+Mysterians או כ-%3F%20and%20the%20Mysterians.

תווים נפוצים שצריך לקודד

דוגמאות לתווים נפוצים שצריך לקודד:

תו לא בטוח ערך מקודד
רווח %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

לפעמים קשה להמיר כתובת URL שמתקבלת מקלט של משתמש. לדוגמה, משתמש יכול להזין כתובת כ-"5th&Main St." באופן כללי, צריך ליצור את כתובת ה-URL מהחלקים שלה, ולהתייחס לכל קלט של משתמשים כאל תווים מילוליים.

בנוסף, כתובות URL מוגבלות ל-16,384 תווים בכל שירותי האינטרנט של Google Maps Platform ובממשקי API סטטיים של אינטרנט. ברוב השירותים, לא תגיעו למגבלת התווים הזו. עם זאת, חשוב לזכור שלשירותים מסוימים יש כמה פרמטרים שיכולים לגרום לכתובות URL ארוכות.

שימוש הוגן ב-Google APIs

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

השהיה מעריכית לפני ניסיון חוזר (exponential backoff)

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

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

לדוגמה, נניח שאפליקציה רוצה לשלוח את הבקשה הבאה ל-Time Zone API:

https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510&timestamp=1331161200&key=YOUR_API_KEY

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

import json
import time
import urllib.error
import urllib.parse
import urllib.request

# The maps_key defined below isn't a valid Google Maps API key.
# You need to get your own API key.
# See https://developers.google.com/maps/documentation/timezone/get-api-key
API_KEY = "YOUR_KEY_HERE"
TIMEZONE_BASE_URL = "https://maps.googleapis.com/maps/api/timezone/json"


def timezone(lat, lng, timestamp):

    # Join the parts of the URL together into one string.
    params = urllib.parse.urlencode(
        {"location": f"{lat},{lng}", "timestamp": timestamp, "key": API_KEY,}
    )
    url = f"{TIMEZONE_BASE_URL}?{params}"

    current_delay = 0.1  # Set the initial retry delay to 100ms.
    max_delay = 5  # Set the maximum retry delay to 5 seconds.

    while True:
        try:
            # Get the API response.
            response = urllib.request.urlopen(url)
        except urllib.error.URLError:
            pass  # Fall through to the retry loop.
        else:
            # If we didn't get an IOError then parse the result.
            result = json.load(response)

            if result["status"] == "OK":
                return result["timeZoneId"]
            elif result["status"] != "UNKNOWN_ERROR":
                # Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or
                # ZERO_RESULTS. There is no point retrying these requests.
                raise Exception(result["error_message"])

        if current_delay > max_delay:
            raise Exception("Too many retry attempts.")

        print("Waiting", current_delay, "seconds before retrying.")

        time.sleep(current_delay)
        current_delay *= 2  # Increase the delay each time we retry.


if __name__ == "__main__":
    tz = timezone(39.6034810, -119.6822510, 1331161200)
    print(f"Timezone: {tz}")

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

בקשות מסונכרנות

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

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

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

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

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

עיבוד תשובות

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

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

סכימת הניתוח שבה משתמשים תלויה בפורמט הפלט – XML או JSON. תגובות JSON, שכבר נמצאות בפורמט של אובייקטים ב-JavaScript, יכולות לעבור עיבוד ב-JavaScript עצמה בצד הלקוח. צריך לעבד תגובות XML באמצעות מעבד XML ושפת שאילתות XML כדי להתייחס לרכיבים בפורמט XML. בדוגמאות הבאות אנחנו משתמשים ב-XPath, כי יש לו תמיכה נפוצה בספריות לעיבוד XML.

עיבוד XML באמצעות XPath

‫XML הוא פורמט מידע מובנה יחסית מבוסס, שמשמש להעברת נתונים. למרות ש-XML לא קליל כמו JSON, הוא מספק תמיכה בשפות נוספות וכלים חזקים יותר. לדוגמה, קוד לעיבוד XML ב-Java מוטמע בחבילות javax.xml.

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

ביטויי XPath

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

ב-XPath משתמשים בביטויים כדי לבחור רכיבים במסמך XML, באמצעות תחביר שדומה לזה שמשמש לנתיבי ספריות. הביטויים האלה מזהים רכיבים בעץ של מסמך XML, שהוא עץ היררכי שדומה ל-DOM. באופן כללי, ביטויי XPath הם מדויקים, כלומר הם יתאימו לכל הצמתים שעומדים בקריטריונים שצוינו.

בדוגמאות שלנו נשתמש ב-XML מופשט:

<WebServiceResponse>
 <status>OK</status>
 <result>
  <type>sample</type>
  <name>Sample XML</name>
  <location>
   <lat>37.4217550</lat>
   <lng>-122.0846330</lng>
  </location>
 </result>
 <result>
  <message>The secret message</message>
 </result>
</WebServiceResponse>

בחירת צמתים בביטויים

בחירות של XPath בוחרות צמתים. צומת הבסיס כולל את כל המסמך. בוחרים את הצומת הזה באמצעות הביטוי המיוחד /. חשוב לזכור שצומת הבסיס הוא לא הצומת ברמה העליונה במסמך ה-XML, אלא הוא נמצא רמה אחת מעל הרכיב ברמה העליונה וכולל אותו.

צמתי רכיבים מייצגים את הרכיבים השונים בעץ של מסמך ה-XML. לדוגמה, רכיב <WebServiceResponse> מייצג את הרכיב ברמה העליונה שמוחזר בשירות לדוגמה שלמעלה. בוחרים צמתים ספציפיים באמצעות נתיבים מוחלטים או יחסיים, שמסומנים בנוכחות או בהיעדר של התו / בתחילת הנתיב.

  • נתיב מוחלט: הביטוי /WebServiceResponse/result בוחר את כל הצמתים <result> שהם צאצאים של הצומת <WebServiceResponse>. (שימו לב ששני הרכיבים האלה הם צאצאים של צומת הבסיס /).
  • נתיב יחסי מההקשר הנוכחי: הביטוי "result" יתאים לכל רכיבי <result> בהקשר הנוכחי. בדרך כלל לא צריך לדאוג לגבי ההקשר, כי בדרך כלל מעבדים את התוצאות של שירותי האינטרנט באמצעות ביטוי יחיד.

אפשר להוסיף לכל אחד מהביטויים האלה נתיב עם תו כללי לחיפוש, שמסומן באמצעות קו נטוי כפול ('//'). התו הכללי הזה מציין שאפשר להתאים אפס רכיבים או יותר בנתיב שביניהם. לדוגמה, הביטוי XPath ‏ "//formatted_address," יתאים לכל הצמתים עם השם הזה במסמך הנוכחי. הביטוי //viewport//lat יתאים לכל רכיבי <lat> שאפשר לאתר את <viewport> כרכיב הורה שלהם.

כברירת מחדל, ביטויי XPath מתאימים לכל הרכיבים. אפשר להגביל את הביטוי כך שיתאים לרכיב מסוים על ידי ציון פרדיקט, שמוקף בסוגריים מרובעים ([]). לדוגמה, ביטוי ה-XPath ‏/GeocodeResponse/result[2] תמיד מחזיר את התוצאה השנייה.

סוג הביטוי
צומת בסיס
ביטוי XPath:‏  "/"
בחירה:
    <WebServiceResponse>
     <status>OK</status>
     <result>
      <type>sample</type>
      <name>Sample XML</name>
      <location>
       <lat>37.4217550</lat>
       <lng>-122.0846330</lng>
      </location>
     </result>
     <result>
      <message>The secret message</message>
     </result>
    </WebServiceResponse>
    
נתיב מוחלט
ביטוי XPath:‏  "/WebServiceResponse/result"
בחירה:
    <result>
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    </result>
    <result>
     <message>The secret message</message>
    </result>
    
נתיב עם תו כללי לחיפוש
ביטוי XPath:‏  "/WebServiceResponse//location"
בחירה:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
נתיב עם תנאי
ביטוי XPath:‏  "/WebServiceResponse/result[2]/message"
בחירה:
    <message>The secret message</message>
    
כל הצאצאים הישירים של result
ביטוי XPath:‏  "/WebServiceResponse/result[1]/*"
בחירה:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
name של result שהטקסט type שלו הוא sample.
ביטוי XPath:‏  "/WebServiceResponse/result[type/text()='sample']/name"
בחירה:
    Sample XML
    

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

שימו לב: XPath תומך גם בצמתי מאפיינים, אבל כל שירותי האינטרנט של מפות Google מציגים רכיבים ללא מאפיינים, ולכן אין צורך בהתאמה של מאפיינים.

בחירת טקסט בביטויים

טקסט במסמך XML מצוין בביטויי XPath באמצעות אופרטור text node. האופרטור הזה "text()" מציין חילוץ של טקסט מהצומת שצוין. לדוגמה, הביטוי //formatted_address/text() יחזיר את כל הטקסט בתוך רכיבי <formatted_address>.

סוג הביטוי
כל צמתי הטקסט (כולל רווחים)
ביטוי XPath:‏  "//text()"
בחירה:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
בחירת טקסט
ביטוי XPath:‏  "/WebServiceRequest/result[2]/message/text()"
בחירה:
    The secret message
    
בחירה רגישה להקשר
ביטוי XPath:‏  "/WebServiceRequest/result[type/text() = 'sample']/name/text()"
בחירה:
    Sample XML
    

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

מידע נוסף על XPath זמין במפרט XPath W3C.

הערכת XPath ב-Java

‫Java תומכת באופן נרחב בניתוח XML ובשימוש בביטויי XPath בחבילה javax.xml.xpath.*. לכן, בקוד לדוגמה שבקטע הזה נעשה שימוש ב-Java כדי להמחיש איך לטפל ב-XML ולנתח נתונים מתגובות של שירות XML.

כדי להשתמש ב-XPath בקוד Java, צריך קודם ליצור מופע של XPathFactory ולקרוא ל-newXPath() בפקטורי הזה כדי ליצור אובייקט XPath . לאחר מכן, האובייקט יכול לעבד ביטויי XML ו-XPath שעברו באמצעות השיטה evaluate().

כשמעריכים ביטויי XPath, חשוב לחזור על כל 'קבוצות הצמתים' האפשריות שעשויות להיות מוחזרות. מכיוון שהתוצאות האלה מוחזרות כצמתי DOM בקוד Java, צריך ללכוד את הערכים המרובים האלה באובייקט NodeList ולבצע איטרציה על האובייקט הזה כדי לחלץ טקסט או ערכים מהצמתים האלה.

בדוגמה הבאה מוצג קוד שיוצר אובייקט XPath, מקצה לו XML וביטוי XPath, ומעריך את הביטוי כדי להדפיס את התוכן הרלוונטי.

import org.xml.sax.InputSource;
import org.w3c.dom.*;
import javax.xml.xpath.*;
import java.io.*;

public class SimpleParser {

  public static void main(String[] args) throws IOException {

	XPathFactory factory = XPathFactory.newInstance();

    XPath xpath = factory.newXPath();

    try {
      System.out.print("Web Service Parser 1.0\n");

      // In practice, you'd retrieve your XML via an HTTP request.
      // Here we simply access an existing file.
      File xmlFile = new File("XML_FILE");

      // The xpath evaluator requires the XML be in the format of an InputSource
	  InputSource inputXml = new InputSource(new FileInputStream(xmlFile));

      // Because the evaluator may return multiple entries, we specify that the expression
      // return a NODESET and place the result in a NodeList.
      NodeList nodes = (NodeList) xpath.evaluate("XPATH_EXPRESSION", inputXml, XPathConstants.NODESET);

      // We can then iterate over the NodeList and extract the content via getTextContent().
      // NOTE: this will only return text for element nodes at the returned context.
      for (int i = 0, n = nodes.getLength(); i < n; i++) {
        String nodeString = nodes.item(i).getTextContent();
        System.out.print(nodeString);
        System.out.print("\n");
      }
    } catch (XPathExpressionException ex) {
	  System.out.print("XPath Error");
    } catch (FileNotFoundException ex) {
      System.out.print("File Error");
    }
  }
}