如何接收流式传输的HTTP响应

7

当使用Go发送HTTP请求并接收响应时,考虑到ResponseBody非常大(1 GB或更多),我希望在流式传输的同时接收响应。

resp, err: = http.Client.Do(req)

在这种情况下,如果主体很大,我无法读取标头并且不知道响应的状态。有解决方法吗?

你正在寻找分块读取器吗?https://golang.org/pkg/net/http/httputil/#NewChunkedReader。 - whitespace
即使HTTP头部有Content-Length,我可以使用NewChunkedReader吗? - hr20k_
2
为什么你无法读取头文件? - Jonathan Hall
1个回答

8
(编辑:如果您无法从响应中获取“Content-length”头,则可能是您访问的Web服务没有返回该头。在这种情况下,没有办法在完全读取响应体之前知道响应体的长度。您可以通过在响应中删除设置Content-length标头的行来模拟以下示例。)
标准的Go net/http包非常好地处理大型响应。以下是一个自包含的示例以进行演示:
// Start a mock HTTP server that returns 2GB of data in the response. Make a
// HTTP request to this server and print the amount of data read from the
// response.
package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"
    "time"
)

const oneMB = 1024 * 1024
const oneGB = 1024 * oneMB
const responseSize = 2 * oneGB

const serverAddr = "localhost:9999"

func startServer() {
    // Mock HTTP server that always returns 2GB of data
    go http.ListenAndServe(serverAddr, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Content-length", fmt.Sprintf("%d", responseSize))

        // 1MB buffer that'll be copied multiple times to the response
        buf := []byte(strings.Repeat("x", oneMB))

        for i := 0; i < responseSize/len(buf); i++ {
            if _, err := w.Write(buf); err != nil {
                log.Fatal("Failed to write to response. Error: ", err.Error())
            }
        }
    }))

    // Some grace period for the server to start
    time.Sleep(100 * time.Millisecond)
}

func main() {
    startServer()

    // HTTP client
    req, err := http.NewRequest("GET", "http://"+serverAddr, nil)
    if err != nil {
        log.Fatal("Error creating HTTP request: ", err.Error())
    }

    client := http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal("Error making HTTP request: ", err.Error())
    }

    // Read the response header
    fmt.Println("Response: Content-length:", resp.Header.Get("Content-length"))

    bytesRead := 0
    buf := make([]byte, oneMB)

    // Read the response body
    for {
        n, err := resp.Body.Read(buf)
        bytesRead += n

        if err == io.EOF {
            break
        }

        if err != nil {
            log.Fatal("Error reading HTTP response: ", err.Error())
        }
    }

    fmt.Println("Response: Read", bytesRead, "bytes")
}

如果响应数据太大,您不希望将其全部存储在内存中。相反,将其写入临时文件,然后再处理。

如果您希望在网络不是很可靠的情况下仍能可靠地完成下载,则可以寻找“HTTP范围请求”选项,使用它可以恢复部分已下载的数据。


resp, err := client.Do(req) 这个过程必须接收到所有响应后才能继续下一个过程,对吗? - hr20k_
4
@hr20k_,在读取标题后返回。标准库不会读取正文,除了可能读取到某种缓冲区中的几 KB,但绝对不会读取千兆字节的内容。 - Peter
@Peter 哇,我不太清楚。我会尝试一下。谢谢。 - hr20k_
@hr20k_ 尝试在 w.Write(buf) 语句后加上 time.Sleep(5 * time.Second) 然后看看 :) - svsd
标准库的往返传输器并不是那么高效,它们可能会在你不知道的情况下读取几KB或几MB的数据。始终检查您的网络流量并做出决定。 - Inanc Gumus
我替换了一个“常规”的 http.Get(...),在我的看来,它的行为是相同的。 - Zach Young

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