我该如何倒带一个io.ReadCloser



我总是被阅读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))
}

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

即当我运行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"}

我错过了什么?

除非基础值也是io.ReadSeeker,否则不能倒带io.ReadCloser

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

相比之下,io.ReadSeeker有两种方法:ReadSeek,后者允许重绕。

如果你只需要接受同样可搜索的io.ReadCloser,你可以很容易地将这两者结合起来:

type ReadSeekCloser interface {
io.Reader
io.Seeker
io.Closer
}

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

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

如何在Go[…]中回放io.ReadCloser?

您不能。ReadCloser可以读取和关闭。除非实际的基础类型有某种倒带方法,否则您根本无法倒带。(对于您的情况,您可以只使用bytes.Buffer,可能是在通过io/iutil.ReadCloser添加Close方法作为Request.Body之后;但这不是"倒带",而是"替换"。)

https://golang.org/pkg/net/http/httputil/#DumpRequest

DumpRequest以HTTP/1.x有线表示形式返回给定请求。它应该仅由服务器用于调试客户端请求。

显然Dubug使用了DumpRequest。

但如果你不在乎的话。戈多克还提到:

如果body为true,DumpRequest也会返回body。为此,它消耗req。Body,然后用产生相同字节的新io.ReadCloser替换它。

因此,您可以先调用DumpRequest,然后从Body调用ReadAll,因为DumpRequest之后的Body仍然相同。

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
payload, err := httputil.DumpRequest(r, true)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("dump request", string(payload))        
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Println("body", string(body))
w.WriteHeader(http.StatusOK)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}

请求体不可搜索。没有倒带,它们没有缓冲在内存中。当该数据被读取时,它是从网络流中读取的。想象一下,一个大的上传,默认情况下将所有数据缓冲在内存中会很浪费。

也就是说,你可以做一些事情来获得你想要的输出。它涉及到在你阅读后更换r.身体。

你提到希望进行皮棉检查。我发现声明一个缓冲区并仅使用该缓冲区会有所帮助。如果你喜欢的话,你甚至可以用这个缓冲区来代替r身体。你仍然需要记住";倒带";使用Seek。


func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
b := &bytes.Buffer{}    
_, err := io.Copy(b, r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
bodyCopy := bytes.NewReader(b.Bytes())
log.Println("body", b.String())
r.Body = io.NopCloser(bodyCopy)
bodyCopy.Seek(0, 0)
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))
}

最新更新