当前位置: 首页 > news >正文

HTTP文件上传时出现ERR_CONNECTION_RESET问题

触发场景:服务器需要将文件保存在本机,客户端在文件上传期间,服务器提前响应导致客户端报错ERR_CONNECTION_RESET

在做文件上传接口时,遇到一个很容易误判的问题:

前端上传文件时,如果用户携带了一个已经过期的 session,后端中间件明明执行了:

response.WriteJSON(w,http.StatusUnauthorized,response.Fail(response.CodeAuthFail,"session expired"))return

但是浏览器 Network 里看到的不是401,而是返回了:net::ERR_CONNECTION_RESET

这会让人以为后端没有返回响应,或者以为是网络问题。

实际上,这不是普通 JSON 接口的问题,而是“文件上传请求还在发送 body,服务端提前拒绝请求”导致的连接层现象。

问题出现的场景

现在的上传接口大致是:

mux.Handle("POST /v1/audio/upload",chain(http.HandlerFunc(audioHandler.AudioUpload),middleware.AuthMiddleware(cfg.SessionStore),middleware.LoggingMiddleware,))

中间件先校验 session:

userValue,ok:=sessionStore.Get(cookie.Value)if!ok{response.WriteJSON(w,http.StatusUnauthorized,response.Fail(response.CodeAuthFail,"session expired"))return}

而真正解析文件是在 handler 里:

