如何将 io.Reader 实例传递给 Golang 中的函数?

How to pass io.Reader instance to a function in Golang?

我一直在处理一个问题,我必须将一个 io.Reader 实例作为参数传递给由 api 作为端点提供的函数。我要做的任务是将本地文件夹上传到公司的云存储。

func (s *server) uploadFileToPutIo(w http.ResponseWriter, r *http.Request) {
    tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
    oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
    client := putio.NewClient(oauthClient)

    var testIO io.Reader // ? 

    upload, err := client.Files.Upload(context.TODO(), testIO, "test", 0)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(upload.File)
    sendResponse(w, []byte("successful"), http.StatusOK)
}

当我在 POST 方法下向此端点 /upload 发出请求时。我收到以下错误。

2021/12/01 18:28:47 http: panic serving 127.0.0.1:61057: runtime error: invalid memory address or nil pointer dereference
goroutine 8 [running]:
net/http.(*conn).serve.func1(0xc000108d20)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:1804 +0x153
panic(0x1390ae0, 0x164fdd0)
        /usr/local/Cellar/go/1.16.6/libexec/src/runtime/panic.go:971 +0x499
io.copyBuffer(0x1462700, 0xc000026360, 0x0, 0x0, 0xc000170000, 0x8000, 0x8000, 0x0, 0x0, 0x13d5e01)
        /usr/local/Cellar/go/1.16.6/libexec/src/io/io.go:423 +0x10b
io.Copy(...)
        /usr/local/Cellar/go/1.16.6/libexec/src/io/io.go:382
github.com/putdotio/go-putio/putio.(*FilesService).Upload(0xc000010088, 0x1468390, 0xc00001c088, 0x0, 0x0, 0x13ef46f, 0x6, 0x0, 0x170d108, 0x90, ...)
        /Users/barisertas/go/pkg/mod/github.com/putdotio/go-putio/putio@v0.0.0-20200123120452-16d982cac2b8/files.go:235 +0x187
main.(*server).uploadFileToPutIo(0xc000010028, 0x1467d60, 0xc00014a2a0, 0xc000154500)
        /Users/barisertas/workspace/mini-project/api/handler.go:79 +0xe5
net/http.HandlerFunc.ServeHTTP(0xc000012db0, 0x1467d60, 0xc00014a2a0, 0xc000154500)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:2049 +0x44
github.com/gorilla/mux.(*Router).ServeHTTP(0xc000144000, 0x1467d60, 0xc00014a2a0, 0xc000154300)
        /Users/barisertas/go/pkg/mod/github.com/gorilla/mux@v1.8.0/mux.go:210 +0xd3
net/http.serverHandler.ServeHTTP(0xc00014a000, 0x1467d60, 0xc00014a2a0, 0xc000154300)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:2867 +0xa3
net/http.(*conn).serve(0xc000108d20, 0x1468400, 0xc00005e300)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:1932 +0x8cd
created by net/http.(*Server).Serve
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:2993 +0x39b
2021/12/01 18:28:47 http: panic serving 127.0.0.1:61059: runtime error: invalid memory address or nil pointer dereference
goroutine 11 [running]:
net/http.(*conn).serve.func1(0xc000108dc0)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:1804 +0x153
panic(0x1390ae0, 0x164fdd0)
        /usr/local/Cellar/go/1.16.6/libexec/src/runtime/panic.go:971 +0x499
io.copyBuffer(0x1462700, 0xc000026400, 0x0, 0x0, 0xc000198000, 0x8000, 0x8000, 0x0, 0x0, 0x13d5e01)
        /usr/local/Cellar/go/1.16.6/libexec/src/io/io.go:423 +0x10b
io.Copy(...)
        /usr/local/Cellar/go/1.16.6/libexec/src/io/io.go:382
github.com/putdotio/go-putio/putio.(*FilesService).Upload(0xc0000100c8, 0x1468390, 0xc00001c088, 0x0, 0x0, 0x13ef46f, 0x6, 0x0, 0x170d108, 0x90, ...)
        /Users/barisertas/go/pkg/mod/github.com/putdotio/go-putio/putio@v0.0.0-20200123120452-16d982cac2b8/files.go:235 +0x187
main.(*server).uploadFileToPutIo(0xc000010028, 0x1467d60, 0xc00014a380, 0xc000154800)
        /Users/barisertas/workspace/mini-project/api/handler.go:79 +0xe5
net/http.HandlerFunc.ServeHTTP(0xc000012db0, 0x1467d60, 0xc00014a380, 0xc000154800)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:2049 +0x44
github.com/gorilla/mux.(*Router).ServeHTTP(0xc000144000, 0x1467d60, 0xc00014a380, 0xc000154600)
        /Users/barisertas/go/pkg/mod/github.com/gorilla/mux@v1.8.0/mux.go:210 +0xd3
