0

現在、c/c++ で PHP 拡張機能を作成しています。ユーザーがファイルをアップロードします (POST または PUT メソッドの可能性がありますが、POST のみに制限できます)。サーバー上のディスクに書き込むことなく、アップロード中にファイル データをキャプチャする必要があります。データを処理し、(場合によっては) 別の場所に送信するか、ディスクに保存する必要があります。もちろん、ファイルがアップロードされた(サーバー上のディスクに保存された)後にファイルを処理できることは知っていますが、それは避けたいと思います。また、逆のことも行う必要があります。「オンザフライ」でファイルを生成し、ユーザーに送信する必要があります。生成されたファイルのすべてのメタデータは事前​​にわかっています (サイズ、名前など)。

私はしばらくの間探し回っていましたが、解決策に近いものさえ見つけることができませんでした。このようなことを行う例または既存のPHP拡張機能はありますか(少なくとも似たようなもの)?

4

1 に答える 1

1

アップロード プロセスへのフックについてコメントすることはできませんが、ダウンロードの部分については次のものが必要です。

  1. ダウンロード要求を処理し、http ヘッダーを送信する php スクリプト。
    RFC 2183 に従って、実際には us-ascii のみが許可されているため、ファイル名に関して注意する必要があります。
  2. データをブラウザにストリーミングする PHP 拡張機能の関数/メソッド

PHPスクリプト

必要なファイルの範囲のみが要求されているかどうかをさらにチェックする完全な php スクリプトを次に示します。

<?php

// sanity checks ...



// script must not timeout
set_time_limit(0);
// user abortion is checked in extension while streaming the data
ignore_user_abort(true);


$filename = $_GET['filename'];
// TODO determine filesize
$filesize = 0;
$offset = 0;
$range_len = -1;
$have_valid_range = false;

if (isset($_SERVER['HTTP_RANGE']))
{
    // split 'bytes=n-m'
    list($range_type, $range) = explode('=', $_SERVER['HTTP_RANGE']);
    // split 'n-m' or 'n-'
    $range = explode('-', $range);
    // range type can only be 'bytes', check it anyway
    $have_valid_range = ($range_type == 'bytes') && is_array($range);
    if (!$have_valid_range)
    {
        header('HTTP/1.1 416 Requested Range Not Satisfiable', true, 416);
        exit;
    }

    if ($range[0] > $filesize)
    {
        $range[0] = $filesize;
    }
    if ((!$range[1]             )   || 
        ($range[1] > $filesize  )   )
    {
        $range[1] = $filesize;
    }
    $offset = $range[0];
    $range_len = $range[1]-$range[0]+1;
}

$attachment_filename = 'xyz';


// send metadata
header('Accept-Ranges: bytes');
if ($have_valid_range)
{
    header('HTTP/1.1 206 Partial Content', true, 206);
    header('Content-Length: ' . $range_len);
    header('Content-Range: bytes ' . $range[0] . '-' . $range[1] . ($filesize > 0 ? ('/' . $filesize) : ''));
}
else if ($filesize > 0)
{
    header('Content-Length: ' . $filesize);
}

// a note about the suggested filename for saving the attachment:
// It's not as easy as one might think!
// We deal (in our php scripts) with utf-8 and the filename is either the export profile's name or a term 
// entered by the user in the download form. Now the big problem is:
// According to the rfc for the Content-Disposition header only us-ascii characters are allowed! 
// (see http://greenbytes.de/tech/webdav/rfc2183.html, section "the filename parameter")
// However, all major browsers accept the filename to be encoded in iso-8859-1 (at least).
// There are other forms like: filename*="utf-8''<urlencoded filename>" but not 
// all browsers support this (most notably IE, only firefox and opera at the moment);
// (see http://greenbytes.de/tech/tc2231/ for testcases)
// 
// Additionally, IE doesn't like so much the '.' and ';' because it treats them as the beginning of the file extension,  
// and then thinks that it deals with a .*&%$§ file instead of a .zip file.
// The double quote '"' is already used as a delimiter for the filename parameter and it's unclear to me 
// how browsers would handle it.
// 
// Hence the procedure to produce a safe suggested filename as the least common denominator is as follows:
// Replace characters to be known as problematic with an underscore and encode the filename in iso-8859-1;
// Note that '?' (they can also result from utf8_decode()), '*', '<', '>', '|', ';', ':', '.', '\' are replaced by 
// firefox and IE with '_' anyway, additionally '#' by IE - meaning that they offer a filename with the mentioned 
// characters replaced by the underscore, i.e.: abc äöü +~*?ß=}'!§$%&/()´`<>|,-_:__@?\_{[]#.zip  -->  abc äöü +~__ß=}'!§$%&_()´`___,-____@___{[]#.zip 
$safe_attachment_fname = utf8_decode(str_replace(array('.', ';', '"'), '_', $attachment_filename)) . '.zip';
$filename_param = 'filename="' . $safe_attachment_fname . '"';

header('Content-Transfer-Encoding: binary');
header('Content-Type: application/zip');
header('Content-Disposition: attachment; ' . $filename_param);
// file can be cached forever by clients and proxies
header('Cache-Control: public');


// disable output buffering, stream directly to the browser;
// in fact, this is a must, otherwise php might crash
while (ob_get_level())
    ob_end_flush();


// stream data
ext_downstreamdata($filename, $offset, $range_len);

?>

C/C++ からのストリーミング

現在、C++ の部分では、ext_downstreamdata()上記の php-script で言及されている関数は完全に実装固有ですが、データ ストリーミング自体は一般化できます。

たとえば、多層アプリケーションのファイル データをアプリケーション サーバーからブラウザに直接ストリーミングするタスクがありました。

次の関数は、C++ コード内からストリーミング関数へのコールバックとして機能し、データとその長さへのポインターを受け取ります (Windows エラー コードを返します)。

unsigned long stream2browser(const void* pdata, size_t nLen)
{
    if (nLen)
    {
        // fetch zend's tls stuff
        TSRMLS_FETCH();

        // send data via the zend engine to the browser;
        // this method uses whatever output buffer mechanism (compression, ...) is in use;
        // It's a really good idea to turn off all output buffer levels in the php script because of 
        // strange crashes somewhere within the zend engine (or in one of the extensions?)
        // I did some debugging and the browser really crashes and it's not our fault, turning off the output 
        // buffering solves all problems; you turn it off like this in the script:
        //  <code>
        //  while (ob_get_level())
        //      ob_end_flush();
        //  </code>
        // I'm thinking to use an unbuffered output function (e.g. php_ub_body_write) but don't know for sure how to use it, so 
        // still stay away from it and rely on the script having disabled all output buffers

        // note: php_write returns int but this value is the bytes sent to the browser (which is nLen)
        size_t nSent = php_write((void*) pdata, uint(nLen) TSRMLS_CC);
        if (nSent < nLen)
        {
            if (PG(connection_status) & PHP_CONNECTION_ABORTED)
                return ERROR_CANCELLED;
            else
                return ERROR_NOT_CAPABLE;
        }
    }

    return ERROR_SUCCESS;
}
于 2013-10-26T20:45:29.283 に答える