Продвинутое использование cURL в PHP

15-04-23 Php cURL 4

cURL — это инструмент, позволяющий взаимодействовать с различными серверами и поддерживающий множество протоколов: HTTP, FTP, TELNET и др. Изначально cURL — это служебная программа для командной строки. Но, к счастью для нас, PHP поддерживает работу с библиотекой cURL. В этой статье мы рассмотрим нетривиальные примеры работы с cURL.

Почему cURL?

На самом деле, есть много других способов отправить запрос на другой сервер чтобы, например, получить содержимое страницы. Многие, в основном из-за лени, используют простые PHP функции, вместо cURL:

$content = file_get_contents("http://www.example.com");

// или

$lines = file("http://www.example.com");

// или

readfile("http://www.example.com");

Однако они не позволяют эффективно обрабатывать ошибки. Также есть ряд задач, которые им вовсе не под силу — например, работа с cookies, авторизация, post запросы, загрузка файлов.

    cUrl — мощный инструмент, который поддерживает множество протоколов и предоставляет полную информацию о запросе.

Основы cUrl

Прежде чем перейти к сложным примерам, рассмотрим базовую структуру cURL запроса в PHP. Для выполнения cURL запроса в PHP необходимо сделать 4 основных шага:

  1. Инициализация.
  2. Установка опций.
  3. Выполнение запроса.
  4. Очистка ресурсов.
// 1. инициализация
$ch = curl_init();

// 2. устанавливаем опции, включая урл
curl_setopt($ch, CURLOPT_URL, "http://www.google.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);

// 3. выполнение запроса и получение ответа
$output = curl_exec($ch);

// 4. очистка ресурсов
curl_close($ch);

В основном в этой статье мы будем рассматривать шаг №2, так как там происходит основная магия. Список cURL опций очень большой, поэтому все опции рассматривать сегодня мы не будем, а используем те, которые пригодятся для решения конкретных задач.

Отслеживание ошибок

При необходимости, вы можете добавить следующие строки для отслеживания ошибок:

// ...

$output = curl_exec($ch);

if ($output === FALSE) {

    echo "cURL Error: " . curl_error($ch);

}

// ...

Обратите внимание, мы используем «===» вместо «==», т.к. надо отличать пустой ответ сервера от булевского значения FALSE, которое возвращается в случае ошибки.

 Получение информации о запросе

Другой необязательный шаг — получение информации о cURL запросе, после его выполнения.

// ...

curl_exec($ch);

$info = curl_getinfo($ch);

echo 'Took ' . $info['total_time'] . ' seconds for url ' . $info['url'];

// ...

В результате вы получите массив со следующей информацией:

  • «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»

 Отслеживание редиректов, в зависимости от браузера

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

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

// URLs
$urls = array(
    "http://www.cnn.com",
    "http://www.mozilla.com",
    "http://www.facebook.com"
);
// браузеры
$browsers = array(

    "standard" => array (
        "user_agent" => "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)",
        "language" => "en-us,en;q=0.5"
        ),

    "iphone" => array (
        "user_agent" => "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A537a Safari/419.3",
        "language" => "en"
        ),

    "french" => array (
        "user_agent" => "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; GTB6; .NET CLR 2.0.50727)",
        "language" => "fr,fr-FR;q=0.5"
        )

);

foreach ($urls as $url) {

    echo "URL: $url\n";

    foreach ($browsers as $test_name => $browser) {

        $ch = curl_init();

        // установим адрес
        curl_setopt($ch, CURLOPT_URL, $url);

        // укажем используемый браузер и язык
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                "User-Agent: {$browser['user_agent']}",
                "Accept-Language: {$browser['language']}"
            ));

        // содержимое страницы нам не нужно
        curl_setopt($ch, CURLOPT_NOBODY, 1);

        // нужны только заголовки
        curl_setopt($ch, CURLOPT_HEADER, 1);

        // вернем результат, вместо его вывода
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $output = curl_exec($ch);

        curl_close($ch);

        // определим перенаправления в HTTP заголовках?
        if (preg_match("!Location: (.*)!", $output, $matches)) {

            echo "$test_name: redirects to $matches[1]\n";

        } else {

            echo "$test_name: no redirection\n";

        }

    }
    echo "\n\n";
}

В цикле проверяем браузеры для каждого урла. Сперва мы устанавливаем опции для нашего запроса: URL и тестируемый браузер и язык.

