Создаём собственный API-4
Всем привет. Итак, пришло время создать классы 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
В тему:
В ридере уже есть ссылка на пятую часть, а тут нету. Когда появится она?
Gena, March 23, 2011 8:53 pmЯ не нашёл подсветку и форматирование кода. Вячеслав вернётся в районе 27-28го числа и опубликует 5ю и 6ю части.
Кирилл Евсеев, March 23, 2011 8:58 pmОтлично. Жду. А сколько частей будет в итоге?
Gena, March 23, 2011 9:09 pmЯ думаю, всего 7.
Кирилл Евсеев, March 23, 2011 9:17 pm