javascript - सर्वर और क्लाइंट दोनों पर ब्लॉक किए बिना अपलोड की गई फ़ाइल के आकार को वास्तविक समय में सर्वर पर कैसे पढ़ा और पढ़ा जा सकता है?




php multithreading (2)

आपको जावास्क्रिप्ट के साथ विखंडू पर ब्रेक फ़ाइल की आवश्यकता है और उन विखंडू को भेजें। जब चंक अपलोड किया जाता है तो आपको पता होता है कि कितने डेटा भेजे गए थे।

यह एकमात्र तरीका है और वैसे यह कठिन नहीं है।

file.startByte  += 100000;
file.stopByte   += 100000;

var reader = new FileReader();

reader.onloadend = function(evt) {
    data.blob = btoa(evt.target.result);
    /// Do upload here, I do with jQuery ajax
}

var blob = file.slice(file.startByte, file.stopByte);
reader.readAsBinaryString(blob);

सवाल:

सर्वर और क्लाइंट दोनों पर ब्लॉक किए बिना अपलोड की गई फ़ाइल के आकार को वास्तविक समय में सर्वर पर कैसे पढ़ा और पढ़ा जा सकता है?

प्रसंग:

ArrayBuffer fetch() , जहां body को Blob , File , ArrayBuffer , या ArrayBuffer ऑब्जेक्ट पर सेट किया गया है, द्वारा POST किए गए फ़ाइल अपलोड की प्रगति सर्वर से लिखी जा रही है।

वर्तमान कार्यान्वयन body ऑब्जेक्ट पर File ऑब्जेक्ट को भ्रूण के दूसरे पैरामीटर fetch() पारित कर देता है।

आवश्यकता:

क्लाइंट को text/event-stream रूप में फाइलसिस्टम पर फाइल के फाइल साइज़ को क्लाइंट को पढ़ें और echo करें। जब सभी बाइट्स बंद हो जाएं, तो GET अनुरोध पर क्वेरी स्ट्रिंग पैरामीटर के रूप में स्क्रिप्ट के लिए एक चर के रूप में प्रदान किया गया है। वर्तमान में फ़ाइल का वाचन एक अलग स्क्रिप्ट वातावरण में होता है, जहाँ स्क्रिप्ट को पढ़ने के लिए GET कॉल किया जाता है, जिसे POST के बाद स्क्रिप्ट से बनाया जाता है जो फ़ाइल को सर्वर में लिखता है।

फ़ाइल के सर्वर पर फ़ाइल को पढ़ने या वर्तमान फ़ाइल आकार प्राप्त करने के लिए फ़ाइल के पढ़ने के साथ संभावित समस्या से निपटने में त्रुटि तक नहीं पहुंचे हैं, हालांकि फ़ाइल आकार के भाग की echo पूरी हो जाने के बाद यह अगला चरण होगा।

वर्तमान में php का उपयोग करके आवश्यकता को पूरा करने का प्रयास। हालांकि c , bash , nodejs , python में भी रुचि; या अन्य भाषाएँ या दृष्टिकोण जिनका उपयोग समान कार्य करने के लिए किया जा सकता है।

क्लाइंट साइड javascript भाग कोई समस्या नहीं है। बस नहीं है कि php में निपुण, सबसे आम सर्वर साइड भाषाओं में से एक, जो दुनिया भर में व्यापक वेब पर उपयोग की जाती है, उन हिस्सों को शामिल किए बिना पैटर्न को लागू करने के लिए जो आवश्यक नहीं हैं।

प्रेरणा:

लाने के लिए प्रगति संकेतक?

सम्बंधित:

ReadableStream के साथ प्राप्त करें

मुद्दे:

मिल रहा

PHP Notice:  Undefined index: HTTP_LAST_EVENT_ID in stream.php on line 7

terminal

इसके अलावा, अगर विकल्प

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

के लिये

while(true)

EventSource पर त्रुटि पैदा करता है।

sleep() कॉल के बिना, सही फ़ाइल का आकार 3.3MB फ़ाइल के लिए message ईवेंट में भेजा गया था, 3321824 , console 61921 , 26214 , और 38093 बार में मुद्रित किया गया था, जब उसी फ़ाइल को तीन बार अपलोड किया गया था। अपेक्षित परिणाम फ़ाइल का आकार फ़ाइल है जैसा कि फ़ाइल में लिखा जा रहा है

stream_copy_to_stream($input, $file);

अपलोड की गई फ़ाइल ऑब्जेक्ट के फ़ाइल आकार के बजाय। stream_copy_to_stream() fopen() या stream_copy_to_stream() अन्य अलग php प्रक्रिया के रूप में अवरुद्ध कर रहे हैं। php ?

अब तक की कोशिश:

php को जिम्मेदार ठहराया है

php

// can we merge `data.php`, `stream.php` to same file?
// can we use `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>

वास्तविक फ़ाइल आकार प्राप्त करने के लिए आपको clearstatcache करने की आवश्यकता है। कुछ अन्य बिट्स के साथ, आपकी स्ट्रीम.php निम्नलिखित की तरह दिख सकती है:

<?php

header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Connection: keep-alive");
// Check if the header's been sent to avoid `PHP Notice:  Undefined index: HTTP_LAST_EVENT_ID in stream.php on line `
// php 7+
//$lastId = $_SERVER["HTTP_LAST_EVENT_ID"] ?? 0;
// php < 7
$lastId = isset($_SERVER["HTTP_LAST_EVENT_ID"]) ? intval($_SERVER["HTTP_LAST_EVENT_ID"]) : 0;

