关于图片:在Go中传递响应正文(response.Body)的有效方法是什么?

 2020-01-14 

What's an efficient way to pass a response body (response.Body) in Go?

如果我有一些代码(例如下面的示例),该代码从链接中获取图像,然后将其保存到磁盘,那么传递图像数据的最佳方法是什么?

我曾考虑过使用ioutil.ReadAll(res.Body)将其转换为[]byte,但传递它的代价似乎很昂贵,尽管我无法从文档中得知它是否返回切片或数组。 我也尝试返回指向res.Body(*io.ReadCloser类型)的指针,但是我不知道如何在指向的接口上正确调用.Close()方法。

我知道将保存代码移到FetchImage函数中可能是解决此问题的最简单方法,但我希望将这些片段分开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
type ImageData struct {
    Data      io.ReadCloser
    Name      string
}

func FetchImage(url string) (io.ReadCloser, error) {
    res, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    return res.Body, nil
}

func Save(data *ImageData) error {
    defer data.Data.Close()
    file, err := os.Create(data.Name)
    defer file.Close()
    if err != nil {
        return err
    }
    _, err = io.Copy(file, data.Data)
    if err != nil {
        return err
    }
    return nil
}

func main() {
    body, err := fetcher.FetchImage("https://imgur.com/asdf.jpg")
    if err != nil {
        panic(err)
    }
    imageData := ImageData{body,"asdf.jpg"}
    saver := Saver{config.BaseDir, 1}
    err = saver.Save(&imageData)
    if err != nil {
        panic(err)
    }
}

另外,我对Go还是很陌生,所以如果这段代码中有什么看起来很糟糕,请告诉我。


在Go中,所有数组都有一个大小说明符,看起来像[8]byte。切片将没有此外观,并且看起来像[]byte。切片在内部仅存储指向数据的指针,切片的长度和切片的容量。在64位系统上,这仅为24字节,因此您不必担心传递它。

但是,我建议从FetchImage返回一个*ImageData,因为它已经具有像图像名称这样的元数据。

至于为什么不能使用指向接口的指针,SO上有一篇文章解释了为什么。

另外,在Save中,您defer file.Close()在检查错误之前。您应该交换此位置,因为如果出现错误,文件将为nil,并且可能会出现段错误。


使用ioutil.ReadAll。该函数返回一个字节片。

切片有效地传递。切片是指向后备数组,长度和容量的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type ImageData struct {
    Data      []byte
    Name      string
}

func FetchImage(url string) ([]byte, error) {
    res, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    if res.StatusCode != 200 {
        return nil, fmt.Errorf("%s: %d", url, res.StatusCode)
    }
    return ioutil.ReadAll(resp.Body)
}

func Save(data *ImageData) error {
    file, err := os.Create(data.Name)
    if err != nil {
        return err
    }
    defer file.Close()
    _, err := file.Write(data.Data)
    return err
}

您也可以绕过响应正文,但要小心。必须关闭响应主体以释放基础连接。问题中的代码确实关闭了响应主体,但是很难看到,因为响应主体是沿着关闭它的函数传递的。