分享

如何解决HttpWebResponse乱码?

 vavava 2010-10-14

Note:本文未对gzip等压缩http内容的处理。

如果我们使用类WebRequest获取多个网站内容,很可能遭遇编码问题:由于汉字有多种编码,不同的Web程序可能提供给客户端的流编码有所不同,这些流如果没有正确解码就会出现乱码。所谓正确解码就是说服务器返回的流是何种编码则解析时就需要用哪一种编码(对于中文来说)。

从浏览器来分析

我们知道大多数时候浏览器都能够正确处理网页的编码,当我们使用浏览器浏览不同编码的中文网站时不会出现“乱码”,但有时候也会出现乱码,这是为什么呢?浏览器如何知道返回来的流是什么编码?为什么有时候浏览器也不能正确处理编码问题?

浏览器很可能是通过两种方式来获得服务器返回流的字符编码。 首先,浏览器会从尝试从Http Header来获得字符串编码,如果得不到(有些服务器返回的Http Header中没有包含CharSet)则尝试从HTML的Meta标签中获得。如果有的网站做得实在太烂,Http Header和Html的Meta中都没有返回编码信息,那就有可能出现乱码。

由于ASP.NET可以通过Response.CharSet和Response.ContentType来控制发送给客户端的字符编码信息,因此浏览器的这些行为是可以验证的。

这篇文章有列出验证实验:CodePage、ContentEncoding、Charset、ContentType、meta charset 有什么区别?

问题的抛出

因此问题就在于如何获得HttpWebResponse返回的流的编码。对于Header里面的CharSet我们直接通过HttpWebResponse的属性CharacterSet即可得到——当然如果服务器没有返回则会得到可能一个不正确的字符编码“ISO-8859-1”。这个时候我们只有从字符串本身包含的meta标签来获得编码信息,如果字符串不包含meta标签或者meta标签里面没有CharSet,那问题就难办了。

如何处理?

原理很简单,上面的问题的抛出已经说明了如何处理。这里直接贴代码:

internal static HttpFormResponse ReadResponse(WebRequest request) 
        { 
            const int MaxTry = 3;

            int tryCount = 0; 
            var httpFormResponse = new HttpFormResponse();

            // Sometimes the external site can throw exception so we might 
            // have to retry few more times 
            while (string.IsNullOrEmpty(httpFormResponse.Response) && (tryCount < MaxTry)) 
            { 
                try 
                { 
                    using (WebResponse response = request.GetResponse()) 
                    { 
                        PopulateHeadersAndCookies(response, httpFormResponse);

                        var httpWebResponse = (HttpWebResponse) response; 
                        var charSet = httpWebResponse.CharacterSet; 
                        var buffer = GetBytes(httpWebResponse); 
                        httpFormResponse.Response = GetStringFromBuffer(buffer, charSet); 
                    } 
                } 
                catch (WebException) 
                { 
                    tryCount += 1; 
                    Thread.Sleep(200); 
                } 
            }

            return httpFormResponse; 
        }

        private static byte[] GetBytes(WebResponse response) 
        { 
            var length = (int)response.ContentLength; 
            byte[] data;

            using (var memoryStream = new MemoryStream()) 
            { 
                var buffer = new byte[0x100];

                using (var rs = response.GetResponseStream()) 
                { 
                    for (var i = rs.Read(buffer, 0, buffer.Length); i > 0; i = rs.Read(buffer, 0, buffer.Length)) 
                    { 
                        memoryStream.Write(buffer, 0, i); 
                    } 
                }

                data = memoryStream.ToArray();    
            }

            return data; 
        }

        private static string GetStringFromBuffer(byte[] buffer, string charSet) 
        { 
            if (string.IsNullOrEmpty(charSet) || string.Compare(charSet, "ISO-8859-1") == 0) 
            { 
                charSet = GetEncodingFromBody(buffer); 
            }

            try 
            { 
                var encoding = Encoding.GetEncoding(charSet); 
                var str = encoding.GetString(buffer); 
                return str; 
            } 
            catch (Exception ex) 
            { 
                Log.Exception(ex); 
                return string.Empty; 
            } 
        }

        private static string GetEncodingFromBody(byte[] buffer) 
        { 
            var regex = new Regex(@"<meta(\s+)http-equiv(\s*)=(\s*""?\s*)content-type(\s*""?\s+)content(\s*)=(\s*)""text/html;(\s+)charset(\s*)=(\s*)(?<charset>[a-zA-Z0-9-]+?)""(\s*)(/?)>", RegexOptions.IgnoreCase | RegexOptions.Compiled); 
            var str = Encoding.ASCII.GetString(buffer); 
            var regMatch = regex.Match(str); 
            if (regMatch.Success) 
            { 
                var charSet = regMatch.Groups["charset"].Value; 
                return charSet; 
            }

            return Encoding.ASCII.BodyName; 
        }

        internal static void PopulateHeadersAndCookies(WebResponse webResponse, HttpFormResponse response) 
        { 
            response.Headers.Add(webResponse.Headers);

            var httpWebResponse = webResponse as HttpWebResponse;

            if (httpWebResponse != null) 
            { 
                foreach (Cookie cookie in httpWebResponse.Cookies) 
                { 
                    response.Cookies.Add(cookie.Name, cookie.Value); 
                } 
            } 
        }

其中关键的代码就是这几行:

var httpWebResponse = (HttpWebResponse) response; 
var charSet = httpWebResponse.CharacterSet; 
var buffer = GetBytes(httpWebResponse); 
httpFormResponse.Response = GetStringFromBuffer(buffer, charSet); 

我觉得最关键的就是var buffer =  GetBytes(httpWebResponse); ,通过这行代码将流拷贝到byte数组中保存起来,当无法从Http Header中获得编码信息是就用ASCII编码从buffer中获得字符串。我们知道HTML的标签都是字母,使用ASCII编码虽然中文或者其他双字节字符会出现乱码,但是HTML标签还是能够解析出来。这样我们就可以检测HTML的meta标签从而获得charset。 
这个问题的处理花费了我不少时间,最初是发现有些网站在Header里面返回了编码信息而有的没有,然后就是因为对流的处理不熟悉,不知道如何把流拷贝到内存里面可以多次使用。系统的学习很重要啊。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多