Особенности использования 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, ничего сложного в нём нет, приведу участок ниже.

// ...



{
$ch = curl_init();

curl_setopt($ch, CURLOPT_HEADER, 0);


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
X-Powered-By PHP/5.2.3
Expires
Cache-Control
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 тип полученного содержимого. Код приведен ниже.

// ...

//если 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 пользователя, таким образом, что между запросом пользователя и запросом сервера-посредника (прокси-сервера) почти не будет разницы.

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

(PHP 4 >= 4.0.4, PHP 5, PHP 7)

curl_getinfo — Get information regarding a specific transfer

Parameters

This may be one of the following constants:

  • CURLINFO_EFFECTIVE_URL - Last effective URL
  • CURLINFO_HTTP_CODE - The last response code. As of PHP 5.5.0 and cURL 7.10.8, this is a legacy alias of CURLINFO_RESPONSE_CODE
  • CURLINFO_FILETIME - Remote time of the retrieved document, with the CURLOPT_FILETIME enabled; if -1 is returned the time of the document is unknown
  • CURLINFO_TOTAL_TIME - Total transaction time in seconds for last transfer
  • CURLINFO_NAMELOOKUP_TIME - Time in seconds until name resolving was complete
  • CURLINFO_CONNECT_TIME - Time in seconds it took to establish the connection
  • CURLINFO_PRETRANSFER_TIME - Time in seconds from start until just before file transfer begins
  • CURLINFO_STARTTRANSFER_TIME - Time in seconds until the first byte is about to be transferred
  • CURLINFO_REDIRECT_COUNT - Number of redirects, with the CURLOPT_FOLLOWLOCATION option enabled
  • CURLINFO_REDIRECT_TIME - Time in seconds of all redirection steps before final transaction was started, with the CURLOPT_FOLLOWLOCATION option enabled
  • CURLINFO_REDIRECT_URL - With the CURLOPT_FOLLOWLOCATION option disabled: redirect URL found in the last transaction, that should be requested manually next. With the CURLOPT_FOLLOWLOCATION option enabled: this is empty. The redirect URL in this case is available in CURLINFO_EFFECTIVE_URL
  • CURLINFO_PRIMARY_IP - IP address of the most recent connection
  • CURLINFO_PRIMARY_PORT - Destination port of the most recent connection
  • CURLINFO_LOCAL_IP - Local (source) IP address of the most recent connection
  • CURLINFO_LOCAL_PORT - Local (source) port of the most recent connection
  • CURLINFO_SIZE_UPLOAD - Total number of bytes uploaded
  • CURLINFO_SIZE_DOWNLOAD - Total number of bytes downloaded
  • CURLINFO_SPEED_DOWNLOAD - Average download speed
  • CURLINFO_SPEED_UPLOAD - Average upload speed
  • CURLINFO_HEADER_SIZE - Total size of all headers received
  • CURLINFO_HEADER_OUT - The request string sent. For this to work, add the CURLINFO_HEADER_OUT option to the handle by calling curl_setopt()
  • CURLINFO_REQUEST_SIZE - Total size of issued requests, currently only for HTTP requests
  • CURLINFO_SSL_VERIFYRESULT - Result of SSL certification verification requested by setting CURLOPT_SSL_VERIFYPEER
  • CURLINFO_CONTENT_LENGTH_DOWNLOAD - Content length of download, read from Content-Length: field
  • CURLINFO_CONTENT_LENGTH_UPLOAD - Specified size of upload
  • CURLINFO_CONTENT_TYPE - Content-Type: of the requested document. NULL indicates server did not send valid Content-Type: header
  • CURLINFO_PRIVATE - Private data associated with this cURL handle, previously set with the CURLOPT_PRIVATE option of curl_setopt()
  • CURLINFO_RESPONSE_CODE - The last response code
  • CURLINFO_HTTP_CONNECTCODE - The CONNECT response code
  • CURLINFO_HTTPAUTH_AVAIL - Bitmask indicating the authentication method(s) available according to the previous response
  • CURLINFO_PROXYAUTH_AVAIL - Bitmask indicating the proxy authentication method(s) available according to the previous response
  • CURLINFO_OS_ERRNO - Errno from a connect failure. The number is OS and system specific.
  • CURLINFO_NUM_CONNECTS - Number of connections curl had to create to achieve the previous transfer
  • CURLINFO_SSL_ENGINES - OpenSSL crypto-engines supported
  • CURLINFO_COOKIELIST - All known cookies
  • CURLINFO_FTP_ENTRY_PATH - Entry path in FTP server
  • CURLINFO_APPCONNECT_TIME - Time in seconds it took from the start until the SSL/SSH connect/handshake to the remote host was completed
  • CURLINFO_CERTINFO - TLS certificate chain
  • CURLINFO_CONDITION_UNMET - Info on unmet time conditional
  • CURLINFO_RTSP_CLIENT_CSEQ - Next RTSP client CSeq
  • CURLINFO_RTSP_CSEQ_RECV - Recently received CSeq
  • CURLINFO_RTSP_SERVER_CSEQ - Next RTSP server CSeq
  • CURLINFO_RTSP_SESSION_ID - RTSP session ID

Return Values

If opt is given, returns its value. Otherwise, returns an associative array with the following elements (which correspond to opt), or FALSE on failure:

  • "url"
  • "content_type"
  • "http_code"
  • "header_size"
  • "request_size"
  • "filetime"
  • "ssl_verify_result"
  • "redirect_count"
  • "total_time"
  • "namelookup_time"
  • "connect_time"
  • "pretransfer_time"
  • "size_upload"
  • "size_download"
  • "speed_download"
  • "speed_upload"
  • "download_content_length"
  • "upload_content_length"
  • "starttransfer_time"
  • "redirect_time"
  • "certinfo"
  • "primary_ip"
  • "primary_port"
  • "local_ip"
  • "local_port"
  • "redirect_url"
  • "request_header" (This is only set if the CURLINFO_HEADER_OUT is set by a previous call to curl_setopt() )
Note that private data is not included in the associative array and must be retrieved individually with the CURLINFO_PRIVATE option.

Changelog

Version Description
5.5.0 Introduced CURLINFO_RESPONSE_CODE , CURLINFO_HTTP_CONNECTCODE , CURLINFO_HTTPAUTH_AVAIL , CURLINFO_PROXYAUTH_AVAIL , CURLINFO_OS_ERRNO , CURLINFO_NUM_CONNECTS , CURLINFO_SSL_ENGINES , CURLINFO_COOKIELIST , CURLINFO_FTP_ENTRY_PATH , CURLINFO_APPCONNECT_TIME , CURLINFO_CONDITION_UNMET , CURLINFO_RTSP_CLIENT_CSEQ , CURLINFO_RTSP_CSEQ_RECV , CURLINFO_RTSP_SERVER_CSEQ and CURLINFO_RTSP_SESSION_ID .
5.4.7 Introduced CURLINFO_PRIMARY_IP , CURLINFO_PRIMARY_PORT , CURLINFO_LOCAL_IP and CURLINFO_LOCAL_PORT .
5.3.7 Introduced CURLINFO_REDIRECT_URL .
5.3.0 Introduced CURLINFO_CERTINFO .
5.2.4 Introduced CURLINFO_PRIVATE .
5.1.3 Introduced CURLINFO_HEADER_OUT .

Examples

Example #1 curl_getinfo() example

// Create a cURL handle
$ch = curl_init ("http://www.example.com/" );

// Execute
curl_exec ($ch );

// Check if any error occurred
if (! curl_errno ($ch )) {
$info = curl_getinfo ($ch );
echo "Took " , $info [ "total_time" ], " seconds to send a request to " , $info [ "url" ], "\n" ;
}

// Close handle
curl_close ($ch );
?>

В этой статье мы с Вами продолжим заниматься изучением модуля cURL , и в этот раз Вы узнаете, как узнать HTTP-заголовки сервера с помощью cURL .

Для получения HTTP-заголовков сервера через cURL надо запустить следующий код:

if($curl = curl_init()) {
curl_setopt($curl,CURLOPT_URL,"http://сайт");
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
curl_setopt($curl,CURLOPT_NOBODY,true);
curl_setopt($curl,CURLOPT_HEADER,true);
$out = curl_exec($curl);
echo $out;
curl_close($curl);
}
?>

Как и раньше, curl_init() инициализирует сеанс cURL . Затем мы начинаем устанавливать следующие опции:

  • CURLOPT_URL = "http://сайт" . Это адрес сайта, от которого мы хотим получить заголовок.
  • CURLOPT_RETURNTRANSFER = true . Это опцией мы требуем, чтобы ответ возвращался, а не выводился сразу в браузер.
  • CURLOPT_NOBODY = true . Здесь мы требуем, чтобы в ответ не входило содержимое самого документа.
  • CURLOPT_HEADER = true . Вот это самая главная опция, именно она включает в ответ от сервера его HTTP-заголовки .

Затем мы выполняем наш cURL-запрос с помощью функции curl_exec() и получаем ответ, который записываем в переменную $out . Затем мы выводим её и закрываем соединение.

Как можно использовать данную информацию? Самый простой пример - это проверять: доступен сайт или нет . Соответственно, если код статуса - 404 , то сайт недоступен. Вот Вы можете создать такой полезный сервис, на котором люди смогут проверить: работает сайт или же нет. Это бывает очень полезно, так как иногда Ваш компьютер блокирует доступ к каким-нибудь сайтам, а также какой-нибудь сайт может блокировать к Вам доступ по IP-адресу . Но благодаря такому сервису станет понятно о реальном положении дел на сервере. Так что дерзайте, и удачи Вам!