2

我使用 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() 注册了一个关闭函数,其中包括 $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;
}
4

1 回答 1

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);

谢谢你的帮助!这非常有用!

于 2011-02-27T20:17:57.693 回答