Т.к. мы установили специальную опцию, результат выполнения запроса будет содержать только HTTP заголовки. С помощью простого регулярного выражения мы можем проверить содержит ли ответ строку «Location:».

Результат выполнения скрипта:

URL: http://www.cnn.com
standard: redirects to http://edition.cnn.com/
iphone: redirects to http://edition.cnn.com/
french: redirects to http://edition.cnn.com/

URL: http://www.mozilla.com
standard: redirects to https://www.mozilla.org/firefox/
iphone: redirects to https://www.mozilla.org/firefox/
french: redirects to https://www.mozilla.org/firefox/

URL: http://www.facebook.com
standard: redirects to https://www.facebook.com/
iphone: redirects to http://m.facebook.com/?refsrc=http%3A%2F%2Fwww.facebook.com%2F&_rdr
french: no redirection

Отправляем POST запросы

При выполнении GET запросов данные можно передавать в строке запроса. Например, когда вы ищете в гугле, ваш запрос передается в URL:

http://www.google.com/search?q=google

Чтобы получить результат этого запроса, вам даже не понадобится cURL, вы можете быть ленивым и использовать «file_get_contents()».

Но некоторые HTML формы используют метод POST. В таком случае данные отправляются в теле сообщения запроса, а не в самом URL.

Напишем скрипт, который будет отправлять POST запросы. Для начала создадим простой PHP файл, который будет принимать эти запросы и возвращать отправленные ему данные. Назовем его post_output.php:

print_r($_POST);

Далее напишем PHP скрипт, который отправит cURL запрос:

$url = "http://localhost/post_output.php";

$post_data = array (
    "foo" => "bar",
    "query" => "FooBar",
    "action" => "Submit"
);

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// делаем POST запрос
curl_setopt($ch, CURLOPT_POST, 1);
// добавляем данные
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);

$output = curl_exec($ch);

curl_close($ch);

echo $output;

Данный скрипт выведет:

Array
(
    [foo] => bar
    [query] => FooBar
    [action] => Submit
)

Данный скрипт отправил POST запрос файлу post_output.php. который вывел содержимое массива $_POST и мы получили этот ответ с помощью cURL.

Загрузка файлов

Загрузка файлов очень похожа на предыдущий скрипт, т.к. загрузка файлов всегда выполняется с помощью POST запросов.

Так же как и в предыдущем примере, создадим файл, который будет принимать запросы, upload_output.php:

print_r($_FILES);

И сам скрипт, загружающий файлы:

$url = "http://localhost/upload_output.php";

$post_data = array (
    "foo" => "bar",
    // файл для загрузки
    "upload" => "@/tmp/desert.jpg"
);

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_POST, 1);

curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);

$output = curl_exec($ch);

curl_close($ch);

echo $output;

Если вы хотите загрузить файл, все что необходимо — это передать путь к нему, так же как обычный параметр POST запроса, поставив вначале «@». Результат работы скрипта:

Array
(
    [upload] => Array
        (
            [name] => desert.jpg
            [type] => application/octet-stream
            [tmp_name] => /tmp/phpAhEvXy
            [error] => 0
            [size] => 845941
        )

)

Multi cURL

Одна из продвинутых возможностей cURL в PHP — это возможность выполнения нескольких запросов одновременно и асинхронно.

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

// создаем обработчики
$ch1 = curl_init();
$ch2 = curl_init();

// устанавливаем опции
curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);

//create the multiple cURL handle
$mh = curl_multi_init();

// добавляем обработчики
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);

$running = null;
// выполняем запросы
do {
    curl_multi_exec($mh, $running);
} while ($running > 0);

// освободим ресурсы
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

Идея состоит в том, что вы можете создать множество cURL дескрипторов, объединить их под одним мульти-дескриптором и выполнять их асинхронно.

Сначала все как и с обычным cURL запросом — создается дескриптор (curl_init()), задаются параметры (curl_setopt()). Далее создается мульти-дескриптор (curl_multi_init()) и добавляются ранее созданные обычные дескрипторы (curl_multi_add_handle()). Вместо обычного вызова curl_exec() мы будем многократно вызывать curl_multi_exec() данная функция информирует нас о количестве активных соединений с помощью второго параметра — $running. Поэтому цикл работает пока $running не станет равным 0. И, конечно, после окончания работы необходимо освободить ресурсы.

В данном примере мы просто выводим результат запросов в STDOUT. Рассмотрим нетривиальный случай применения multi cURL.

