从读取器多次读取

4
我正在构建一个简单的缓存代理,拦截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这几行,它就能正常工作。但是如果包括这几行,请求将返回一个空的主体。你有什么想法可以让我只获取http.ResponseBody并将响应完整地写入http.ResponseWriter中吗?

最后一行不应该是 w.Write(response) 吗? - DanG
你不能直接使用 Write() 方法写入一个 http.Response 对象(因为它无法转换为 []byte)。但是,你可以使用名为 wResponseWriter 对象来进行 Write() 操作。我已经仔细检查过了,你提出的方法行不通。 - jeffknupp
不是最理想的方法,但你可以创建自己的结构体实现io.ReadCloser,将body放回其中,然后将其赋值回response.Body。 - DanG
3个回答

6

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

根据Dewy Broto(感谢)的说法,您可以使用以下更简单的方法:

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

2

1
你不需要第二次从响应中读取数据。你已经拥有数据并可以直接将其写入响应输出流。
调用:
response.Write(w)

将响应以线路格式写入服务器响应正文。 这不适用于代理。 您需要分别复制标题,状态和正文到服务器响应中。
我已在下面的代码注释中注意到其他问题。
我建议使用标准库的ReverseProxy或复制它并修改以满足您的需求。
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)
}

我不确定为什么你要区分复制头文件(因为在原始版本中默认情况下已经完成了),但是你的答案确实是正确的。感谢你的帮助。 - jeffknupp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接