因为很好奇为什么 ioutil.ReadAll 之后如果不做其他的操作,就会没办法再次读取 io.Reader 的数据。所以研究一下为什么会酱紫。
场景
Post 一个接口的时候会先经过一个中间件,中间件要读取 Body 里的数据,然后接口逻辑再读取 Body 的时候会读取到空数据。
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"time"
)
func ping(w http.ResponseWriter, r *http.Request) {
// 两种方式都可以返回 ping
//_, _ = fmt.Fprintf(w, "ping")
_, _ = w.Write([]byte("ping"))
}
func first(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
fmt.Println("first:", string(bodyBytes))
next.ServeHTTP(w, r)
})
}
func next(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
fmt.Println("next:", string(bodyBytes))
return
}
func main() {
go func() {
time.Sleep(500 * time.Millisecond)
req, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1:8000/test", bytes.NewBufferString(`{"param":0}`))
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}()
http.HandleFunc("/ping", ping)
http.Handle("/test", first(http.HandlerFunc(next)))
_ = http.ListenAndServe(":8000", nil)
}
终端输出结果:
first: {"param":0}
next:
可以看到,到 next 方法的时候,Body 已经是空的了。
官方库的解决方法
将 Body 的内容重新写回去
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)
我们来修改下 first 方法:
func first(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
fmt.Println("first:", string(bodyBytes))
// NopCloser returns a ReadCloser with a no-op Close method wrapping
// the provided Reader r.
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
next.ServeHTTP(w, r)
})
这个方法有一个问题,就是如果中间件要多次读取 Body 的话,需要多次重复写。让我们再看看 gin 是怎么做的。
gin 的解决方法
gin 有一个解析 Body 的方法 ShouldBindBodyWith ,是在读取之后将数据保存到 Context ,然后下次只要继续使用 ShouldBindBodyWith 或者直接从 Context 读取 key
为 gin.BodyBytesKey 再转换为 []byte
类型, 即可复用请求的数据:
// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request
// body into the context, and reuse when it is called again.
//
// NOTE: This method reads the body before binding. So you should use
// ShouldBindWith for better performance if you need to call only once.
func (c *Context) ShouldBindBodyWith(
obj interface{}, bb binding.BindingBody,
) (err error) {
var body []byte
if cb, ok := c.Get(BodyBytesKey); ok {
if cbb, ok := cb.([]byte); ok {
body = cbb
}
}
if body == nil {
body, err = ioutil.ReadAll(c.Request.Body)
if err != nil {
return err
}
c.Set(BodyBytesKey, body)
}
return bb.BindBody(body, obj)
}
具体怎么用这个方法,可以去了解下 gin框架 。
以上只是餐前小菜,解析造成的原因,才是本文的重点。
ioutil.ReadAll 的源码解析
Buffer
在开始之前必须先了解 Buffer
Buffer 是具有 Read 和 Write 方法的可变大小的字节缓冲区。
Buffer 的零值是空缓冲区。
它的结构包含如下字段:
- buf 缓存区 缓存的内容是 buf[off : len(buf)]
- off 可以先理解成指针
- lastRead 可以先理解成最后进行了什么读操作
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
lastRead readOp // last read operation, so that Unread* can work correctly.
}
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}
// Read reads the next len(p) bytes from the buffer or until the buffer
// is drained. The return value n is the number of bytes read. If the
// buffer has no data to return, err is io.EOF (unless len(p) is zero);
// otherwise it is nil.
func (b *Buffer) Read(p []byte) (n int, err error) {
b.lastRead = opInvalid
if b.empty() {
// Buffer is empty, reset to recover space.
b.Reset()
if len(p) == 0 {
return 0, nil
}
return 0, io.EOF
}
n = copy(p, b.buf[b.off:])
b.off += n
if n > 0 {
b.lastRead = opRead
}
return n, nil
}
首先先是场景:读取请求包的 Body
bodyBytes, err := ioutil.ReadAll(r.Body)
然后我们可以看到 ioutil.ReadAll 调用的是 readAll()
// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
func ReadAll(r io.Reader) ([]byte, error) {
return readAll(r, bytes.MinRead)
}
readAll 的参数是 r io.Reader, capacity int64 看代码上的注释意思好像是从 r 读取数据,然后将数据分配到 capacity 的 b 存储起来。后面我再把这里的方法扩展解析一下,也可以根据读者自己的需要,直接跳到相应部分:
-
func (b *Buffer) Grow(n int)
首先在这里可以看到 buf.Grow(int(capacity)) 方法是用来分配空间的。
// readAll reads from r until an error or EOF and returns the data it read
// from the internal buffer allocated with a specified capacity.
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
var buf bytes.Buffer
// If the buffer overflows, we will get bytes.ErrTooLarge.
// Return that as an error. Any other panic remains.
defer func() {
e := recover()
if e == nil {
return
}
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
err = panicErr
} else {
panic(e)
}
}()
if int64(int(capacity)) == capacity {
buf.Grow(int(capacity))
}
_, err = buf.ReadFrom(r)
return buf.Bytes(), err
}
func (b *Buffer) Grow(n int)
在这里可以看到 buf.Grow(int(capacity)) 方法
// Grow grows the buffer's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to the
// buffer without another allocation.
// If n is negative, Grow will panic.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) Grow(n int) {
if n < 0 {
panic("bytes.Buffer.Grow: negative count")
}
m := b.grow(n)
b.buf = b.buf[:m]
}
func (b *Buffer) grow(n int)
源码解析这种东西真的是越陷越深... 开弓没有回头箭,冲!首先获取当前 Buffer 的缓存区长度
// grow grows the buffer to guarantee space for n more bytes.
// It returns the index where bytes should be written.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) grow(n int) int {
m := b.Len()
// If buffer is empty, reset to recover space.
if m == 0 && b.off != 0 {
b.Reset()
}
// Try to grow by means of a reslice.
if i, ok := b.tryGrowByReslice(n); ok {
return i
}
if b.buf == nil && n <= smallBufferSize {
b.buf = make([]byte, n, smallBufferSize)
return 0
}
c := cap(b.buf)
if n <= c/2-m {
// We can slide things down instead of allocating a new
// slice. We only need m+n <= c to slide, but
// we instead let capacity get twice as large so we
// don't spend all our time copying.
copy(b.buf, b.buf[b.off:])
} else if c > maxInt-c-n {
panic(ErrTooLarge)
} else {
// Not enough space anywhere, we need to allocate.
buf := makeSlice(2*c + n)
copy(buf, b.buf[b.off:])
b.buf = buf
}
// Restore b.off and len(b.buf).
b.off = 0
b.buf = b.buf[:m+n]
return m
}
持续更新中... 遛狗先...
网友评论