Проверка внешних ссылок в WordPress

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

Напишем скрипт, который найдет все нерабочие ссылки и покажет их нам.

Для начала нам необходимо вытащить все внешние ссылки из базы данных:

// CONFIG
$db_host = 'localhost';
$db_user = 'root';
$db_pass = '';
$db_name = 'wordpress';
$excluded_domains = array('localhost', 'easy-code.ru');
$max_connections = 10;

$url_list = array();
$working_urls = array();
$dead_urls = array();
$not_found_urls = array();
$active = null;

// соединимся с MySQL
if (!mysql_connect($db_host, $db_user, $db_pass)) {
    die('Could not connect: ' . mysql_error());
}

if (!mysql_select_db($db_name)) {
    die('Could not select db: ' . mysql_error());
}

// берем все посты со ссылками в тексте
$q = "SELECT post_content FROM wp_posts
    WHERE post_content LIKE '%href=%'
    AND post_status = 'publish'
    AND post_type = 'post'";
$r = mysql_query($q) or die(mysql_error());

while ($d = mysql_fetch_assoc($r)) {
    // собираем все ссылки с помощью регулярки
    if (preg_match_all("/href=\"(.*?)\"/", $d['post_content'], $matches)) {
        foreach ($matches[1] as $url) {
            // фильтруем ненужные домены
            $tmp = parse_url($url);

            if (isset($tmp['host']) && in_array($tmp['host'], $excluded_domains)) {
                continue;
            }

            // собираем вместе
            $url_list [] = $url;
        }
    }
}

// удаляем повторения
$url_list = array_values(array_unique($url_list));

if (!$url_list) {
    die('No URL to check');
}

В этой части скрипта мы просто вытаскиваем из базы все внешние ссылки. Проверим их:

$mh = curl_multi_init();

// 1. добавим ссылки
for ($i = 0; $i < $max_connections; $i++) {
    add_url_to_multi_handle($mh, $url_list);
}

// основной цикл
do {
    curl_multi_exec($mh, $curRunning);

    // 2. один из потоков завершил работу
    if ($curRunning != $running) {
        $mhinfo = curl_multi_info_read($mh);

        if (is_array($mhinfo) && ($ch = $mhinfo['handle'])) {
            // 3. один из запросов выполнен, можно получить информацию о нем
            $info = curl_getinfo($ch);

            // 4. нерабочая ссылка
            if (!$info['http_code']) {
                $dead_urls[] = $info['url'];

            // 5. 404?
            } else if ($info['http_code'] == 404) {
                $not_found_urls[] = $info['url'];

            // 6. верная ссылка
            } else {
                $working_urls[] = $info['url'];
            }

            // 7. удаляем отработавший дескриптор
            curl_multi_remove_handle($mh, $mhinfo['handle']);
            curl_close($mhinfo['handle']);

            // 8. добавим новый урл
            add_url_to_multi_handle($mh, $url_list);
            $running = $curRunning;
        }
    }
} while ($curRunning > 0);

curl_multi_close($mh);

echo "==Dead URLs==\n";
echo implode("\n", $dead_urls) . "\n\n";

echo "==404 URLs==\n";
echo implode("\n", $not_found_urls) . "\n\n";

echo "==Working URLs==\n";
echo implode("\n", $working_urls);
echo "\n\n";

// 9. добавляет дескриптор с заданным урлом
function add_url_to_multi_handle($mh, $url_list) {
    static $index = 0;

    // если еще есть ссылки
    if (isset($url_list[$index])) {
        // все как обычно
        $ch = curl_init();

        // устанавливаем опции
        curl_setopt($ch, CURLOPT_URL, $url_list[$index]);
        // возвращаем, а не выводим результат
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        // разрешаем редиректы
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        // получаем только заголовки для экономии времени
        curl_setopt($ch, CURLOPT_NOBODY, 1);

        // добавляем к мульти-дескриптору
        curl_multi_add_handle($mh, $ch);

        $index++;
    }
}

Рассмотрим код подробнее (нумерация соответствует комментариям в коде):

  1. Добавляем начальное количество дескрипторов, чтобы не перегружать систему потоками. Количество регулируется переменной $max_connections.
  2. В переменной $curRunning хранится количество работающих потоков, в $running — предыдущее значение, если они станут не равными, то один из потоков завершил работу.
  3. Получаем информацию о выполненном запросе.
  4. Если нет ответа сервера — ссылка не рабочая.
  5. Ответ сервера — 404.
  6. Иначе ссылка работает.
  7. Запрос выполнен, освобождаем ресурсы.
  8. Добавим новый урл к мульти дескриптору.
  9. Функция add_url_to_multi_handle() добавляет новый дескриптор с заданным урлом к мульти-дескриптору.

