【茶包射手日記】LineBotSDK NullReferenceException 錯誤調查
| | 1 | | ![]() |
讀者 Edward 問了一個有趣問題,花了點時間追查,發現是 TLS 1.2 問題的變形茶包,累積了一些經驗值,簡單整理分享。
錯誤出現在使用 LineBotSDK 傳送 LINE 訊息的超簡單範例,甚至可簡化成一行就好:(參數都是亂給的,不可能傳送成功,正常情況應得到 HTTP 401,在特定環境則可重現錯誤)
isRock.LineBot.Utility.PushMessage("NoSuchUserId", "Never Sent", "InvalidToken");
我先試著在 .NET 6 Console 跑這行程式,可正確得到 HTTP 401 Authentication failed. Confirm that the access token in the authorization header is valid.
後來在 Visual Studio 2022 改用 .NET Framework Console 跑,我的電腦只裝了 .NET 4.7.2 跟 4.8,這兩個版本也都不會出錯。依 Edward 提供的情報,他的出錯環境是 .NET 4.5,莫非跟 .NET Framework 版本有關?
為了方便測試,我把程式搬進 Inline ASPX:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Net" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Response.ContentType = "text/plain";
try
{
isRock.LineBot.Utility.PushMessage("NoSuchUserId", "Never Sent", "InvalidToken");
Response.Write("WTF! You should never see this.");
}
catch (Exception ex)
{
Response.Write(ex);
}
}
</script>
ASP.NET Runtime 指定 4.5,我成功重現了 NullReferenceException 錯誤:
懷疑跟呼叫 API 的部分有關,我另寫了一段程式成功重現錯誤,也找到真正原因:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Net" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Response.ContentType = "text/plain";
string webErrMsg = string.Empty;
try
{
try
{
var wc = new WebClient();
wc.DownloadString("https://api.line.me/v2/bot/message/push");
Response.Write("WTF! You should never see this.");
}
catch (System.Net.WebException wex)
{
webErrMsg = wex.Message;
using (var sr = new StreamReader(wex.Response.GetResponseStream()))
{
Response.Write(sr.ReadToEnd());
}
}
}
catch (Exception ex)
{
Response.Write(webErrMsg + "\r\n");
Response.Write(ex);
}
}
</script>
要求已經中止: 無法建立 SSL/TLS 的安全通道。
是標準的 TLS 1.2 相容問題,LINE API 依循安全規範限定 TLS 1.2,而 .NET Framework 4.6+ 才預設使用 TLS 1.2,如果是 .NET 4.5 若沒加上 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
參考 或修改 Registry 參考,將無法建立 TLS 連線。而在 catch WebException 部分,程式未考量到完全無法連線時 WebException.Response 為 null 的狀況,未經檢查呼叫 Response.GetResponseStream() 是 NullReferenceException 的來源。(已提報 Issue 給 David 老師)
修改程式,指定使用 TLS 1.2,問題排除:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Net" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Response.ContentType = "text/plain";
try
{
// 設定使用 TLS 1.2
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
isRock.LineBot.Utility.PushMessage("NoSuchUserId", "Never Sent", "InvalidToken");
Response.Write("WTF! You should never see this.");
}
catch (Exception ex)
{
Response.Write(ex);
}
}
</script>
試著縮減出最小可重現問題的程式片段,比對不同環境的結果,抽取可疑行為聚焦觀察,最終找出原因破案,這回也算一次標準的射茶包範例吧! 分享給大家參考。
Debugging a NullReferenceException issue in the LineBotSDK code to discover the root cause of a TLS 1.2 support issue.
Comments
# by Edward
感謝,黑大