ホームページ  >  記事  >  バックエンド開発  >  サーバーとクライアントをブロックせずに、サーバーにアップロードされたファイルの現在書き込みサイズをリアルタイムで読み取り、エコーする方法は?

サーバーとクライアントをブロックせずに、サーバーにアップロードされたファイルの現在書き込みサイズをリアルタイムで読み取り、エコーする方法は?

DDD
DDDオリジナル
2024-10-20 22:02:30757ブラウズ

How to Read and Echo Currently Writing Size of Uploaded File in Server in Realtime Without Blocking Server and Client?

サーバーとクライアントをブロックせずに、サーバー側で書き込まれているアップロードされたファイルのサイズをリアルタイムで読み取り、出力するにはどうすればよいですか?

この問題をさらに詳しく見てみましょう:

ファイルのアップロードの進行状況をリアルタイムで取得するために、フェッチを通じて Blob、File、TypedArray、または ArrayBuffer オブジェクトから設定します。 POST リクエストの () 本文オブジェクト。

現在の実装では、body オブジェクトに渡される fetch() の 2 番目の引数として File オブジェクトが設定されます。

要件:

テキスト/イベント ストリームとして、サーバーのファイル システムに書き込まれるファイルのサイズを読み取り、クライアントにエコー バックします。 GET リクエストの var クエリ文字列パラメータで指定されたすべてのバイトが書き込まれると停止します。現在、ファイルの読み取りは別のスクリプト環境で行われ、ファイルを読み取るスクリプトに対して GET 呼び出しが行われ、次にファイルをサーバーに書き込むスクリプトに対して POST が行われます。

現在のファイル サイズを取得するためのサーバー側のファイル書き込みまたはファイル読み取りの処理に関する潜在的な問題を解決する前に、ファイル サイズのエコー部分が完了するまで待ってください。

現在、要件を満たすために php を使用しようとしています。しかし、C、bash、nodejs、Python、または同じタスクを実行するために使用できる他の言語やメソッドにも興味があります。

クライアント側の JavaScript 部分は問題ありません。私は、不必要な部分を含めずにパターンを実装するための php (世界で最も広く使用されているサーバーサイド言語の 1 つ) に精通していません。

動機:

フェッチの進行状況インジケーター?

関連:

ReadableStream によるフェッチ

質問:

Get

PHP Notice:  Undefined index: HTTP_LAST_EVENT_ID in stream.php on line 7
ターミナル内の

また、

while(file_exists($_GET["filename"]) 
  &amp;&amp; filesize($_GET["filename"]) < intval($_GET["filesize"]))

while(true)

に置き換えると、EventSource でエラーが生成されます。

sleep() 呼び出しがない場合、サイズ 3.3MB のファイルのメッセージ イベントに正しいファイル サイズが送出され、同じファイルが 3 回アップロードされると、それぞれ 3321824、61921、26214、および 38093 が出力されます。期待される結果は、アップロードされたファイル オブジェクトのファイル サイズではなく、

stream_copy_to_stream($input, $file);

にファイルを書き込むときのファイル サイズを取得することです。 fopen() または stream_copy_to_stream() は、他の php プロセスが stream.php にアクセスするのを防ぎますか?

これまでに試したこと:

php

  • からの引用 $_POST、$_GET、$_FILE を超えて: JavaScriptPHP の BLOB 処理
  • PHP の例によるサーバー送信イベントの概要

php

// 能否合并 `data.php`、`stream.php` 为同一个文件?
// 能否使用 `STREAM_NOTIFY_PROGRESS` 
// "Indicates current progress of the stream transfer 
// in bytes_transferred and possibly bytes_max as well" to read bytes?
// do we need to call `stream_set_blocking` to `false`
// data.php
<?php

  $filename = $_SERVER["HTTP_X_FILENAME"];
  $input = fopen("php://input", "rb");
  $file = fopen($filename, "wb"); 
  stream_copy_to_stream($input, $file);
  fclose($input);
  fclose($file);
  echo "upload of " . $filename . " successful";

?>
// stream.php
<?php

  header("Content-Type: text/event-stream");
  header("Cache-Control: no-cache");
  header("Connection: keep-alive");
  // `PHP Notice:  Undefined index: HTTP_LAST_EVENT_ID in stream.php on line 7` ?
  $lastId = $_SERVER["HTTP_LAST_EVENT_ID"] || 0;
  if (isset($lastId) &amp;&amp; !empty($lastId) &amp;&amp; is_numeric($lastId)) {
      $lastId = intval($lastId);
      $lastId++;
  }
  // else {
  //  $lastId = 0;
  // }

  // while current file size read is less than or equal to 
  // `$_GET["filesize"]` of `$_GET["filename"]`
  // how to loop only when above is `true`
  while (true) {
    $upload = $_GET["filename"];
    // is this the correct function and variable to use
    // to get written bytes of `stream_copy_to_stream($input, $file);`?
    $data = filesize($upload);
    // $data = $_GET["filename"] . " " . $_GET["filesize"];
    if ($data) {
      sendMessage($lastId, $data);
      $lastId++;
    } 
    // else {
    //   close stream 
    // }
    // not necessary here, though without thousands of `message` events
    // will be dispatched
    // sleep(1);
    }

    function sendMessage($id, $data) {
      echo "id: $id\n";
      echo "data: $data\n\n";
      ob_flush();
      flush();
    }
?>

javascript

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input type="file">
<progress value="0" max="0" step="1"></progress>
<script>

const [url, stream, header] = ["data.php", "stream.php", "x-filename"];

const [input, progress, handleFile] = [
        document.querySelector("input[type=file]")
      , document.querySelector("progress")
      , (event) => {
          const [file] = input.files;
          const [{size:filesize, name:filename}, headers, params] = [
                  file, new Headers(), new URLSearchParams()
                ];
          // set `filename`, `filesize` as search parameters for `stream` URL
          Object.entries({filename, filesize})
          .forEach(([...props]) => params.append.apply(params, props));
          // set header for `POST`
          headers.append(header, filename);
          // reset `progress.value` set `progress.max` to `filesize`
          [progress.value, progress.max] = [0, filesize];
          const [request, source] = [
            new Request(url, {
                  method:"POST", headers:headers, body:file
                })
            // https://stackoverflow.com/a/42330433/
          , new EventSource(`${stream}?${params.toString()}`)
          ];
          source.addEventListener("message", (e) => {
            // update `progress` here,
            // call `.close()` when `e.data === filesize` 
            // `progress.value = e.data`, should be this simple
            console.log(e.data, e.lastEventId);
          }, true);

          source.addEventListener("open", (e) => {
            console.log("fetch upload progress open");
          }, true);

          source.addEventListener("error", (e) => {
            console.error("fetch upload progress error");
          }, true);
          // sanity check for tests, 
          // we don't need `source` when `e.data === filesize`;
          // we could call `.close()` within `message` event handler
          setTimeout(() => source.close(), 30000);
          // we don't need `source' to be in `Promise` chain, 
          // though we could resolve if `e.data === filesize`
          // before `response`, then wait for `.text()`; etc.
          // TODO: if and where to merge or branch `EventSource`,
          // `fetch` to single or two `Promise` chains
          const upload = fetch(request);
          upload
          .then(response => response.text())
          .then(res => console.log(res))
          .catch(err => console.error(err));
        }
];

input.addEventListener("change", handleFile, true);
</script>
</body>
</html>

以上がサーバーとクライアントをブロックせずに、サーバーにアップロードされたファイルの現在書き込みサイズをリアルタイムで読み取り、エコーする方法は?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。