Gmail API with Go 和 gmail.NewService 无效的内存地址或零指针取消引用

Gmail API with Go and gmail.NewService invalid memory address or nil pointer dereference

我对 gmail api 的新 NewService 功能有疑问。如果我使用已弃用的 gmail.New() 一切正常。 使用 NewService() 我得到 invalid memory address or nil pointer dereference

我的实现如下

type MailData struct {
    To      string
    Name    string
    Subject string
    Content template.HTML
}    

func doSend(msg *gmail.Message, srv *gmail.Service) error {
    _, err := srv.Users.Messages.Send("me", msg).Do()
    if err != nil {
        return err
    }
    return nil
}

func ComposeMessage(m models.MailData) *gmail.Message {

    var gmailMessage gmail.Message

    from := mail.Address{Name: "Sender", Address: os.Getenv("MAIL_FROM")}
    replyTo := os.Getenv("MAIL_REPLYTO")
    to := mail.Address{Name: m.Name, Address: m.To}

    header := make(map[string]string)
    header["From"] = from.String()
    header["Reply-To"] = replyTo
    header["To"] = to.String()
    header["Subject"] = m.Subject
    header["MIME-Version"] = "1.0"
    header["Content-Type"] = "text/html; charset=\"utf-8\""
    header["Content-Transfer-Encoding"] = "base64"

    var msg string
    for k, v := range header {
        msg += fmt.Sprintf("%s: %s\r\n", k, v)
    }
    msg += "\r\n" + string(m.Content)

    gmailMessage.Raw = base64.RawURLEncoding.EncodeToString([]byte(msg))
    return &gmailMessage
}

使用旧的 gmail.New() 可以,但它指出该功能已弃用,因此我需要将其更改为新的 gmail.NewService。虽然像下面那样实现它不起作用

func sendGMail(m models.MailData) error {
    credentials := "../gmail_credentials.json"

    ctx := context.Background()
    srv, err := gmail.NewService(
        ctx,
        option.WithCredentialsFile(credentials),
        option.WithScopes("https://www.googleapis.com/auth/gmail.send"),
    )
    if err != nil {
        return errors.New(fmt.Sprintf("unable to retrieve gmail client: %s", err))
    }

    // Create message
    gMessage := ComposeMessage(m)

    if err := doSend(gMessage, srv); err != nil {
        return errors.New(fmt.Sprintf("could not send mail: %s", err))
    }
    fmt.Println("Email sent")

    return nil
}

编辑:我得到的错误是

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x9a161b]

goroutine 13 [running]:
golang.org/x/oauth2/authhandler.authHandlerSource.Token({{0xc2fb30, 0xc00003c108}, 0xc0002a1340, 0x0, {0x0, 0x0}})
        /home/joss/go/pkg/mod/golang.org/x/oauth2@v0.0.0-20211104180415-d3ed0bb246c8/authhandler/authhandler.go:48 +0x5b
golang.org/x/oauth2.(*reuseTokenSource).Token(0xc00011d1e0)
        /home/joss/go/pkg/mod/golang.org/x/oauth2@v0.0.0-20211104180415-d3ed0bb246c8/oauth2.go:304 +0xd5
golang.org/x/oauth2.(*Transport).RoundTrip(0xc00011d220, 0xc000124600)
        /home/joss/go/pkg/mod/golang.org/x/oauth2@v0.0.0-20211104180415-d3ed0bb246c8/transport.go:45 +0xa7
net/http.send(0xc000124600, {0xc1d200, 0xc00011d220}, {0xb13600, 0xc000263701, 0x0})
        /usr/local/go/src/net/http/client.go:252 +0x5d8
net/http.(*Client).send(0xc000483200, 0xc000124600, {0xc0002637f8, 0x4f49b5, 0x0})
        /usr/local/go/src/net/http/client.go:176 +0x9b
net/http.(*Client).do(0xc000483200, 0xc000124600)
        /usr/local/go/src/net/http/client.go:725 +0x908
net/http.(*Client).Do(...)
        /usr/local/go/src/net/http/client.go:593
google.golang.org/api/internal/gensupport.SendRequest({0x0, 0x0}, 0xb33a63, 0xc000124600)
        /home/joss/go/pkg/mod/google.golang.org/api@v0.30.0/internal/gensupport/send.go:43 +0xb8
google.golang.org/api/gmail/v1.(*UsersMessagesSendCall).doRequest(0xc000263e10, {0xb2b9fa, 0x4})
        /home/joss/go/pkg/mod/google.golang.org/api@v0.30.0/gmail/v1/gmail-gen.go:6836 +0xa05
google.golang.org/api/gmail/v1.(*UsersMessagesSendCall).Do(0xc000263e10, {0x0, 0x1b, 0xb4e56a})
        /home/joss/go/pkg/mod/google.golang.org/api@v0.30.0/gmail/v1/gmail-gen.go:6848 +0x78
github.com/user/mailprj/internal.doSend(0xc0000f6180, 0x12)
        /home/joss/user/mailprj/internal/gmail-api.go:159 +0xa5
github.com/user/mailprj/internal.sendGMail({{0xb3cd52, 0x12}, {0xb2b38e, 0x4}, {0xb4e56a, 0x29}, {0xc000610000, 0x8a66}})
        /home/joss/user/mailprj/internal/gmail-api.go:149 +0x1b2
github.com/user/mailprj/internal.ListenForGMail.func1()
        /home/joss/user/mailprj/internal/gmail-api.go:114 +0xc6
created by github.com/user/mailprj/internal.ListenForGMail
        /home/joss/user/mailprj/internal/gmail-api.go:111 +0x25
exit status 2

问题不是很明显,但很容易解决。 在初始化 gmail.NewService() 时,我需要将配置参数作为选项传递,就像之前 gmail.New() 的实现使用的是

之前是

client := getClient(config)
srv, err := gmail.New(client)

现在是

client := getClient(config)
ctx := context.Background()
srv, err := gmail.NewService(
    ctx,
    option.WithHTTPClient(client),
)

在 Go 的 Gmail API 实现的更多上下文中,您需要使用

中使用的所有函数

https://developers.google.com/gmail/api/quickstart/go

https://github.com/googleworkspace/go-samples/blob/master/gmail/quickstart/quickstart.go

并将它们放入您的 go 文件中。这些将生成 AccessToken 和凭据,如果您正确遵循指南,您将需要另存为 json 文件并重新使用。 credentials.json 用于更新您的 AccessToken。

现在不使用gmail.New(),正确的方法是使用如上所示的函数。

整个sendGmail函数如下

func sendGMail(m models.MailData) error {
    credentials := "../gmail_credentials.json"
    b, err := ioutil.ReadFile(credentials)
    if err != nil {
        return errors.New(fmt.Sprint("unable to read credentials file:", err))
    }

    config, err := google.ConfigFromJSON(b, gmail.GmailSendScope)
    if err != nil {
        return errors.New(fmt.Sprint("unable to parse credentials file config:", err))
    }

    client := getClient(config)
    ctx := context.Background()
    srv, err := gmail.NewService(
        ctx,
        option.WithHTTPClient(client),
        option.WithScopes("https://www.googleapis.com/auth/gmail.send"),
    )
    if err != nil {
        return fmt.Errorf("unable to retrieve gmail client: %s", err)
    }

    // Create message
    gMessage := ComposeMessage(m)

    if err := doSend(gMessage, srv); err != nil {
        return fmt.Errorf("could not send mail: %s", err)
    }
    fmt.Println("Email sent")

    return nil
}