【问题标题】:How to abort XMLHttpRequest.upload from the external script?如何从外部脚本中止 XMLHttpRequest.upload?
【发布时间】:2020-07-20 22:30:05
【问题描述】:

作为Why is $_FILES[] always empty?的后续行动:

当使用 PhP 通过 AJAX 上传文件时,通常会执行以下操作(我是新手,所以请告诉我是否有更好的方法来做到这一点):

  • 有一个包含表单和 javascript 的文件,您可以在其中设置和发送 XMLHttpRequest,例如upload_form.php
  • 有另一个外部 php 文件来处理上传请求,例如上传.php

我的问题是:

是否可以在外部upload.php 文件中中止XMLHttpRequest?

想要这样做的原因是,如果文件已经存在于服务器上,我想中止XMLHttpRequest,而不是让用户等到上传完成才能被告知失败。


一些图片来说明问题:

为了夸大问题,我在网络选项卡中限制了速度。

用户上传了一个存在于服务器uploads目录中的文件:

用户上传确实存在于服务器上 uploads 目录中的文件:

一些代码来说明问题:

upload_form.php:

<!-- enctype and method added on suggestions from previous question -->
<form class="form" id="upload_form" enctype="multipart/form-data" method="POST">
    <input type="file" name="file_to_upload" id="file_to_upload"><br>
    <input class="button" type="submit" value="Upload">
</form>

<script>
  const upload_form  = document.getElementById('upload_form');
  var file_to_upload = document.getElementById('file_to_upload');

  upload_form.addEventListener("submit", upload_file);

  function upload_file (e) {
    e.preventDefault();

    const xhr = new XMLHttpRequest()

    xhr.open("POST", "upload.php");
    xhr.upload.addEventListener("progress", e => {
      const percent = e.lengthComputable ? (e.loaded / e.total) * 100 : 0;
      console.log(percent.toFixed(0) + "%");
    });

    // ================= MAIN PART OF QUESTION ===================
    // Can I force this to fire from upload.php if file exists?
    xhr.addEventListener("abort", e => {
      console.log(e);
    });
    // ===========================================================

    xhr.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        // update some response area here
      }
    };

    xhr.send(new FormData(upload_form));
  }
</script>

上传.php:

<?php

$target_path = "uploads/".basename($_FILES["file_to_upload"]["name"]);
$uploaded_file = $_FILES['file_to_upload']['tmp_name'];

$upload_ok = true;
$errors = [];

// ================= MAIN PART OF QUESTION ==========================
// I want this to cause the abort event to fire on the XMLHttpRequest
if (file_exists($target_path)) {
  $upload_ok = false;
  array_push($errors, 'The file already exists on the server. Please rename the file and try again.');
}
// ==================================================================

if(!$upload_ok) {
  echo 'The file was not uploaded because:<br>';
  foreach ($errors as $err) {
    echo $err.'<br>';
  }
} else {
  if(move_uploaded_file($_FILES["file_to_upload"]["tmp_name"], $target_path)) {
    echo 'File uploaded successfully';
  } else {
    echo 'Something went wrong. Please try again.';
  }
}
?>

我尝试在 upload_form.php 中检查 readyStatestatus 的不同组合,但这没有帮助。

【问题讨论】:

    标签: javascript php ajax file-upload xmlhttprequest


    【解决方案1】:

    当 PHP 代码开始运行时,文件已经完全上传,所以没有必要以这种方式中止它,这只是简单地返回消息。相反,您可以在上传之前进行 AJAX 调用并检查文件是否存在,并且仅当文件不存在时才上传文件或向用户发送有关它的消息。

    使用以下代码创建一个简单的exists.php

    <?php
    
    $target_path = "uploads/".$_GET['file'];
    $file_exists = file_exists($target_path);
    
    header('Content-Type: application/json');
    
    // Return a JSON object with a single boolean property to indicate if the file exists
    echo json_encode(['exists' => $file_exists]);
    

    创建一个 Promise 进行 XHR 调用并获得 exists.php 结果

    function check_file_exists() {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
    
        xhr.open("GET", `exists.php?file=${encodeURIComponent(file_to_upload.files[0].name)}`);
        xhr.responseType = 'json';
        xhr.onreadystatechange = function() {
          if (this.readyState == 4 && this.status == 200) {
            resolve(this.response && this.response.exists);
          }
        };
    
        xhr.send();
      });
    }
    

    重构 upload_file 提交函数以首先调用 check_file_exists 承诺

    // An element to display a message to the user
    const message = document.getElementById('message');
    
    function upload_file(e) {
      e.preventDefault();
    
      message.textContent = '';
    
      // Call the promise to check if the selected file exists on the server
      check_file_exists().then(exists => {
        if(exists) {
          // If it exists message the user about it and do nothing else
          message.textContent = `File "${file_to_upload.files[0].name} already exists`;
        } else {
          // If it does not exists upload the file
          const xhr = new XMLHttpRequest();
    
          xhr.open("POST", "upload.php");
          xhr.upload.addEventListener("progress", e => {
            const percent = e.lengthComputable ? (e.loaded / e.total) * 100 : 0;
            console.log(percent.toFixed(0) + "%");
          });
    
          xhr.onreadystatechange = function() {
    
            if (this.readyState == 4 && this.status == 200) {
              // update some response area here
              message.textContent = this.responseText;
            }
          };
    
          xhr.send(new FormData(upload_form));
        }
      });
    }
    

    【讨论】:

    • 非常好的答案。我才刚刚开始了解 javascript 中的 Promise 对象,所以很高兴看到它们在与我自己的问题直接相关的上下文中使用。您能否评论一下在您的服务器上使用$_GETexists.php 脚本是否存在任何安全隐患? - 例如,有人可以窥探使用它的文件吗?
    • Promise 非常强大,您应该花时间了解它们的工作原理以及何时可以使用它们。这两个 PHP 文件,不仅是 exists.php,还有 upload.php 都是完全不安全的,永远不应该照原样使用;相反,您必须创建一个身份验证系统(或使用一些现成的身份验证系统)来处理登录会话,并且只允许他们已登录并有权上传和仅检查文件和应该有权访问的文件夹。
    • GET 请求在有后端身份验证系统来检查用户和访问时完全没问题。没有什么特别之处可以使POST 请求比GET 请求更安全;始终必须有一个中间件身份验证系统来相应地检查和允许/拒绝这些请求。
    猜你喜欢
    • 2018-01-26
    • 2019-10-28
    • 1970-01-01
    • 2016-11-06
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多