Почему для выполнения запросов к API следует использовать HTTP-функции WordPress

Иногда WordPress-сайт должен взаимодействовать с другими веб-сервисами. Обычно это осуществляется с помощью HTTP-протокола. Типичный пример: ваша установка WordPress связывается с серверами wordpress.org для проверки наличия новых версий плагинов, тем, а также ядра WP.

Нередко подобное взаимодействие можно видеть в плагинах и темах WordPress. Любой плагин, взаимодействующий с внешним сервисом, будет выполнять некоторые HTTP-запросы. Добавление подписчиков в ваш список Mailchimp, отправка email через Amazon SES, выгрузка изображений в Amazon S3 – для всего этого требуется выполнение нескольких HTTP-запросов.

В этой статье мы подробно рассмотрим встроенные HTTP-функции, которые предлагает WordPress для выполнения указанных запросов. Также мы коснемся вопроса, почему всегда стоит обращаться именно к этим функциям.

Однако для начала немного предыстории.

Ошибочный путь: использование PHP-функций

PHP предлагает несколько разных способов выполнения HTTP-запросов. Вы можете напрямую работать с сетевыми функциями для ваших HTTP-запросов, такими как fsockopen(), fread(), fwrite(), но в конечном счете вы просто потеряете время и изобретете велосипед без какой-либо на то причины.

Подход чуть получше – использование одной из функций чтения файлов. Эти функции могут обрабатывать оболочку HTTP-протокола в PHP. К ним относится несколько громоздкая функция fopen(), а также более простые file() и file_get_contents(). Пример простого однострочного кода:

// Example code only, don’t do this!
$response = file_get_contents('https://www.google.com/');

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

Также стоит понимать, что даже если с помощью PHP-функций можно получить контент с URL-адреса в сети, это не означает, что такой подход является лучшим в разработке. Простое, казалось бы, требование указать заголовок в HTTP-запросе заметно усложнит приведенный выше пример (пример взят с Stack Overflow):

// Example code only, don’t do this!
// Create a stream
$opts = array(
  "http" => array(
    "method" => "GET",
    "header" => "Accept-language: en\r\n",
  )
);
 ​
// DOCS: https://www.php.net/manual/en/function.stream-context-create.php
$context = stream_context_create( $opts );
 ​
// Open the file using the HTTP headers set above
// DOCS: https://www.php.net/manual/en/function.file-get-contents.php
$file = file_get_contents( 'https://www.google.com/', false, $context );

Уже не так просто. При этом учтите, что в данном примере нет обработки ошибок. Если вы не уверены на 100%, что удаленный сервер даст корректный ответ (подсказка: у вас не может быть такой уверенности), вам придется добавить дополнительный код для обертки file_get_contents(), и только потом уже код можно будет использовать в рабочей среде.

Обилие PHP HTTP-библиотек

Поскольку выполнение HTTP-запросов с помощью сырого PHP является достаточно неудобным процессом, неудивительно, что есть десятки вспомогательных библиотек, которые упрощают создание HTTP-запросов.

Если рассматривать более широкий мир PHP, выходящий за пределы экосистемы WordPress (где используется Composer для управления зависимостями), можно выделить популярные библиотеки Guzzle и Httpful для обработки HTTP-запросов. При этом имеются и другие хорошие решения. У всех есть свои сильные и слабые стороны.

В мире WordPress нужно проявлять некоторую осторожность при использовании Composer-библиотек, особенно если вы хотите потом передавать код другим WordPress-юзерам. На то есть две причины.

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

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

Есть способы обойти это, используя плагин Imposter – с его помощью можно обернуть все зависимости Composer в ваше собственное пространство имен PHP. Конечно, можно вполне пойти этим путем. Но заметьте, как, казалось бы, простая идея использовать пакет Composer для управления HTTP-запросами превратилась в более серьезную проблему, которая, скорее всего, существенно повлияет на ваш процесс сборки.

Простой пример: в плагине WP Offload Media используются сторонние библиотеки поддерживаемых хранилищ (Amazon AWS, Digital Ocean и т.д.). Они, в свою очередь, используют HTTP-библиотеку Guzzle. Чтобы избежать проблем с совместимостью с другими плагинами, в WP Offload Media используется Imposter для обертки всех зависимостей в отдельное пространство имен.

Вторая причина, почему не стоит использовать сторонние библиотеки для HTTP-запросов в WordPress: перестанет работать фильтрация.

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

wp_remote_get, wp_remote_post и т.д.

В течение последних 12 лет WordPress поставлялся со встроенными функциями для работы с HTTP-запросами: wp_remote_get(), wp_remote_post() и wp_remote_head(). По одной функции для каждого глагола GET, POST и HEAD. Все три глобальные функции работают как оболочки для класса WP_Http, который, в свою очередь, использует библиотеку Requests.

Вызов wp_remote_get() очень простой:

$response = wp_remote_get( 'https://www.google.com/' );

Есть множество причин, почему вы должны предпочесть функции из семейства wp_remote_*. Вот самые важные из них.

Они всегда под рукой

Функции wp_remote_* присутствуют в WP практически с самого появления движка, потому нет абсолютно никакой необходимости переживать о том, что они куда-то пропадут.

Некоторые основополагающие возможности ядра WordPress, такие как установка плагинов и автоматические обновления, полагаются на эти функции. Нет никакой реальной причины добавлять сторонние библиотеки для HTTP-запросов. У вас уже есть доступ к проверенной, надежной библиотеке в ядре.

Они подходят под разные нужды

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

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

Вот пример, в котором я немного меняю HTTP-запрос перед его отправкой на удаленный сервер:

$args = array(
  // Increase the timeout from the default of 5 to 10 seconds
  'timeout'    => 10,

  // Overwrite the default: "WordPress/5.8;www.mysite.tld" header:
  'user-agent' => 'My special WordPress installation',

  // Add a couple of custom HTTP headers
  'headers'    => array(
     'X-Custom-Id' => 'ABC123',
     'X-Secret-Thing' => 'secret',
  ),

  // Skip validating the HTTP servers SSL cert;
  'sslverify' => false,
);

$response = wp_remote_get( 'https://www.example.com/', $args );

Полный список допустимых аргументов нигде не задокументирован, но его можно найти в исходном файле для класса HTTP (или в этом полезном комментарии).

Короче говоря, wp_remote_get(), wp_remote_post() и wp_remote_head() способны обрабатывать большинство вариантов использования HTTP-запросов. Потому на практике существует лишь малый процент ситуаций, когда вам потребуется обращаться к сторонней HTTP-библиотеке в WordPress.

С помощью базовой библиотеки Requests можно даже использовать встроенные функции для одновременного выполнения нескольких запросов, но это выходит за рамки данного поста.

Их легко настроить

Во время выполнения HTTP-запроса все функции wp_remote_* будут вызывать множество разных WordPress-фильтров, которые позволят вам и другим пользователям задавать точное поведение или даже блокировать запросы внешних URL-адресов.

Вы можете не только детально контролировать свои собственные HTTP-запросы, но и вмешиваться в HTTP-запросы, совершаемые другими плагинами или даже самим ядром WP.

Представьте, что вы отвечаете за несколько внутренних установок WordPress в большой компании. Чтобы соответствовать принятым правилам IT-безопасности, вам необходимо скрыть URL-адрес сайта, который автоматически добавляется в строку пользовательского агента в HTTP-запросах. Как этого добиться?

С помощью инициирования фильтра http_request_args перед отправкой запроса. Пример:

function my_http_request_args($args) {
  $args['user-agent'] = 'WordPress';

  return $args;
}
add_filter('http_request_args', 'my_http_request_args');

Некоторым владельцам сайтов может потребоваться полностью заблокировать HTTP-трафик на внешние URL-адреса. Все запросы, которые выполняются с использованием wp_remote_get(), wp_remote_post() и wp_remote_head(), будут учитывать константы WP_HTTP_BLOCK_EXTERNAL и the WP_ACCESSIBLE_HOSTS.

Если они определены, эти константы позволяют владельцам сайтов блокировать весь исходящий трафик, но при желании сделать набор исключений:

define( 'WP_HTTP_BLOCK_EXTERNAL', true );
define( 'WP_ACCESSIBLE_HOSTS', 'api.example.com,www.example.com' );

Запрос к любому другому хосту теперь будет возвращать WP_Error:

$response = wp_remote_get( 'https://www.google.com' );

/* Returns:
WP_Error Object
(
    [errors] => Array
    (
      [http_request_not_executed] => Array
      (
        [0] => User has blocked requests through HTTP.
      )
    )
  …
)*/

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

Они не привязаны к какому-либо типу транспорта

Особенность библиотеки Requests заключается в том, что она не зависит от типа транспорта (транспортно-агностическая структура). В частности, если касаться HTTP-запросов, работоспособность библиотеки не зависит от того, установлен ли cURL на сервере.

Библиотека Requests будет использовать cURL, если он установлен. Но если его не будет (в редких случаях такое встречается), то тогда библиотека откатится к использованию fsockopen().

В любой ситуации библиотека Requests выполнит свою работу, потому у вас будет на одну зависимость меньше.

Что по поводу wp_remote_put и wp_remote_delete?

Если вы уже успели поработать с удаленными API и знаете, как функционируют HTTP-глаголы, вы, возможно, заметили, что я до сих пор не упоминал wp_remote_put() и wp_remote_delete(). Поскольку у нас есть встроенные реализации для GET, POST и HEAD, очевидно, что должны быть реализации и для PUT и DELETE. Верно?

Нет. Команда WordPress Core еще не реализовала все стандартные HTTP-глаголы. Но, возможно, они появятся в следующих релизах WP, так как обсуждение их ведется.

Если вам нужны эти функции уже сейчас, вы можете легко добавить их с помощью класса WP_Http в сочетании с оболочкой wp_remote_request:

if ( ! function_exists( 'wp_remote_put' ) ) {
  function wp_remote_put($url, $args) {
     $defaults = array('method' => 'PUT');
     $r = wp_parse_args( $args, $defaults );
     return wp_remote_request($url, $r);
  }
}

if ( ! function_exists( 'wp_remote_delete' ) ) {
  function wp_remote_delete($url, $args) {
     $defaults = array('method' => 'DELETE');
     $r = wp_parse_args( $args, $defaults );
     return wp_remote_request($url, $r);
  }
}

Обратите внимание, что включение этих объявлений функций в function_exists() очень важно, поскольку есть вполне реальная вероятность того, что другой плагин на вашем сайте уже их реализовал. Также это станет защитой от разных проблем, связанных с возможным появлением этих реализаций в ядре WP в будущем.

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

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

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