$upload = $_GET["filename"];
$data = 0;
// if file already exists, its initial size can be bigger than the new one, so we need to ignore it
$wasLess = $lastId != 0;
while ($data < $_GET["filesize"] || !$wasLess) {
    // system calls are expensive and are being cached with assumption that in most cases file stats do not change often
    // so we clear cache to get most up to date data
    clearstatcache(true, $upload);
    $data = filesize($upload);
    $wasLess |= $data <  $_GET["filesize"];
    // don't send stale filesize
    if ($wasLess) {
        sendMessage($lastId, $data);
        $lastId++;
    }
    // not necessary here, though without thousands of `message` events will be dispatched
    //sleep(1);
    // millions on poor connection and large files. 1 second might be too much, but 50 messages a second must be okay
    usleep(20000);
}

function sendMessage($id, $data)
{
    echo "id: $id\n";
    echo "data: $data\n\n";
    ob_flush();
    // no need to flush(). It adds content length of the chunk to the stream
    // flush();
}

कुछ चेतावनी:

सुरक्षा। मेरा मतलब है किस्मत। जैसा कि मैं समझता हूं कि यह अवधारणा का प्रमाण है, और सुरक्षा चिंताओं का कम से कम है, फिर भी अस्वीकरण होना चाहिए। यह दृष्टिकोण मौलिक रूप से त्रुटिपूर्ण है, और इसका उपयोग केवल तभी किया जाना चाहिए जब आप डॉस के हमलों की परवाह नहीं करते हैं या आपकी फ़ाइलों के बारे में जानकारी बाहर जाती है।

सी पी यू। usleep के बिना स्क्रिप्ट सिंगल कोर के 100% उपभोग करेगी। लंबी नींद के साथ आपको एक ही पुनरावृत्ति के भीतर पूरी फ़ाइल को अपलोड करने का जोखिम होता है और बाहर निकलने की स्थिति कभी पूरी नहीं होगी। यदि आप इसका स्थानीय स्तर पर परीक्षण कर रहे हैं, तो usleep को पूरी तरह से हटा दिया जाना चाहिए, क्योंकि यह स्थानीय रूप से MB को अपलोड करने के लिए मिलीसेकंड की बात है।

कनेक्शन खोलें। अपाचे और nginx / fpm दोनों में php प्रक्रियाओं की सीमित संख्या है जो अनुरोधों को पूरा कर सकती है। फ़ाइल अपलोड करने में लगने वाले समय के लिए एक एकल फ़ाइल अपलोड में 2 लगेंगे। धीमे बैंडविड्थ या जाली अनुरोधों के साथ, यह समय काफी लंबा हो सकता है, और वेब सर्वर अनुरोधों को अस्वीकार करना शुरू कर सकता है।

क्लाइंटसाइड भाग। आपको प्रतिक्रिया का विश्लेषण करने की आवश्यकता है और अंत में उन घटनाओं को सुनना बंद कर देना चाहिए जब फ़ाइल पूरी तरह से अपलोड हो जाती है।

संपादित करें:

इसे अधिक या कम उत्पादन के अनुकूल बनाने के लिए, आपको फ़ाइल मेटाडेटा को संग्रहीत करने के लिए इन-मेमोरी स्टोरेज जैसे रेडिस, या मेमेचे की आवश्यकता होगी।

पोस्ट अनुरोध करते हुए, एक अद्वितीय टोकन जोड़ें जो फ़ाइल, और फ़ाइल आकार की पहचान करता है।

आपकी जावास्क्रिप्ट में:

const fileId = Math.random().toString(36).substr(2); // or anything more unique
...

const [request, source] = [
    new Request(`${url}?fileId=${fileId}&size=${filesize}`, {
        method:"POST", headers:headers, body:file
    })
    , new EventSource(`${stream}?fileId=${fileId}`)
];
....

Data.php में टोकन दर्ज करें और प्रगति की रिपोर्ट करें:

....

$fileId = $_GET['fileId'];
$fileSize = $_GET['size'];

setUnique($fileId, 0, $fileSize);

while ($uploaded = stream_copy_to_stream($input, $file, 1024)) {
    updateProgress($id, $uploaded);
}
....


/**
 * Check if Id is unique, and store processed as 0, and full_size as $size 
 * Set reasonable TTL for the key, e.g. 1hr 
 *
 * @param string $id
 * @param int $size
 * @throws Exception if id is not unique
 */
function setUnique($id, $size) {
    // implement with your storage of choice
}

/**
 * Updates uploaded size for the given file
 *
 * @param string $id
 * @param int $processed
 */
function updateProgress($id, $processed) {
    // implement with your storage of choice
}

तो आपकी स्ट्रीम.php को डिस्क को हिट करने की आवश्यकता नहीं है, और जब तक यह UX द्वारा स्वीकार्य है तब तक सो सकते हैं:

....
list($progress, $size) = getProgress('non_existing_key_to_init_default_values');
$lastId = 0;

while ($progress < $size) {
    list($progress, $size) = getProgress($_GET["fileId"]);
    sendMessage($lastId, $progress);
    $lastId++;
    sleep(1);
}
.....


/**
 * Get progress of the file upload.
 * If id is not there yet, returns [0, PHP_INT_MAX]
 *
 * @param $id
 * @return array $bytesUploaded, $fileSize
 */
function getProgress($id) {
    // implement with your storage of choice
}

जब तक आप पुराने अच्छे पुलिंग के लिए EventSource नहीं छोड़ते हैं, 2 खुले कनेक्शनों के साथ समस्या हल नहीं हो सकती है। बिना लूप के स्ट्रीम.php का रिस्पॉन्स टाइम मिलीसेकंड का मामला है, और यह कनेक्शन को हर समय खुला रखने के लिए काफी बेकार है, जब तक कि आपको सैकड़ों अपडेट सेकंड की आवश्यकता न हो।





language-agnostic