如何在不阻塞伺服器和客戶端的情況下,即時讀取和列印正在伺服器端寫入的上傳檔案的大小?
讓我們展開來討論這個問題:
為了即時取得檔案上傳的進度,我們在POST 請求中透過fetch() 從Blob、File、TypedArray 或ArrayBuffer 物件設定了body 物件。
目前的實作將 File 物件設定為 fetch() 的第二個參數傳遞給 body 物件。
需求:
作為 text/event-stream 讀取正在寫入伺服器檔案系統的檔案的大小並將其回顯到客戶端。當 GET 請求中 var 查詢字串參數提供的所有位元組都寫入完成後停止。檔案讀取目前在單獨的腳本環境中進行,其中 GET 呼叫向讀取檔案的腳本發出調用,然後 POST 到將檔案寫入伺服器的腳本。
在完成檔案大小回顯部分後再嘗試解決處理伺服器端檔案寫入或檔案讀取以取得目前檔案大小的潛在問題。
目前嘗試使用 php 滿足要求。不過也對 c、bash、nodejs、python 或其他語言或方法感興趣,這些語言或方法可以用來執行相同任務。
客戶端 javascript 部分沒問題。只是不太精通 php(世界上使用最廣泛的伺服器端語言之一),無法在不包含不必要的零件的情況下實施該模式。
動機:
fetch 的進度指示器?
相關:
帶有ReadableStream 的Fetch
問題:
獲得
PHP Notice: Undefined index: HTTP_LAST_EVENT_ID in stream.php on line 7
在終端。
此外,如果將
while(file_exists($_GET["filename"]) && filesize($_GET["filename"]) < intval($_GET["filesize"]))
替換為
while(true)
它會在 EventSource 處產生錯誤。
沒有 sleep() 調用,正確的檔案大小會分發到一個大小為 3.3MB 的檔案的訊息事件中,3321824、61921、26214 和 38093 分別在上傳同一檔案三次時被列印出來。預期結果是在以下位置寫入檔案時取得檔案大小:
stream_copy_to_stream($input, $file);
而不是上傳的檔案物件的 filesize。 fopen() 或 stream_copy_to_stream() 是否會阻止其他 php process 存取 stream.php?
目前嘗試的方法:
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) && !empty($lastId) && 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中文網其他相關文章!