func(h*AudioHandler)AudioUpload(w http.ResponseWriter,r*http.Request){iferr:=r.ParseMultipartForm(10<<20);err!=nil{http.Error(w,"解析表单失败",http.StatusBadRequest)return}file,header,err:=r.FormFile("file")// ...}

所以实际顺序是:

浏览器开始上传 multipart/form-data -> 请求进入 Go 中间件 -> 中间件读取 Cookie -> 中间件发现 session 过期 -> 服务端直接返回 401 并结束处理 -> 浏览器可能还在继续发送文件 body -> 连接被关闭/重置 -> 浏览器显示 ERR_CONNECTION_RESET,而不是 401

关键点是:服务端返回 401 的时候,请求体可能还没有被读完。

普通 JSON 请求很小,通常感觉不到这个问题;但文件上传请求体可能很大,浏览器还在发送数据时,服务端提前结束连接,就可能表现为连接错误。

Gin是否也存在该现象

Gin 的中间件模型本质上也是先进入 middleware,再进入 handler:

r.POST("/upload",AuthMiddleware(),uploadHandler)

如果鉴权中间件里这样写:

if!validSession(c){c.JSON(http.StatusUnauthorized,gin.H{"code":40001,"msg":"session expired",})c.Abort()return}

那么它和net/http的问题是一样的:

先鉴权 还没有读取 multipart body 直接返回 401 浏览器可能看到连接中断

Gin 并不会自动把上传 body 读完以后再返回错误。

如果在 Gin handler 里先调用:

file,err:=c.FormFile("file")

再鉴权,那么 body 已经被解析,响应会更稳定。但是这样会让未登录用户也能先把文件传到服务器,鉴权就太晚了。

Java 使用的解决方案

Spring Boot 里如果使用的是 Controller 参数绑定:

@RequestMapping("/uploadVideo")@GlobalInterceptor(checkLogin=true)publicResponseVOuploadVideo(@NotNullMultipartFilechunkFile,@NotNullIntegerchunkIndex,@NotEmptyStringuploadId)throwsIOException{TokenUserInfoDtotokenUserInfoDto=getTokenUserInfoDto();}

再配合 AOP:

@Before("@annotation(com.workshareplatform.web.annotation.GlobalInterceptor)")publicvoidinterceptorDo(JoinPointpoint){Methodmethod=((MethodSignature)point.getSignature()).getMethod();GlobalInterceptorinterceptor=method.getAnnotation(GlobalInterceptor.class);if(null==interceptor){return;}if(interceptor.checkLogin()){checkLogin();}}

这个流程通常是:

请求进入 -> Servlet Filter 链 -> DispatcherServlet -> multipart 解析 -> 参数绑定 MultipartFile -> 准备调用 Controller -> AOP @Before 执行 checkLogin -> Controller 方法体

也就是说,AOP 鉴权执行时,MultipartFile通常已经被 Spring MVC 解析出来了。请求体已经被读过,所以更容易正常返回业务错误。

但代价是:

未登录用户也可以先把文件上传到服务器临时目录,然后才被 checkLogin 拦截。

所以 Spring Boot 不是天然解决了这个问题,而是很多项目的鉴权点比较靠后,刚好避开了连接中断现象。

如果使用 Spring Security Filter 在前面鉴权,流程又会变成:

请求进入 -> Spring Security Filter 鉴权 -> DispatcherServlet -> multipart 解析

这种情况下,如果 Filter 在 body 没读完前直接拒绝上传请求,也可能出现类似连接中断问题。

现代项目为什么少见

现代项目里,大文件通常不会经过业务服务器,而是采用对象存储直传:

前端 -> 后端:申请上传凭证 后端 -> 前端:返回预签名 URL 前端 -> 对象存储:直接上传文件 前端 -> 后端:上传完成后保存文件记录

例如 S3、阿里云 OSS、腾讯云 COS 都支持类似模式。

这样业务服务器只负责:

  • 校验用户是否有上传权限
  • 签发短期上传凭证
  • 保存文件 metadata
  • 处理上传完成回调

文件本身不经过业务服务器,所以不会在业务服务器上出现“大文件上传到一半,session 过期,中间件提前返回导致连接中断”的问题。

但这不代表鉴权问题消失了,只是模型变了:

只要用户申请上传凭证时是合法的,这次上传就允许继续完成。

即使 session 在上传过程中刚好过期,预签名 URL 在自己的有效期内仍然可以完成上传。

当前项目解决方案

这个项目是 Go 原生 Web 学习项目,文件上传也有限制:

r.ParseMultipartForm(10<<20)

所以当前最合适的做法是后端中间件在 multipart 鉴权失败时,先丢弃请求体,再返回 401 JSON。

这样可以让浏览器更稳定地拿到业务响应。

可以在中间件里封装:

funcwriteAuthFail(w http.ResponseWriter,r*http.Request,msgstring){ifisMultipartRequest(r){_,_=io.Copy(io.Discard,r.Body)_=r.Body.Close()}response.WriteJSON(w,http.StatusUnauthorized,response.Fail(response.CodeAuthFail,msg))}funcisMultipartRequest(r*http.Request)bool{returnstrings.HasPrefix(r.Header.Get("Content-Type"),"multipart/form-data")}

然后把原来的:

response.WriteJSON(w,http.StatusUnauthorized,response.Fail(response.CodeAuthFail,"session expired"))return

改成:

writeAuthFail(w,r,"session expired")return

需要引入:

import("io""strings")

为了让客户端稳定收到 401,后端愿意把这个无效上传请求的 body 读完并丢弃。

它的缺点也很明确:session 已经过期了,但这次上传 body 仍然会被完整发送到服务器。
对于小文件上传,这个取舍是可以接受的。

前端兜底异常处理

即使后端 drain body,也不能保证浏览器永远拿到 401。因为还有很多真正的网络错误:

  • 服务端没启动
  • 端口写错
  • 浏览器取消请求
  • 文件超过限制
  • 网络中断
  • 代理或网关关闭连接

所以前端仍然要同时处理两类错误:

asyncfunctionfileUpload(file){constdata=newFormData()data.set("file",file)try{constresponse=awaitfetch(`${serverUrl}/v1/audio/upload`,{method:"POST",body:data,credentials:"include",})constresult=awaitresponse.json()if(response.status===401||result.code===40001){localStorage.removeItem("user-info")updateHeaderDOM()dialogDOM.showModal()return}if(!response.ok||result.code!==0){console.error("上传失败:",result.msg)return}returnresult}catch(err){console.error("上传失败,请检查网络或服务器状态:",err)}}

后端负责尽量返回明确的业务错误,前端负责兜底处理真实的网络错误。

总结

文件上传接口的鉴权顺序有一个天然取舍:

  1. 先鉴权:省资源,但可能在上传 body 未发送完时提前关闭连接,浏览器显示连接错误。
  2. 先解析 multipart 再鉴权:响应更稳定,但未登录用户也能先消耗服务器上传资源。

以后如果做生产级大文件上传,更推荐:对象存储直传,短期预签名 URL,分片上传,上传完成后后端保存 metadata。这样可以把业务服务器从大文件传输链路中解放出来。

http://www.jsqmd.com/news/866597/

相关文章:

  • 龙芯PMON内核:ioconf.c与设备配置全解析
  • 【CDA干货】数据分析面试常考20个核心知识点(附面试问法+标准回答+避坑指南)
  • 仅需1张RTX 4090就能跑满DeepSeek-R1 67B?——本地化部署性价比极限压测(含量化精度损失对照表)
  • YOLOv8 ROS 2深度解析:机器人视觉感知系统的架构设计与实践指南
  • 在嵌入式开发中如何通过curl调用大模型API优化代码注释
  • 使用 vxe gantt 实现行拖拽排序
  • 工业吸尘器常见维修方法
  • 管道腐蚀评估机构排名
  • 做品牌生成式搜索占位,爱学AI GEO优化实测收录率超九成
  • 揭秘CPU-Z:比鲁大师更精准的硬件检测软件!CPU-Z下载、安装及使用全攻略
  • 反爬与绕过反爬技术总结
  • 2026最最最新的JAVA后端开发八股文
  • 武汉江岸区学钢琴哪家好?乐飞钢琴二十一年深耕 - 资讯纵览
  • 专业的郑州苹果手机维修联系电话口碑佳的
  • 如何快速下载并配置Taotoken的CLI工具实现一键接入
  • 专职会计太贵!长沙财务合规、税务顾问、财务顾问机构更省钱 - 讲清楚了
  • 2026年5月23日芝柏官方售后网点权威评测:基于真实体验与第三方佐证的核验报告 - 资讯纵览
  • 【OpenClaw 进阶配置】如何让 MiniMax 搜索替代 SearXNG 作为 Web Search provider
  • 烟台口碑好的装修公司怎么选?8步指南帮你避坑,烟台兴北居装饰值得参考
  • OBS Source Record插件深度解析:实现多源独立录制的进阶解决方案
  • 独立开发者如何借助Taotoken快速构建并迭代AI应用原型
  • ncmdumpGUI:Windows平台免费NCM文件转换终极指南
  • 浙江话语音合成紧急上线倒计时!3小时完成ElevenLabs定制Voice微调+合规备案(含方言伦理审查清单)
  • 软文营销媒体发稿效果倍增逻辑内容渠道平台三维协同运营解析
  • 视频号视频下载去水印方法全是坑?全网视频一键拿捏!2026封神玩法!
  • 办理科技成果评价对企业有何作用?有哪些流程?需要哪些材料?
  • 东南大学论文模板:8倍效率的学术排版革命
  • 精选!2026重庆黄金回收好口碑快速上门TOP5 渝北本土标杆引领安全变现 - 资讯纵览
  • 边际效应在数据分析中的应用
  • 2026年初中中考英语单词表1600词高频速记必背词汇表带音标听力音频默写PDF版