福爆 | 部落格升級 .NET Core 3.0 又踩一坑

  • 2019 年 10 月 10 日
  • 筆記

導語

昨天剛發了一篇《與時俱進 | 部落格現已運行在 .NET Core 3.0 及 Azure 上》得瑟,本以為踩完了坑,結果晚上部落格又爆了。Azure Application Insights 監控里發現了大量異常。我們來看看我如何收取福爆。

生產大爆炸

發生問題的是已經被刪除的部落格文章,正常情況下,這些不存在的文章會直接顯示自定義的404頁面,但實際上產生了500異常。日誌如下:

2019-09-26 00:11:50.8405|RD00155DB89A5B|WARN|Moonglade.Web.Controllers.PostController|Post not found, parameter '2014/7/23/my-surface-pro-3-review-system-software'.,GET https://edi.wang/post/2014/7/23/my-surface-pro-3-review-system-software,Slug,66.249.71.135

2019-09-26 00:11:51.1174|RD00155DB89A5B|WARN|Moonglade.Web.Controllers.PostController|Post not found, parameter '2014/7/23/my-surface-pro-3-review-system-software'.,GET https://edi.wang/error,Slug,66.249.71.135

2019-09-26 00:11:51.1174|RD00155DB89A5B|ERROR|Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware|An unhandled exception has occurred while executing the request.,System.ArgumentException: An item with the same key has already been added. Key: x-pingback

at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)

at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)

at Microsoft.AspNetCore.HttpSys.Internal.HeaderCollection.Add(String key, StringValues value)

看上去像是個Pingback HTTP頭被重複添加的問題。但實際上這個頭被添加產生異常的本質原因是請求部落格文章的Slug這個Action被執行了兩次

重現故障

這個問題在開發時並沒有發現,staging環境可以重現,但由於偷懶,沒測過exception path,happy path過了就發布了。之所以開發環境 works on my machine 是因為這樣一個設定,大部分 ASP.NET Core 程式都會這麼做,畢竟是默認模板里的實踐:

if (env.IsDevelopment())

{

ListAllRegisteredServices(app);

app.UseDeveloperExceptionPage();

}

else

{

app.UseExceptionHandler("/error");

app.UseStatusCodePagesWithReExecute("/error", "?statusCode={0}");

}

出問題的是 UseStatusCodePagesWithReExecute() 這個中間件。

最終在 GitHub 上找到了一個已知問題:

https://github.com/aspnet/AspNetCore/issues/13715

我用 VS2019 16.3.1 + .NET Core 3.0 正式版建了個測試工程,重現了這個問題。

public IActionResult Index(int id = 0)

{

if (id == 1)

{

return NotFound();

}

return View();

}

[Route("/error")]

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]

public IActionResult Error(int? statusCode = null)

{

return Content($"Test Error Action: {statusCode}");

}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

{

//app.UseStatusCodePages();

//app.UseExceptionHandler("/error");

app.UseStatusCodePagesWithReExecute("/error", "?statusCode={0}");

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>

{

endpoints.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");

});

}

訪問 /Home/Index?id=1

id=1的請求成功執行到 NotFound(); 正常情況應該立即執行 /error?statusCode=404,當實際上 Error 這個 Action 根本沒有跑進去,而是馬上再次執行了 Index,id=0

而因為執行的邏輯是ReExecute,也就是把action的執行結果放到「父」action里輸出,所以會觸發兩次pingback頭的添加,導致我部落格大爆炸。

複製粘貼 能跑就行

微軟並不打算在 3.0 的修補程式更新中修復這個問題,而是直接放到了 3.1。好在微軟提供了 workaround,所以我們只能先忍幾個月。

在 UseRouting() 和 UseStatusCodePagesWithReExecute() 之間加入一段神奇的程式碼,即可結束福爆。

app.UseStatusCodePagesWithReExecute("/error", "?statusCode={0}");

// Workaround .NET Core 3.0 known bug

// https://github.com/aspnet/AspNetCore/issues/13715

app.Use((context, next) => {

context.SetEndpoint(null);

return next();

});

實在不行 刪庫跑路 也挺省心

目前 .NET Core 3.0 升級問題多多,資料少少,一不小心就容易領取福報。如果追求刺激和擁抱開源的樂趣,可以像我或者部落格園一樣直接踩坑。如果追求穩定,不想被公司開除,建議等 3.1 再更新吧~ 畢竟微軟擁抱開源以後的產品,.1 才是能用的(早上更新的 VS2019 16.3.1笑而不語)。