我一直被阅读 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"}
我想念什么?
答案 0 :(得分:2)
如何在Go [...]中倒回io.ReadCloser?
您不能。可以读取和关闭ReadCloser。除非实际的基础类型有某种方法可以倒带,否则您根本无法。 (对于您而言,您可能只使用bytes.Buffer,可能是通过io / ioutil.ReadCloser将Close方法添加为Request.Body之后;但这不是“倒带”而是“替换”。)
答案 1 :(得分:2)
除非基础值也是io.ReadSeeker
,否则您不能倒退 <Target Name="ManagedIncrementalBuildPostProcessDependencyGraph"
Condition="'@(ClCompile)' != '' and '$(EnableManagedIncrementalBuild)' == 'True'"
DependsOnTargets="GetReferenceAssemblyPaths"
AfterTargets="$(ManagedIncrementalBuildProcessDependencyGraphAfterTarget)"
>
<PropertyGroup>
<MIBProcessDependencyGraphExcludedFolders Condition="'$(MIBProcessDependencyGraphExcludedFolders)' == ''">$(ExcludePath);$(FrameworkDir);$(VSInstallDir);$(_FullFrameworkReferenceAssemblyPaths)</MIBProcessDependencyGraphExcludedFolders>
<MIBSearchPaths>$(ReferencePath);@(ClCompile->'%(AdditionalUsingDirectories)'->Distinct())</MIBSearchPaths>
</PropertyGroup>
<MIBPostProcessDependencyGraph
Sources ="@(ClCompile)"
SearchPath ="$(MIBSearchPaths)"
ExcludedInputPaths ="$(MIBProcessDependencyGraphExcludedFolders)"
IntDir ="$([System.IO.Path]::GetFullPath($(TLogLocation)))"
ContinueOnError ="true"
TLogReadFiles ="@(CLTLogReadFiles)"
TLogWriteFiles ="@(CLTLogWriteFiles)"
/>
</Target>
。
根据定义,io.ReadCloser
具有两种方法:io.ReadCloser
和Read
。因此,显然没有倒带的选项。
与此相反,Close
有两种方法:io.ReadSeeker
和Read
,后者允许倒带。
如果您只需要接受也可以搜索的Seek
,则可以轻松地将两者结合起来:
io.ReadCloser
现在,您可以使用自定义的type ReadSeekCloser interface {
io.Reader
io.Seeker
io.Closer
}
类型代替ReadSeekCloser
,并且可以选择倒带阅读器。
当然,很少有io.ReadCloser
实际上符合该接口(io.ReadCloser
将是主要的接口)。如果您的os.File
没有实现io.ReadCloser
方法(例如网络流),则使其成为Seek
可用的最简单方法是将内容转储到文件中,然后打开该文件。还有其他方法可以使内存中的缓冲区变为可查找状态(例如Seek
),但是要进行变体,首先需要将流读取到内存或磁盘中。
答案 2 :(得分:1)
请求正文不可搜索。没有倒带,它们没有缓冲在内存中。读取该数据后,将从网络流中读取该数据。想象一下,如果上传大量文件,则默认情况下将所有数据缓存在内存中都是很浪费的。
也就是说,您可以做一些事情来获得所需的输出。阅读后,需要更换r.Body。
您提到希望进行皮棉检查。我发现声明缓冲区并仅使用该缓冲区会有所帮助。如果愿意,您甚至可以用该缓冲区替换r.Body。您仍然需要记住与Seek一起“倒带”。
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var body ReedSeekCloser // Declare body as this interface, now it is a little easier to remember to use body, and it is seekable.
body = &bytes.Buffer{}
_, err := io.Copy(body, r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Println("body", string(body))
r.Body = body
body.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))
}
答案 3 :(得分:1)
https://golang.org/pkg/net/http/httputil/#DumpRequest
DumpRequest以其HTTP / 1.x线路表示形式返回给定请求。服务器只能使用它调试客户端请求。
DumpRequest显然是供Dubug使用的。
但是,如果您不在乎。 Godoc还提到:
如果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))
}