C# WebClient 增加 Header 後出現亂碼的解決方式(gzip)

在前幾篇有提到因為專案版本限制所以使用 WebClient 來呼叫 API ,後來在測試時出現一個奇怪的問題:
某個 API 需要在 Header 增加自訂字串,傳入校驗資訊。使用 Postman 測試正常,使用 WebClient 呼叫時不加入 Header 伺服器回傳錯誤訊息,但是加上自訂 Header 後回應訊息就變成亂碼,回傳的亂碼訊息範例如下:
    
}?MO?0@?K?`???:???p&&?p?k?*&Zec?S?4?@?????:??b????CC??/╗??>&&_?>?)??T?SRV?e??y8?7?oSR
?!f?J3L?lN&&?e#??T?v'??????^?╗??═??1?N?????MC????]_A???HGd?   -???a?|o??|K?^ K???╔Α╝?,?1\?cY????c<??
    

經過一翻研究後發現是因為加入 Header 後驗證通過,回應的內容比較多,所以目標伺服器自動使用 gzip 壓縮回應內容,而 WebClient 不像 HttpClient 可以自動解壓縮 gzip ,於是就出現亂碼了。

判斷是否有 gzip 壓縮

response 的壓縮格式會記錄在 Header 中,名稱為 Content-Encoding ,所以只要檢查如果有 Content-Encoding 且等於 gzip ,就需要解壓縮。判斷壓縮的程式碼:
    
using (var client = new WebClient())
{
    try
    {
        string url = "https://localhost:7041/api/user/2";
        client.Encoding = Encoding.UTF8; // 不指定的話中文會是亂碼

        // 發送 GET 請求並取得回應
        string response = client.DownloadString(url);

        Console.WriteLine(response);
        
        // 如果有 Content-Encoding 代表經過壓縮
		string clientResponseHeader = client.ResponseHeaders["Content-Encoding"];
		Console.WriteLine("Content-Encoding: " + clientResponseHeader);
    }
    catch (WebException e)
    {
        Console.WriteLine("API 錯誤: " + e.Message);
    }
}
    

gzip 解壓縮

在 WebClient 中取回的內容是 byte[] ,把它解壓縮並轉換成字串的程式碼如下:
    
	public static string FromGZipBytes(byte[] input)
	{
		using (var decompressedStream = new MemoryStream(input))
		using (var gzipStream = new GZipStream(decompressedStream, CompressionMode.Decompress))
		using (var reader = new StreamReader(gzipStream))
		{
			return reader.ReadToEnd();
		}
	}
    

完整程式碼

    
public static void Get()
{
    using (var client = new WebClient())
    {
        try
        {
            const string url = "http://localhost:5000/User/1";
            string body = "body";

            client.Encoding = Encoding.UTF8;
            client.Headers.Add("Content-Type", "application/json");
            client.Headers.Add("MyHeader", "MyHeader");

            var bytesResult = client.UploadData(url, "POST", Encoding.UTF8.GetBytes(body));

            string clientResponseHeader = client.ResponseHeaders["Content-Encoding"];

            string result = "";
            if (clientResponseHeader == "gzip")
            {
                result = FromGZipBytes(bytesResult);
            }
            else
            {
                result = Encoding.UTF8.GetString(bytesResult);
            }

            Console.WriteLine(result);
        }
        catch (Exception e)
        {
            Console.WriteLine("API 錯誤: " + e);
            throw;
        }
    }
}

public static string FromGZipBytes(byte[] input)
{
    using (var decompressedStream = new MemoryStream(input))
    using (var gzipStream = new GZipStream(decompressedStream, CompressionMode.Decompress))
    using (var reader = new StreamReader(gzipStream))
    {
        return reader.ReadToEnd();
    }
}
    



參考資料:
Microsoft.Learn - HttpClientHandler.AutomaticDecompression Property
stack overflow - Automatically decompress gzip response via WebClient.DownloadData

留言