一次content-length的教训

本文探讨了HTTP协议中Content-Length与Transfer-Encoding的使用场景及其相互之间的关系。解释了这两种传输长度指示方式的区别,并提供了如何在实际应用中正确选择的指导。

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

有一次使用springMVC写的接口,在调用的时候传了content-lengt,在传单个数字的时候可以调通,其他参数时死活就是不通。最后经过查资料发现是content-lengt计算错误。

对于http的请求返回结果要进行内容的长度校验主要有两种方式,二者互斥使用

1.客户端在http头(head)加Connection:keep-alive时,服务器的response是Transfer-Encoding:chunked的形式,通知页面数据是否接收完毕,例如长连接或者程序运行中可以动态的输出内容,例如一些运算比较复杂且需要用户及时的得到最新结果,那就采用chunked编码将内容分块输出。

2.除了如1所述之外的情况一般都是可以获取到Content-Length的。



在HTTP协议中,Content-Length用于描述HTTP消息实体的传输长度the transfer-length of the message-body。在HTTP协议中,消息实体长度和消息实体的传输长度是有区别,比如说gzip压缩下,消息实体长度是压缩前的长度,消息实体的传输长度是gzip压缩后的长度。

在具体的HTTP交互中,客户端是如何获取消息长度的呢,主要基于以下几个规则:

  • 响应为1xx,204,304相应或者head请求,则直接忽视掉消息实体内容。
  • 如果有Transfer-Encoding,则优先采用Transfer-Encoding里面的方法来找到对应的长度。比如说Chunked模式。
  • “如果head中有Content-Length,那么这个Content-Length既表示实体长度,又表示传输长度。如果实体长度和传输长度不相等(比如说设置了Transfer-Encoding),那么则不能设置Content-Length。如果设置了Transfer-Encoding,那么Content-Length将被忽视”。这句话翻译的优点饶,其实关键就一点:有了Transfer-Encoding,则不能有Content-Length。
  • Range传输。不关注,没详细看了:)
  • 通过服务器关闭连接能确定消息的传输长度。(请求端不能通过关闭连接来指明请求消息体的结束,因为这样可以让服务器没有机会继续给予响应)。这种情况主要对应为短连接,即非keep-alive模式。
  • HTTP1.1必须支持chunk模式。因为当不确定消息长度的时候,可以通过chunk机制来处理这种情况。
  • 在包含消息内容的header中,如果有content-length字段,那么该字段对应的值必须完全和消息主题里面的长度匹配。
    “The entity-length of a message is the length of the message-body before any transfer-codings have been applied”
    也就是有chunk就不能有content-length 。

其实后面几条几乎可以忽视,简单总结后如下:

1、Content-Length如果存在并且有效的话,则必须和消息内容的传输长度完全一致。(经过测试,如果过短则会截断,过长则会导致超时。)

2、如果存在Transfer-Encoding(重点是chunked),则在header中不能有Content-Length,有也会被忽视。

3、如果采用短连接,则直接可以通过服务器关闭连接来确定消息的传输长度。(这个很容易懂)

结合HTTP协议其他的特点,比如说Http1.1之前的不支持keep alive。那么可以得出以下结论:

1、在Http 1.0及之前版本中,content-length字段可有可无。

2、在http1.1及之后版本。如果是keep alive,则content-length和chunk必然是二选一。若是非keep alive,则和http1.0一样。content-length可有可无






我总结我的例子   如果  要是 js  css  html 这样的文件的话   会返回 contengt-length  字节  前提是    nginx 里的gzip  off   如果是on 的话   返回  chunk

执行的如果是 动态 脚本的话,    还是返回chunk 前提是  content:keep-alive     如果不是长连接的话   返回的头里面没有content-leght   ()


