HttpClient中,存在一些汉字字符不能被正确解码的情况,反编译HttpContent,可以观察它是如何实现的?以便于找到原因:
编码解析
- HttpContent 类中,预定义了4种字符编码
private static Encoding[] EncodingsWithBom = new Encoding[4]
{
Encoding.UTF8,
Encoding.UTF32,
Encoding.Unicode,
Encoding.BigEndianUnicode
};
- HttpContent 默认字符编码是 UTF8
private Encoding DefaultStringEncoding = Encoding.UTF8;
- 判断字符编码过程
当我们接受到HttpContent 返回的流数据 bufferedContent 时,利用 MemoryStream的GetBuffer()方法,得到创建此流的无符号字节数组
byte[] buffer = bufferedContent.GetBuffer();//返回创建此流的无符号字节数组
int num2 = (int)bufferedContent.Length;
根据Headers设置判断字符类型,如果Headers.ContentType.CharSet 设置了字符编码,则encoding返回设定的编码
if (Headers.ContentType != null && Headers.ContentType.CharSet != null)
{
try
{
encoding = Encoding.GetEncoding(Headers.ContentType.CharSet);
}
catch (ArgumentException innerException)
{
tcs.TrySetException(new InvalidOperationException(SR.net_http_content_invalid_charset, innerException));
return;
}
}
如果 encoding 为空,遍历EncodingsWithBom,利用 ByteArrayHasPrefix 方法核对编码,看是否是预置编码。如果不是预置编码,则返回默认编码。
if (encoding == null)
{
Encoding[] encodingsWithBom = EncodingsWithBom;
foreach (Encoding encoding2 in encodingsWithBom)
{
byte[] preamble = encoding2.GetPreamble();
if (ByteArrayHasPrefix(buffer, num2, preamble))
{
encoding = encoding2;
num = preamble.Length;
break;
}
}
}
encoding = (encoding ?? DefaultStringEncoding);
ByteArrayHasPrefix
private static bool ByteArrayHasPrefix(byte[] byteArray, int dataLength, byte[] prefix)
{
if (prefix != null && byteArray != null && prefix.Length <= dataLength && prefix.Length != 0)
{
for (int i = 0; i < prefix.Length; i++)
{
if (prefix[i] != byteArray[i])
{
return false;
}
}
return true;
}
return false;
}
解决对策
综上,部分汉字不能正确解码的原因,是因为预置编码集不包含所使用的编码,并且 Headers.ContentType.CharSet没有设定相应的字符编码所导致。
解决办法:
在返回HttpResponseMessage前, 设置charSet。
string charSet="GB18030" ;//
private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req)
{
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseContentRead);
//设置charSet
var contentType = response.Content.Headers.ContentType;
//自动判断字符集(不推荐使用)
// string charSet = await getCharSetAsync(response.Content);
contentType.CharSet = charSet;
return response;
}
//如下是网上发现的一段代码:虽然汉字是乱码,描述字符编码的字符还应该是正确解析的。缺点是对返回数据进行了二次读取。
private async Task<string> getCharSetAsync(HttpContent httpContent)
{
var charset = httpContent.Headers.ContentType.CharSet;
if (!string.IsNullOrEmpty(charset))
return charset;
var content = await httpContent.ReadAsStringAsync();
var match = Regex.Match(content, @"charset=(?<charset>.+?)""", RegexOptions.IgnoreCase);
string charsetValue = null;
if (!match.Success)
{
charsetValue = match.Groups["charset"].Value;
if (charset == "gb2312")
charset = "GB18030" ; //"gbk";
}
//这里主要是解决charset=""这种情况,为null,程序就会默认为utf-8
if (string.IsNullOrEmpty(charset))
charset = "utf-8";
return charsetValue;
}