9

【茶包射手日記】PowerShell Invoke-WebRequest 呼叫 IIS 出錯時顯示亂碼

 1 year ago
source link: https://blog.darkthread.net/blog/ps-invoke-webrequest-error-encoding/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【茶包射手日記】PowerShell Invoke-WebRequest 呼叫 IIS 出錯時顯示亂碼-黑暗執行緒

同事反映 WebAPI 有問題,我提供了一段 PowerShell Invoke-WebRequest -Method POST 程式片段請他對照錯誤,他回報出現 502 錯誤,但訊息是亂碼:(為方便觀察及說明,以下用 HTTP GET 500 錯誤代替)

Fig1_638254588213056643.png

觀察錯誤回傳結果,找到原因:IIS 所在作業系統是中文版 Windows,出錯時吐回的自訂錯誤畫面,採用的還是古老的 charset=big5 編碼。

Fig2_638254588216866445.png

由此推測 PowerShell 在出錯時,試圖解析 HTTP 錯誤網頁的 HTML 將其轉換為純文字,但錯在未能正確識別成 BIG5 編碼,錯用 UTF-8 解析,才噴出亂碼。用一小段 C# 模擬用 UTF-8 硬解 BIG5 編碼的「伺服器錯誤 500 - 內部伺服器錯誤。 您要尋找的資源有問題而無法顯示。」字串,得到完全相同的結果,可得證。

Fig3_638254588218915642.png

猜想應是網站吐回的內容未指定 CharSet,PowerShell 應會採用預設編碼解析,我如果能將預設編碼暫時改為 Big5,問題就有解了。找不到有文件提及如何修改 Invoke-WebRequest 解析 HTML 時的預設語系編碼,決定從原始碼找答案。我查到當 HTTP 狀態碼失敗時的解析邏輯,是透過 WebResponseHelper.GetCharacterSet(response) 取得 CharSet,作為後續 StreamHelper.DecodeStream 時的參數。

if (ShouldCheckHttpStatus && !_isSuccess)
{
    string message = string.Format(
        CultureInfo.CurrentCulture,
        WebCmdletStrings.ResponseStatusCodeFailure,
        (int)response.StatusCode,
        response.ReasonPhrase);

    HttpResponseException httpEx = new(message, response);
    ErrorRecord er = new(httpEx, "WebCmdletWebResponseException", ErrorCategory.InvalidOperation, request);
    string detailMsg = string.Empty;
    try
    {
        // We can't use ReadAsStringAsync because it doesn't have per read timeouts
        TimeSpan perReadTimeout = ConvertTimeoutSecondsToTimeSpan(OperationTimeoutSeconds);
        string characterSet = WebResponseHelper.GetCharacterSet(response);
        var responseStream = StreamHelper.GetResponseStream(response, _cancelToken.Token);
        int initialCapacity = (int)Math.Min(contentLength ?? StreamHelper.DefaultReadBuffer, StreamHelper.DefaultReadBuffer);
        var bufferedStream = new WebResponseContentMemoryStream(responseStream, initialCapacity, this, contentLength, perReadTimeout, _cancelToken.Token);
        string error = StreamHelper.DecodeStream(bufferedStream, characterSet, out Encoding encoding, perReadTimeout, _cancelToken.Token);
        detailMsg = FormatErrorMessage(error, contentType);
    }
    catch (Exception ex)
    {
        // Catch all
        er.ErrorDetails = new ErrorDetails(ex.ToString());
    }

    if (!string.IsNullOrEmpty(detailMsg))
    {
        er.ErrorDetails = new ErrorDetails(detailMsg);
    }

    ThrowTerminatingError(er);
}

追進去,WebResponseHelper.GetCharsetSet()response.Content.Headers.ContentType?.CharSet 取得 CharSet (可能是 null),

檢查 IIS 出錯時回傳自訂錯誤頁面的 Response Header,Content-Type 只有 text/html 無 CharSet,故 PowerShell 會抓到 null。

Fig4_638254588221013763.png

當 CharSet 作為參數傳入 StreamHelper.DecodeStream() 解析 HttpResponseStream,CharSet 缺值或不對時會改用 ContentHelper.GetDefaultEncoding() 為準,該方法寫死 internal static Encoding GetDefaultEncoding() => Encoding.UTF8; 沒得改,宣告此路不通。

題外話,Github 現在認得 C#,能簡單識別類別、方法,列出有參考到它的地方,還蠻好用的。

Fig7_638254595458451505.png

出不轉路轉,如果你有 Cmder、Git Bash 或 Windows 10/11 (見文末更新),可以改用 curl 存成 HTML 再看:

Fig5_638254588223099985.png

如果吞不下這口氣,或測試機器上沒有 curl 等工具,一定要用 PowerShell 讀出結果,可以 catch WebException 再用 StreamReader 指定 BIG5 編碼開啟 ResonseStream,即能正確讀出內容。

try {
    Invoke-WebRequest -Uri http://192.168.50.3
}
catch [System.Net.WebException] {
    $stm = $_.Exception.Response.GetResponseStream()
    $stm.Position = 0
    $big5Text = (New-Object System.IO.StreamReader($stm, [System.Text.Encoding]::GetEncoding(950))).ReadToEnd()
    $html = New-Object -Com 'HTMLFile'
    $html.IHTMLDocument2_write($big5Text)
    $html.body.InnerText.Split([System.Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)
}

Fig6_638254588225026182.png

2023-07-21 更新

2017/11/19 起 Windows 10/11 已經內建 curl 了 (Windows Server 沒有) 參考,因為在 PowerShell curl 被導向 Invoke-WebRequest,我一直沒發現。感謝讀者 Kuo Chin-Yuan 補充。

Fig8_638255425615962639.png


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK