使用 Map Management API Web 服务的最佳实践

Google Maps Platform 网络服务是一组 Google 服务的 HTTP 接口,可为您的地图应用提供地理数据。

本指南介绍了一些有助于设置您的 网络服务 请求和处理服务响应的常见做法。如需查看 Map Management API 的完整文档,请参阅开发者指南 。

什么是网络服务?

Google Maps Platform Web 服务是一个接口,用于从外部服务请求地图 API 数据,并在您的地图应用中使用这些数据。根据 Google Maps Platform 服务条款中的许可限制,这些服务旨在与地图结合使用。

Maps API 网络服务使用 HTTP(S) 请求向特定网址传递网址参数和/或 JSON 格式的 POST 数据,作为服务的实参。通常,这些服务会在响应正文中以 JSON 格式返回数据,供您的应用解析和/或处理。

以下示例展示了对 list Map Configs 方法的 REST GET 请求:

https://mapmanagement.googleapis.com/v2/projects/PROJECT_NUMBER_OR_ID/mapConfigs

在请求的 Authorization header 中添加 OAuth 令牌。

SSL/TLS 访问权限

对于使用 API 密钥或包含用户数据的所有 Google Maps Platform 请求,都必须使用 HTTPS。通过 HTTP 发出的包含敏感数据的请求可能会被拒绝。

构建有效网址

您可能认为“有效”网址不言自明,但实际并非如此。例如,在浏览器地址栏中输入的网址可能包含特殊字符(例如 "上海+中國");浏览器需要先在内部将这些字符转换为其他编码,然后再进行传输。同样,任何生成或接受 UTF-8 输入的代码都可能会将包含 UTF-8 字符的网址视为“有效”,但同样需要先转换这些字符,然后再将其发送给网络服务器。该过程称为 网址编码或百分号编码。

特殊字符

我们之所以需要转换特殊字符,是因为所有网址都需要符合统一资源标识符 (URI) 规范所规定的语法。实际上,这意味着网址必须只包含一个特殊的 ASCII 字符子集:大家熟悉的字母数字符号以及一些在网址内用作控制字符的预留字符。下表汇总了这些字符:

有效网址字符汇总
字符集字符在网址中的用法
字母数字 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 文本字符串、在 scheme 中使用 (http)、端口 (8080) 等
非预留字符 - _ . ~~ 文本字符串
预留字符 ! * ' ( ) ; : @ & = + $ , / ? % # [ ]% # [ ] 控制字符和/或文本字符串

构建有效网址时,您必须确保网址只包含 表格中显示的那些字符。让网址按照上述字符集使用字符通常会带来两个问题,一个是遗漏问题,一个是替换问题:

  • 您要处理的字符未包含在上述字符集内。举例来说,非英语字符(例如 上海+中國)需要使用上述字符进行编码。按照常见惯例,空格(网址内不允许使用空格)通常也使用加号字符 '+' 表示。
  • 字符在上述字符集内存在且属于预留字符,但需要按原义使用。例如,? 在网址内用于表示查询字符串的开头;如果您想要使用字符串“? and the Mysterions”,则需要对 '?' 字符进行编码。

所有要进行网址编码的字符都会使用一个 '%' 字符和一个与其 UTF-8 字符对应的双字符十六进制值进行编码。例如,UTF-8 中的 上海+中國 在进行网址编码后将变为 %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B。字符串 ? and the Mysterians 在进行网址编码后将变为 %3F+and+the+Mysterians 或 %3F%20and%20the%20Mysterians。

需要编码的常见字符

以下是一些必须进行编码的常见字符:

不安全的字符 编码后的值
空格 %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

转换您通过用户输入获取的网址有时颇为棘手。例如,用户可能会输入“5th&Main St.”这样的地址。一般而言,您应该根据网址的组成部分来构建网址,将所有用户输入都视为原义字符。

此外,对于所有的 Google Maps Platform 网络服务 API 或静态网络 API,网址最多可包含 16384 个字符。对于大多数服务,很少出现接近这一字符数限制的情况。但请注意,某些服务具有的若干参数可能会导致网址较长。

合理使用 Google API

未经合理设计的 API 客户端可能会给网络和 Google 服务器增加额外负载。 此部分提供 API 客户端的一些最佳做法。遵循这些最佳做法有助于您的应用避免因无意中滥用 API 而遭到屏蔽。

指数后退

在极少数情况下,您的请求可能会在传送时出错;您可能会收到 4XX 或 5XX HTTP 响应代码,或者 TCP 连接可能会在客户端与 Google 服务器之间的某处出现错误。通常情况下,重试请求值得一试。首次请求失败后,后续请求可能会成功。但是,切勿进行循环操作,即反复向 Google 服务器发出请求。这种循环行为可能会使客户端与 Google 之间的网络过载,给多方造成问题。

较好的方法是不断增加两次重试之间的延迟。通常情况下,可利用乘积因子增加每次重试中的 延迟,此方法称为 指数后退。

例如,假设某应用希望向 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 发起大量同步请求,我们会将其视为对 Google 基础设施的分布式拒绝服务 (DDoS) 攻击,并采取相应处理措施。为避免这种情况,请确保避免在客户端之间同步发起 API 请求。

例如,假设某应用显示当前时区的时间。此应用可能会在客户端操作系统中设置警报,并在分钟开始时唤醒该操作系统,以便更新显示时间。当处理与该警报相关的事务时,此应用不能调用任何 API。

但通过 API 调用来响应固定警报的做法并不妥当,因为这会导致 API 调用与分钟开始时间同步(即使在不同设备之间亦是如此),而非在一段时间内均匀分布。若应用未经合理设计,则此操作会导致在每分钟开始时产生正常水平 60 倍的数据流量。

相较之下,我们可以采用一种较好的设计,即将第二个警报设为随机选择的时间。 触发第二个警报时,应用会调用所需的任何 API 并存储结果。 当应用想要在分钟开始时更新其显示时间时,它会使用先前存储的结果,而非再次调用该 API。借助此方法,API 调用即可在一段时间内均匀分布。此外,当显示时间更新时,这类 API 调用操作并不会导致延迟呈现内容。

除了分钟开始时间,您还应注意不要设置其他常见同步时间,如小时开始时间和每天开始时间(午夜)。

处理响应

此部分介绍如何以动态方式从 Web 服务响应中提取这些值。

Google 地图网络服务提供的响应易于理解,但并非完全用户友好。执行查询时,您可能希望提取一些特定值,而不是显示一组数据。通常,您会希望解析网络服务的响应,并仅提取您感兴趣的值。

您使用的解析方案取决于您是否以 JSON 格式返回输出。JSON 响应已采用 JavaScript 对象的形式,因此可以在客户端上的 JavaScript 本身中进行处理。