惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
AI
AI
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
Scott Helme
Scott Helme
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Engineering at Meta
Engineering at Meta
博客园 - 叶小钗
The GitHub Blog
The GitHub Blog
Microsoft Azure Blog
Microsoft Azure Blog
N
News and Events Feed by Topic
Cloudbric
Cloudbric
B
Blog
Cisco Talos Blog
Cisco Talos Blog
V
Vulnerabilities – Threatpost
N
News and Events Feed by Topic
V
Visual Studio Blog
A
Arctic Wolf
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
U
Unit 42
S
Security @ Cisco Blogs
博客园 - 聂微东
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
Y
Y Combinator Blog
G
GRAHAM CLULEY
L
LINUX DO - 热门话题
量子位
NISL@THU
NISL@THU
Webroot Blog
Webroot Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tenable Blog
月光博客
月光博客
S
Security Affairs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
D
Docker
www.infosecurity-magazine.com
www.infosecurity-magazine.com
雷峰网
雷峰网
博客园 - 司徒正美
T
The Exploit Database - CXSecurity.com
Hugging Face - Blog
Hugging Face - Blog
Help Net Security
Help Net Security
D
DataBreaches.Net

博客园 - 柒零壹

Ubuntu 24.04 安装最新版podman@5.6.1 [转贴]在前端如何玩转 Word 文档 一个命令行参数解决open-webui镜像启动失败的问题(huggingface网站访问失败问题) Go Template 常用疑难知识点 从数据库中随机选取数据(基于golang,xorm) go-ElasticSearch TypedClient学习笔记 golang用pgx查询数据时如何将查询结果方便的放入Map中 go语言如何使用elastic官方客户端go-elasticsearch/v8实现数据批量更新 【转】网络常用颜文字(文字表情) GoCV下实现多图片单窗口内同时显示 解决GoCV/OpenCV不支持中文的问题 golang中xorm自动维护表结构自动导入数据的实现 [golang]filepath.Glob的缺陷,不支持多级目录 浏览器中javascript简易实现json数据保存到客户端 最简单搭建前端轻量级项目开发服务 [转]Three.js做一个酷炫的城市展示可视化大屏 [转]css实现不同样式的tooltip对话框小三角 [原]升级项目到Rails7.0.3,使用自己手动方案编译打包css及js资源 puma web server如何监听所有IP地址
[golang] gin的中间中调用Abort方法导致的带附件的表单提交时,浏览器报net::ERR_CONNECTION_RESET错误的原因及解决方法
柒零壹 · 2023-02-09 · via 博客园 - 柒零壹

一、软件环境

服务端基于go@1.19.3, gin@1.8.2 开发

浏览器端使用chrome、edge及firefox进行测试

二、错误再现

server端使用gin中间件技术自定义一些中间件,这里导致问题出现的主要涉及到“授权监测”和“附件大小限制”两个中间件。之所以如此是因为这两个中间件中都有需要调用gin context的Abort方法的情况。

这里就以很简单的限制附件大小的中间件为例,其基本代码如下:

func SizeLimit(mb int) gin.HandlerFunc {
    return func(c *gin.Context) {
        slog.Warn("[SizeLimit] middleware invoked")
        if c.Request.Method == "GET" || c.Request.Method == "DELETE" {            
      return } ct := c.Request.Header.Get("Content-Type") if !strings.HasPrefix(ct, "multipart/form-data; boundary") {       return } if mb > 0 { bytes := mb * 1024 * 1024 maxBodyBytes := bytes + 512 sl := c.Request.Header.Get("Content-Length") slog.Warn("[SizeLimit]", "c.Request.Header.Content-Length", sl) if len(sl) > 0 { cl, _ := strconv.Atoi(sl) if cl > maxBodyBytes { msg := fmt.Sprintf("附件过大,超出限制。(size limit: %dMb)", mb) slog.Error(msg, errors.New(msg)) if clientAcceptJson(c) { c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{ "code": http.StatusRequestEntityTooLarge, "data": EmptyDs, "msg": msg, }) return } else { c.Abort() c.String(http.StatusRequestEntityTooLarge, "S:"+msg) return } } } else { var w http.ResponseWriter = c.Writer c.Request.Body = http.MaxBytesReader(w, c.Request.Body, int64(maxBodyBytes)) } } } }

