суббота, 22 ноября 2008 г.

Особенности использования cURL и передачи заголовков

Здравствуйте, читатели.

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

В данной статье хочу рассказать о библиотеке Libcurl. Libcurl — это кроссплатформенное программное обеспечение, библиотека, клиент использующийся для передачи URL. Она поддерживает огромное множество протоколов, таких как FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS и FILE. Так же libcurl поддерживает SSL сертификаты, HTTP POST, HTTP PUT, FTP загрузку, HTTP загрузку, основанную на формах, прокси, cookies, и многое другое. Пользователи могут довольно легко встроить её в свои программы при помощи API cURL. cURL действует как автономная обёртка для библиотеки libcurl. Для неё имеется более 30 различных привязок к языкам программирования, но я бы хотел показать механизм работы на примере PHP.

На работе столкнулся с такой задачей. Нужно было реализовать серверный метод защиты директорий и файлов. Сам механизм работы защиты долго рассказывать, и выходит за рамки статьи, но для этой задачи мне понадобилось делать запрос от самого сервера (то есть через прокси-сервер), получать результат, и уже потом, выдавать обратно пользователю в браузер. Плюс, чтобы для пользователя это было незаметно и абсолютно прозрачно.

Немного почитав документацию по cURL, принялся писать код на PHP, ничего сложного в нём нет, приведу участок ниже.

< ?php
// ...

//если cURL доступен – то используем его
if ( (bool)function_exists("curl_init"))
{
  // создаём новый cURL resource
  $ch = curl_init();
  // выставляем соответствующие опции и требуемый URL
  curl_setopt($ch, CURLOPT_URL, $FILE_URL);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);

  // сделать запрос по URL и вывести в браузер
  curl_exec($ch);

// ...
?>


Перед использованием cURL нужно проверить присутствует ли вообще у вас данный модуль, поэтому следует использовать метод function_exists("curl_init"). Создав новый cURL resource и выставив необходимые опции (подробнее по каждой из них можно узнать здесь: http://php.net/manual/ru/function.curl-setopt.php) мы делаем запрос curl_exec и выводим ответ в браузер.

Запускаем скрипт на исполнение, проверяем на конкретной странице, выставляем параметр CURLOPT_URL равный http://webaurum.dev/index.php. И, о чудо, заработало! Мы получили результат исполнения страницы index.php. Так как мне нужен прокси для получения любого контента, решил проверить и на других видах содержимого, подставил jpg-файл. Задав в качестве запрашиваемого URL изображение http://webaurum.dev/image.jpg, но в ответ получил только каракули в окне браузера, вместо ожидаемой картинки.

Начал разбираться в чём же дело. Перехватив заголовки, получил следующее в Response Headers:

Date Fri, 21 Nov 2008 10:24:00 GMT
Server Apache/2.2.4 (Unix) mod_ssl/2.2.4 OpenSSL/0.9.8e DAV/2 PHP/5.2.3
X-Powered-By PHP/5.2.3
Expires Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma no-cache
Content-Type text/html
Keep-Alive timeout=5, max=100
Connection Keep-Alive


причём и для изображения и для скрипта php в заголовке Content-Type почему-то стояло text/html. Получается curl не передаёт ответ сервера в чистом виде, хотя и получает адекватно содержимое. В документации описана опция CURLOPT_HEADER, там сказано, что при установке этого параметра в ненулевое значение результат будет включать полученные заголовки. Включаем его, и заново проводим эксперимент, подав изображение на вход. В итоге в браузере увидел следующее:

HTTP/1.1 200 OK Date: Fri, 21 Nov 2008 10:24:01 GMT Server: Apache/2.2.4 (Unix) mod_ssl/2.2.4 OpenSSL/0.9.8e DAV/2 PHP/5.2.3 Last-Modified: Fri, 19 Sep 2008 14:23:29 GMT ETag: "4927-4b124-72007a40" Accept-Ranges: bytes Content-Length: 307492 Content-Type: image/jpeg ╪ р� JFIF� �H�H�� с �Exif��MM�*��� � � ���

… и дальше шла куча иероглифов. Как ни странно, но curl смог адекватно определить mime тип запрашиваемого содержимого (Content-Type: image/jpeg), и даже правильно сформировал заголовки. Тем не менее, в респонсе сервера и дальше красовалось Content-Type: text/html. Почему же не выставляются таким образом заголовки, если до того никакого вывода в браузер не было, для меня осталось загадкой.

Недолго думая, решил распарсить эти заголовки вручную, и установить их явно при получении ответа от curl. У библиотеки curl есть мощный механизм для получения технических аспектов отработки запроса – метод curl_getinfo. Он может принимать множество параметров, но нас, в частности, интересует ключ CURLINFO_HEADER_SIZE. При использовании этого ключа, метод возвращает длину заголовков в ответе curl_exec. Зная длину заголовков, можно вырезать их из общего содержимого, установить их явно при помощи функции header(), а потом уже вывести остаток содержимого в бинарном виде. Тогда браузер должен адекватно проинтерпретировать mime тип полученного содержимого. Код приведен ниже.

< ?php
// ...

//если cURL доступен – то используем его
if ( (bool)function_exists("curl_init"))
{
  // создаём новый cURL resource
  $ch = curl_init();
  // выставляем соответствующие опции и требуемый URL
  curl_setopt($ch, CURLOPT_URL, $FILE_URL);
  curl_setopt($ch, CURLOPT_HEADER, 1);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);

  // сделать запрос по URL и вывести в браузер
  $data = curl_exec($ch);

  $headers = substr($data, 0, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
  $data = substr($data, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
  // парсим заголовки
  $headers = explode("\r\n", $headers);
  foreach ($headers as $header)
  {
  // устанавливаем каждую часть заголовка отдельно
  header($header);
  }
  // выводим остатки контента в браузер
  print($data);
  // закрыть cURL ресурс и высвободить системные ресурсы
  curl_close($ch);
}

// ...
?>


Теперь перехватим заголовки ответа сервера, и проверим результат:


Date Fri, 21 Nov 2008 11:12:49 GMT
Server Apache/2.2.4 (Unix) mod_ssl/2.2.4 OpenSSL/0.9.8e DAV/2 PHP/5.2.3
X-Powered-By PHP/5.2.3
Expires Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma no-cache
Last-Modified Fri, 19 Sep 2008 14:23:29 GMT
Etag "4927-4b124-72007a40"
Accept-Ranges bytes
Content-Length 307492
Content-Type image/jpeg
Keep-Alive timeout=5, max=100
Connection Keep-Alive

После запуска PHP скрипта на исполнение мы получили долгожданную картинку в окне браузера, а не набор иероглифов, как было раньше. При желании с помощью метода curl_setopt можно задать любые параметры запроса или передать точную копию cookie пользователя, таким образом, что между запросом пользователя и запросом сервера-посредника (прокси-сервера) почти не будет разницы.

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

Комментариев нет:

Отправить комментарий

Рекоммендую

Попробуйте надёжный хостинг от Scala Hosting