开发者

PHP: Detecting fopen() failures when downloading images

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:

PHP: Detecting fopen() failures when downloading images

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: ' . 开发者_StackOverflowfilesize($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.


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
        ...
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