记一次前端提交文件请求超时问题

探讨了在华为云环境中,前端使用Axios发送包含大文件的请求时遇到的超时问题,分析了请求各阶段耗时,指出前端传输耗时(RequestSent)主要受浏览器至ELB外网传输速度影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题环境:

华为云

问题现象:

前端浏览器提交请求(请求中包含表单参数、文件,且文件大小超过1.5M左右),浏览器在发出请求10秒后提示网络异常,并在console控制台中打印如下日志:

 

同时在后端服务日志中看到整个请求进入到具体服务的处理时长为3秒左右,远小于浏览器的10秒超时;

同时单独的上传附件(异步、el-upload控件),并没有出现以上超时问题;

 

问题原因:

前端Axios中统一设置了10秒超时,导致前端浏览器在后端请求返回时间超过10秒后自动超时断开;

 

问题分析:

前端浏览器显示了10秒超时,而后端服务的处理时间远小于10秒,所以问题没有出在后端服务的处理速度上,起初认为是各种环境(Isito、K8s)引发了超时问题,但是其实我们普遍都忽略了一个问题,就是具体服务日志的处理时间不代表整个Http请求的处理时间,尤其在包含文件上传的请求中尤为明显;

通过Chrome浏览器控制台可以查看一个请求的具体Timing,如下图为具体的一次Timing统计:

大家注意到这次请求的总共耗时15.34s,而其中Request Sent耗时9.87s,Waiting耗时5.46s,Content Download耗时6.20ms,

Request Sent可以简单理解为浏览器端将所有请求数据(表单参数、文件)传输到服务器的时间,

Wating为服务器收到前端浏览器提交的数据后进行处理的时间,

Content Download为服务器返回响应到浏览器的传输时间,

也就是说在提交请求时,花费了将近10秒左右的时间才把浏览器端的请求(参数、>1.5Mb文件)提交到服务器,尽管后端具体服务的处理时间可能才2~3s的时间,但总共的请求时间早已超过10s,最终引发了前端浏览器报出10s超时;

以上Timing是在调大了前端超时时间(60s)后获得,引发超时的请求无法获得Timing统计;

具体Timing说明可参考如下图片:

 

其实,最开始这个问题的排查是从后端Nginx入手并发现问题的,在后端Nginx日志中发现如下异常:

100.125.68.209 - - [12/Sep/2019:17:06:08 +0800] xxx.xxx.com "POST /mx_dspt_service/web/500105/13/98427144886030336/formDispatcher/11/257/0/dGJveC9yZWYvc2F2ZUZvdGFUYm94VXBncmFkZVJlZg== HTTP/1.1" xxx.xxx.com 499 0 "http://xxx.xxx.com/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3730.400 QQBrowser/10.5.3805.400" "-" 9.954 - /mx_dspt_service/web/500105/13/98427144886030336/formDispatcher/11/257/0/dGJveC9yZWYvc2F2ZUZvdGFUYm94VXBncmFkZVJlZg== -

Http返回码为499,且文件>1Mb,dspt、ota服务都处理完成,且日志均已打印成功,

通过查看Nginx相关文档,发现499含义如下:

 

即客户端主动关闭了连接,导致服务端无法返回响应;

加之在反复提交请求的过程中出现了一次成功请求,并注意到了Timing统计,才最终确定问题根源出现在前端的10s超时设置;

 

扩展:

之前在测试环境都好好的,怎么一切到云环境上就出现超时呢?

测试环境都走的内网,而云环境都需要通过外网后再进入到ELB,

外网的传输速度相比于内网速度会相应慢一些,所以外网才会会出现超时;

如下为目前华为云服务架构:

 

我们在浏览器发出的请求过程如下:

(1)浏览器发出请求,

(2)首先会进入到华为云的ELB(弹性负载均衡,可以简单理解为云上的Nginx,做请求负载、请求转发的,云环境的入口),

(3)之后由ELB转发请求到Nginx(之所以添加了Nginx层,是由于Https证书、私钥绑定在Nginx上),

(4)Nginx将请求转发到Istio的网关Gateway(Istio集群的外部域名调用入口,在k8s中为Ingress),

(5)Istio Gateway将请求最终转发到我们的具体服务(其实在Istio中请求首先转发到每个服务对应的边车Sidecar Envoy上,之后由边车再将请求转发到我们的服务)

以上 浏览器->ELB,ELB->Nginx,Nginx-> Gateway,Gateway->Sidecar,Sidecar->app均有传输耗时,但是只有 浏览器->ELB需要通过外网传输,而其他传输都发生在内网耗时较少,

所以一开始提到的前端传输超时(Request Sent)可以理解为主要是受到 浏览器->ELB 传输耗时的影响;

关于Nginx,即可以将Https相关设置放到Istio Gateway中,如此只需依赖Istio即可(将Https与ELB、Nginx进行解耦),

日后亦可对华为云架构调整如下:

 

解决方案:

(1)针对个别请求(带有文件提交、导入文件的请求)调整前端请求超时时间(例如由10s调整为60s);

(2)优化网络传输速度(外网速度、华为云ELB传输速度);

补充:

目前大多数服务都是通过后端将文件上传到云存储(浏览器 -> 服务网关Dspt-> 具体服务 -> 云存储),

即大致存在3次文件上传:

(1)浏览器->服务网关Dspt的文件上传(耗时最长,通过外网进入到ELB或网关,后再进入dspt,此处存在多次文件上传,为了便于理解统一归为1次),

(2)服务网关dspt->具体服务的文件上传(内网传输,耗时相对较短),

(3)具体服务->云存储的文件上传(耗时较长,视具体云存储服务性能和网络情况)

若文件可以进行异步上传(附件上传),则均可通过前端浏览器直接上传(前端请求后端获取相应token后直接在前端进行云存储的上传操作),并将上传成功后的文件URL提交给后端服务,以上方式即可省掉前2次的文件上传(节省服务器资源、减少网络传输耗时),而仅通过浏览器直接进行1次文件上传即可,以减少文件上传耗时;

### Element UI ElUpload 请求超时解决方案 在处理 `ElUpload` 组件上传大文件时,可能会因为网络延迟或服务器配置不当而引发请求超时问题。以下是针对该问题的分析与解决方案。 #### 1. 调整 Axios 的超时时间 默认情况下,Axios 的请求超时时间为 0(即无限制),但在某些场景下可能被设置为较短的时间。可以通过修改 Axios 配置来延长超时时间: ```javascript import axios from 'axios'; // 设置全局超时时间 axios.defaults.timeout = 60000; // 单位为毫秒 (此处设为60秒) // 或者单独调整某个实例的超时时间 const instance = axios.create({ baseURL: 'https://your-api-endpoint.com', timeout: 60000, }); ``` 此操作适用于前端层面,能够有效缓解因网络传输缓慢而导致的超时问题[^1]。 #### 2. 修改后端 API 的超时设置 对于 .NET Core 后端服务而言,默认也有一定的请求超时限制。可以在控制器或者中间件中增加超时时间支持: ```csharp public async Task<IActionResult> UploadLargeFile(IFormFile file, CancellationToken cancellationToken) { var options = new JsonSerializerOptions { WriteIndented = true }; using (var memoryStream = new MemoryStream()) { await file.CopyToAsync(memoryStream, cancellationToken); // 处理业务逻辑... } return Ok(new { Message = "Success!" }); } ``` 同时,在启动配置 (`Startup.cs`) 中也可以调整 Kestrel 的最大请求体大小以及读取数据的超时时间: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); app.UseEndpoints(endpoints => { endpoints.MapControllers() .WithMetadata(new AllowAnonymousAttribute()) // 可选:允许匿名访问 .RequireCors("DefaultPolicy"); // CORS策略名称 }); // 配置Kestrel选项 app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>() .ApplicationStarted.Register(() => { var webHost = app.ApplicationServices.GetService<IWebHost>(); ((KestrelServer)webHost.Services.GetService<IServer>()) ?.Features?.Get< IKestrelControlFeature>()? .LimitRequestBodySize(long.MaxValue); Console.WriteLine("Max request body size set to unlimited."); }); } public static IServiceCollection AddCustomizedTimeouts(this IServiceCollection services) { services.Configure<KestrelServerOptions>(options => { options.Limits.MaxRequestBodySize = null; // 移除请求体大小限制 options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(5); // 请求超时时间 options.Limits.MinResponseDataRate = null; // 最小响应速率不限制 }); return services; } ``` 上述代码片段展示了如何通过扩展方法自定义 Kestrel 的行为以适应更大的文件上传需求[^3]。 #### 3. 使用分片上传技术优化体验 当单次上传的数据量过大时,建议采用分片上传的方式减少每次 HTTP 请求的压力。Element-UI 提供了相应的钩子函数支持这一特性实现: ```html <el-upload ref="upload" :action="'/api/upload'" :headers="{'Authorization': token}" :on-progress="handleProgress" :before-upload="handleBeforeUpload" multiple> </el-upload> <script> export default { data() { return { chunkSize: 1 * 1024 * 1024, // 每个分片大小为1MB currentChunkIndex: 0, totalChunks: 0, uploadedChunks: [], }; }, methods: { handleBeforeUpload(file) { const fileSize = file.size; this.totalChunks = Math.ceil(fileSize / this.chunkSize); let start = 0; while(start < fileSize){ const end = start + this.chunkSize >= fileSize ? fileSize:start + this.chunkSize; const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const chunkBlob = blobSlice.call(file,start,end); fetch('/api/chunk',{ method:'POST', headers:{'Content-Type':'application/octet-stream'}, body:chunkBlob }).then(res=>res.json()).then(data=>{ console.log(`Uploaded Chunk ${start}-${end}`); if(++this.currentChunkIndex === this.totalChunks){ alert('All chunks have been successfully uploaded.'); } }) start += this.chunkSize; } return false; // 防止自动提交整个文件 }, handleProgress(event,file,fileList){ console.log(`${Math.round((event.loaded/event.total)*100)}%`); } } }; </script> ``` 以上脚本实现了基于浏览器原生接口手动切割并逐一发送各部分二进制流至服务器的功能[^2]。 --- ### 总结 综上所述,解决 `ElUpload` 请求超时可以从以下几个方面入手: - **前端**:适当增大 Axios 默认超时时长; - **后端**:提升 .NET Core 应用程序接收大型 POST 数据的能力; - **架构改进**:引入断点续传机制降低单一事务复杂度。 这些措施共同作用可显著改善用户体验,并增强系统的稳定性与可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗小爬EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值