客户端使用chrome访问,编辑一条数据并添加超过限制大小的图片,此时用提交数据就会报那个经典的错误net::ERR_CONNECTION_RESET(当然,只能在浏览器console终端中看到)

Edge也报出同样错误,Firefox则不会在console中打出来,但也是可以catch到网络失败错误(我用的jquery jqXHR.status ==0)。

此时,查看server端log输出则一切正常

time=2023-02-09T14:44:42.053+08:00 level=WARN source=D:/GolangDev/projects/sps/middlewares.go:126 msg="[SizeLimit] middleware invoked"
time=2023-02-09T14:44:42.053+08:00 level=WARN source=D:/GolangDev/projects/sps/middlewares.go:140 msg=[SizeLimit] c.Request.Header.Content-Length=9792577
time=2023-02-09T14:44:42.053+08:00 level=ERROR source=D:/GolangDev/projects/sps/middlewares.go:145 msg="附件过大,超出限制。(size limit: 5Mb)" err="附件过大,超出限制。(size limit: 5Mb)"
[GIN] 2023/02/09 - 14:44:42 | 413 |            0s |    192.168.2.26 | PUT      "/admin/dicts/customers/1?r_num=617123760302072" response size:80

使用ApiFox程序测试接口,一切正常,可以收到server返回的413错误及JSON信息。

遂利用ApiFox生成的测试代码,分别测试了 shell脚本、python、ruby、javascript及go代码,其中javascript脚本和页面代码中一致不能正确收到server响应,其他语言除了ruby则都可以正常返回。ruby则表现的和javascript脚本一样是网络错误。

三、原因排查

1,提交数据但是不带附件,或附件大小不超标,则一切正常(没有被拦截);

2,但是如果我的自定义大小设置很小,比如1Mb,上传1.7mb的文件,虽然被拦截了,但是可以正常收到响应!(迷惑)

3,改为POST方式,同样问题,排除PUT方式问题;

4,bing搜索,看到这篇文章,“Why might a 413 not be flushed to the client immediately?”。

至此,我灵光一现,大概知道了怎么回事儿!上传大的文件时,gin框架没有全部读取完Request数据,此时被中间件拦截并Abort处理后,尽管在中间件中返回了信息,但是浏览器却没有读取,而是始终等着数据发送完成。而此时连接被gin服务端关闭,因此就报出连接reset错误!

小的文件正常是因为gin已经读取解析完成了Reqeust数据(我实验好像2mb以下都没问题),所以浏览器正常接收返回信息了。

四、不是解决方案的解决方案。

虽然不情愿,但是我只能采取读完Request数据并丢弃的方式解决问题,因为程序只能通过浏览器访问,否则用户只能得知发生错误了,不能知道什么错误原因!

我的实现方式是再实现一个中间件,统一处理Abort的Request数据问题。

func RequestTracer() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        req := c.Request
        slog.Info("[RequestTracer] <---开始--->", "method", req.Method, "request path", req.URL.Path, "client", c.Request.RemoteAddr)
        //        c.Next()
        //
        latency := time.Since(t)
        status := c.Writer.Status()
     // 
        if c.IsAborted(){
            // 读取并丢弃全部Request数据,以免浏览器报错: net::ERR_CONNECTION_RESET
            buf := make([]byte, 1024)
            for n, err := c.Request.Body.Read(buf); err == nil && n > 0; n, err = c.Request.Body.Read(buf) {
                //discard
            }
        }
        slog.Info("[RequestTracer] <---结束--->", "elapased time", latency, "response status", status)
    }
}

五、其他

当然,更进一步解决方式可以在浏览器端实现上传文件大小限制,但是并不能替代服务端的限制,并且考虑到其他中间件也会Abort请求,更是需要服务端处理此问题。

不知道这是否算是浏览器设计中的一个Bug?