Как стать автором
Обновить

XML-шлюз своими руками

Время на прочтение6 мин
Количество просмотров7.9K
Если вам приходилось работать с платежными системами, сервисами регистрации и другими ресурсами, реализующими удаленное взаимодействие с центральным сервером, то наверняка сталкивались с понятием «шлюз». Это сервис, который предназначен для проведения операций на центральном сервере путем обмена электронными сообщениями между центральным сервером (сервером системы, имеющей шлюз) и программным обеспечением некого клиента.

Пожалуй, идеальный шлюз должен уметь принимать и отдавать данные в формате XML, хотя бы потому, что это стандартизированный, самодокументируемый формат, поддержка которого реализована во всех современных языках программирования (и даже на аппаратном уровне). Кроме того, XML поддерживает Юникод-кодировки UTF-8, UTF-16 и даже UTF-32. Вкратце, но с примерами, расскажу о принципах создания простых XML-шлюзов. Отправку запросов, простоты и, одновременно, разнообразия ради рассмотрим на примере POST/GET методов.

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

image

Предлагаю для наглядности рассмотреть следующий пример: у вас на сервере есть некая база информационных материалов, которые вы готовы предоставлять за абонентскую плату. Материал разбит на категории, существует база данных партнеров в которой содержится информация о том, к каким категориям каждый партнер имеет доступ.

Для того, что бы партнер мог получать материал только в пределах своего доступа нам необходимо четко идентифицировать его. В противном случае он или любой другой смекалистый сможет «вытянуть» из вас больше положенного, а это чревато подмоченной репутацией и финансовыми потерями, т.к. проект у нас коммерческий.

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

Например, есть компания ОАО «Шишкин лес», которая оплатила пожизненный доступ к материалам из категорий 1, 2, 5 и 9. Сервер компании стоит дома под кроватью директора, но IP адрес статический и мы его знаем – 12.34.56.78. Теперь нам нужно сгенерировать уникальный ключ доступа для этого партнера, что можно сделать примерно так:
  1. <?php
  2.  
  3. define('SECRET_WORD', 'LsXUS~J');
  4.  
  5. function getUniqueKey($ip, $access) {
  6.  
  7.  return substr(md5($ip . SECRET_WORD . $access), 5, 20);
  8.  
  9. }
  10.  
  11. ?>

Обратите внимание на константу «SECRET_WORD», которая является постоянной вне зависимости от прочих условий. Функция getUniqueKey() принимает в качестве параметров IP-адрес сервера ($ip = ’12.34.56.78’) и строку сложенных ID-категорий, к которым у партнера есть доступ ($access = ‘1259’). Далее генерируется md5-хэш из всех этих параметров, который обрезается до 15-символьной строки (начиная, в нашем примере, с 5-го символа хэша). Это и есть наш уникальный ключ, который генерируется для каждого партнера. Корректность ключа мы будем проверять при каждом запросе от клиента.

Как обсуждалось ранее, запрос к серверу клиент может осуществить посредством POST/GET методов, передав в них необходимые параметры. Рассмотрим пример такого запроса:
mysite.com/gateway.php?id=1&type=list&token=8381ad87b37986ac7bb6

Итак, мы осуществляем запрос к шлюзу и передаем ему всего 3 параметра:
  • id=1, персональный ID клиента в вашей системе
  • type=list, это придуманный для мною параметр, который сообщает серверу что именно и в каком виде клиент хочет от него получить
  • token=8381ad87b37986ac7bb6, уникальный ключ клиента, который он отправляет серверу при каждой транзакции для подтверждения подлинности

Шлюзу (gateway.php) остается только проверить подлинность подключения и ответить клиенту на запрос. Ответить, кстати, нужно в любом случае, даже если ключ неправильный или возникла ошибка выборки/формирования ответа – клиент все равно обязан получить ответ.

Рассмотрим функцию проверки ключа при запросе к шлюзу:
  1. <?php
  2.  
  3. function checkValidation($clientID, $clientToken) {
  4.  
  5.  // IP-адрес клиента
  6.  $clientRemoteAddr = (getenv('HTTP_X_FORWARDED_FOR')) ? getenv('HTTP_X_FORWARDED_FOR') : getenv('REMOTE_ADDR');
  7.  
  8.  // Уровень доступа клиента к категориям
  9.  // На самом деле здесь должна быть выборка
  10.  // прав доступа клиента по его $clientID
  11.  $clientAccessLevel = '1259';
  12.  
  13.  // Правильный ключ
  14.  $checkToken = getUniqueKey($clientRemoteAddr, $clientAccessLevel);
  15.  
  16.  // Сравнение проверочного и полученного от клиента ключей
  17.  return ($clientToken == $checkToken);
  18.  
  19. }
  20.  
  21. ?>

Таким образом, если функция checkValidation() возвращает истину, то проверка подлинности подключения пройдена. Теперь можно приступать к формированию и подготовке ответа.

В нашем примере ответ должен содержать некую выборку информации из разных категорий, предположим, что такая выборка содержит следующий набор полей:
  • element_id – идентификатор каждой конкретной записи, уникален
  • category_id – идентификатор рубрики, к которой относится запись (напомню, что у в нашем примере клиент имеет доступ к рубрикам 1, 2, 5 и 9)
  • title – заголовок элемента
  • text – содержимое записи
  • time – дата и время публикации записи (формат даты выбирайте сами)

Структура ответа в таком случае будет примерно такой:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <response>
  3.  <error-code>0</error-code>
  4.  <content>
  5.   <item>
  6.    <element_id>1</element_id>
  7.    <category_id>5</category_id>
  8.    <title>Заголовок 1</title>
  9.    <text>Текст элемента номер 1, про всякую фигню</text>
  10.    <time>1248770309</time>
  11.   </item>
  12.   <item>
  13.    <element_id>2</element_id>
  14.    <category_id>9</category_id>
  15.    <title>Заголовок 2</title>
  16.    <text>Текст элемента номер 2, про всякую фигню</text>
  17.    <time>1248770319</time>
  18.   </item>
  19.  </content>
  20. </response>

Атрибут «error-code» в случае успешного осуществления операции должен быть равнее нулю, что буквально говорит клиенту «все хорошо, ниже что ты просил». Если возникла какая-либо ошибка, то в данном атибуте передается код этой ошибки (атрибута «content» и всего его содержимого в данном случае уже не будет).

Предположим, что наш обработчик ошибок знает следующие возможные проблемы:
  • Ошибка 100 – ошибка проверки ключа (token не соответствует действительности)
  • Ошибка 101 – не передан ID клиента
  • Ошибка 102 – не передан уникальный ключ клиента
  • Ошибка 201 – запрошенная информация не найдена в базе данных
  • Ошибка 202 – возникла ошибка при выборе запрошенной информации


Ну и так далее. Ответ с ошибкой будет таким:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <response>
  3.  <error-code>102</error-code>
  4. </response>

Последнее, что осталось рассмотреть – это формирование такого ответа. Реализаций, как и всего вышеописанного, может быть много, но я предложу такой простой вариант:
  1. <?php
  2.  
  3. function sendResponse($code, $content = '') {
  4.  
  5.  // Создание атрибута
  6.  function getNode($key, $value) {
  7.   if ($value != '') {
  8.    return '<' . $key . '>' . $value . '</' . $key . '>';
  9.   }
  10.  }
  11.  
  12.  // Дополнительные поля
  13.  $items = '';
  14.  
  15.  // Если $content не пуста и является массивом
  16.  if (!empty($content) && is_array($content)) {
  17.   foreach ($content as $key => $value) {
  18.    $items .= '<user>';
  19.    if (is_array($value)) {
  20.     foreach ($value as $skey => $svalue) {
  21.      $items .= getNode($skey, $svalue);
  22.     }
  23.    } else {
  24.     $items .= getNode($key, $value);
  25.    }
  26.    $items .= '</user>';
  27.   }
  28.  }
  29.  
  30.  // Формирование и отправка ответа
  31.  print '<response><error-code>' . $code . '</error-code>' . $items . '</response>';
  32.  exit();
  33.  
  34. }
  35.  
  36. ?>

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

Критика, дополнения, другие реализации в комментариях приветствуются :-)
Теги:
Хабы:
Всего голосов 20: ↑11 и ↓9+2
Комментарии26

Публикации

Истории

Работа

PHP программист
148 вакансий

Ближайшие события