英文:
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
}
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
}
使用旧的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
英文:
I have an issue with the new NewService functionality of gmail api. If I use the deprecated gmail.New() everything works.
With NewService() I get invalid memory address or nil pointer dereference
My implementation is the following
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
}
Using the old gmail.New() works, but it points out that the function is deprecated, so I need to change it to the new gmail.NewService. Though implementing it like below it doesn't work
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
}
Edit: the error I get is
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
答案1
得分: 0
问题并不明显,但很容易解决。
在初始化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实现的上下文中,你需要将所有在以下链接中使用的函数放入你的go文件中:
https://developers.google.com/gmail/api/quickstart/go 或者
https://github.com/googleworkspace/go-samples/blob/master/gmail/quickstart/quickstart.go
这些函数将生成一个AccessToken和Credentials,如果你按照指南正确操作,你需要将其保存为一个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("无法读取凭据文件:", err))
}
config, err := google.ConfigFromJSON(b, gmail.GmailSendScope)
if err != nil {
return errors.New(fmt.Sprint("无法解析凭据文件配置:", 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("无法获取gmail客户端:%s", err)
}
// 创建消息
gMessage := ComposeMessage(m)
if err := doSend(gMessage, srv); err != nil {
return fmt.Errorf("无法发送邮件:%s", err)
}
fmt.Println("邮件已发送")
return nil
}
英文:
The issue was not that obvious but easy to solve.
While initializing the gmail.NewService() I needed to pass the config parameter as option, just like the previous implementation of gmail.New() was using
So before was
client := getClient(config)
srv, err := gmail.New(client)
And now it is
client := getClient(config)
ctx := context.Background()
srv, err := gmail.NewService(
ctx,
option.WithHTTPClient(client),
)
In more context of the Gmail API implementation for Go, you need to take all the functions used at
https://developers.google.com/gmail/api/quickstart/go or
https://github.com/googleworkspace/go-samples/blob/master/gmail/quickstart/quickstart.go
and put them in your go file. These will generate both an AccessToken and the Credentials which, if you follow the guide correctly, you will need to save as a json file and reuse. credentials.json are used to update your AccessToken.
Now instead of using gmail.New(), the correct way is to use the function as shown above.
The entire sendGmail function is as follows
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论