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

发布于 2024-09-30 04:07:24 字数 4214 浏览 1 评论 0原文

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

对于头像,我编写了一个简短的 proxy.php 脚本,它将图像 URL 作为 ?img= 参数传递给它,下载它并保存在 /var/www/cached_avatars 下/md5_of_that_url 在我的 CentOS 5 机器上。下次使用相同的 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 等变量传递到回调中,或者我无法设置 $length 变量在 STREAM_NOTIFY_FILE_SIZE_IS 事件的回调中,然后将其与主脚本中的 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 个文件,因此这种同时写入在这里不是问题。

I have a card game (screenshot below) in which I display player avatars.

For the avatars I've written a short proxy.php script, which would take an image URL passed to it as ?img= parameter, download it and save under /var/www/cached_avatars/md5_of_that_url at my CentOS 5 machine. Next time the script is called with the same URL, it will find that image in the dir and serve it directly to STDOUT.

This works mostly well, but for some avatars the initial download fails (I suppose it times out) and you don't see the lower part of the player picture:

alt text

I'd like to detect this image download failure and delete the cached partial file, so that it is re-downloaded on the next proxy.php call.

I've tried detecting STREAM_NOTIFY_FAILURE or STREAM_NOTIFY_COMPLETED events in my callback, but they are not fired. The only events I see are: 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

And my even bigger problem is, that I can't pass variables like $img or $cached into the callback or I can't set a $length variable in the callback on a STREAM_NOTIFY_FILE_SIZE_IS event and then compare it with filesize($cached) in the main script (I could detect the mismatch and delete the file):

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=

Does anybody have a solution for my problem?

I've looked at the PHP curl library, but don't see how could it help me here.

Below is my script, I've omitted the URL sanity checks for brevity:

<?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;
        }
}

?>

I don't use any file locking in my script: it's ok for a read from cache to return an incomplete file (because it is still being downloaded) once in a while. But I want to keep my cache free of any partially downloaded images. Also if you look at my script I use "xb" which should prevent from several scripts writing into 1 file, so this simultaneous writing is not a problem here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

终陌 2024-10-07 04:07:24

curl 库是您希望我们下载图像的库。它处理超时、重定向和错误检查。例如,您可以检查您正在连接的服务器是否有 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
        ...
    }
}

The curl library is what you would want to us to download the image. It handles timeouts, redirects and error checking. For example, you can check for a 404 (missing file) response from the server you are connecting to. If everything works, then you write the contents to a cache file using 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
        ...
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文