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

Go的http响应数据写入顺序错误,造成实际响应头与预期不一致问题

简单说下背景

在gin中,封装了一个代理请求,将传入的请求参数封装为新请求并请求第三方,将响应重新包装并返回;
再封装响应时,先调用了c.Writer.Write(resBody),然后操作Header,后加的header实际响应中缺失

package mainimport ("io""net/http""github.com/gin-gonic/gin"
)func main() {g := gin.Default()g.Any("/proxy1/*proxyPath", func(c *gin.Context) {proxyReq, err := http.NewRequest(c.Request.Method,"https://test.cn:9443/api/v1/Download",c.Request.Body)res, err := http.DefaultClient.Do(proxyReq)if err != nil {c.Error(err)return}defer func(Body io.ReadCloser) { _ = Body.Close() }(res.Body)c.Writer.WriteHeader(res.StatusCode)var resBody []byteresBody, err = io.ReadAll(res.Body)_, err = c.Writer.Write(resBody) // 先Writeif err != nil {c.Error(err)return}for k, v := range res.Header {c.Header(k, v[0]) // 再操作了 Header}return})g.Run(":8080")
}

排查过程省略了,直接说原因

标准库net/http的ResponseWriter写入方法调用有顺序要求:

  • 在执行Write时,如果没有WriteHeader,会先执行WriteHeader,且WriteHeader只会执行一次

标准库和gin的ResponseWriter接口定义

// 标准库
type ResponseWriter interface {Header() HeaderWrite([]byte) (int, error)WriteHeader(statusCode int)
}// gin ResponseWriter 包装了标准库的ResponseWriter
type ResponseWriter interface {http.ResponseWriterhttp.Hijackerhttp.Flusherhttp.CloseNotifier// Status returns the HTTP response status code of the current request.Status() int// Size returns the number of bytes already written into the response http body.// See Written()Size() int// WriteString writes the string into the response body.WriteString(string) (int, error)// Written returns true if the response body was already written.Written() bool// WriteHeaderNow forces to write the http header (status code + headers).WriteHeaderNow()// Pusher get the http.Pusher for server pushPusher() http.Pusher
}
  • gin的c.Writer.WriteHeader(res.StatusCode) 实际不会调用标准库的WriteHeader,而是等到Write(resBody)时再调WriteHeaderNow
// github.com/gin-gonic/gin@v1.10.1/response_writer.go:64
func (w *responseWriter) WriteHeader(code int) {if code > 0 && w.status != code {if w.Written() {debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)return}w.status = code}
}
// github.com/gin-gonic/gin@v1.10.1/response_writer.go:74
func (w *responseWriter) WriteHeaderNow() {if !w.Written() {w.size = 0w.ResponseWriter.WriteHeader(w.status)}
}

代码

package mainimport ("net/http"
)func main() {http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 会更新到实际响应w.Header().Add("C1111", "会更新到实际响应")// 写入数据 会自动WriteHeader且code为200_, err := w.Write([]byte("hello world"))_, err = w.Write([]byte("hello world11"))// 不生效了w.Header().Add("C2222", "不会添加到最终的响应头中")// 触发:http: superfluous response.WriteHeader call from main.main.func1 (net_http_server.go:23)// 警告:已经执行过 WriteHeader// 此时请求返回依然是500w.WriteHeader(500)if err != nil {panic(err)}})_ = http.ListenAndServe(":7878", nil)
}

总结

  • header():Header().Add(k,v), w.Header().Del()等
  • WriteHeader(statusCode)
    • 标准库WriteHeader(statusCode)会写入所有header,且后面无法再写入
    • gin框架的ResponseWriter包装了标准库的ResponseWriter,WriteHeader只会记录一下status,WriteHeaderNow()才会实际调用标准库的WriteHeader(statusCode)
  • Write([]byte)
    • 如果没有WriteHeader,会自动WriteHeader且code为200,Write后无法再返回其他状态码:404, 500等;go1.24/src/net/http/server.go:1687
    • 调用write后,再读取请求体可能报错;
    • Write可以调多次
http://www.dtcms.com/a/494529.html

相关文章:

  • 小型企业网站建设模板找人做jsp网站
  • 【DevOps】基于Nexus3部署Docker内网私有代理仓库docker proxy
  • [嵌入式系统-134]:智能体以及其嵌入式硬件架构
  • 不止于“看”:视频汇聚平台EasyCVR视频监控系统功能特点详解
  • R-切割数据
  • 探秘蚂蚁 S21 XP Immersion 300T:液冷技术如何提升挖矿效能
  • Steps + Input.TextArea 实现弹窗内容
  • 重庆装修公司排名表杭州网站建设优化
  • HarmonyOS应用开发指南:Toast无法显示的完整排查流程与实战案例
  • 【研究生随笔】Pytorch中的线性代数
  • 小米开源端到端语音模型 MiMo-Audio-7B-Instruct 语音智能与音频理解达 SOTA
  • 深度学习进阶(六)——世界模型与具身智能:AI的下一次跃迁
  • RV1106+es8388音频采集和播放调试
  • 【图像超分】论文复现:轻量化超分 | FMEN的Pytorch源码复现,跑通源码,整合到EDSR-PyTorch中进行训练、重参数化、测试
  • 网站设计的公司排名无极电影网首页
  • vue3引入海康监控视频组件并实现非分屏需求一个页面同时预览多个监控视频(3)-接口分页篇(最终版)
  • 新华三H3CNE网络工程师认证—OSPF多区域概念与配置
  • 软件开发商网站html网站用什么空间
  • 免费炫酷网站模板wordpress 模板 破解版
  • Linux1017 shell:awk print printf
  • 服务器对网站的作用有哪些?
  • linux系统编程(十③)RK3568 socket之 TCP 服务器的实现
  • 29、构建可视化日志管理服务器
  • 代码解析:《AGENTREVIEW: Exploring Peer Review Dynamics with LLM Agents》
  • 嵌入式软件面试
  • 安卓前后端连接教程
  • linux系统编程(十③)RK3568 socket之 TCP 服务器的实现【更新客户端断开重连依旧可以收发】
  • Windows系统错误6118全面解决方案:修复此工作组的服务器列表当前无法使用
  • 衡阳网站页面设计公司昆明抖音代运营
  • 昆明网站建设是什么意思WordPress添加PHP代码