如何在Golang中倒带io.ReadCloser?

时间:2019-01-14 03:14:44

标签: 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))
}

我的“转储请求”日志行中没有遗体。

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

我想念什么?

4 个答案:

答案 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.ReadCloserRead。因此,显然没有倒带的选项。

与此相反,Close有两种方法:io.ReadSeekerRead,后者允许倒带。

如果您只需要接受也可以搜索的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))
}