Запустим скрипт:

==Dead URLs==
xample1234.com/

==404 URLs==
www.google.com/dsfasdfafd

==Working URLs==
ru.php.net/manual/ru/function.time.php
www.cssbuttongenerator.com/
csslint.net/
codex.wordpress.org/Plugin_API/Action_Reference
fortawesome.github.io/Font-Awesome/
fortawesome.github.io/Font-Awesome/
www.oracle.com/technetwork/java/javafx/downloads/index.html
codex.wordpress.org/Plugin_API/Filter_Reference
codex.wordpress.org/Roles_and_Capabilities
code.google.com/p/google-api-php-client/wiki/OAuth2#Google_APIs_Console
jplayer.org/
code.google.com/p/google-api-php-client/
developers.google.com/+/
accounts.google.com/ServiceLogin?service=devconsole&passive=1209600&continue=https%3A%2F%2Fcode.google.com%2Fapis%2Fconsole%2F&followup=https%3A%2F%2Fcode.google.com%2Fapis%2Fconsole%2F
daneden.github.io/animate.css/
github.com/daneden/animate.css
ru2.php.net/manual/ru/function.autoload.php
www.google.com/recaptcha/api/verify
phpunit.de/
phpunit.de/manual/current/en/phpunit-book.html

Проверка заняла около 2 секунд. Запуская одновременно по 10 потоков производительность возрастает в 10 раз, по сравнению с обычными cURL запросами. Чтобы получить содержимое ответа сервера используйте функцию curl_multi_getcontent($ch), где $ch — дескриптор, полученный из curl_multi_info_read().

Другие возможности cURL в PHP

HTTP аутентификация

Если HTTP запрос требует аутентификацию, используйте следующий код:

$url = "http://www.somesite.com/members/";

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// отправляем имя и пароль
curl_setopt($ch, CURLOPT_USERPWD, "myusername:mypassword");

// если разрешить редиректы
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
// cURL отправит пароль и после перенапрвлений
curl_setopt($ch, CURLOPT_UNRESTRICTED_AUTH, 1);

$output = curl_exec($ch);

curl_close($ch);

Загрузка по FTP

В PHP есть своя библиотека для работы с FTP, но можно использовать и cURL:

// читаем файл
$file = fopen("/path/to/file", "r");

// урл уже содержит необходимые данные
$url = "ftp://username:password@mydomain.com:21/path/to/new/file";

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// опции
curl_setopt($ch, CURLOPT_UPLOAD, 1);
curl_setopt($ch, CURLOPT_INFILE, $fp);
curl_setopt($ch, CURLOPT_INFILESIZE, filesize("/path/to/file"));
curl_setopt($ch, CURLOPT_FTPASCII, 1);

$output = curl_exec($ch);
curl_close($ch);

Использование прокси

Запросы можно выполнять через определенный proxy:

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL,'http://www.example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// адрес прокси
curl_setopt($ch, CURLOPT_PROXY, '11.11.11.11:8080');

// если требуется авторизация
curl_setopt($ch, CURLOPT_PROXYUSERPWD,'user:pass');

$output = curl_exec($ch);

curl_close ($ch);

Колбэки (callback functions)

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

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL,'http://example.com');
curl_setopt($ch, CURLOPT_WRITEFUNCTION,"progress_function");

curl_exec($ch);

curl_close ($ch);

function progress_function($ch,$str) {
    echo $str;
    return strlen($str);
}

Колбэк функция должна возвращать длину строки для правильной работы запроса.

Каждый раз, когда будет получена очередная часть ответа сервера, будет вызван колбэк.

Заключение

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

На этом всё!

Хочешь получать статьи на почту?

Подпишись на обновления!
* Ваш email не будет разглашен/продан. Вы сможете отписаться в любое время.

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

  1. sergue rogue says:

    Спасибо, полезно!

  2. Руслан says:

    Спасибо, до этого читал статьи, но было все как то не так. Вдохновили поэкспериментировать с этой библиотекой.

  3. Дмитрий says:

    Удобно, все в одном месте.
    Спасибо:)

  4. nadmax says:

    Отличный пример, лучше чем в документации. Спасибо!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *