如何检测file_get_contents超时引发的图片未完整下载问题?
如何检测PHP下载的图片是否完整?
我写了一段从国内服务器下载图片的PHP脚本,由于CDN因素,经常出现图片仅部分下载(显示不全)的情况——推测是超时导致的。但尴尬的是,这种情况下$content并不会等于FALSE,原来的判断逻辑完全失效。请问有什么方法可以检测图片是否未完整下载?
当前使用的代码如下:
function addNewFile($url) { $newFileName = 'newFileName.png'; $content = @file_get_contents($url); if ($content === FALSE) { echo ' - sleep 30 seconds and try again... ' . "\n"; sleep(30); return addNewFile($url); } else { file_put_contents($newFileNameInLocal, $content); } }
预期下载的正确图片:
实际下载到的不完整图片:
这个场景我太熟悉了!用file_get_contents下载资源时,遇到网络不稳定或者CDN的限流策略,很容易出现“下载了一部分但没报错”的情况。给你几个靠谱的检测思路,按优先级排序:
1. 对比远程文件与本地内容的字节大小
最直接的方式是先获取远程图片的实际大小,再和下载后的内容长度对比。
实现方式:通过get_headers获取Content-Length
function getRemoteFileSize($url) { $headers = get_headers($url, true); // 检查是否返回了Content-Length字段 if (isset($headers['Content-Length'])) { return (int)$headers['Content-Length']; } return false; } function addNewFile($url) { $newFileName = 'newFileName.png'; $remoteSize = getRemoteFileSize($url); $content = @file_get_contents($url); if ($content === FALSE) { echo ' - sleep 30 seconds and try again... ' . "\n"; sleep(30); return addNewFile($url); } else { // 对比大小(如果能获取到远程大小) if ($remoteSize !== false && strlen($content) != $remoteSize) { echo ' - Incomplete download, retry...' . "\n"; sleep(30); return addNewFile($url); } file_put_contents($newFileName, $content); } }
注意:有些服务器或CDN可能不会返回Content-Length(比如采用分块传输),这时候这个方法就失效了,需要搭配其他方案。
2. 用图片处理库验证完整性
不完整的图片无法被图片解析库正常识别,我们可以利用这一点来检测。比如用PHP内置的GD扩展:
function isImageComplete($imageContent) { // 把内容写入临时文件(或者用内存流) $tmpFile = tmpfile(); fwrite($tmpFile, $imageContent); $meta = stream_get_meta_data($tmpFile); $tmpPath = $meta['uri']; // 尝试获取图片信息,失败则说明不完整 $imageInfo = getimagesize($tmpPath); fclose($tmpFile); return $imageInfo !== false; } function addNewFile($url) { $newFileName = 'newFileName.png'; $content = @file_get_contents($url); if ($content === FALSE) { echo ' - sleep 30 seconds and try again... ' . "\n"; sleep(30); return addNewFile($url); } else { // 验证图片完整性 if (!isImageComplete($content)) { echo ' - Corrupted image, retry...' . "\n"; sleep(30); return addNewFile($url); } file_put_contents($newFileName, $content); } }
这个方法兼容性更强,不管服务器是否返回Content-Length都能生效,缺点是需要GD扩展启用(大部分环境都默认开启)。
3. 改用CURL替代file_get_contents,增强超时控制
file_get_contents的超时设置比较简陋,改用CURL可以更精细地控制连接超时、读取超时,同时能直接获取传输的字节数,方便对比:
function curlDownload($url, $timeout = 30) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 设置连接超时(秒) curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); // 设置读取超时(秒) curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 跟随重定向 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 获取响应头 curl_setopt($ch, CURLOPT_HEADER, true); $response = curl_exec($ch); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $headers = substr($response, 0, $headerSize); $content = substr($response, $headerSize); // 获取远程文件大小和实际下载大小 $remoteSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD); $downloadedSize = strlen($content); $error = curl_errno($ch) ? curl_error($ch) : null; curl_close($ch); return [ 'content' => $content, 'is_complete' => $remoteSize == $downloadedSize, 'error' => $error ]; } function addNewFile($url) { $newFileName = 'newFileName.png'; $result = curlDownload($url); if ($result['error'] || !$result['is_complete']) { echo ' - Download failed or incomplete, retry after 30s... ' . "\n"; sleep(30); return addNewFile($url); } else { file_put_contents($newFileName, $result['content']); } }
CURL的优势在于更稳定的超时控制,以及更丰富的传输信息,适合在复杂网络环境下使用。
额外建议
- 给重试逻辑加个次数限制,比如最多重试3次,避免无限递归导致栈溢出;
- 针对不同图片格式(JPG/PNG/GIF),可以调整验证逻辑(比如GIF的多帧检测);
- 如果CDN经常出现分段传输的情况,优先用图片解析的方式验证完整性。
内容的提问来源于stack exchange,提问作者Pak Ho Cheung
相关产品推荐
相关产品推荐

