Go json.NewDecoder().Decode()未遵循context deadline问题咨询
结论
这不是json.NewDecoder(resp.Body).Decode存在的bug,本质是两种读取逻辑的行为差异、叠加HTTP响应的本地缓冲机制导致的预期内表现。
原因说明
基础执行逻辑
你设置的context超时时间为5秒,当http.DefaultClient.Do(req)成功返回resp时,代表HTTP响应头已经接收完成,此时体积很小的响应内容(你测试的IP接口返回内容仅20余字节),已经被底层读入了本地缓冲区,不需要再走网络IO就能读取到。
你后续主动Sleep 6秒后context确实已经超时,但context的取消逻辑只会中断处于阻塞等待网络新数据状态的IO操作,读取本地已就绪缓冲区的操作不会被拦截。两个读取方法的核心差异
json.NewDecoder(...).Decode:反序列化时只会读取刚好够完成JSON结构解析的字节,只要读到和JSON结构匹配的闭合标记就会停止读取。你的测试场景下完整JSON已经在本地缓冲区,Decode全程不需要触发网络IO就能完成解析,自然不会返回超时错误。ioutil.ReadAll:会持续读取直到碰到响应体结束的EOF标记,哪怕已经读完了缓冲区里的全部JSON内容,还是会继续尝试读取后续字节确认是否有剩余内容,这一步会触发网络IO,直接被已超时的context中断,因此返回context.DeadlineExceeded错误。
验证方案
如果把测试接口换成返回几十MB以上大体积响应的服务,让响应内容无法在Do方法返回时全部加载到本地缓冲区,那么json.Decode解析到需要读取未缓冲网络数据的阶段时,同样会正常返回context超时错误。
最佳实践
不要依赖读取响应体返回的错误判断context状态,不管用什么方式读取响应,都可以在读取前后主动校验ctx.Err()来准确判断context是否超时/取消:
// 读取前前置校验 if err := ctx.Err(); err != nil { return err } ipResponse := new(IpResponse) err = json.NewDecoder(resp.Body).Decode(ipResponse) // 读取后兜底校验 if err == nil { err = ctx.Err() }
内容的提问来源于stack exchange,提问作者jordan
相关产品推荐
相关产品推荐

