Go语言实战 - 使用SendCloud群发邮件
山坡网需要能够每周给注册用户发送一封名为“本周最热书籍”的邮件,而之前一直使用的腾讯企业邮箱罢工了,提示说发送请求太多太密集。
一番寻找之后发现了大家口碑不错的搜狐SendCloud服务,看了看文档,价格实惠用起来也方便,于是准备使用它做邮件发送服务器。按照文档的配置一步步走下来发现在发送邮件的时候竟然出错了,错误提示是“unencrypted connection”,奇怪了。
由于用的是smtp包的PLAIN认证方式,所以打开源代码看了看(SublimeText3+GoSublime里ctrl+. ctrl+a输入包名和结构名直接查看源代码,谁用谁喜欢),发现这里要求使用加密连接,否则就会出上述错误。恩,也能理解,毕竟这里明文发送密码了。关键代码如下。
auth := smtp.PlainAuth("", Config.Username, Config.Password, Config.Host)
smtp.SendMail(addr, auth, from, to, []byte(self.String()))
问题明白之后思路也出来了,自己写一个不需要加密链接的PLAIN认证就好了。这里提一下smtp包的设计,看下面这段代码。
// Auth is implemented by an SMTP authentication mechanism.
type Auth interface {
// Start begins an authentication with a server.
// It returns the name of the authentication protocol
// and optionally data to include in the initial AUTH message
// sent to the server. It can return proto == "" to indicate
// that the authentication should be skipped.
// If it returns a non-nil error, the SMTP client aborts
// the authentication attempt and closes the connection.
Start(server *ServerInfo) (proto string, toServer []byte, err error)// Next continues the authentication. The server has just sent
// the fromServer data. If more is true, the server expects a
// response, which Next should return as toServer; otherwise
// Next should return toServer == nil.
// If Next returns a non-nil error, the SMTP client aborts
// the authentication attempt and closes the connection.
Next(fromServer []byte, more bool) (toServer []byte, err error)
}
// SendMail connects to the server at addr, switches to TLS if
// possible, authenticates with the optional mechanism a if possible,
// and then sends an email from address from, to addresses to, with
// message msg.
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error
smtp.SendMail的第二个参数是一个Auth接口,用来实现多种认证方式。标准库中实现了两种认证方式,PLAIN和CRAMMD5Auth,关于这部分知识大家可以自行参考smtp协议中认证部分的定义。这里就不赘述了。
搞清楚了原理就动手吧。直接把标准库中PLAIN的实现拿过来,删除其中需要加密函数的部分,如下红字部分。
type plainAuth struct {
identity, username, password string
host string
}func UnEncryptedPlainAuth(identity, username, password, host string) Auth {
return &plainAuth{identity, username, password, host}
}func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
if !server.TLS {if server.Name != a.host {
advertised := false
for _, mechanism := range server.Auth {
if mechanism == "PLAIN" {
advertised = true
break
}
}
if !advertised {
return "", nil, errors.New("unencrypted connection")
}
}
return "", nil, errors.New("wrong host name")
}
resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
return "PLAIN", resp, nil
}func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
return nil, nil
}
把发送邮件的代码改成下面这样,再试试看。
auth := UnEncryptedPlainAuth("", Config.Username, Config.Password, Config.Host)
smtp.SendMail(addr, auth, from, to, []byte(self.String()))
恩,还是出错,这次的错误变成“unrecognized command”,看来是SendCloud的服务器并不支持这种验证方式。于是我打开它的文档,发现smtp使用介绍的页面有几种语言的范例代码,看了看Python的代码后发现SendCloud应该用的是Login认证。好吧,之前是犯了经验主义错误了。
再次打开smtp协议的定义,翻到WikiPedia上smtp的(这里标红是因为wiki上的文档也是会过期的)LOGIN认证的文档,上面说,采用LOGIN认证服务器和客户端应该会产生如下对话,下面S代表服务器,C代表客户端。
C:auth login ------------------------------------------------- 进行用户身份认证
S:334 VXNlcm5hbWU6 ----------------------------------- BASE64编码“Username:”
C:Y29zdGFAYW1heGl0Lm5ldA== ----------------------------------- 用户名,使用BASE64编码
S:334 UGFzc3dvcmQ6 -------------------------------------BASE64编码"Password:"
C:MTk4MjIxNA== ----------------------------------------------- 密码,使用BASE64编码
S:235 auth successfully -------------------------------------- 身份认证成功
看起来挺简单,照着写了一个LoginAuth。
type loginAuth struct {
username, password string
}func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
}
}
return nil, nil
}
把发送邮件的代码改成下面这样。
auth := LoginAuth(Config.Username, Config.Password)
smtp.SendMail(addr, auth, from, to, []byte(self.String()))
运行,还报错,这次错误信息是 Authentication Failed,认证失败。这说明Login认证的方式是对的,但登录失败了。再三确定账号和密码的正确之后我决定用WireShark抓包看看过程。
注意看,AUTH LOGIN之后来了两条334 Password,咦?这里不应该是先来Username接着来Password的吗?为什么是来了两次Password。难道是LOGIN协议改了?
为了确认登陆过程,我用SendCloud文档中Python的代码跑了一遍,终于发现了不同。原来,在发送AUTH LOGIN之后需要带上Username。修改LoginAuth的Start函数。
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
return "LOGIN", []byte(a.username), nil
}
好了!邮件发送成功!大约花了30分钟,我从一个完全不懂SMTP协议的人完成了LOGIN协议的补充。感叹一下Go的简单,标准库没有黑盒子一样的厚重感,薄的一捅就透,一看就懂。
Go语言实战 - 使用SendCloud群发邮件的更多相关文章
- R入门<三>-R语言实战第4章基本数据管理摘要
入门书籍:R语言实战 进度:1-4章 摘要: 1)实用的包 forecast:用于做时间序列预测的,有auto.arima函数 RODBC:可以用来读取excel文件.但据说R对csv格式适应更加良好 ...
- R语言实战(三)基本图形与基本统计分析
本文对应<R语言实战>第6章:基本图形:第7章:基本统计分析 =============================================================== ...
- R语言实战(二)数据管理
本文对应<R语言实战>第4章:基本数据管理:第5章:高级数据管理 创建新变量 #建议采用transform()函数 mydata <- transform(mydata, sumx ...
- R语言实战(一)介绍、数据集与图形初阶
本文对应<R语言实战>前3章,因为里面大部分内容已经比较熟悉,所以在这里只是起一个索引的作用. 第1章 R语言介绍 获取帮助函数 help(), ? 查看函数帮助 exampl ...
- Swift语言实战晋级
Swift语言实战晋级基本信息作者: 老镇 丛书名: 爱上Swift出版社:人民邮电出版社ISBN:9787115378804上架时间:2014-12-26出版日期:2015 年1月开本:16开页码: ...
- swift语言实战晋级-第9章 游戏实战-跑酷熊猫-9-10 移除平台与视差滚动
9.9 移除场景之外的平台 用为平台是源源不断的产生的,如果不注意销毁,平台就将越积越多,虽然在游戏场景中看不到.几十个还看不出问题,那几万个呢?几百万个呢? 所以我们来看看怎么移除平台,那什么样的平 ...
- swift语言实战晋级-第9章 游戏实战-跑酷熊猫-7-8 移动平台的算法
在上个小节,我们完成了平台的产生.那么我们来实现一下让平台移动.平台的移动,我们只需要在平台工厂类中写好移动的方法,然后在GameScene类中统一控制就行了. 在GameScene类中,有个upda ...
- Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-5-6 踩踏平台是怎么炼成的
在游戏中,有很多分来飞去的平台,这个平台长短不一.如果每种长度都去创建一张图片那是比较繁琐的事情.实际上,我们只用到3张图.分别是平台的,平台的中间部分,平台的右边.关键是平台的中间部分,两张中间部分 ...
- Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-4 熊猫的跳和打滚
之前我们学会了跑的动作,现在我们可以利用同样的方法来实现了跳和打滚的动画. …… class Panda : SKSpriteNode { …… //跳的纹理集合 let jumpAtlas = SK ...
随机推荐
- dotNET跨平台相关文档整理
一直在从事C#开发的相关技术工作,从C# 1.0一路用到现在的C# 6.0, 通常情况下被局限于Windows平台,Mono项目把我们C#程序带到了Windows之外的平台,在工作之余花了很多时间在M ...
- Linux下服务器端开发流程及相关工具介绍(C++)
去年刚毕业来公司后,做为新人,发现很多东西都没有文档,各种工具和地址都是口口相传的,而且很多时候都是不知道有哪些工具可以使用,所以当时就想把自己接触到的这些东西记录下来,为后来者提供参考,相当于一个路 ...
- Socket聊天程序——初始设计
写在前面: 可能是临近期末了,各种课程设计接踵而来,最近在csdn上看到2个一样问答(问题A,问题B),那就是编写一个基于socket的聊天程序,正好最近刚用socket做了一些事,出于兴趣,自己抽了 ...
- Docker笔记一:基于Docker容器构建并运行 nginx + php + mysql ( mariadb ) 服务环境
首先为什么要自己编写Dockerfile来构建 nginx.php.mariadb这三个镜像呢?一是希望更深入了解Dockerfile的使用,也就能初步了解docker镜像是如何被构建的:二是希望将来 ...
- 【解决方案】Myeclipse 10 安装 GIT 插件 集成 步骤 图解
工程开发中,往往要使用到集成GIT ,那么下面说说插件安装步骤 PS:以Myeclipse 10 为例,讲解集成安装步骤. ----------------------main------------ ...
- javascript:逆波兰式表示法计算表达式结果
逆波兰式表示法,是由栈做基础的表达式,举个例子: 5 1 2 + 4 * + 3 - 等价于 5 + ((1 + 2) * 4) - 3 原理:依次将5 1 2 压入栈中, 这时遇到了运算符 + ...
- Asp.Net 操作XML文件的增删改查 利用GridView
不废话,直接上如何利用Asp.NET操作XML文件,并对其属性进行修改,刚开始的时候,是打算使用JS来控制生成XML文件的,但是最后却是无法创建文件,读取文件则没有使用了 index.aspx 文件 ...
- python10作业思路及源码:类Fabric主机管理程序开发(仅供参考)
类Fabric主机管理程序开发 一,作业要求 1, 运行程序列出主机组或者主机列表(已完成) 2,选择指定主机或主机组(已完成) 3,选择主机或主机组传送文件(上传/下载)(已完成) 4,充分使用多线 ...
- 深入理解javascript函数定义与函数作用域
最近在学习javascript的函数,函数是javascript的一等对象,想要学好javascript,就必须深刻理解函数.本人把思路整理成文章,一是为了加深自己函数的理解,二是给读者提供学习的途径 ...
- iOS从零开始学习直播之3.美颜
任何一款直播软件都必须进行美颜,不然哪来的那么多美女,所以技术改变世界,不只是说说而已.美颜在采集的时候就得就行,让主播实时看到直播的效果. 1.美颜原理 其实美颜的本质就是美白和磨皮,分别通 ...