<think>我们正在讨论Go语言中如何设置HTTP请求或响应的Content-Length头。根据引用[2],我们知道在Go中,如果响应体的大小超过2KB,服务器会自动使用Transfer-Encoding: chunked,除非我们显式设置Content-Length。 设置Content-Length的方法: 1. 在响应中:在写入响应体之前,先设置Content-Length头,并指定正确的长度。 2. 在请求中:当我们构建一个请求并需要设置请求体的Content-Length时,可以使用`http.Request`的ContentLength字段。 下面分别介绍: 一、设置HTTP响应的Content-LengthHTTP响应中,我们可以通过以下步骤设置: - 在调用`Write`方法之前,先设置Header中的Content-Length-Content-Length的值设置为响应体字节数的字符串形式。 示例代码: ```go package main import ( "net/http" "strconv" ) func handler(w http.ResponseWriter, r *http.Request) { // 假设我们有一个要返回的字节切片 data := []byte("Hello, World!") // 设置Content-Length w.Header().Set("Content-Length", strconv.Itoa(len(data))) // 注意:在设置头之后,再写入数据 w.Write(data) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } ``` 二、设置HTTP请求的Content-Length 当我们创建HTTP请求时,可以通过设置`http.Request`的ContentLength字段来指定请求体的长度。 示例代码: ```go package main import ( "net/http" "strings" ) func main() { // 创建一个请求 body := strings.NewReader("Hello, Server!") req, err := http.NewRequest("POST", "http://example.com/upload", body) if err != nil { panic(err) } // 设置Content-Length // 由于body是strings.Reader,它已经实现了Len()方法,所以我们可以这样获取长度 req.ContentLength = int64(body.Len()) // 发送请求 client := &http.Client{} resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() } ``` 注意:如果使用`io.ReadCloser`作为请求体,并且你不知道长度,那么可以不设置ContentLength,这时Go会根据实际传输的数据自动处理(可能会使用chunked编码)。 三、注意事项 1. 在响应中,如果设置了Content-Length,那么写入的字节数必须与设置的长度完全一致,否则会出现错误(如引用[4]中提到的“wrote more than the declared Content-Length”)。 2. 在请求中,如果设置了ContentLength,那么请求体的长度必须与设置的值一致,否则可能导致请求失败。 四、关于Content-Length为0的情况 根据引用[3],当Content-Length为0时,表示没有请求体或响应体。在Go中,如果我们要返回一个没有响应体的响应(如204 No Content),可以这样设置: ```go w.WriteHeader(http.StatusNoContent) // 此时不需要设置Content-Length,因为状态码204会自动忽略响应体 ``` 但是,如果我们要返回一个空响应体(例如200 OK),并且想显式设置Content-Length为0,可以: ```go w.Header().Set("Content-Length", "0") w.WriteHeader(http.StatusOK) // 注意:不要写入任何数据,否则会报错 ``` 五、避免并发写入问题 引用[4]中提到,在handler返回后,后台协程仍然尝试写入ResponseWriter会导致错误。因此,在设置Content-Length后,确保在同一个goroutine中完成写入,并且写入的数据量必须与设置的长度一致。 总结: 在Go中设置Content-Length的方法就是在写入数据之前,通过`Header().Set("Content-Length", strconv.Itoa(length))`(对于响应)或设置`req.ContentLength = length`(对于请求)来实现。同时,必须确保写入的数据长度与设置的长度一致。 如果遇到问题,请检查: - 是否在写入数据之前设置了Content-Length- 设置的长度是否与实际写入的数据长度一致? - 是否有多个goroutine同时写入? 相关问题: 1. 在Go中,如何避免在HTTP响应中写入超过Content-Length的数据? 2. 在Go中,如何正确处理HTTP请求的Content-Length? 3. 在Go中,如何实现一个可重试的HTTP请求? 4. 在Go中,如何正确处理HTTP响应的Transfer-Encoding: chunked?</think>### 在 Go 中设置 HTTP Content-Length 的完整指南 在 Go 中设置 HTTP 响应的 Content-Length 头是确保协议合规性和性能优化的关键操作。以下是具体实现方法和注意事项: #### 📌 核心方法 ##### 1. 手动设置 Content-Length ```go package main import ( "net/http" "strconv" ) func handler(w http.ResponseWriter, r *http.Request) { data := []byte("Hello, World!") // 关键步骤:设置 Content-Length w.Header().Set("Content-Length", strconv.Itoa(len(data))) // 写入响应体 w.Write(data) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } ``` > 注意:**必须在调用 `w.Write()` 前设置 Content-Length**,否则 Go 会自动使用 chunked 编码[^2] ##### 2. 使用固定长度响应 ```go func fixedLengthHandler(w http.ResponseWriter, r *http.Request) { // 直接指定 Content-Length w.Header().Set("Content-Length", "42") // 写入固定长度的数据 w.Write(make([]byte, 42)) // 42字节的空数据 } ``` #### ⚠️ 关键注意事项 1. **顺序要求** Content-Length 必须在写入响应体**之前**设置,否则无效: ```go // 错误示例(设置无效) w.Write(data) w.Header().Set("Content-Length", "100") // 太迟了! ``` 2. **长度一致性** 实际写入的字节数必须与 Content-Length 严格一致: ```go // 错误示例(长度不匹配) w.Header().Set("Content-Length", "10") w.Write([]byte("Hello, World!")) // 实际写入13字节 ``` > 会导致 `http: wrote more than declared Content-Length` 错误[^4] 3. **空响应处理** 对于无响应体的请求(如 204 No Content),显式设置 Content-Length=0: ```go w.WriteHeader(http.StatusNoContent) w.Header().Set("Content-Length", "0") // 明确指示无响应体[^3] ``` 4. **并发写入防护** 避免在 handler 返回后继续写入,防止协程冲突: ```go // 危险示例(协程冲突) go func() { w.Write([]byte("late data")) // 可能引发 panic }() ``` #### 🔧 特殊场景处理 ##### 大文件响应(>2KB) 默认情况下,Go 对 >2KB 的响应会自动使用 chunked 编码。强制使用 Content-Length: ```go func largeFileHandler(w http.ResponseWriter, r *http.Request) { data, _ := os.ReadFile("largefile.bin") // 显式设置长度 w.Header().Set("Content-Length", strconv.Itoa(len(data))) w.Write(data) } ``` ##### 动态生成内容 当内容长度动态变化时: ```go func dynamicHandler(w http.ResponseWriter, r *http.Request) { buffer := new(bytes.Buffer) fmt.Fprintf(buffer, "Current time: %s", time.Now().String()) // 计算最终长度 data := buffer.Bytes() w.Header().Set("Content-Length", strconv.Itoa(len(data))) w.Write(data) } ``` #### 🚫 常见错误排查 1. **`Content-Length` 未生效** - 检查是否在 `Write()` 后设置 - 验证响应是否超过 2KB 未显式设置 - 使用 Wireshark 抓包确认实际响应头 2. **`wrote more than declared Content-Length`** - 使用 `len()` 精确计算字节数 - 避免多次调用 `Write()` - 确保未启用 gzip 压缩(可能改变长度) 3. **空指针 panic** - 禁止在 handler 返回后操作 ResponseWriter - 使用同步机制确保写入完成: ```go var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() // 安全写入 }() wg.Wait() ``` #### 💡 最佳实践 1. 使用中间件统一设置: ```go func ContentLengthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rw := &responseWriter{ResponseWriter: w} next.ServeHTTP(rw, r) w.Header().Set("Content-Length", strconv.Itoa(rw.size)) }) } type responseWriter struct { http.ResponseWriter size int } func (rw *responseWriter) Write(b []byte) (int, error) { size, err := rw.ResponseWriter.Write(b) rw.size += size return size, err } ``` 2. 结合 `http.DetectContentType` 自动检测类型: ```go data := []byte("...") w.Header().Set("Content-Type", http.DetectContentType(data)) w.Header().Set("Content-Length", strconv.Itoa(len(data))) ``` --- ### 总结 | 场景 | 方法 | 注意事项 | |------|------|----------| | 静态内容 | `Set("Content-Length", strconv.Itoa(len(data)))` | 在 `Write()` 前调用 | | 动态内容 | 使用 `bytes.Buffer` 计算长度 | 避免多次写入 | | 空响应 | 显式设置 `Content-Length: 0` | 配合 `204 No Content` | | 大文件 | 强制设置长度 | 绕过 2KB 自动分块 | 正确设置 Content-Length 能显著提升 HTTP 性能(减少 30-50% 延迟),避免 chunked 编码开销,并确保协议兼容性[^2][^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值