获取readfile引起的流量()

时间:2011-02-26 16:16:18

标签: php curl download traffic readfile

我使用readfile让客户端通过我的服务器下载文件。因此,我将从readfile('external-url')收到的数据直接输出到客户端。

现在我想确定由readfile()引起的流量。

我可以通过readfile的返回值来确定它,但仅限于客户端完成下载。否则脚本停止工作,readfile()的返回值为0。

首先我尝试了这段代码:

//outputs download headers
//creating $stream_context with request headers for the external download server
$traffic = readfile($url, false, $stream_context);
//save traffic...

当客户端停止下载时,从未调用过流量。

然后我用register_shutdown_function()注册了一个shutdown-function,其中包含$ traffic作为全局变量来保存流量。现在已创建流量文件,但已使用的流量为0。

我无法访问服务器日志或其他内容。我只能使用php和htaccess。

我现在使用的一种解决方法是启动对文件的请求,解析文件大小并将完整的文件大小添加到客户端流量。然后我用readfile()开始下载。如果客户端停止下载,则会像下载整个文件一样处理。

第三种方法可以是curl及其CURLOPT_WRITEFUNCTION设置。但这对于服务器而言是太多的开销,与我想要做的事情无关:保存真实的流量。

在下载文件之前保存客户端流量还有另一个问题:我想支持恢复和分块下载(多个连接到一个文件以便更快下载)。这仍然有效,但问题是计算流量!对于块,我可以解析HTTP-RANGE标头以确定所请求的文件部分并将其保存为流量,但是恢复如何?

世界上有可能的解决方案吗?

我仍然不使用数据库,我只使用带有htaccess -logininformation的文件来识别客户端,并将每个客户端的使用流量保存在我的网站空间的单独文件中。

这也是我的代码:

//$download = array(url, filesize, filename) got it whith a separate curl request to the external file
$downloadHeader = CreateDownloadHeaders($download, $_hoster->AcceptRanges());

$requestOptions = array(
    'http'=>array(
        'method' => 'GET',
        'header' => CreateRequestHeaders($download['filesize'], $_hoster->AcceptRanges())
    )
);

$requestOptions['http']['header'] = array_merge($requestOptions['http']['header'], $_hoster->GetAdditionalHeaders());

//Output download headers for our client
foreach($downloadHeader as $header) {
    header($header);
}


register_shutdown_function('SaveTraffic', $username, $givenUrl, $download['filename'], $download['filesize']);
//SaveTraffic($username, $givenUrl, $download['filename'], $download['filesize']);

$context = stream_context_create($requestOptions);
$traffic = readfile($download['url'], false, $context);

现在功能:

function CreateDownloadHeaders($download, $acceptRanges) {
    //IE workaround for downloads
    $type = (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'],'MSIE')) ? 'force-download' : 'octet-stream';

    $headers = array(
        'Content-Type: application/' . $type,
        'Content-Disposition: attachment; filename="'.$download['filename'].'"',
        'Content-Length: '.$download['filesize'],
        'Content-Transfer-Encoding: Binary',
        'Expires: 0',
        'Cache-Control: must-revalidate, post-check=0, pre-check=0',
        'Pragma: public',
        'Connection: close'
    );

    $headers = AddDownloadRangeHeaders($headers, $acceptRanges, $download['filesize']);

    return $headers;
}


function CreateRequestHeaders($filesize, $acceptRanges) {
    $headers = array();

    $headers = AddRequestRangeHeaders($headers, $acceptRanges, $filesize);
    $headers[] = 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13';
    $headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
    $headers[] = 'Accept-Language: de, en-gb;q=0.9, en;q=0.8';
    $headers[] = 'Accept-Encoding: gzip, deflate';
    $headers[] = 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7';
    $headers[] = 'Cache-Control: no-cache';
    $headers[] = 'Pragma: no-cache';
    $headers[] = 'Connection: close';

    return $headers;
}


function AddDownloadRangeHeaders($headers, $acceptRanges, $filesize) {
    if($acceptRanges !== true) {
        $headers[] = 'Accept-Ranges: none';
    }
    elseif(isset($_SERVER['HTTP_RANGE'])) {
        preg_match('/bytes([[:space:]])?=([[:space:]])?(\d+)?-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);

        $start = intval($matches[3]);
        $stop = intval($matches[4]);

        if($stop == 0) {
            $stop = $filesize;
        }

        $headers[] = 'HTTP/1.1 206 Partial Content';
        $headers[] = 'Accept-Ranges: bytes';
        $headers[] = 'Content-Range: bytes ' . $start . '-' . $stop . '/' . $filesize;

        $newSize = $stop - $start + 1;
        $key = array_search('Content-Length: '.$filesize, $headers);
        $headers[$key] = 'Content-Length: '.$newSize;
    }

    return $headers;
}


function AddRequestRangeHeaders($headers, $acceptRanges, $filesize) {
    if($acceptRanges === true && isset($_SERVER['HTTP_RANGE'])) {
        preg_match('/bytes([[:space:]])?=([[:space:]])?(\d+)?-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);

        $start = intval($matches[3]);
        $stop = intval($matches[4]);

        if($stop == 0) {
            $stop = $filesize;
        }

        $headers[] = 'Range: bytes='.$start.'-'.$stop;
    }

    return $headers;
}

1 个答案:

答案 0 :(得分:1)

我在考虑curl如何实现save-to-filestream功能。 我意识到它必须像特殊的CURLOPT_WRITEFUNCTION,因为在保存到文件流时停止脚本会在我的网站空间上留下一个包含已加载部分的文件。

因此我用CURLOPT_WRITEFUNCTION尝试了它,它看起来并不像我想象的那样资源密集。

现在我使用register_shutdown_function调用一个保存已用流量的函数。我的CURLOPT_WRITEFUNCTION计算加载的数据=流量。

如果要将流量保存在文件中,将当前工作目录存储在变量中也很重要。因为在注册的关闭函数中调用的每个相对路径都与您的根目录无关,所以它相对于服务器根目录!您也可以使用绝对路径而不是cwd。

function readResponse($ch, $data) {
    global $traffic;

    $length = mb_strlen($data, '8bit');

    //count traffic
    $traffic += $length;
    //output loaded data
    echo $data;

    return $length;
}

function saveTraffic($username, $cwd) {
    global $traffic;

    $h = fopen($cwd.'/relative-path/traffic_'.$username.'.txt', 'ab');
    fwrite($h, $traffic);
    fclose($h);
}

//...

$cwd = getcwd();
register_shutdown_function('saveTraffic', $username, $cwd);

$traffic = 0;

//...
//Output download header information to client
//...

curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'readResponse');

//...

curl_exec($ch);

感谢您的帮助!这非常有用!