Лучшие практики использования веб-служб Distance Matrix API

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

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

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

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

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

Типичный запрос Distance Matrix API обычно имеет следующую форму:

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

где output указывают формат ответа (обычно json или xml ).

Примечание . Все приложения Distance Matrix API требуют аутентификации. Получите дополнительную информацию об учетных данных для аутентификации .

SSL/TLS-доступ

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

Создание действующего URL-адреса

Вы можете подумать, что «действительный» URL-адрес самоочевиден, но это не совсем так. Например, URL-адрес, введенный в адресную строку браузера, может содержать специальные символы (например, "上海+中國" ); браузеру необходимо внутренне перевести эти символы в другую кодировку перед передачей. Точно так же любой код, который генерирует или принимает входные данные UTF-8, может рассматривать URL-адреса с символами UTF-8 как «действительные», но ему также потребуется перевести эти символы перед отправкой их на веб-сервер. Этот процесс называется URL-кодированием или процентным кодированием .

Специальные символы

Нам необходимо перевести специальные символы, поскольку все URL-адреса должны соответствовать синтаксису, указанному в спецификации универсального идентификатора ресурса (URI) . Фактически это означает, что URL-адреса должны содержать только специальный подмножество символов ASCII: знакомые буквенно-цифровые символы и некоторые зарезервированные символы для использования в качестве управляющих символов в URL-адресах. В этой таблице суммированы эти символы:

Сводка допустимых символов URL-адреса
Набор персонажи использование URL-адреса
Буквенно-цифровой abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLM NOPQRSTUVWXYZ 0 1 2 3 4 5 6 7 8 9 Текстовые строки, использование схемы ( http ), порт ( 8080 ) и т. д.
незарезервированный - _ . ~ Текстовые строки
Сдержанный ! * ' ( ) ; : @ & = + $ , / ? % # [ ] Управляющие символы и/или текстовые строки

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

  • Персонажи, с которыми вы хотите работать, существуют за пределами вышеуказанного набора. Например, символы иностранных языков, такие как上海+中國необходимо закодировать с использованием вышеуказанных символов. По общепринятому соглашению, пробелы (которые не разрешены в URL-адресах) также часто обозначаются знаком '+' .
  • Символы в приведенном выше наборе существуют как зарезервированные символы, но их нужно использовать буквально. Например, ? используется в URL-адресах для обозначения начала строки запроса; если вы хотите использовать строку «? и Мистерионы», вам нужно будет закодировать знак '?' характер.

Все символы, подлежащие кодированию 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-адреса, полученного в результате пользовательского ввода, иногда бывает непростой задачей. Например, пользователь может ввести адрес «5-я и Мейн-стрит». Как правило, вам следует создавать URL-адрес из его частей, рассматривая любой ввод пользователя как буквальные символы.

Кроме того, длина URL-адресов всех веб-служб платформы Google Maps и статических веб-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 в ответ на фиксированный сигнал тревоги — это плохо, поскольку в результате вызовы API синхронизируются с началом минуты, даже между разными устройствами, а не распределяются равномерно во времени. Плохо спроектированное приложение, делающее это, будет вызывать всплеск трафика, в шестьдесят раз превышающий нормальный уровень в начале каждой минуты.

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

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

Обработка ответов

В этом разделе обсуждается, как динамически извлекать эти значения из ответов веб-службы.

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

Используемая вами схема анализа зависит от того, возвращаете ли вы выходные данные в формате 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 «образец».
Выражение XPath: « /WebServiceResponse/result[type/text()='sample']/name »
Выбор:
    Sample XML
    

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

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

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

Текст в 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 можно найти в спецификации 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");
    }
  }
}