PHP і cURL: як в WordPress виконуються HTTP-запити

cURL – «Ломовий кінь» сучасного інтернету. Як зазначено в слогані, cURL – це програма, яка використовується для «передачі даних за допомогою URL-адрес». Згідно веб-сайту cURL, бібліотекою щодня користуються мільярди людей. Вона застосовується у всьому, починаючи від автомобілів і закінчуючи мобільними телефонами. Це мережева основа тисяч додатків і сервісів, в тому числі деякі цікаві – наприклад, різних NASA-проектів.

Багато проектів і бібліотеки PHP, передають і отримують дані по мережі, використовують cURL як мережевої бібліотеки за замовчуванням. Не дивно, що це базова утиліта, яка використовується в Requests API в WordPress, а також в більшості плагінів, таких як WP Migrate DB Pro, WP Offload Media і т.д.

Якщо вам цікаво дізнатися про можливості бібліотеки cURL, про те, як вона працює в WordPress і на що звертати увагу при взаємодії з нею (особливо на macOS), то ви потрапили в потрібне місце.

Що таке cURL?

Почнемо з того, що ж таке cURL. В реальності cURL складається з двох частин: libcurl (C-бібліотека) і cURL CLI. Мови програмування, такі як PHP, включають бібліотеку libcurl у вигляді модуля, що дозволяє їм надавати функціонал cURL в нативному форматі.

libcurl – це open source бібліотека для передачі URL-адрес з використанням різних протоколів. Підтримуються такі протоколи, як HTTP, HTTPS, SCP, SFTP, HTTP / 2, HTTP / 3 і навіть Gopher. Практично будь-який протокол, який ви можете собі уявити, підтримується cURL.

cURL існує з 1998 року, в 2021 році йому стукне аж 23 роки. Він як і раніше досить потужний і сучасний, дуже активно розвивається. Так, у нього є свої примхи і проблеми, а тому розробнику корисно знати, як він працює і що він вміє робити.

Перша причина, чому варто вдатися до cURL – він дуже акуратний. Це щось на зразок швейцарського ножа. Якщо у вас є програма, яка вимагає виконання мережевого запиту, – будь то HTTP POST-запит до віддаленого URL або завантаження файлу з допомогою SFTP, – cURL буде для вас найбільш підходящим варіантом.

Наприклад, щоб відправити HTTP POST-запит з завантаженим файлом, використовуючи cURL CLI, нам треба буде виконати наступне:

curl --form name=Peter --form age=34 --form [email protected]/Users/petertasker/photos/image-1.jpg http://httpbin.org/post

Как насчет скачивания крупного файла?

curl -O http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz

Або отримання HTTP-заголовків з сервера?

curl -I https://blog.net.ua

У підсумку ми отримаємо HTTP-заголовки:

server: nginx
date: Mon, 04 Oct 2021 19:43:19 GMT
content-type: text/html; charset=UTF-8
link: ; rel="https://api.w.org/"
link: ; rel="alternate"; type="application/json"
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 6990e602aba74e97-FRA
alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400, h3-28=":443"; ma=86400, h3-27=":443"; ma=86400

Друга причина, по якій варто вивчити cURL, полягає в тому, що він доступний майже на будь-якій платформі, встановити його легко і швидко. Якщо у вас є веб-сервер, то, можна сказати, у вас є і cURL.

Наприклад, якщо у вас ще не встановлено cURL на macOS, ви можете швидко отримати його через Homebrew:

brew install curl

До цього моменту ми розглядали cURL, CLI-інструмент, проте прив’язки cURL доступні для більшості мов, включаючи PHP. Якщо ви працювали з PHP-програмами, які виконують мережеві запити, ви, ймовірно, вже використовували cURL.

cURL і PHP

У світі PHP підтримка cURL аналогічна підтримки будь-якого іншого модуля – наприклад, mysqli або GD.

Більшість версій PHP за замовчуванням скомпільовані з використанням cURL. При цьому cURL в технічному плані має вигляд розширення, як і в випадку з mysqli (і з усім іншим, що перераховано в секції extensions при виведенні phpinfo()):

PHP и cURL: как в WordPress выполняются HTTP-запросы

Однак реалізація cURL в PHP залишає бажати кращого. Сам інструмент cURL CLI відносно простий, а от реалізація PHP виявляється дещо складніше.

При роботі з PHP-бібліотекою cURL вам необхідно використовувати функцію curl_setopt(). Вона дозволяє вам задати опції cURL. Наприклад, настройка HTTP POST-запиту буде виглядати наступним чином:

$curl = curl_init( 'https://httpbin.org/post' );
curl_setopt( $curl, CURLOPT_POST, true );
curl_setopt( $curl, CURLOPT_POSTFIELDS, array( 'field1' => 'some data', 'field2' => 'some more data' ) );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
$response = curl_exec( $curl );
curl_close( $curl );

Звичайно, нічого страшного в цьому немає, але важливо розуміти, що при використанні більших запитів і більш складних параметрів CURLOPT_ все може швидко вийти з-під контролю.

