当管道破裂时如何处理AJAX响应?

Arn*_*ter 8 ajax jqxhr jquery-file-upload

我有一个(Spring Boot 1.5)REST服务器处理文件uploads(multipart/form-data)和一个使用jQuery fileupload的JS客户端.在大多数情况下它工作正常,但是当我想上传一个更大的文件(大约4MB)时,服务器会发现它超出了限制并发回包含错误消息的响应.

然而,似乎服务器停止读取请求(当然是正确的),这导致客户端管道损坏.在这种情况下,不处理响应.该fail回调函数调用的内容如下data.jqXHR(data.response未定义):

{"readyState":0,"responseText":"","status":0,"statusText":"error"}
Run Code Online (Sandbox Code Playgroud)

使用时进行呼叫curl,结果是:

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8

curl: (55) Send failure: Broken pipe
{"files":[{"size":3863407,"error":"upload_uploadSizeExceeded"}]}
Run Code Online (Sandbox Code Playgroud)

所以返回了一个响应,但JS客户端似乎忽略了它.有没有选项让jQuery处理响应,即使请求只是部分发送?

顺便说一句:更奇怪的是,我在服务器日志中看到这样的请求重复了几次,这可能是JS中的一种重试机制?

Raf*_*ael 3

即使请求仅部分发送,是否有任何选项可以让 jQuery 处理响应?

简答

不,浏览器使用 XMLHttpRequest 和 Fetch API,根据规范,这被认为是网络错误,并且网络错误故意为空。

CURL 不根据 XMLHttpRequest 规范处理响应。

长答案

模拟服务器

读取请求的 ReadableStream 并中途取消:

const http = require('http');

const server = http.createServer((req, res) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Content-Type', 'text/plain');

    //Close Request ReadableStream Prematurely
    //to simulate close pipe on exceed file size
    if (req.url === '/data' && req.method === 'POST') {
        console.log('simulating...');

        let i = 0;
        req.on('data', chunk => {
            if (i++ === 10)
                req.destroy('broken pipe');
        });
    }

    res.end('fooby\n');
}).listen(8080);
Run Code Online (Sandbox Code Playgroud)

客户端测试

方法 1:XMLHttpRequest

我仔细检查了每个事件,没有发现发送字节的迹象。如果我们确实有一个 Sent bytes 值(应该在 中)loaded,我们就可以知道请求是否在中途被取消,以处理这种没有响应的情况:

let req = new XMLHttpRequest();
req.open('POST', 'http://localhost:8080/data');
    req.onloadstart = function (event) {
            console.log(event);
    };
    req.onprogress = function (event) {
            console.log(event);
    };
    req.onabort = function (event) {
            console.log(event);
    };
    req.onerror = function (event) {
            console.log(event);
    };
    req.onload = function (event) {
            console.log(event);
    };
    req.ontimeout = function (event) {
            console.log(event);
    };
    req.onloadend = function (event) {
            console.log(event);
    };
    req.onreadystatechange = function (event) {
            console.log(event);
    };
req.send(new ArrayBuffer(100000000));
Run Code Online (Sandbox Code Playgroud)

不幸的是,什么也没有。

只读 XMLHttpRequest.status 属性返回 XMLHttpRequest 响应的数字状态代码。状态将是一个未签名的短。在请求完成之前,status 的值为 0。值得注意的是,如果 XMLHttpRequest 错误,浏览器也会报告 status 0。

来自XMLHttpRequest 规范

类型为“错误”的响应称为网络错误。

网络错误是指状态始终为 0 的响应,状态消息始终为空字节序列,标头列表始终为空,正文始终为空,尾部始终为空。

方法二:获取API

我希望拦截一个低级 ReadableStream 并获取一些内容,但不幸的是,在网络错误时不会调用解析回调:

fetch('http://localhost:8080/data', {
        method: 'POST',
        body: new ArrayBuffer(100000000),
        mode: 'cors'
}).then(resp => {
        console.log(resp);
        //hopefully we can get readable stream here
        //...nope, networkerrors do not trigger resolve
}).catch(error => {
        console.log(error);//TypeError: "NetworkError when attempting to fetch resource."
}).then(retry => {
        console.log(retry);
});
Run Code Online (Sandbox Code Playgroud)

当遇到网络错误时, fetch() Promise 会拒绝并返回 TypeError,尽管这通常意味着权限问题或类似问题。对成功 fetch() 的准确检查包括检查 Promise 是否已解析,然后检查 Response.ok 属性的值为 true。HTTP 状态 404 并不构成网络错误

获取文档

获取规格

浏览器不会将此视为 HTTP 错误,而是网络错误,因此不会转发与用户代码相关的任何 HTTP。

结论

XHR 和 Fetch 规范规定网络错误将作为空响应进行处理。