net/http.serverHandler.ServeHTTP(0xc00014a000, 0x1467d60, 0xc00014a380, 0xc000154600)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:2867 +0xa3
net/http.(*conn).serve(0xc000108dc0, 0x1468400, 0xc00005e580)
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:1932 +0x8cd
created by net/http.(*Server).Serve
        /usr/local/Cellar/go/1.16.6/libexec/src/net/http/server.go:2993 +0x39b

这是我尝试使用的函数的文档:

// Upload reads from given io.Reader and uploads the file contents to Put.io
// servers under directory given by parent. If parent is negative, user's
// preferred folder is used.
//
// If the uploaded file is a torrent file, Put.io will interpret it as a
// transfer and Transfer field will be present to represent the status of the
// tranfer. Likewise, if the uploaded file is a regular file, Transfer field
// would be nil and the uploaded file will be represented by the File field.
//
// This method reads the file contents into the memory, so it should be used for
// <150MB files.
func (f *FilesService) Upload(ctx context.Context, r io.Reader, filename string, parent int64) (Upload, error) {
    if filename == "" {
        return Upload{}, fmt.Errorf("filename cannot be empty")
    }

    var buf bytes.Buffer
    mw := multipart.NewWriter(&buf)

    // negative parent means use user's preferred download folder.
    if parent >= 0 {
        err := mw.WriteField("parent_id", itoa(parent))
        if err != nil {
            return Upload{}, err
        }
    }

    formfile, err := mw.CreateFormFile("file", filename)
    if err != nil {
        return Upload{}, err
    }

    _, err = io.Copy(formfile, r)
    if err != nil {
        return Upload{}, err
    }

    err = mw.Close()
    if err != nil {
        return Upload{}, err
    }

    req, err := f.client.NewRequest(ctx, "POST", "/v2/files/upload", &buf)
    if err != nil {
        return Upload{}, err
    }
    req.Header.Set("Content-Type", mw.FormDataContentType())

    var response struct {
        Upload
    }
    _, err = f.client.Do(req, &response)
    if err != nil {
        return Upload{}, err
    }
    return response.Upload, nil
}

我对将 io.Reader 实例放入函数感到困惑。在这种情况下,我怎样才能正确地使用 io.Reader 函数。创建后的一个实例如下: var testIO io.Reader 还是我应该做一些额外的操作?

这个:

var testIO io.Reader

相当于:

testIO := io.Reader(nil)

所以这就是为什么您对 nil 指针引用感到恐慌:

2021/12/01 18:28:47 http: panic serving 127.0.0.1:61059: runtime error: invalid memory address or nil pointer dereference
goroutine 11 [running]:

io.Reader 是一种接口,允许传递泛型值,前提是它们实现了接口(即实现 Read 方法)。

由于您正在上传文件,因此您的字节流应该来自 OS 文件。 os.File 实现了正确的 Read 方法 - 所以兼容 io.Reader.

所以尝试:

f, err := os.Open(uploadFilePath)
if err != nil { /* ... */ }

upload, err := client.Files.Upload(context.TODO(), f, "test", 0)

当您定义一个变量时,它的初始值是该类型的 zero valueio.Reader 是一个接口,它的零值为 nil。因此 nil pointer dereference error。在将 io.Reader 传递给 Upload:

之前简单地对其进行初始化
file, err := os.Open("path/to/file")
// if err != nil { ... }

upload, err := client.Files.Upload(context.TODO(), file, "test", 0)
runtime error: invalid memory address or nil pointer dereference

你肯定知道,这是因为你声明了一个io.Reader但是你没有设置它的值,所以它仍然等于接口的默认值,即[=14] =].

    var testIO io.Reader // ? 

io.Reader传递给Upload的目的是提供要上传的数据。通过传递 io.Reader,任意数据源可以提供任意数量的字节,不受内存可用性的限制(不像 []byte,这需要在 之前将所有数据保存在内存中 上传)。 io.Reader 通常用于为这种“流式”操作提供数据。

Upload reads from given io.Reader and uploads the file contents

io.Reader 应该是上传数据的来源。

io.Reader 可能是来自 os.Open() 的文件。

但它可以是任何满足 io.Reader 的东西——例如,它也可以是 bytes.Buffer.

它甚至可以是更深奥的东西,like the result of an GetObject API call against the popular S3 service from AWS,它也是 returns 满足 io.Reader.

io.ReadCloser

io.Reader 是 Go 接口如何允许独立库相互连接的一个很好的例子。您使用的 SDK 不关心它传递了什么 io.Reader;该值满足 io.Reader 就足够了,这是编译时强制执行的要求。你可以传给它任何满足io.Reader的,接口类型保证Upload()能正常处理

Uploadio.Reader。如果你想传递类似 os.Open*os.File 或 S3 GetObject 的 io.ReadCloser 之类的东西,那是可行的,因为 *os.Fileio.ReadCloser 满足 io.Reader.但是由于 Upload 需要 io.Reader,你可以确信它只会调用 io.Reader 中定义的函数。这意味着您必须在 Upload 被调用后自行关闭。

一定要花时间了解 io.Reader 如何让这个函数的输入保持开放状态,同时还要具体说明它期望的接口。这是 Go 中最重要的概念之一。