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

クライアントがダウンロードを停止したときに、トラフィックの保存が呼び出されませんでした。

次に、トラフィックを節約するためのグローバル変数として $traffic を含む register_shutdown_function() でシャットダウン関数を登録しました。これで、トラフィック ファイルが作成されましたが、使用されたトラフィックは 0 でした。

サーバーログなどにアクセスできません。php と htaccess しか使えません。

私が現在使用している 1 つの回避策は、ファイルへの要求を開始し、ファイル サイズを解析して、完全なファイル サイズをクライアント トラフィックに追加することです。次に、readfile() でダウンロードを開始します。クライアントがダウンロードを停止すると、ファイル全体をダウンロードしたかのように処理されます。

3 番目の方法は、curl とその CURLOPT_WRITEFUNCTION 設定です。しかし、これはサーバーのオーバーヘッドが大きすぎて、私がやりたいこと、つまり実際のトラフィックを節約することとは関係ありません。

ファイルをダウンロードする前にクライアント トラフィックを保存することには、別の問題もあります。ダウンロードの再開とチャンク ダウンロード (ダウンロードを高速化するために 1 つのファイルへの複数の接続) をサポートしたいのです。これでも機能しますが、問題はトラフィックのカウントです! チャンクの場合、HTTP-RANGE ヘッダーを解析して要求されたファイルの部分を特定し、これをトラフィックとして保存できますが、再開についてはどうでしょうか。

では、世の中に解決策はあるのでしょうか?

私はまだデータベースを使用しておらず、htaccess -logininformation を含むファイルのみを使用してクライアントを識別し、各クライアントの使用済みトラフィックを Web スペースの個別のファイルに保存しています。

これも私のコードです:

//$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 に答える