共计 1888 个字符,预计需要花费 5 分钟才能阅读完成。
- 代码片段
public boolean authBySMTP(String name, String password) throws IOException {String encodedName = BaseEncoding.base64().encode(name.getBytes());
String encodedPassword = BaseEncoding.base64().encode(password.getBytes());
boolean authed = false;
Socket socket = new Socket("smtp.exmail.qq.com", 25);
socket.setSoTimeout(10000);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));) {writer.write("helo" + "smtp.exmail.qq.com" + "rn");
writer.write("auth login" + "rn");
writer.write(encodedName + "rn");
writer.write(encodedPassword + "rn");
writer.write("quit" + "rn");
writer.flush();
String response = null;
String code = null;
while ((response = reader.readLine()) != null) {code = response.substring(0, 3);
if ("235".equals(code)){ // 返回码 235 表示认证成功
authed = true;
}
if ("221".equals(code)){ // 返回码 235 表示认证成功
break;
}
}
} finally {socket.close();
}
return authed;
}
-
问题背景
这是一段几年前的代码(作者已经不在公司了),对应的系统一直使用好几年也没发现有啥问题,2024-04-09 下午突然好多人反馈好几个系统无法登录(使用的是腾讯企业邮箱和对应的邮箱密码),提示账号密码错误。经过我着急忙慌的排查,最终把问题定位到这段通过 smtp 进行企业邮箱认证的逻辑上。 -
问题现象
由于系统没有发现任何日志和报错信息,在本地跑起来系统后(庆幸这个系统没有其他的系统依赖,直接运行很简单),通过 debug 发现,正常登录的情况下可以获取到所有发送指令对应的响应信息。但是,异常的时候,在 while 循环中,只遍历到前 2 条指令对应的响应信息,加上建立连接时的初始响应信息,总计只有 3 条响应信息。 -
原因猜测
由于我工作中几乎接触不到 IO 流相关的内容,所以这个猜测很可能不正确。
我猜测的是代码在发送完所有指令后,在只响应了 3 条信息后,while 循环就已经运行结束了,不过现在想好像也不太可能,因为问题虽然是偶现的,但是每次出现,必定是只响应 3 条然后 reader.readLine() 就读取到 null 从而结束循环了。 -
临时修复
由于是线上问题比较紧急,再结合我之前的猜测,我当时采用了最 low 的处理方式,通过 Threed.sleep(500) 进行等待,然后问题修复上线了。 -
彻底修复
以前不知道 smtp 可以这样通过指令进行邮箱的操作,在了解后,找了机器在终端里通过 telnet 的方式手动进行了代码中的操作,尝试了无数遍没有得到任何的重现,哪怕是直接在出问题的网络环境中。
基于这个终端内的体验,我想到了代码中可能导致前几条指令并没有真正发出,最终 flush 才真正一起发出的可能,所以我把代码优化成严格的串行操作,每发出一条指令就进行 flush 并接收其响应,根据响应再发出下一条指令。经过自测,问题完全修复。
个人认为这样更加标准,因为完全模拟了手动在终端里的操作流程。 -
疑惑
7.1 原作者的代码逻辑是哪里有什么隐患吗?我最终的处理是否标准合理?
7.2 为什么好几年没有更改的情况下,突然出现这个问题?我们机房或者腾讯企业邮箱机房网络严重波动?
7.3 关于 Threed.sleep(500) 临时修复有个奇怪的现象,在 flush 前进行 Threed.sleep(500) 可以修复问题,但是在 flush 后进行 Threed.sleep(500) 却不能修复,这是为什么?
感谢有懂的大佬进行指点