发生管道损坏时如何处理 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.jqXHR
(data.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 响应?的主要内容,如果未能解决你的问题,请参考以下文章