На щастя, PHP-співтовариство придумало бібліотеки, які усувають деякі проблеми. Дві з найбільш популярних мережевих бібліотек – Guzzle і Requests. Оскільки Requests підтримує старі версії PHP, як і WordPress (так склалося історично), то в ядрі WP використовується саме бібліотека Requests.

cURL в WordPress

У WordPress для мережевих запитів використовується клас WP_Http, який, в свою чергу, покладається на бібліотеку Requests. Це означає, що всі службові методи HTTP, такі як wp_remote_get() і wp_remote_post(), використовують Requests. На високому рівні всі оновлення WordPress, завантаження плагінів, оновлення плагінів і практично всі функції завантаження / скачування в ядрі WordPress використовують абстракцію Requests для прив’язок і опцій cURL.

Давайте подивимося на те, як бібліотека Requests виконує HTTP-запити. Якщо ви відкриєте wp-includes/class-http.php, ви зможете побачити всі ті механізми, які керують HTTP-запитами в WP. Починаючи з WordPress 4.6, метод WP_Http :: request(); використовує Requests::request().

У WordPress 5.7 ви можете знайти цей виклик в рядку під номером 394 класу WP_Http, описаного вище.

$requests_response = Requests::request( $url, $headers, $data, $type, $options );

Досить просто, вірно? Тепер порівняйте HTTP POST-запит з сирим методом CURLOPTS (який був вище):

$data = array( 'key1' => 'value1', 'key2' => 'value2' );
$response = Requests::post( 'http://httpbin.org/post', array(), $data );

Набагато простіше. Якщо ви працюєте в контексті плагіна або теми WP, ви можете використовувати функцію wp_remote_post() для ще більшої абстракції:

$data = array( 'key1' => 'value1', 'key2' => 'value2' );
$response = wp_remote_post( 'http://httpbin.org/post', array( 'data' => $data ) );

wp_remote_post() просто викликає WP_Http::request() за допомогою POST як параметр методу.

Тепер перейдемо до того, що стосується внутрішньої роботи Requests. Давайте подивимося на рядок 359 файлу wp-includes/class-requests.php.

У методі Requests::request() відбувається наступне: код спочатку шукає опцію $transport. У WP-реалізації Requests є тільки два параметри за замовчуванням, cURL і fsockopen, в зазначеному порядку. Fsockopen використовує PHP-потоки і є fallback-функцією, коли розширення cURL не встановлено.

...
if (!empty($options['transport'])) {
    $transport = $options['transport'];

    if (is_string($options['transport'])) {
        $transport = new $transport();
    }
}
else {
    $need_ssl = (0 === stripos($url, 'https://'));
    $capabilities = array('ssl' => $need_ssl);
    $transport = self::get_transport($capabilities);
}
$response = $transport->request($url, $headers, $data, $options);
...

Як тільки транспорт визначено, запит передається в обраний клас $transport. Далі ми швидко розглянемо те, як бібліотека Requests використовує cURL.

В файлі wp-includes/Requests/Transport/cURL.php на 130 рядку ми можемо бачити, як насправді працює Requests. Цей клас показує, наскільки складною може бути робота з cURL в PHP. Велика частина логіки класу пов’язана з перевіркою і обробкою заголовків запиту і відповіді, а також з установкою коректних CURLOPTS на базі параметрів, переданих методом.

Обробка параметрів проводиться в методі Requests_Transport_cURL::setup_handler(), який заданий в рядку 309. Він проходить по переданим опцій і встановлює відповідний CURLOPT_:

...
switch ($options['type']) {
        case Requests::POST:
            curl_setopt($this->handle, CURLOPT_POST, true);
            curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
            break;
        case Requests::HEAD:
            curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
            curl_setopt($this->handle, CURLOPT_NOBODY, true);
            break;
        case Requests::TRACE:
            curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
            break;
        case Requests::PATCH:
        case Requests::PUT:
        case Requests::DELETE:
        case Requests::OPTIONS:
        default:
            curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
            if (!empty($data)) {
                curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
            }
    }
...

В кінцевому рахунку все зводиться до виклику curl_exec () після установки всіх опцій. Якщо вам це здалося складним, то так, це так і є! Різні сервери і хости мають різні вимоги до HTTP-заголовками і до обробки SSL. Requests добре справляється з адаптацією до широкого спектру установок.

Крім того, в мережевих функціях WordPress є кілька хуков, які дозволяють при необхідності перевизначити опції cURL. Наприклад, у мене є такі коментарі до плагіну в моїй локальній середовищі розробки:

 add_action('http_api_curl', function( $handle ){
    //Don't verify SSL certs
    curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, false);

    //Use Charles HTTP Proxy
    curl_setopt($handle, CURLOPT_PROXY, "127.0.0.1");
    curl_setopt($handle, CURLOPT_PROXYPORT, 8888);
 }, 10);

