Рекомендации по использованию веб-сервисов Places API

Веб-сервисы платформы Google Карт – это набор HTTP-интерфейсов для сервисов Google, предоставляющих географические данные для ваших приложений с картами.

В этом руководстве описаны некоторые распространенные методы, которые могут быть полезны при настройке запросов к веб-сервису и обработке ответов. Полная документация по Places API приведена в руководстве для разработчиков.

Что такое веб-сервис?

Веб-сервисы платформы Google Карт – это интерфейс для запроса данных Maps API из внешних сервисов и использования этих данных в приложениях Карт. Эти сервисы предназначены для использования вместе с картой, как указано в Лицензионных ограничениях Условий использования платформы Google Карт.

Веб-сервисы Maps API используют HTTP(S)-запросы к определенным URL, передавая параметры URL и/или данные POST в формате JSON в качестве аргументов сервисам. Как правило, эти сервисы возвращают данные в теле ответа в формате JSON или XML, чтобы ваше приложение могло их проанализировать и обработать.

Обычно запрос к Places API выглядит следующим образом:

https://places.googleapis.com/v1/places/PLACE_ID?parameters

Примечание. Для всех приложений Places API требуется аутентификация. Подробнее об учетных данных для аутентификации…

Доступ по протоколу SSL/TLS

Протокол HTTPS является обязательным для всех запросов к платформе Google Карт, в которых используются ключи API или содержатся данные пользователей. Запросы, отправленные по протоколу HTTP и содержащие конфиденциальные данные, могут быть отклонены.

Создание действительного 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-адресах для обозначения начала строки запроса. Если вы хотите передать строку "? 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

Процентное кодирование текста, который вводит пользователь, может оказаться непростой задачей. Например, он может ввести адрес как "5th&Main St." Обычно URL необходимо создавать из отдельных частей, обрабатывая все вводимые пользователем данные как символьные литералы.

Кроме того, URL для всех веб-сервисов платформы Google Карт и Maps Static API могут содержать не более 16 384 символов. Для большинства служб этого размера более чем достаточно. но в некоторых случаях из-за ряда параметров длина URL может существенно увеличиться.

Бережное использование API Google

Неправильно разработанные API-клиенты могут создавать излишнюю нагрузку на интернет и серверы Google. В этом разделе описываются практические рекомендации для клиентов API. Следуя этим рекомендациям, вы сможете избежать блокировки приложения за непреднамеренное злоупотребление API.

Экспоненциальная выдержка

В редких случаях при обработке запроса может произойти ошибка. Вы можете получить код ответа HTTP 4XX или 5XX или же TCP-подключение может просто не установиться между вашим клиентом и сервером Google. Часто стоит повторить запрос, поскольку последующий запрос может быть выполнен, даже если предыдущий не удался. Однако не следует просто циклически отправлять запросы на серверы Google. Такое поведение может перегрузить сеть между клиентом и Google, что приведет к проблемам для многих пользователей.

Повторно пробовать лучше с возрастающими задержками между попытками. Обычно задержка увеличивается в несколько раз при каждой попытке. Такой подход называется экспоненциальной выдержкой.

Например, рассмотрим приложение, которое хочет отправить следующий запрос к 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}")

Также убедитесь, что в цепочке вызовов приложения нет кода повторных попыток, который приводит к тому, что запросы отправляются слишком часто.

Синхронизированные запросы

Большое количество синхронных запросов к API Google может быть расценено как распределенная атака типа "отказ в обслуживании" (DDoS) на инфраструктуру Google. Чтобы избежать этого, убедитесь, что запросы к API не синхронизируются между клиентами.

Например, рассмотрим приложение, которое показывает время в текущем часовом поясе. Это приложение, скорее всего, установит будильник в операционной системе клиента, который будет срабатывать в начале каждой минуты, чтобы обновлять отображаемое время. Приложение не должно выполнять вызовы API в рамках обработки, связанной с этим сигналом.

Вызовы API в ответ на фиксированный сигнал тревоги нежелательны, поскольку они синхронизируются с началом минуты, даже на разных устройствах, а не распределяются равномерно во времени. Если приложение плохо разработано, то в начале каждой минуты трафик будет в 60 раз выше обычного.

Вместо этого, одним из хороших вариантов реализации будет будильник, устанавливаемый в случайно выбранную секунду. Когда срабатывает второй будильник, приложение вызывает нужные 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. В этом разделе мы расскажем, как с помощью XPath обращаться к элементам XML-документа, чтобы вы могли обращаться к нескольким элементам и создавать сложные запросы.

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 которого – "пример".
Выражение XPath: /WebServiceResponse/result[type/text()='sample']/name.
Выбор
    Sample XML
    

При выборе элементов вы выбираете узлы, а не только текст в этих объектах. Как правило, вам нужно будет перебрать все найденные узлы и извлечь текст. Вы также можете сопоставлять текстовые узлы напрямую. Подробнее о текстовых узлах …

Обратите внимание, что XPath также поддерживает узлы атрибутов. Однако все веб-сервисы Google Карт обслуживают элементы без атрибутов, поэтому сопоставление атрибутов не требуется.

Выбор текста в выражениях

Текст в XML-документе указывается в выражениях XPath с помощью оператора текстового узла. Оператор "text()" указывает на извлечение текста из указанного узла. Например, выражение XPath //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 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");
    }
  }
}