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

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

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函数中可能是解决此问题的最简单方法,但如果可能的话,我希望将这些部分分开。

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语言非常陌生,如果这段代码中有任何问题,请告诉我。

英文:

If I have some code, like the example below, that fetches an image from a link and then saves it to disk, what is the best way to pass around the image data?

I thought about using ioutil.ReadAll(res.Body) to convert it to []byte but passing that around seems expensive, although I can't tell from the documentation whether or not it returns a slice or an array. I also tried returning a pointer to res.Body, a *io.ReadCloser type, but I couldn't figure out how to properly call the .Close() method on the pointed to interface.

I understand that moving the save code into the FetchImage function would probably be the easiest way to solve this but I would like to have these pieces separate if possible.

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)
    }
}

Additionally, I am very new to Go so if there's anything in this code that looks bad please let me know.

答案1

得分: 2

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

切片在传递时非常高效。切片是指向支持数组的指针,具有长度和容量。

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
}

你也可以传递响应体,但要小心使用。必须关闭响应体以释放底层连接。问题中的代码确实关闭了响应体,但很难看到,因为响应体在函数中传递并在那里关闭。

英文:

Use ioutil.ReadAll. The function returns a slice of bytes.

Slices are efficient to pass around. A slice is a pointer to the backing array, a length and a capacity.

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
}

You can also pass around the response body, but use caution. The response body must be closed to release the underlying connection. The code in the question does close the response body, but it's difficult to see because the response body is passed down the function where it's closed.

答案2

得分: 1

在Go语言中,所有的数组都有一个大小指定符号,看起来像[8]byte。切片则没有大小指定符号,看起来像[]byte。切片内部只存储了指向数据的指针、切片的长度和切片的容量。在64位系统上,这只占用了24个字节,所以你不必担心传递它。

然而,我建议从FetchImage返回一个*ImageData,因为它已经包含了像图像名称这样的元数据。

至于为什么不能将指针指向接口,这里有一个在Stack Overflow上的帖子(链接:https://stackoverflow.com/questions/13511203/why-cant-i-assign-a-struct-to-an-interface)解释了原因。

另外,在Save函数中,你在检查错误之前使用了defer file.Close()。你应该交换它们的顺序,因为如果出现错误,file将为nil,可能会导致段错误。

英文:

In Go all arrays have a size specifier, and will look like [8]byte. Slices will not have this and look like []byte. Slices internally just store a pointer to the data, the length of the slice, and the capacity of the slice. This is just 24 bytes on a 64 bit system, so you don't have to worry about passing it around.

However, I would recommend to return a *ImageData from FetchImage as it already has metadata like the name of the image.

As to why you cant take a pointer to a interface, there is a post here on SO that explains why.

Also, in Save you defer file.Close() before checking for a error. You should swap this because file will be nil if there is a error, and will probably segfault.

huangapple
  • 本文由 发表于 2016年3月20日 07:18:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/36108433.html
匿名

发表评论

匿名网友

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

确定