PHP:下载图像时检测fopen()失败

时间:2010-11-03 18:22:25

标签: php stream fopen

我有一个纸牌游戏(下面的截图),其中我显示了玩家头像。

对于头像,我写了一个简短的proxy.php脚本,它会将传递给它的图像网址作为?img = 参数,下载并保存在 / var /下我的CentOS 5机器上有www / cached_avatars / md5_of_that_url 。下次使用相同的URL调用脚本时,它将在目录中找到该图像并直接将其提供给STDOUT。

这种方法效果很好,但是对于一些头像来说,初始下载失败了(我想它会超时)并且你没有看到播放器图片的下半部分:

alt text

我想检测此图像下载失败并删除缓存的部分文件,以便在下一次proxy.php调用时重新下载。

我已尝试在回调中检测到STREAM_NOTIFY_FAILURE或STREAM_NOTIFY_COMPLETED事件,但不会触发它们。我看到的唯一事件是:STREAM_NOTIFY_CONNECT,STREAM_NOTIFY_MIME_TYPE_IS,STREAM_NOTIFY_FILE_SIZE_IS,STREAM_NOTIFY_REDIRECTED,STREAM_NOTIFY_PROGRESS:

Nov  3 18:48:27 httpd: 2  0
Nov  3 18:48:27 httpd: 4 image/jpeg 0
Nov  3 18:48:27 httpd: 5 Content-Length: 45842 0
Nov  3 18:48:27 httpd: 7  0
Nov  3 18:48:27 last message repeated 16 times
Nov  3 18:48:39 httpd: 2  0
Nov  3 18:48:40 httpd: 4 image/jpeg 0
Nov  3 18:48:40 httpd: 5 Content-Length: 124537 0
Nov  3 18:48:40 httpd: 7  0

我更大的问题是,我无法将$ img或$ cached等变量传递给回调,或者我无法在STREAM_NOTIFY_FILE_SIZE_IS事件的回调中设置$ length变量,然后将其与filesize进行比较(主脚本中的$ cached)(我可以检测到不匹配并删除文件):

Nov  3 18:50:17 httpd: PHP Notice:  Undefined variable: length in /var/www/html/proxy.php on line 58
Nov  3 18:50:17 httpd: length=

有没有人能解决我的问题?

我看过PHP curl库,但是看不出它对我有什么帮助。

下面是我的脚本,为简洁起见,我省略了URL健全性检查:

<?php

define('MAX_SIZE', 1024 * 1024);
define('CACHE_DIR', '/var/www/cached_avatars/');

$img = urldecode($_GET['img']);

$opts = array(
        'http' => array(
                'method' => 'GET'
        )
);

$cached = CACHE_DIR . md5($img);

$finfo = finfo_open(FILEINFO_MIME);
$readfh = @fopen($cached, 'rb');
if ($readfh) {
        header('Content-Type: ' . finfo_file($finfo, $cached));
        header('Content-Length: ' . filesize($cached));

        while (!feof($readfh)) {
                $buf = fread($readfh, 8192);
                echo $buf;
        }

        fclose($readfh);
        finfo_close($finfo);
        exit();
}

$ctx = stream_context_create($opts);
stream_context_set_params($ctx, array('notification' => 'callback'));
$writefh = fopen($cached, 'xb');
$webfh = fopen($img, 'r', FALSE, $ctx);
if ($webfh) {
        $completed = TRUE;

        while (!feof($webfh)) {
                $buf = fread($webfh, 8192);
                echo $buf;
                if ($writefh)
                        fwrite($writefh, $buf);
        }

        fclose($webfh);
        if ($writefh)
                fclose($writefh);

        # XXX can't access $length in callback
        error_log('length=' . $length);

        # XXX can't access $completed in callback
        if (!$completed)
                unlink($cached);
}

function callback($code, $severity, $message, $message_code, $bytes_transferred, $bytes_total) {
        error_log(join(' ', array($code, $message, $message_code)));

        if ($code == STREAM_NOTIFY_PROGRESS && $bytes_transferred > MAX_SIZE) {
                exit('File is too big: ' . $bytes_transferred);

        } else if ($code == STREAM_NOTIFY_FILE_SIZE_IS) {
                if ($bytes_total > MAX_SIZE)
                        exit('File is too big: ' . $bytes_total);
                else {
                        header('Content-Length: ' . $bytes_total);
                        # XXX can't pass to main script
                        $length = $bytes_total;
                }

        } else if ($code == STREAM_NOTIFY_MIME_TYPE_IS) {
                if (stripos($message, 'image/gif') !== FALSE ||
                    stripos($message, 'image/png') !== FALSE ||
                    stripos($message, 'image/jpg') !== FALSE ||
                    stripos($message, 'image/jpeg') !== FALSE) {
                        header('Content-Type: ' . $message);
                } else {
                        exit('File is not image: ' . $mime);
                }
        } else if ($code == STREAM_NOTIFY_FAILURE) {
                $completed = FALSE;
        }
}

?>

我的脚本中没有使用任何文件锁定:从缓存中读取一段时间后返回一个不完整的文件(因为它仍在下载中)是可以的。但是我希望我的缓存不受任何部分下载的图像的影响。另外,如果你查看我的脚本,我会使用“xb”,它可以防止多个脚本写入1个文件,所以这里同时写入不是问题。

1 个答案:

答案 0 :(得分:1)

您希望我们下载图像的卷曲库。它处理超时,重定向和错误检查。例如,您可以检查要连接的服务器的404(丢失文件)响应。如果一切正常,那么使用fopen将内容写入缓存文件。

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_URL, $img_url);
$content = curl_exec($ch);
$info = curl_getinfo($ch);
$errorCode      = curl_errno($ch);
$errorMsg       = curl_error($ch);
curl_close($ch);
// Check for errors
if ( $errorCode==0 ) {
    // No connection errors, just for response type
    if ($info['http_code'] != 200) {
        // Something happened on the other side
        ...
    } else {
        // Image is in $content variable, save to cache file
        ...
    }
}