【问题标题】:Forcing Client to Stop AJAX from Node.js Server强制客户端从 Node.js 服务器停止 AJAX
【发布时间】:2022-01-15 13:10:47
【问题描述】:

我查看了几篇 SO 帖子,试图找到一种方法,让 Node.js 服务器告诉客户端在达到特定文件大小后停止上传。其中最有前途的是ed-taAvoiding further processing on busyboy file upload size limit 的技术。

根据 ed-ta,我的 Node.js 服务器似乎在做它应该做的事情。一旦达到大小限制,服务器就会发送 455 状态代码并停止接受更多数据。不幸的是,我的客户一直在处理文件,直到它完全完成为止。当用户尝试上传非常大的文件时,这是一种不太理想的体验,因为在 AJAX 请求完全完成之前,客户端不会提醒用户已达到阈值。

如何让客户端及时看到455状态码?

我尝试检查 xhr.onreadystatechange 中的 455 状态,但我似乎无法从 onreadystatehange 中找到该信息,即使服务器已经在响应 1 中发送了 455。此外,onreadystatechange 事件似乎直到整个文件已被客户端处理后才触发。

我试图通过删除不相关的细节来简化问题,我当前的演示代码如下:

Server.js

// This code is based on 
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors');  // [4]

const app = express();

// See [2][4]
app.use(
  function(req, res, next) {
    res.setHeader("Access-Control-Allow-Origin", "null");
    res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
    next();
  }
);
app.options('*', cors());

app.use(
  busboy({ limits: { files: 1, fileSize: 500000000 } }) // [1]
);

app.post('/uploadEndpoint', function (req, res, next) {
  var fStream;
  req.pipe(req.busboy);

  req.busboy.on('file', function (fieldName, file, filename) {
    console.log("Uploading: " + filename);

    var destPath = './' + filename;
    fStream = fs.createWriteStream(destPath);
    file.pipe(fStream);

    // ed-ta [3]
    // Despite being asynchronous limit_reach 
    // will be seen here as true if it hits max size 
    // as set in file.on.limit because once it hits
    // max size the stream will stop and on.finish
    // will be triggered.
    var limit_reach = false;
    req.busboy.on('finish', function() {
      if(!limit_reach){
        res.send(filename + " uploaded");
      }
    });
    file.on('limit', function() {
      fs.unlink(destPath, function(){ 
        limit_reach = true;
        res.status(455).send("File too big.");
        console.log('Telling client to stop...');
      }); 
    });
  });
})

app.listen(8000);

test.html

<!DOCTYPE html>
<!-- 
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest

[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
    function uploadFile() {
    var progress = document.getElementById("output");
    progress.innerText = "Starting";
    var fileCtl = document.getElementById("theFile");
    var file = fileCtl.files[0];
    var xhr = new XMLHttpRequest();

    // timdream
    var formData = new FormData();
    formData.append('theFile', file);

    xhr.upload.onprogress = (progressEvent) => {
        var percentCompleted = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
        );
        progress.innerText = 
        "Percent Uploaded: " + percentCompleted + "%";
    };
    xhr.onreadystatechange = function(e) {
        if (this.status === 455) {
        alert('Sorry, file was too big.');
        }
    };
    xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
    // Apparently this line causes issues Multipart Boundary not
    // found error [1]
    // xhr.setRequestHeader("Content-Type","multipart/form-data"); 

    // timdream
    xhr.send(formData);
    }
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>

1 - 我可以在 Node.js 服务器上设置另一个端点,并使用 AJAX 轮询该端点以了解客户端 onprogress 中的当前状态,但这似乎是一种浪费带宽的笨拙解决方案。

【问题讨论】:

  • 到目前为止,这是我找到的唯一可能的解决方案:npmjs.com/package/socketio-file-upload 不幸的是,我无法在客户端及时触发 XMLHttpRequest 中止、错误和 onreadystatechange 事件...遗憾的是,这条评论实际上并没有回答这个问题,因为从 socketio-file-upload 包上传的 WebSocket 与 multipart/form-data 帖子不同。
  • 我刚刚尝试将我的客户端切换到 Axios 并使用取消令牌。即使我调用 cancelTokenSource.cancel(),我的客户也会继续。这让 no 有意义... stackoverflow.com/questions/38329209/… stackoverflow.com/questions/55051917/…
  • 回到 XMLHttpRequest。为服务器打开了一个单独的 WebSockets 通道。当服务器通过 WebSockets 通道发回中止请求时,我的客户端能够在实例化的 XMLHttpRequest 对象上调用中止:developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort

标签: node.js ajax express xmlhttprequest busboy


【解决方案1】:

为了解决上述问题,我最终使用了一个单独的 WebSocket 通道将消息从服​​务器发送回客户端,以告诉所述客户端停止上传。然后我根据 Mozilla 文档在客户端的 XMLHttpRequest 对象上调用了 abort。

最终示例代码如下所示:

Server.js

// This code is based on 
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors');  // [4]
var ws = require('ws');
var WebSocketServer = ws.WebSocketServer;

var g_ws; 
// BEGIN FROM: https://www.npmjs.com/package/ws
const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
    g_ws = ws;
});
// END  FROM: https://www.npmjs.com/package/ws

const app = express();

// See [2][4]
app.use(
    function(req, res, next) {
        res.setHeader("Access-Control-Allow-Origin", "null");
        res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
        next();
    }
);
app.options('*', cors());

app.use(
    busboy({ limits: { files: 1, fileSize: 300000000 } }) // [1]
);

app.post('/uploadEndpoint', function (req, res, next) {
    var fStream;
    req.pipe(req.busboy);

    req.busboy.on('file', function (fieldName, file, fileNameObject) {
        var filename = fileNameObject.filename;
        console.log("Uploading: " + filename);

        var destPath = './' + filename;
        fStream = fs.createWriteStream(destPath);
        file.pipe(fStream);

        // ed-ta [3]
        // Despite being asynchronous limit_reach 
        // will be seen here as true if it hits max size 
        // as set in file.on.limit because once it hits
        // max size the stream will stop and on.finish
        // will be triggered.
        var limit_reach = false;
        req.busboy.on('finish', function() {
            var message;
            if(!limit_reach){
                message = 'success';
                res.send(filename + " uploaded");
            } else {
                message = 'TooBig';
            }
            g_ws.send(message);
        });
        file.on('limit', function() {
            fs.unlink(destPath, function(){ 
                limit_reach = true;
                res.status(455).send("File too big.");
                console.log('Telling client to stop...');

                // https://www.npmjs.com/package/ws
                g_ws.send("TooBig");
            }); 
        });
    });
})

app.listen(8000);

test.html

<!DOCTYPE html>
<!-- 
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest

[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
function uploadFile() {
    var progress = document.getElementById("output");
    progress.innerText = "Starting";
    var fileCtl = document.getElementById("theFile");
    var file = fileCtl.files[0];
    var xhr = new XMLHttpRequest();

    // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
    const socket = new WebSocket('ws://localhost:8080');
    socket.addEventListener('message', function (event) {
        if (event.data === 'TooBig') {
            // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort
            xhr.abort(); 
            alert('Server says file was too big.');
        } else if (event.data === 'success') {
            alert('File uploaded sucessfully.');
        } else {
            alert('Unknown server error');
        }
        socket.close();
    });

    // timdream
    var formData = new FormData();
    formData.append('theFile', file);

    xhr.upload.onprogress = (progressEvent) => {
        var percentCompleted = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
        );
        progress.innerText = 
        "Percent Uploaded: " + percentCompleted + "%";
    };
    xhr.onreadystatechange = function(e) {
        if (this.status === 455) {
        alert('Sorry, file was too big.');
        }
    };
    xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
    // Apparently this line causes issues Multipart Boundary not
    // found error [1]
    // xhr.setRequestHeader("Content-Type","multipart/form-data"); 

    // timdream
    xhr.send(formData);
}
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>

这不是我用于解决方案的确切代码,但这个简化的工作演示说明了这个概念。

顺便说一句:服务器上的文件大小限制已降低到 300000000,以使测试更容易,但这没关系。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-05
    • 1970-01-01
    • 2013-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-05
    相关资源
    最近更新 更多