應用程式離線檔案(app_offline.htm) 是部署程式或暫時停用 ASP.NET / ASP.NET Core 常用的做法。IIS 在應用程式根目錄偵測到名為 app_offline.htm 的檔案(注意是 .htm 不是 .html),ASP.NET / ASP.NET Core 模組便會嘗試正常關閉應用程式並停止處理傳入的要求,逾時未正常停止(超過 shutdownTimeLimit 秒),則會強制停止執行中的處理序。(延伸閱讀:ASP.NET Core 停機行為觀察)

在停機期間,存取該網站會一律改顯示 app_offline.htm 的內容,故可當成停機公告或提供相關資訊的管道。app_offline.htm 本身是個標準網頁,但設計上存在一個限制 - 停機期間,ASP.NET Core (或 ASP.NET 設定 <modules runAllManagedModulesForAllRequests="true" />) 網站的靜態檔案 (.js/.css/.jpg/.png/.webp) 將無法使用(也會被導向 app_offline.htm),有幾種解決方式:

  1. 將 .js 與 .css 改為 <script> 及 <style> 內嵌,至於圖檔也可用 DataUri 形式內嵌
  2. 將靜態資源檔改放在其他未停用的網站,以絕對網址載入
  3. 將整個停機公告網頁及相關資源都放在其他網站,app_offline.htm 加入邏輯導向該網址

我個人偏好島上的事留在島上高內聚低耦合的設計架構,網站自給自足,避免與其他網站產生相依性。故我選擇 JavaScript/CSS/圖檔全部內嵌式的做法。

實務上可能遇到一個小困擾 - 若圖檔偏大,DataUri 格式 data:image/png;base64,UklGRj7HAQBXRUJQVlA... 的 Base64 編碼可能長達數百 KB,有些文字編譯工具處理這類一行字數超長的內容易出現卡頓,可讀性也較差,這裡分享我愛用的一個小技巧。

先用 Windows UNIX 工具包 - Coreutils for Windows 的 base64 工具將圖檔轉成指定寬度換行的 Base64 編碼寫入檔案:base64 -w 128 .\app_offline.webp > app_offine.b64 (指定寬度 128 字元換行,未指定時預設值為 76):(註:順便測了 WebP 的節省效果,本案例的 .png 圖檔 165KB,改存無損 .webp 為 114KB,可節省約 31% 空間及頻寬)

開啟 .b64 檔案複製內容,在網頁新增 <template id="image-content"> 節點,在其中放入圖檔 Base64 編碼內容。最後寫一小段 JavaScript 抓取 Base64 組裝成 DataUri 新增 <img> 元素,網頁範例如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>網站維護中</title>
        <style>
            body {
                background-color: #fcfbf9;
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100vh;
            }
        </style>
        <script>
            document.addEventListener('DOMContentLoaded', function() {
                const imgContent = document.getElementById('image-content').innerHTML.replace(/\s/g, '');
                const img = document.createElement('img');
                img.src = `data:image/webp;base64,${imgContent}`;
                document.body.appendChild(img);
            });
        </script>        
    </head>
    <body>
        <template id="image-content">
UklGRj7HAQBXRUJQVlA4TDLHAQAvz8J3AI1IchtJkiRIHt2zqP7/4YyM7PUe0f8J4Pcc8CsMLLDq84Jq8XxWCJ51Ae/yRorFw8W1dsLsamA1Dz6+Ausj5iO2H2TVQZcA
... 略 ...
Xr+3PSV5DnYAXfCdp6U4aDG6h4OOngpGY2bN1GIppJKir5YUt1PLghQ3MnfNrGRYaZ0AlUMYgQoQqcxrIx7cd8koBNyKfw7mElAWsHQzGgGggfJdMjtQfF/0/xf9/0X/
f9H/3112
        </template>
    </body>
</html>

Shows how to create a self-contained ASP.NET app_offline.htm maintenance page by embedding JavaScript, CSS, and images as Base64 Data URIs, avoiding unavailable static files during shutdown.


Comments

# by wwwcat

進入維護時,CSP是否還是有作用? 如果有這可能跟CSP衝突

# by cs8425

to wwwcat: 看你CSP是從哪邊給的, 如果是asp.net app裡面設定response header就會消失 如果是從IIS/web.config就會維持不變

# by cs8425

app_offline.htm老實說蠻不好用的 要想辦法把資源全打包進單html, 黑暗大這篇就是例子 停機時間也無法縮短 內建的shadow copy又有很多問題(檔案io太慢/太大造成racing, 撞到後只能整個停掉+刪除+啟動) 我個人是用另一種解案: 自己弄一個更完善的A/B slot切換 ANCM切成Out-of-Process mode 啟動一隻小程式, 帶起A slot的asp.net app 監控特定檔案(作為flag) 偵測到就把A slot的shutdown, 啟動B slot的app 實作上有兩個細節: 1. 環境變數"ASPNETCORE_PORT" "ASPNETCORE_APPL_PATH" "ASPNETCORE_TOKEN" 這堆"ASPNETCORE"開頭的要pass給asp.net app 2. 用Directory Junction可以解決佈署時不知道要複製到哪個slot的問題 新版未啟動的固定放在app-new/ 啟動的固定放在app/ 由那隻小程式切換指向到slotA/還是slotB/ 佈署一律放到app-new/底下 整個複製完後再觸發切換就行 Directory Junction可以用mklink /J建立, 我是直接call kernel32.dll沒用到shell -------- 雖然是很想用reverse proxy切換就好, 但環境就是卡著IIS只好搞這些有的沒的幫IIS擦屁股...

# by SHIH HUNG YANG

使用 NLB 做 HA 分流進行更新作業,應該是更穩定的選項吧?

Post a comment