发生管道损坏时如何处理 AJAX 响应?

Posted

技术标签:

【中文标题】发生管道损坏时如何处理 AJAX 响应?【英文标题】:How to handle AJAX response when broken pipe happens? 【发布时间】:2019-01-28 15:11:04 【问题描述】:

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

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

"readyState":0,"responseText":"","status":0,"statusText":"error"

使用curl调用时,结果为:

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

curl: (55) Send failure: Broken pipe
"files":["size":3863407,"error":"upload_uploadSizeExceeded"]

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

BTW:更奇怪的是,在这种情况下,我在服务器日志中看到请求重复了几次,可能是JS中的一种重试机制?

【问题讨论】:

你试过Axios吗? github.com/axios/axios @EduardoAguad 不,但需要一些努力来交换和文档看起来不太有希望处理这种错误,但会看看,谢谢 【参考方案1】:

是否有任何选项可以让 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);

客户端测试

方法一:XMLHttpRequests

我仔细检查了每个事件,没有任何已发送字节的迹象。如果我们确实有一个 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));

很遗憾,什么都没有。

只读 XMLHttpRequest.status 属性返回数值 XMLHttpRequest 响应的状态码。状态将是 无符号短。在请求完成之前,status的值 将为 0。值得注意的是,浏览器在 XMLHttpRequest 错误的情况。

来自XMLHttpRequest specification:

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

网络错误是状态始终为 0 的响应,状态消息 始终为空字节序列,头列表始终为空,正文 始终为 null,并且预告片始终为空。

方法2:获取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);
);

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

Fetch Documentation

Fetch Specification

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

结论

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

【讨论】:

这不是我所希望的,但感谢您的努力和有根据的国家摘要!是否有任何选项可以区分网络错误(如连接被拒绝、管道损坏等)? 不幸的是,不,阿恩。响应为空,事件不提供可区分的信息。服务器必须为浏览器处理这个问题。【参考方案2】:

您是否尝试在 application.properties 中添加/调整它?

spring.servlet.multipart.max-file-size=1MB # Max file size. Values can use the suffixes "MB" or "KB" to indicate megabytes or kilobytes, respectively.
spring.servlet.multipart.max-request-size=10MB # Max request size. Values can use the suffixes "MB" or "KB" to indicate megabytes or kilobytes, respectively.

我不知道您所说的如何处理故障是什么意思,因为 fail 回调是在客户端执行的(从技术上讲,这已经是您处理 AJAX 故障响应的地方了)。我认为公平地说,您应该在 fail 代码块中向您的用户显示错误模式/弹出窗口。

【讨论】:

问题不在服务器端,而是在客户端处理案例! 您对上传文件字段有控制权吗?一个好的做法是通过您的应用程序检查您的文件大小(在提交时),如果超出限制,则发送相应的错误消息。这样,您将获得应用程序的响应,而不会出现难以处理的服务器错误... 是的,但是有一个服务器端只检查剩余磁盘空间,我的应用正在处理 GB 上传,所以只有客户端不起作用【参考方案3】:

您是否尝试过 jQuery $.post 来处理失败响应?

$.post('server.php', deviceId: id)
.done( function(msg)  ...  )
.fail( function(xhr, textStatus, errorThrown) 
    console.log(xhr.responseText);
);

【讨论】:

这就是问题所在,没有调用fail()

以上是关于发生管道损坏时如何处理 AJAX 响应?的主要内容,如果未能解决你的问题,请参考以下文章

离开页面时如何处理jQuery ajax发布错误

离开页面时如何处理jQuery ajax发布错误

运行 Mongoose 时执行 ajax 调用时如何处理 Node.js 发现

使用 ActiveMerchant 时如何处理超时?

获取对象时如何处理“匹配查询不存在”

使用 Azure 文本翻译 API 时如何处理限制?