解决 golang 中 wrote more than the declared Content-Length
写个网关还是很能练手的,这不在对接新的项目时,就遇到题头的报警。_, err = fmt.Fprintf(w, string(str))if err != nil {fmt.Println(err.Error())}一开始,没想到这块会报错,而是直接忽略的,这样就造成了测试时啥也没返回,但是上游是有数据返回的,最后追踪到这里。于是,便开始万能的断点调试了,发现 w 也就是 http.Respons
写个网关还是很能练手的,这不在对接新的项目时,就遇到题头的报警。
_, err = fmt.Fprintf(w, string(str))
if err != nil {
fmt.Println(err.Error())
}
一开始,没想到这块会报错,而是直接忽略的,这样就造成了测试时啥也没返回,但是上游是有数据返回的,最后追踪到这里。
于是,便开始万能的断点调试了,发现 w 也就是 http.ResponseWriter 有两个属性是对应此错误的,分别是 written 和 contentLength。其中 contentLength 是接口返回值中 body 的长度,在初始化时值为 -1。
// net/http/server.go
// Read next request from connection.
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
……
w = &response{
conn: c,
cancelCtx: cancelCtx,
req: req,
reqBody: req.Body,
handlerHeader: make(Header),
contentLength: -1,
closeNotifyCh: make(chan bool, 1),
// We populate these ahead of time so we're not
// reading from req.Header after their Handler starts
// and maybe mutates it (Issue 14940)
wants10KeepAlive: req.wantsHttp10KeepAlive(),
wantsClose: req.wantsClose(),
}
……
}
而题头的错误,也定义此文件中。
// net/http/server.go
// ErrContentLength is returned by ResponseWriter.Write calls
// when a Handler set a Content-Length response header with a
// declared size and then attempted to write more bytes than
// declared.
ErrContentLength = errors.New("http: wrote more than the declared Content-Length")
如果返回体的 header 头直接有 Content-Length ,那么便直接读取的此值。
// net/http/server.go
func (w *response) WriteHeader(code int) {
……
if cl := w.handlerHeader.get("Content-Length"); cl != "" {
v, err := strconv.ParseInt(cl, 10, 64)
if err == nil && v >= 0 {
w.contentLength = v
} else {
w.conn.server.logf("http: invalid Content-Length of %q", cl)
w.handlerHeader.Del("Content-Length")
}
}
……
}
如果是返回体 header 有 Transfer-Encoding: chunked ,那么 contentLength 便为 -1。
接下来便进入了最后的判断处。
// net/http/server.go
// either dataB or dataS is non-zero.
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
……
w.written += int64(lenData) // ignoring errors, for errorKludge
if w.contentLength != -1 && w.written > w.contentLength {
return 0, ErrContentLength
}
……
}
这里,如果 contentLength 不是初始值(也就是返回体 header 头 Content-Length 有值),且 written 的值大于 contentLength 的值,便会报 wrote more than the declared Content-Length。
到此,就知道原因和如何解决了。
之所以出现这个问题,是因为,网关接收转发的返回值已经有了 Content-Length 值,而我的网关会在外面包一层,这样就会出现 written 的值大于 contentLength 的 case 了。想要规避,也很简单,让下游返回的头中加上 Transfer-Encoding:chunked ,这样便不会传 Content-Length 的响应头了。于是,在走上述判断的时便因条件不足而跳过了。
最后的最后,上述解决方案还是有有待提高,还是自身网关健壮的好,因此需要考虑目前出现这情况下,不让三方修改代码,而自身兼容考虑好此种情形。
strByte, _ = json.Marshal(ret)
str := string(strByte)
w.Header().Set("Content-Length", strconv.Itoa(len(str)))
w.WriteHeader(http.StatusOK)
之所以这么做是因为,这样可以更新 contentLength 值,当然了,具体更新是第四行代码,也就是熟读代码并掌握实现后如何为己所用了。这里贴出完整的代码。
func (w *response) WriteHeader(code int) {
if w.conn.hijacked() {
caller := relevantCaller()
w.conn.server.logf("http: response.WriteHeader on hijacked connection from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
return
}
if w.wroteHeader {
caller := relevantCaller()
w.conn.server.logf("http: superfluous response.WriteHeader call from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
return
}
checkWriteHeaderCode(code)
w.wroteHeader = true
w.status = code
if w.calledHeader && w.cw.header == nil {
w.cw.header = w.handlerHeader.Clone()
}
if cl := w.handlerHeader.get("Content-Length"); cl != "" {
v, err := strconv.ParseInt(cl, 10, 64)
if err == nil && v >= 0 {
w.contentLength = v
} else {
w.conn.server.logf("http: invalid Content-Length of %q", cl)
w.handlerHeader.Del("Content-Length")
}
}
}
更多推荐
所有评论(0)