Golang中如何正确将io.Reader返回到主函数
Golang中如何正确将io.Reader返回到主函数 我正在构建自己的Docker客户端,遇到了一个我认为非常基础的问题。这可能源于我对Go如何处理IO流的理解不够扎实,或者别的什么……我猜我遗漏了一些非常基础的东西,希望你们能帮我指出来:
我在这里编写的是相当于docker load的功能,需要处理多种格式的压缩包(tar.gz、tar、tar.bz2、tar.xz)。我在处理这些多种格式时遇到了问题。
目前,主要的“加载器”函数如下所示:
func LoadImage(filepaths []string) {
ctx := context.Background()
cli := hosts.Connect2daemon(true)
for _, filepath := range filepaths {
tarballFile, err := os.Open(filepath)
if err != nil {
fmt.Println("Opening tarball file error: ", err)
os.Exit(-9)
}
defer tarballFile.Close()
tarballFormatReader := getTarballCompressedFormatReader(filepath, tarballFile)
if tarballFormatReader == nil {
fmt.Println("Unknown file format, exiting")
os.Exit(-10)
}
defer tarballFormatReader.Close()
tarReader := tar.NewReader(tarballFormatReader)
}
}
如你所见,它调用了getTarballCompressedFormatReader函数,该函数如下:
func getTarballCompressedFormatReader(tbname string, tbfile *os.File) io.Reader {
if strings.HasSuffix(tbname, ".bz2") {
return bzip2.NewReader(tbfile)
}
if strings.HasSuffix(tbname, ".xz") {
reader, _ := xz.NewReader(tbfile)
return reader
}
if strings.HasSuffix(tbname, ".gz") {
reader, _ := gzip.NewReader(tbfile)
return reader
}
if strings.HasSuffix(tbname, ".tar") {
return tar.NewReader(tbfile)
}
return nil
}
问题出在defer tarballFormatReader.Close()这一行,编译器抱怨它没有Close()方法。我不明白,它应该有这个方法,不是吗?
这是我用Go编写的第一个完整规模的软件,所以我可能遗漏了一些基础的理解……
更多关于Golang中如何正确将io.Reader返回到主函数的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的,我今天早些时候也查看了那个文件。
更多关于Golang中如何正确将io.Reader返回到主函数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
不确定你具体指什么,但常规的 Docker 也必须关闭所有读取器,至少是基于文件的那些。
因此,它确实可能会打开并关闭从打开文件获得的任何内容,但只传递读取器部分,并在打开文件的地方将其关闭。
这实际上是一个很好的通用实践:在打开的地方关闭资源,并且关闭的正是那个资源,而不是在处理后获得的某个东西。
我同意,这也是我非常喜欢GO的一个部分:defer语句允许你在打开某物后立即在其下方关闭它。
我想说的是,我还没有找到一种与压缩格式无关的方法来执行docker load。我想我现在有点不自量力了,因为我显然还没有掌握io流。
func main() {
fmt.Println("hello world")
}
感谢,使用 io.ReadCloser() 我在应用的其他地方也用过,不知道为什么之前没想到。
看来我可能在其他地方卡住了。我打算尝试逆向分析 Docker 的 ImageLoad() 代码,因为我不知道如何无缝地从普通的 tarball 切换到 tar.gz、tar.bz2、tar.xz 等格式,也就是在运行时选择合适的 io.Reader。
根据我有限的理解,我似乎只能选择正确的读取器,或者使用实现了 Close() 的读取器,无法同时兼顾两者。
jfgratton:
问题出在
defer tarballFormatReader.Close()这一行
你的代码中缺少这一行…
jfgratton:
(tbname string, tbfile *os.File) io.Reader {
你真的应该也返回一个 error 值…
jfgratton:
io.Reader
我猜你是想对返回的值调用 Close() 方法?它没有 Close 方法,这可以解释这个错误。
如果我没记错的话,io.Reader 只有 Read() 方法。要关闭的话,你需要 io.ReadCloser 类型。
它只是将输入原样转发给守护进程。
// ImageLoad loads an image in the docker host from the client host.
// It's up to the caller to close the io.ReadCloser in the
// ImageLoadResponse returned by this function.
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
v := url.Values{}
v.Set("quiet", "0")
if quiet {
v.Set("quiet", "1")
}
headers := map[string][]string{"Content-Type": {"application/x-tar"}}
resp, err := cli.postRaw(ctx, "/images/load", v, input, headers)
if err != nil {
return types.ImageLoadResponse{}, err
}
return types.ImageLoadResponse{
Body: resp.body,
JSON: resp.header.Get("Content-Type") == "application/json",
}, nil
}
在Go中,io.Reader接口只定义了Read(p []byte) (n int, err error)方法,不包含Close()方法。你需要使用io.ReadCloser接口,它组合了io.Reader和io.Closer接口。
以下是修改后的代码:
func getTarballCompressedFormatReader(tbname string, tbfile *os.File) io.ReadCloser {
if strings.HasSuffix(tbname, ".bz2") {
return struct {
io.Reader
io.Closer
}{
Reader: bzip2.NewReader(tbfile),
Closer: tbfile,
}
}
if strings.HasSuffix(tbname, ".xz") {
reader, err := xz.NewReader(tbfile)
if err != nil {
return nil
}
return struct {
io.Reader
io.Closer
}{
Reader: reader,
Closer: tbfile,
}
}
if strings.HasSuffix(tbname, ".gz") {
reader, err := gzip.NewReader(tbfile)
if err != nil {
return nil
}
return reader // gzip.Reader 实现了 io.ReadCloser
}
if strings.HasSuffix(tbname, ".tar") {
return struct {
io.Reader
io.Closer
}{
Reader: tar.NewReader(tbfile),
Closer: tbfile,
}
}
return nil
}
对于.tar和.xz格式,需要手动创建组合类型。对于.gz格式,gzip.Reader已经实现了io.ReadCloser。另外,需要处理压缩库返回的错误。
主函数也需要相应调整:
func LoadImage(filepaths []string) {
ctx := context.Background()
cli := hosts.Connect2daemon(true)
for _, filepath := range filepaths {
tarballFile, err := os.Open(filepath)
if err != nil {
fmt.Println("Opening tarball file error: ", err)
os.Exit(-9)
}
tarballFormatReader := getTarballCompressedFormatReader(filepath, tarballFile)
if tarballFormatReader == nil {
fmt.Println("Unknown file format, exiting")
tarballFile.Close()
os.Exit(-10)
}
defer tarballFormatReader.Close()
tarReader := tar.NewReader(tarballFormatReader)
// 处理tarReader...
}
}
这样修改后,tarballFormatReader就具有了Close()方法,可以正确关闭底层文件。

