Создаём собственный API-4

Кирилл Евсеев, March 6, 2011

Всем привет. Итак, пришло время создать классы Gateway и Connector и посмотреть на наш API в деле. Для этого нам понадобится доступ к удалённому хосту или виртуальные хосты Apache. Я буду использовать удалённый хост.

Итак, создайте на удалённом хосте структуру базы, как это было описано во второй части статьи. Теперь нужно написать класс Gateway, который реализует протокол передачи данных. Как я уже говорил, будем исходить из объективной простоты, а именно – наши данные будут передаваться в виде сериализованного и закодированного POST запроса. Хотя, никто не мешает разработать свой протокол и потратить на это пару килобаксов вашего заказчика.

В общем, класс Gateway будет крайне простым. Смотрим -

//интерфейс с единственным методом - принять запрос.
// string get_request(string) принимает запрос от удалённого хоста,
// обрабатвыает его и возвращает результат.
interface api_gateway_itrfc
{
  public function get_request($str);
}

class api_gateway extends api implements api_gateway_itrfc
{
  protected $api_executor = null;
  protected $response_data = array();

  // если не понятно, что происходит в конструкторе, 
  // перечитайте статьи 2 и 3 :) 
  public function __construct($db_object)
  {
    parent::__construct($db_object);
    $this->api_executor = new api_executor($this->db_handler);
  }

  //вот ради этого мы и заварили всю эту кашу
  public function get_request($str)
  {
    //подготавливаем данные (фактически - реализация протокола)
    $this->prepare_data($str);
    // обрабатываем полученный запрос
    $this->process_data(); 
    // возвращаем ответ удалённому хосту
    return $this->send_response(); 
  }

  // void prepare_data(string) - реализует протокол. просто накладываем 
  // требования о том, что переданная строка должна быть сначала 
  // сериализована, затем закодирована функцией urlencode.
  // Соответственно, вытаскиваем данные в обратном порядке - 
  // сначала раскодируем urldecode, затем восстанавливаем сериализацию.
  protected function prepare_data($str)
  {
    $this->request_data = unserialize(urldecode($str));
    return;
  }

  // это немного отличается от первоначальной задумки с Api Handler.
  // дождёмся следующей статьи, возможно, что-то изменится
  protected function process_data()
  {
    if(is_array($this->request_data) && isset(
      $this->request_data['f_name']) &&isset(
      $this->request_data['args']))
    {
      $this->response_data = $this->api_executor->execute(
        $this->request_data['f_name'],$this->request_data['args']);

      return $this->response_data;
    }
    else
    {
      return false;
    }
  }

  //отправляем ответ. тут важно понимать, что оператор echo 
  // это не то же самое, что вызов cout<< или printf в С/С++. 
  // Фактически, оператор echo отсылает браузеру пользователя
  // HTTP заголовок с полным набором необходимой информации. 
  // А браузер уже решает, как именно эту информацию использовать 
  // (например, вывести на экран). В нашем случае, мы будем этот 
  // вывод перехватывать с помощью библиотеки cURL.
  protected function send_response()
  {
    echo urlencode(serialize($this->response_data));
    return;
  }
}

Ну что ж, теперь шлюз готов. Нам осталось только написать драйвер для него:

$db = new simple_db('localhost', 'root', 'root');

if(isset($_POST['api_request']))
{
  $str = $_POST['api_request'];
  $x = new api_gateway($db);
  $x->get_request($str);
}

Драйвер готов. Теперь положим на удалённый хост всё, что получилось. А на локальном разместим объект класса Connector.

Например, так -

//базовый класс. инициализирует cURL.
// параметры инициализации cURL можно посмотреть в справке PHP
// http://www.php.net/manual/en/function.curl-setopt.php
class connector
{
  protected $curl_handler = null;
  protected $url = '';
  protected $request_data = array();

