在Go中读取经过gzip压缩的HTTP响应

39

我正在尝试使用Go读取一个gzip压缩的HTTP响应,但我总是收到以下错误消息:

panic: gzip: invalid header
[...] stack trace [...]
如果我运行 "curl -H "Accept-Encoding: gzip" http://foo.com/ | gunzip -",我可以正确地获得解压后的响应。 我还使用ngrep进行了双重检查,Accept-Encoding/Content-Encoding匹配并被正确发送/返回。
如果我创建一个带有一些虚拟内容的文件并将其gzip压缩,我可以从我的Go!程序中读取它。
用于测试的程序:
package main

import (
    "io"
    //"os"
    "fmt"
    "compress/gzip"
    "net/http"
)

func main() {
    /* This works fine
    f, _ := os.Open("/tmp/test.gz")
    defer f.Close()
    reader, err := gzip.NewReader(f)
    */

    // This does not :/
    resp, _ := http.Get("http://foo.com/")
    defer resp.Body.Close()
    reader, err := gzip.NewReader(resp.Body)

    if err != nil { panic(err) }

    buff := make([]byte, 1024)
    for {
        n, err := reader.Read(buff)

        if err != nil && err != io.EOF {
            panic(err)
        }

        if n == 0 {
            break
        }
    }

    s := fmt.Sprintf("%s", buff)
    fmt.Println(s)
}

我有没有忽略什么?

4个回答

71

编辑:以下是手动处理压缩的示例。如果您不设置标头,那么默认的传输将为您设置标头,然后在读取响应正文时进行解压缩。

client := new(http.Client)

request, err := http.NewRequest("GET", "http://stackoverflow.com", nil)
request.Header.Add("Accept-Encoding", "gzip")

response, err := client.Do(request)
defer response.Body.Close()

// Check that the server actually sent compressed data
var reader io.ReadCloser
switch response.Header.Get("Content-Encoding") {
case "gzip":
    reader, err = gzip.NewReader(response.Body)
    defer reader.Close()
default:
    reader = response.Body
}

io.Copy(os.Stdout, reader) // print html to standard out

出于简洁起见,错误处理已被删除。我保留了defer语句。


http.Get似乎会自动发送Content-Encoding(除非我弄错了)。我尝试使用以下代码:http://play.golang.org/p/haoPEZV6_H,并运行“sudo ngrep -W byline -d en1 port 80”,返回给我这个:http://pastebin.com/PqhCX8gQ。我们清楚地看到一个Accept-Encoding头。但是,如果您运行http://play.golang.org/p/haoPEZV6_H,是否会出现运行时错误?也许我毕竟误用了net/http API。 - Jérôme R
1
太棒了,谢谢!我已经试着玩了一下 - 这是完整的代码 http://play.golang.org/p/Dmf06rhhcs - Nick Craig-Wood
http.NewRequest 的第一个参数应该全部大写以实现最广泛的兼容性。这里的示例在我使用 NginX 时无法正常工作。 - jelder
需要注意的是,有时候一些API可能会通过设置“Accept-Encoding: gzip”来发送压缩响应,即使没有要求。在这种情况下,Go Transport不会自动解压它,因为它没有明确要求。更多信息请参见:https://github.com/golang/go/issues/13298 - Abdul Wasae

37

net/http#Transport 处理 gzip 压缩响应。您无需进行任何特殊操作。

请查看此处的 DisableCompression 选项,这里


我在文档中找不到关于那个的任何信息。你有什么指引吗?谢谢 :) - Jérôme R
嗯...奇怪...net/http.Transport有一个名为DisableCompression的字段。我不久前不得不将其关闭,以防止它解压由sourceforge发送回来的gzip文件。 - simonmenke
2
我正在点赞。标准客户端传输会为您处理gzip。这包括在从response.Body读取数据时自动解压缩数据。OP的问题是他试图再次解压缩。 - Stephen Weinberg
1
如果您想手动解压数据,您需要手动请求gzip压缩的数据。请参考我的答案中的示例。 - Stephen Weinberg
3
似乎它无法处理“deflate”压缩格式。 - Arnaud Le Blanc
这个答案太简短了,对于那些在这个主题上背景不够的人来说并没有帮助。我建议至少包括一个相关评论的引用,https://golang.org/src/net/http/transport.go#L181 - Daniel Dror

22
根据net/http文档(第110行),如果您手动设置了Accept-Encoding请求头,则http.Transport不会自动解压缩gzip响应。否则,行为由Transport的DisableCompression布尔值控制。

与此同时,行号发生了变化,已经移至 https://golang.org/src/net/http/transport.go#L181。 - Daniel Dror

0
以下是手动处理压缩的示例。

import "compress/gzip"

func ReadAll(r io.Reader) ([]byte, error) {
    reader, err := gzip.NewReader(r)
    if err != nil {
        return nil, err
    }
    defer reader.Close()
    buff, err := ioutil.ReadAll(reader)
    return buff, err
}

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