Initial commit
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* Ядро: перехват исходящих HTTP-запросов и маршрутизация через прокси.
|
||||
*
|
||||
* @package AI_Proxy_Manager
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Класс маршрутизации запросов через прокси.
|
||||
*
|
||||
* Реализует ротацию: при ошибке/таймауте текущего прокси WordPress повторно
|
||||
* выполняет запрос, и хук подставляет следующий включённый прокси.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class AIPMSP_Core {
|
||||
|
||||
/**
|
||||
* Текущий индекс прокси для ротации.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $rotation_index = 0;
|
||||
|
||||
/**
|
||||
* Кэш настроек на время запроса.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private $settings = null;
|
||||
|
||||
/**
|
||||
* Конструктор: регистрирует хуки.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
// Применяем прокси к cURL-хендлу.
|
||||
add_action( 'http_api_curl', array( $this, 'apply_proxy' ), 10, 3 );
|
||||
|
||||
// Ловим ошибки ответа для ротации на следующий прокси.
|
||||
add_filter( 'http_response', array( $this, 'maybe_rotate' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Лениво загружает настройки.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function settings() {
|
||||
if ( null === $this->settings ) {
|
||||
$this->settings = AIPMSP_Proxy::get_settings();
|
||||
}
|
||||
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, нужно ли проксировать запрос к указанному URL.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $url URL запроса.
|
||||
* @return bool
|
||||
*/
|
||||
private function should_proxy( $url ) {
|
||||
$settings = $this->settings();
|
||||
|
||||
if ( empty( $settings['proxy_enabled'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$host = wp_parse_url( $url, PHP_URL_HOST );
|
||||
|
||||
if ( empty( $host ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$host = strtolower( $host );
|
||||
|
||||
foreach ( $settings['domains'] as $domain ) {
|
||||
$domain = strtolower( trim( $domain ) );
|
||||
|
||||
if ( '' === $domain ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Точное совпадение или поддомен разрешённого домена.
|
||||
if ( $host === $domain || self::str_ends_with( $host, '.' . $domain ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук http_api_curl: подставляет настройки прокси в cURL-хендл.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param resource $handle cURL-хендл.
|
||||
* @param array $args Аргументы запроса.
|
||||
* @param string $url URL запроса.
|
||||
* @return void
|
||||
*/
|
||||
public function apply_proxy( $handle, $args, $url ) {
|
||||
if ( ! $this->should_proxy( $url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enabled = AIPMSP_Proxy::get_enabled_proxies( $this->settings() );
|
||||
|
||||
if ( empty( $enabled ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ротация: выбираем прокси по текущему индексу (с цикличностью).
|
||||
$count = count( $enabled );
|
||||
$index = $this->rotation_index % $count;
|
||||
$proxy = $enabled[ $index ];
|
||||
|
||||
$pass = isset( $proxy['pass'] ) ? AIPMSP_Crypto::decrypt( $proxy['pass'] ) : '';
|
||||
|
||||
curl_setopt( $handle, CURLOPT_PROXY, $proxy['ip'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||
curl_setopt( $handle, CURLOPT_PROXYPORT, (int) $proxy['port'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||
curl_setopt( $handle, CURLOPT_PROXYTYPE, AIPMSP_Proxy::curl_proxy_type( $proxy['type'] ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||
|
||||
if ( ! empty( $proxy['user'] ) && '' !== $pass ) {
|
||||
curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy['user'] . ':' . $pass ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||
}
|
||||
|
||||
curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 10 ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук http_response: при ошибке соединения пробует следующий прокси.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $response Ответ HTTP API.
|
||||
* @param array $parsed_args Аргументы запроса.
|
||||
* @param string $url URL запроса.
|
||||
* @return array
|
||||
*/
|
||||
public function maybe_rotate( $response, $parsed_args, $url ) {
|
||||
if ( ! $this->should_proxy( $url ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$enabled = AIPMSP_Proxy::get_enabled_proxies( $this->settings() );
|
||||
$count = count( $enabled );
|
||||
|
||||
if ( $count < 2 ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Считаем неудачей ошибку транспорта или 5xx/0 коды.
|
||||
$is_error = is_wp_error( $response );
|
||||
$code = $is_error ? 0 : (int) wp_remote_retrieve_response_code( $response );
|
||||
$failed = $is_error || 0 === $code || $code >= 500;
|
||||
|
||||
// Ограничиваем число попыток числом доступных прокси.
|
||||
$attempts = isset( $parsed_args['_aipmsp_attempts'] ) ? (int) $parsed_args['_aipmsp_attempts'] : 0;
|
||||
|
||||
if ( $failed && $attempts < $count - 1 ) {
|
||||
$this->rotation_index++;
|
||||
|
||||
$retry_args = $parsed_args;
|
||||
$retry_args['_aipmsp_attempts'] = $attempts + 1;
|
||||
// Индекс ротации уже сдвинут, повторный запрос пойдёт через следующий прокси.
|
||||
$retry = wp_remote_request( $url, $retry_args );
|
||||
|
||||
return $retry;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Полифилл str_ends_with для PHP < 8.0.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $haystack Строка.
|
||||
* @param string $needle Окончание.
|
||||
* @return bool
|
||||
*/
|
||||
private static function str_ends_with( $haystack, $needle ) {
|
||||
if ( function_exists( 'str_ends_with' ) ) {
|
||||
return str_ends_with( $haystack, $needle );
|
||||
}
|
||||
|
||||
$len = strlen( $needle );
|
||||
|
||||
if ( 0 === $len ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return substr( $haystack, -$len ) === $needle;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user