Maison  >  Article  >  développement back-end  >  Comment lire et faire écho à la taille en cours d'écriture du fichier téléchargé sur le serveur en temps réel sans bloquer le serveur et le client ?

Comment lire et faire écho à la taille en cours d'écriture du fichier téléchargé sur le serveur en temps réel sans bloquer le serveur et le client ?

DDD
DDDoriginal
2024-10-20 22:02:30758parcourir

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

Comment lire et imprimer la taille d'un fichier téléchargé en cours d'écriture côté serveur en temps réel sans bloquer le serveur et le client ?

Développons ce problème :

Afin d'obtenir la progression du téléchargement du fichier en temps réel, nous le définissons à partir de l'objet Blob, File, TypedArray ou ArrayBuffer via fetch () dans la requête POST L'objet body.

L'implémentation actuelle définit l'objet File comme deuxième argument de fetch() transmis à l'objet body.

Exigences :

En tant que flux de texte/événement, lit la taille d'un fichier en cours d'écriture sur le système de fichiers du serveur et la renvoie au client. S'arrête lorsque tous les octets fournis par le paramètre de chaîne de requête var dans la requête GET ont été écrits. La lecture des fichiers s'effectue actuellement dans un environnement de script distinct, dans lequel les appels GET sont effectués vers le script qui lit le fichier, puis le POST est effectué vers le script qui écrit le fichier sur le serveur.

Attendez d'avoir terminé la partie d'écho de la taille du fichier avant d'essayer de résoudre les problèmes potentiels liés à la gestion des écritures ou des lectures de fichiers côté serveur pour obtenir la taille actuelle du fichier.

J'essaie actuellement d'utiliser php pour répondre aux exigences. Mais aussi intéressé par c, bash, nodejs, python ou d'autres langages ou méthodes pouvant être utilisés pour effectuer la même tâche.

La partie javascript côté client convient. Je ne connais tout simplement pas bien PHP (l'un des langages côté serveur les plus utilisés au monde) pour implémenter le modèle sans inclure de parties inutiles.

Motivation :

Indicateur de progression pour le fetch ?

Connexe :

Récupérer avec ReadableStream

Question :

Obtenir

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

dans la borne.

De plus, si vous remplacez

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

par

while(true)

, cela générera une erreur au niveau de l'EventSource.

Sans l'appel sleep(), la taille de fichier correcte est envoyée à l'événement de message pour un fichier de taille 3,3 Mo, 3321824, 61921, 26214 et 38093 sont imprimés respectivement lorsque le même fichier est téléchargé trois fois. Le résultat attendu est d'obtenir la taille du fichier lors de l'écriture d'un fichier à :

stream_copy_to_stream($input, $file);

au lieu de la taille du fichier de l'objet fichier téléchargé. Est-ce que fopen() ou stream_copy_to_stream() empêchera d'autres processus php d'accéder à stream.php ?

Choses essayées jusqu'à présent :

php Citation de

  • Au-delà de $_POST, $_GET et $_FILE : en JavaScriptPHP gestion des blobs
  • Introduction aux événements envoyés par le serveur avec des exemples 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>

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn