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 upload=@/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://deliciousbrains.com

В итоге мы получим HTTP-заголовки:



server: nginx
date: Thu, 11 Mar 2021 14:56:01 GMT
content-type: text/html; charset=UTF-8
vary: Accept-Encoding
link: <https://deliciousbrains.com/wp-json/>; rel="https://api.w.org/"
link: <https://deliciousbrains.com/wp-json/wp/v2/pages/28>; rel="alternate"; type="application/json"
link: <https://deliciousbrains.com/>; rel=shortlink
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
content-security-policy: default-src 'self' https: data: wss: 'unsafe-inline' 'unsafe-eval';
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
fastcgi-cache: HIT


Вторая причина, по которой стоит изучить 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()):

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

Источник: https://deliciousbrains.com

Блог про WordPress
Добавить комментарий

Получать новые комментарии по электронной почте.