  //конструктор создаёт соединение cURL и подготавливает его для работы
  public function __construct($url)
  {
    $this->curl_handler = @curl_init();
    $this->url = $url;

    if(is_resource($this->curl_handler) && is_string($this->url)&&
      !empty($this->url))
    {
      curl_setopt($this->curl_handler, CURLOPT_URL, $this->url);
      //предотвращаем кэш
      curl_setopt($this->curl_handler, CURLOPT_FRESH_CONNECT, true); 
      //метод передачи данных
      curl_setopt($this->curl_handler, CURLOPT_POST, true); 
      // возвращаем результат запроса вместо вывода на экран 
      // по умолчанию
      curl_setopt($this->curl_handler, CURLOPT_RETURNTRANSFER, true); 
    }
    else
    {
      return false;
    }
  }
  //закрываем cURL соединение
  public function __destruct()
  {
    @curl_close($this->curl_handler);
    return;
  }

  //передаем в cURL необходимые данные и запускаем функцию curl_exec, 
  // которая отрабатывает согласно ранее установленным настройкам.
  public function execute()
  {
    curl_setopt($this->curl_handler, CURLOPT_POSTFIELDS, 
      array('api_request'=>$this->request_data)); // массив с данными
    return curl_exec($this->curl_handler);
  }

  // маленькая ремарка - у меня чесались руки спрятать 
  // CURLOPT_POSTFIELDS в конструктор. это должно было бы выглядеть
  // так - curl_setopt($this->curl_handler, CURLOPT_POSTFIELDS, 
  // &$this->request_data);
  // т.е. фактически передать пустой параметр по ссылке и в RUNTIME 
  // инициализировать эту область памяти.
  // прелесть была бы в том, чтобы использовать динамическую память, 
  // вместо статической и обеспечить более элегантное решение с точки 
  // зрения ООП, однако, эта возможность оказалась deprecated, 
  // равно как и переопределение параметра 
  // allow_call_time_pass_reference в php.ini
}

//наследуем весь предыдущий ливер
class connector_dogbody extends connector
{
  protected $response_data = null;

  //инициируем урлом.
  public function __construct($url)
  {
    parent::__construct($url);
  }

  // Запрос к удалённой системе, предоставляющей API. 
  // Фактически, зависит от требований к протоколу.
  public function request($arr)
  {
    if(is_array($arr) && !empty($arr) && 
      isset($arr['f_name']) &&isset($arr['args']))
    {
      $this->request_data = $this->prepare($arr); //протокол
      //вызов метода из родительского конструктора
      $this->response_data = $this->execute(); 
      //получаем ответ от удалённого API провайдера
      return $this->get_response(); 
    }
    else
    {
      return false;
    }
  }

  protected function prepare($arr)
  {
    return urlencode(serialize($arr)); //протокол
  }

  protected function get_response()
  {
    //приведение данных к нормальному виду
    return unserialize(urldecode($this->response_data)); 
  }
}

Драйвер будет выглядеть не сложнее всех предыдущих:

$c = new connector_dogbody('http://my_api_host/api_test.php');
//вызов одной из API функций
$res = $c->request(array('f_name'=>'get_users', 
  'args'=>array('void'=>''))); 

// наслаждаемся результатом. Если всё было сделано правильно, 
// увидим содержимое таблицы data на удалённом хосте
echo vd($res);
exit;

Подведём итоги. Теперь Наш API работает не только на локальной машине, что обозначает явный прогресс. Мы реализовали классы Connector и Gateway.

В следующей статье мы наконец-то займёмся вопросами безопасности. И, скорее всего, в одну статью они не влезут. Все исходники находятся в аттачменте, см. файл readme.txt

Ради эксперимента, попробуйте реализовать API клиент на C/C++ или на Java. Если вы выложите это в комментах к статье, то это будет круто.

Ну а я жду вашей конструктивной критики и готовлю следующую статью. Коды текущей версии API можно скачать по ссылке Создаём собственный API-4

В тему:

4комментария

В ридере уже есть ссылка на пятую часть, а тут нету. Когда появится она?

Gena, March 23, 2011 8:53 pm Reply

Я не нашёл подсветку и форматирование кода. Вячеслав вернётся в районе 27-28го числа и опубликует 5ю и 6ю части.

Кирилл Евсеев, March 23, 2011 8:58 pm Reply

Отлично. Жду. А сколько частей будет в итоге?

Gena, March 23, 2011 9:09 pm Reply

Я думаю, всего 7.

Кирилл Евсеев, March 23, 2011 9:17 pm Reply
Ваше имя
Ваш email*
Ваш сайт
Текст вашего комментария:

Поиск по блогу:
Подписаться:
Популярные:
Облако тегов:
Разное:
Счетчик: