从 Reader 多次读取

huangapple go评论74阅读模式
英文:

Reading from a Reader multiple times

问题

我正在构建一个简单的缓存代理,拦截HTTP请求,获取响应中的内容,然后将其写回客户端。问题是,一旦我从response.Body中读取,写回客户端的内容就变为空(其他部分,如头部,按预期写入)。

以下是当前的代码:

func requestHandler(w http.ResponseWriter, r *http.Request) {
    client := &http.Client{}
    r.RequestURI = ""
    response, err := client.Do(r)
    defer response.Body.Close()
    if err != nil {
        log.Fatal(err)
    }
    content, _ := ioutil.ReadAll(response.Body)
    cachePage(response.Request.URL.String(), content)
    response.Write(w)
}

如果我删除content, _cachePage这两行,它就能正常工作。但是如果包含这两行,请求返回一个空的body。你有什么办法可以只获取http.ResponseBody并将响应完整地写回http.ResponseWriter吗?

英文:

I'm building a simple caching proxy that intercepts HTTP requests, grabs the content in response.Body, then writes it back to the client. The problem is, as soon as I read from response.Body, the write back to the client contains an empty body (everything else, like the headers, are written as expected).

Here's the current code:

func requestHandler(w http.ResponseWriter, r *http.Request) {
    client := &http.Client{}
    r.RequestURI = ""
    response, err := client.Do(r)
    defer response.Body.Close()
    if err != nil {
	    log.Fatal(err)
    }
	content, _ := ioutil.ReadAll(response.Body)
    cachePage(response.Request.URL.String(), content)
    response.Write(w)
}

If I remove the content, _ and cachePage lines, it works fine. With the lines included, requests return and empty body. Any idea how I can get just the Body of the http.Response and still write out the response in full to the http.ResponseWriter?

答案1

得分: 6

根据我的评论,你可以实现io.ReadCloser。

根据Dewy Broto的建议(谢谢),你可以用以下更简单的方式实现:

content, _ := ioutil.ReadAll(response.Body)
response.Body = ioutil.NopCloser(bytes.NewReader(content))
response.Write(w)
英文:

As in my comment you could implement the io.ReadCloser

As per Dewy Broto (Thanks) you can do this much simpler with:

content, _ := ioutil.ReadAll(response.Body)
response.Body = ioutil.NopCloser(bytes.NewReader(content))
response.Write(w)

答案2

得分: 2

正如你所发现的,你只能从请求的Body中读取一次。

Go语言有一个反向代理(reverse proxy)可以帮助你实现你想要做的事情。请查看httputil.ReverseProxyhttputil.DumpResponse

英文:

As you have discovered, you can only read once from a request's Body.

Go has a reverse proxy that will facilitate what you are trying to do. Check out httputil.ReverseProxy and httputil.DumpResponse

答案3

得分: 1

你不需要再次从响应中读取数据。你已经拿到了数据,可以直接将其写入响应写入器(response writer)。

调用

response.Write(w)

将响应以二进制格式写入服务器的响应体。这对于代理来说并不是你想要的。你需要分别复制头部、状态码和响应体到服务器的响应中。

我在下面的代码注释中指出了其他问题。

我建议使用标准库的ReverseProxy或者复制它并根据你的需求进行修改。

func requestHandler(w http.ResponseWriter, r *http.Request) {

    // 不需要创建一个客户端,使用默认的即可
    // client := &http.Client{} 

    r.RequestURI = ""
    response, err := http.DefaultClient.Do(r)

    // 响应可能为空,在检查错误后关闭
    // defer response.Body.Close() 

    if err != nil {
        log.Fatal(err)
    }
    defer response.Body.Close() 

    // 检查错误!总是要检查。
    // content, _ := ioutil.ReadAll(response.Body)
    content, err := ioutil.ReadAll(response.Body)
    if err != nil {
         // 处理错误
    }
    cachePage(response.Request.URL.String(), content)

    // Write 方法以二进制格式将响应写入 w。
    // 因为服务器处理的是二进制格式,所以你需要复制各个部分。
    // response.Write(w)

    // 复制头部
    for k, v := range response.Header {
       w.Header()[k] = v
    }
    // 复制状态码
    w.WriteHeader(response.StatusCode)

    // 写入响应体
    w.Write(content)
}
英文:

You do not need to read from the response a second time. You already have the data in hand and can write it directly to the response writer.

The call

response.Write(w)

writes the response in wire format to the server's response body. This is not what you want for a proxy. You need to copy the headers, status and body to the server response individually.

I have noted other issues in the code comments below.

I recommend using the standard library's ReverseProxy or copying it and modifying it to meet your needs.

func requestHandler(w http.ResponseWriter, r *http.Request) {

    // No need to make a client, use the default
    // client := &http.Client{} 

    r.RequestURI = ""
    response, err := http.DefaultClient.Do(r)

    // response can be nil, close after error check
    // defer response.Body.Close() 

    if err != nil {
        log.Fatal(err)
    }
    defer response.Body.Close() 

    // Check errors! Always.
    // content, _ := ioutil.ReadAll(response.Body)
    content, err := ioutil.ReadAll(response.Body)
    if err != nil {
         // handle error
    }
    cachePage(response.Request.URL.String(), content)

    // The Write method writes the response in wire format to w.
    // Because the server handles the wire format, you need to do
    // copy the individual pieces.
    // response.Write(w)

    // Copy headers
    for k, v := range response.Header {
       w.Header()[k] = v
    }
    // Copy status code
    w.WriteHeader(response.StatusCode)

    // Write the response body.
    w.Write(content)
}

huangapple
  • 本文由 发表于 2014年9月19日 23:26:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/25937374.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定