У наведеному вище прикладі я використовую хук http_api_curl, щоб спочатку відключити перевірку SSL-сертифіката. Це корисно, коли ви працюєте в локальному середовищі розробки з самоподпісанного сертифікатом, який не вимагає валідації. До речі, відмінною альтернативою перевизначення cURL-опцій для відключення верифікації хоста є створення свого центра, що засвідчує для вашого локального сервера.

Другий блок дозволяє мені встановити проксі-з’єднання через Charles для перевірки проходять мережевих запитів PHP. Charles – відмінний інструмент для налагодження мережевих запитів, який дозволяє вам бачити найдрібніші деталі кожного запиту в вашій локальній середовищі.

Як мені оновити cURL в WordPress?

Це не потрібно. WordPress не має своєї власної бібліотеки cURL, але покладається на версію cURL, яку пропонує установка PHP. Розширення PHP cURL – це оболонка для libcurl, а тому розширення буде використовувати версію libcurl, встановлену на сервері.

Звідси випливає, що оновлення cURL для WordPress майже завжди буде ідентично оновленню cURL на сервері. Якщо ви не керуєте сервером самостійно, вам потрібно звернутися до адміністратора сервера або хостинг-компанії за цим.

Якщо ви керуєте своїм сервером на Ubuntu/Debian, ви можете отримати останню версію libcurl так:

sudo apt update
sudo apt upgrade

Якщо у вас все ще залишається застаріла версія cURL, вам, ймовірно, треба оновити саму Ubuntu. Це виходить за рамки поста.

Найбільш явною ознакою того, що версія cURL на вашому сервері занадто стара, є неможливість підключення WordPress до віддалених серверів через HTTPS. В такому випадку слід шукати повідомлення про помилку «cURL Error 60». Ця помилка означає, що cURL не може перевірити SSL-сертифікат віддаленого хоста. Нерідко така помилка з’являється в результаті застарілого списку кореневих сертифікатів у вашій системі.

Щоб перевірити поточну версію cURL через консоль WordPress, перейдіть в розділ Tools → Site Health → Info:

Порівняйте вашу версію cURL зі списком релізів. Якщо вашої версії більше трьох років, рекомендовано оновити її. Якщо вона вийшла раніше 2017 року, то в такому випадку у вас можуть виникнути проблеми з підключенням до деяких сервісів Amazon.

cURL на Mac

Багато користувачів запитують про те, як cURL і SSL взаємодіють між собою.

OpenSSL – це стандартний набір інструментів SSL / TLS для обробки зашифрованою комунікації. Як і cURL, це ще одна програмна бібліотека. cURL скомпільовано з допомогою SSL / TLS-інструментарію для встановлення з’єднань по протоколу TLS. Якщо ви працюєте з плагіном WP Migrate DB Pro, то установка з’єднання по TLS відбудеться в тому випадку, коли ви спробуєте відправити або отримати дані з сайту, на якому встановлений SSL-сертифікат (HTTPS-сайти). У macOS бувають проблеми з цим, якщо cURL НЕ скомпільований з OpenSSL.

На тему обробки SSL за допомогою cURL можна писати свою величезну статтю. У середовищах macOS проблема полягає в тому, що використовується інша SSL / TLS-бібліотека під назвою SecureTransport.

Хороший тест, що дозволяє дізнатися версію OpenSSL в PHP – запит grep:

php -i | grep "SSL Version"

На виході ви повинні побачити, використовуєте ви OpenSSL або SecureTransport.

# Ubuntu 20.04:
SSL Version => OpenSSL/1.1.1i

# MacOS 11.2.1 / Big Sur
SSL Version => (SecureTransport) OpenSSL/1.1.1i

Як бачите, PHP на сервері розробки з Ubuntu використовує OpenSSL (що переважно), проте інтерпретатор PHP на моєму Mac використовує SecureTransport від Apple з прив’язкою до OpenSSL (тому все повинно бути добре).

Але зачекайте! Є ще дещо пов’язане з cURL і MacOS. Подивіться уважно, що повідомляє cURL CLI на моєму Mac:

curl 7.64.1 (x86_64-apple-darwin20.0) libcurl/7.64.1 (SecureTransport) LibreSSL/2.8.3 zlib/1.2.11 nghttp2/1.41.0
Release-Date: 2019-03-27
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS GSS-API HTTP2 HTTPS-proxy IPv6 Kerberos Largefile libz MultiSSL NTLM NTLM_WB SPNEGO SSL UnixSockets

Інструмент cURL CLI також використовує бібліотеку SecureTransport від Apple, але на цей раз з прив’язкою до LibreSSL, ще однією бібліотеці для обробки SSL. Тут цікаво відзначити, що на Mac cURL в PHP і cURL в командному рядку можуть використовувати різні бібліотеки для обробки SSL.

Якщо ви хочете гарантувати, що cURL в командному рядку і cURL в PHP будуть вести себе однаково, ви можете запустити наступну команду для отримання версії OpenSSL:

brew install curl-openssl

Також можуть виникнути проблеми, якщо обидва сервера використовують різні версії OpenSSL. Як правило, краще мати збігаються версії. Це може вести до тих же помилок, що і при використанні SecureTransport.

Прокоментувати

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *