From b492a5ff7685604dd4c13d9c4ccd2346e6a1b07b Mon Sep 17 00:00:00 2001 From: winchen Date: Sat, 1 Aug 2026 12:05:04 +0800 Subject: [PATCH] fix: prefer healthy native media edges --- dist/api/media_proxy.php | 39 ++++++++++++++++++++++++++++++++++---- public/api/media_proxy.php | 39 ++++++++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/dist/api/media_proxy.php b/dist/api/media_proxy.php index 5cb80e3..764a539 100644 --- a/dist/api/media_proxy.php +++ b/dist/api/media_proxy.php @@ -61,6 +61,27 @@ function mediaProxyCacheRoot(): string return $root; } +function mediaProxyPreferredIps(): array +{ + $configured = preg_split('/[,\s]+/', webImEnv('JC_WEB_IM_MEDIA_PROXY_IPS')) ?: []; + $ips = []; + foreach ($configured as $ip) { + $ip = trim($ip); + if ( + $ip !== '' + && filter_var( + $ip, + FILTER_VALIDATE_IP, + FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE + ) !== false + ) { + $ips[$ip] = $ip; + } + } + + return array_values($ips); +} + function mediaProxyMime(string $path): string { $finfo = finfo_open(FILEINFO_MIME_TYPE); @@ -76,8 +97,14 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi { $deadline = microtime(true) + WEB_MEDIA_PROXY_MAX_SECONDS; $lastError = '媒体源无响应'; + $preferredIps = mediaProxyPreferredIps(); + $targets = array_merge($preferredIps, array_fill(0, max(2, 6 - count($preferredIps)), null)); - for ($attempt = 1; $attempt <= 6 && microtime(true) < $deadline; $attempt++) { + foreach ($targets as $index => $preferredIp) { + if (microtime(true) >= $deadline) { + break; + } + $attempt = $index + 1; $temporaryFile = $cacheFile . '.tmp.' . getmypid() . '.' . $attempt; $stream = @fopen($temporaryFile, 'x+b'); if ($stream === false) { @@ -96,7 +123,7 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi } $remainingMs = max(1000, (int)floor(($deadline - microtime(true)) * 1000)); - curl_setopt_array($curl, [ + $curlOptions = [ CURLOPT_RETURNTRANSFER => false, CURLOPT_FOLLOWLOCATION => false, CURLOPT_CONNECTTIMEOUT_MS => min(2500, $remainingMs), @@ -123,7 +150,11 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi $bytes += $written; return $written; }, - ]); + ]; + if (is_string($preferredIp) && $preferredIp !== '') { + $curlOptions[CURLOPT_RESOLVE] = [$host . ':443:' . $preferredIp]; + } + curl_setopt_array($curl, $curlOptions); $result = curl_exec($curl); $status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE); @@ -147,7 +178,7 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi $lastError = $tooLarge ? '图片超过代理大小限制' : ($curlError !== '' ? $curlError : ($status > 0 ? '媒体源返回 HTTP ' . $status : '媒体源无响应')); - if ($attempt < 6 && microtime(true) < $deadline) { + if ($attempt < count($targets) && microtime(true) < $deadline) { usleep(150000); } } diff --git a/public/api/media_proxy.php b/public/api/media_proxy.php index 5cb80e3..764a539 100644 --- a/public/api/media_proxy.php +++ b/public/api/media_proxy.php @@ -61,6 +61,27 @@ function mediaProxyCacheRoot(): string return $root; } +function mediaProxyPreferredIps(): array +{ + $configured = preg_split('/[,\s]+/', webImEnv('JC_WEB_IM_MEDIA_PROXY_IPS')) ?: []; + $ips = []; + foreach ($configured as $ip) { + $ip = trim($ip); + if ( + $ip !== '' + && filter_var( + $ip, + FILTER_VALIDATE_IP, + FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE + ) !== false + ) { + $ips[$ip] = $ip; + } + } + + return array_values($ips); +} + function mediaProxyMime(string $path): string { $finfo = finfo_open(FILEINFO_MIME_TYPE); @@ -76,8 +97,14 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi { $deadline = microtime(true) + WEB_MEDIA_PROXY_MAX_SECONDS; $lastError = '媒体源无响应'; + $preferredIps = mediaProxyPreferredIps(); + $targets = array_merge($preferredIps, array_fill(0, max(2, 6 - count($preferredIps)), null)); - for ($attempt = 1; $attempt <= 6 && microtime(true) < $deadline; $attempt++) { + foreach ($targets as $index => $preferredIp) { + if (microtime(true) >= $deadline) { + break; + } + $attempt = $index + 1; $temporaryFile = $cacheFile . '.tmp.' . getmypid() . '.' . $attempt; $stream = @fopen($temporaryFile, 'x+b'); if ($stream === false) { @@ -96,7 +123,7 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi } $remainingMs = max(1000, (int)floor(($deadline - microtime(true)) * 1000)); - curl_setopt_array($curl, [ + $curlOptions = [ CURLOPT_RETURNTRANSFER => false, CURLOPT_FOLLOWLOCATION => false, CURLOPT_CONNECTTIMEOUT_MS => min(2500, $remainingMs), @@ -123,7 +150,11 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi $bytes += $written; return $written; }, - ]); + ]; + if (is_string($preferredIp) && $preferredIp !== '') { + $curlOptions[CURLOPT_RESOLVE] = [$host . ':443:' . $preferredIp]; + } + curl_setopt_array($curl, $curlOptions); $result = curl_exec($curl); $status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE); @@ -147,7 +178,7 @@ function fetchMediaProxyImage(string $url, string $host, string $cacheFile): voi $lastError = $tooLarge ? '图片超过代理大小限制' : ($curlError !== '' ? $curlError : ($status > 0 ? '媒体源返回 HTTP ' . $status : '媒体源无响应')); - if ($attempt < 6 && microtime(true) < $deadline) { + if ($attempt < count($targets) && microtime(true) < $deadline) { usleep(150000); } }