我如何倒带 io.ReadCloser

hen*_*dry 1 http go

我总是因为阅读io.ReadCloser而忘记我以前读过它而陷入困境,当我再次阅读它时,我得到一个空的有效负载。我希望对我的愚蠢进行一些检查。尽管如此,我认为我可以使用 TeeReader,但它无法满足我的期望:

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        buf := &bytes.Buffer{}
        tee := io.TeeReader(r.Body, buf)
        body, err := ioutil.ReadAll(tee)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        log.Println("body", string(body))
        payload, err := httputil.DumpRequest(r, true)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        log.Println("dump request", string(payload))
        w.WriteHeader(http.StatusOK)
    })
    log.Fatal(http.ListenAndServe(":8080", nil))
}
Run Code Online (Sandbox Code Playgroud)

我的“转储请求”日志行中缺少正文。

即当我跑步时 curl -i -X POST --data '{"username":"xyz","password":"xyz"}' http://localhost:8080

我想要完整的原始请求:

2019/01/14 11:09:50 dump request POST / HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 35
Content-Type: application/x-www-form-urlencoded
User-Agent: curl/7.63.0

{"username":"xyz","password":"xyz"}
Run Code Online (Sandbox Code Playgroud)

我错过了什么?

Fli*_*mzy 5

您不能倒回io.ReadCloser,除非基础值也是io.ReadSeeker

An io.ReadCloser,根据定义,正好有两种方法:ReadClose。所以显然没有倒带的选项。

io.ReadSeeker相比之下,An有两种方法:Readand Seek,后者允许倒带。

如果您只需要接受io.ReadCloser也是可查找的 s,您可以轻松地将这两者结合起来:

type ReadSeekCloser interface {
    io.Reader
    io.Seeker
    io.Closer
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用自定义ReadSeekCloser类型代替io.ReadCloser,并且可以选择倒回阅读器。

当然,io.ReadCloser实际上很少有s 真正符合这个接口(os.File将是主要的)。如果您有一个io.ReadCloser没有实现该Seek方法的(例如网络流),可能最简单的方法Seek是将内容转储到文件中,然后打开该文件。还有其他方法可以使内存中的缓冲区可bytes.NewReader查找(例如),但首先需要将流读入内存或磁盘。