HttpWebResponse的编码问题

29

这是代码片段:

HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(request.RawUrl);
WebRequest.DefaultWebProxy = null;//Ensure that we will not loop by going again in the proxy
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
string charSet = response.CharacterSet;
Encoding encoding;
if (String.IsNullOrEmpty(charSet))
encoding = Encoding.Default;
else
encoding = Encoding.GetEncoding(charSet);

StreamReader resStream = new StreamReader(response.GetResponseStream(), encoding);
return resStream.ReadToEnd();
问题在于如果我使用以下进行测试:http://www.google.fr,所有的“é”都无法正常显示。我尝试将ASCII更改为UTF8,但仍然显示错误。我在浏览器中测试了HTML文件,并且浏览器可以很好地显示HTML文本,因此我非常确定问题出在我用来下载HTML文件的方法上。我应该改变什么?

“é”即使在ASCII中仍然有效。您是将输出到文件并确定其无法工作,还是在返回的sb.ToString()上进行断点并在Quick Watch中查看它以确定失败了? - cfeduke
8
不,ASCII 中不可能使用重音符号,因为 ASCII 只包含 Unicode 编码 127 及以下的字符。 - Jon Skeet
3
(以防有人想反驳并谈论“扩展ASCII”——请参见http://msdn.microsoft.com/en-us/library/system.text.encoding.ascii.aspx) - Jon Skeet
1
这里的 zabulus 的回答怎么样?看起来简单得多:http://stackoverflow.com/questions/7634113/is-it-possible-to-get-data-from-web-response-in-a-right-encoding - Ignacio Soler Garcia
这基本上就是四年前Jon所回答的 :) - Patrick Desjardins
7个回答

29

如果未在服务器的内容类型头中指定字符集(与HTML中的“charset”元标记不同),则字符集默认为“ISO-8859-1”。我将HttpWebResponse.CharacterSet属性与HTML中的charset属性进行比较。如果它们不同,则使用HTML中指定的字符集再次重新读取页面,但这次使用正确的编码。

请参见代码:

    string strWebPage = "";
    // create request
    System.Net.WebRequest objRequest = System.Net.HttpWebRequest.Create(sURL);
    // get response
    System.Net.HttpWebResponse objResponse;
    objResponse = (System.Net.HttpWebResponse)objRequest.GetResponse();
    // get correct charset and encoding from the server's header
    string Charset = objResponse.CharacterSet;
    Encoding encoding = Encoding.GetEncoding(Charset);
    // read response
    using (StreamReader sr = 
           new StreamReader(objResponse.GetResponseStream(), encoding))
    {
        strWebPage = sr.ReadToEnd();
        // Close and clean up the StreamReader
        sr.Close();
    }

    // Check real charset meta-tag in HTML
    int CharsetStart = strWebPage.IndexOf("charset=");
    if (CharsetStart > 0)
    {
        CharsetStart += 8;
        int CharsetEnd = strWebPage.IndexOfAny(new[] { ' ', '\"', ';' }, CharsetStart);
        string RealCharset = 
               strWebPage.Substring(CharsetStart, CharsetEnd - CharsetStart);

        // real charset meta-tag in HTML differs from supplied server header???
        if(RealCharset!=Charset)
        {
            // get correct encoding
            Encoding CorrectEncoding = Encoding.GetEncoding(RealCharset);

            // read the web page again, but with correct encoding this time
            //   create request
            System.Net.WebRequest objRequest2 = System.Net.HttpWebRequest.Create(sURL);
            //   get response
            System.Net.HttpWebResponse objResponse2;
            objResponse2 = (System.Net.HttpWebResponse)objRequest2.GetResponse();
            //   read response
            using (StreamReader sr = 
              new StreamReader(objResponse2.GetResponseStream(), CorrectEncoding))
            {
                strWebPage = sr.ReadToEnd();
                // Close and clean up the StreamReader
                sr.Close();
            }
        }
    }

2
我认为这应该被标记为答案。这实际上可以从任何网页获取编码并正确解码。但问题是,由于其响应实现不支持Response.CharacterSet,因此在Windows手机上无法正常工作。 - Adarsha
太好了!这正是我想要的。我已经有一个循环来重试意外错误,所以我只需要将charset和realcharset转换为本地变量,以避免请求的额外声明。 - ThunderGr
嗯,现在已经是2020年了,这不再准确。事实上,它变得非常复杂。想要了解有关此问题的完整概述,请查看此答案。简而言之:RFC 7231现在说,除非您是XML内容,否则没有定义编码方式,如果是XML内容,则为us-ascii。但当然,这还不止于此。 - Lynn Crumbling

27

首先,更简单的编写该代码的方式是使用StreamReader和ReadToEnd:

HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(myURL);
using (HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse())
{
    using (Stream resStream = response.GetResponseStream())
    {
        StreamReader reader = new StreamReader(resStream, Encoding.???);
        return reader.ReadToEnd();
    }
}

那么,关键就是找到正确的编码方式。你是如何创建这个文件的?如果是用记事本创建的话,那么你可能需要使用Encoding.Default - 但这显然不是可移植的,因为它是你的PC的默认编码。

在一个良好运行的Web服务器中,响应会在其头部指示编码方式。话虽如此,有时响应头声称一件事情,而HTML却声称另一件事情,在某些情况下。


实际上,我正在尝试获取来自世界各地的文件,但我得到了一些不良输出(PNG文件格式不正确),文本也写得很糟糕(所有字符都像“é”)。 - Patrick Desjardins
1
如果您正在尝试阅读任意HTML,则需要检查标题和有时HTML的开头(可以像XML一样广告编码)。有时,您还必须检测它可能不正确,并通过启发式猜测! - Jon Skeet
好的,我会看一下头信息。我已经试过了你的代码,并且StreamReader(resStream,true)不起作用(应该使用字节查找编码...)。我将尝试从头信息中获取它。稍后我会发布。 - Patrick Desjardins
只是好奇你是否注意到多年来“在服务器未提供字符集时确定使用哪个字符集”的复杂程度已经变得多么高了(请参见我的评论对Alex的回答)。 - Lynn Crumbling
@LynnCrumbling:没有,真的没有。 - Jon Skeet

17

如果您不想下载页面两次,我稍微修改了Alex的代码,使用如何将WebResponse放入MemoryStream中?。以下是结果。

public static string DownloadString(string address)
{
    string strWebPage = "";
    // create request
    System.Net.WebRequest objRequest = System.Net.HttpWebRequest.Create(address);
    // get response
    System.Net.HttpWebResponse objResponse;
    objResponse = (System.Net.HttpWebResponse)objRequest.GetResponse();
    // get correct charset and encoding from the server's header
    string Charset = objResponse.CharacterSet;
    Encoding encoding = Encoding.GetEncoding(Charset);

    // read response into memory stream
    MemoryStream memoryStream;
    using (Stream responseStream = objResponse.GetResponseStream())
    {
        memoryStream = new MemoryStream();

        byte[] buffer = new byte[1024];
        int byteCount;
        do
        {
            byteCount = responseStream.Read(buffer, 0, buffer.Length);
            memoryStream.Write(buffer, 0, byteCount);
        } while (byteCount > 0);
    }

    // set stream position to beginning
    memoryStream.Seek(0, SeekOrigin.Begin);

    StreamReader sr = new StreamReader(memoryStream, encoding);
    strWebPage = sr.ReadToEnd();

    // Check real charset meta-tag in HTML
    int CharsetStart = strWebPage.IndexOf("charset=");
    if (CharsetStart > 0)
    {
        CharsetStart += 8;
        int CharsetEnd = strWebPage.IndexOfAny(new[] { ' ', '\"', ';' }, CharsetStart);
        string RealCharset =
               strWebPage.Substring(CharsetStart, CharsetEnd - CharsetStart);

        // real charset meta-tag in HTML differs from supplied server header???
        if (RealCharset != Charset)
        {
            // get correct encoding
            Encoding CorrectEncoding = Encoding.GetEncoding(RealCharset);

            // reset stream position to beginning
            memoryStream.Seek(0, SeekOrigin.Begin);

            // reread response stream with the correct encoding
            StreamReader sr2 = new StreamReader(memoryStream, CorrectEncoding);

            strWebPage = sr2.ReadToEnd();
            // Close and clean up the StreamReader
            sr2.Close();
        }
    }

    // dispose the first stream reader object
    sr.Close();

    return strWebPage;
}

3
.NET 4及以后版本应该有一个Stream.CopyTo(Stream)方法来简化此过程。 - Manny
为什么您必须将缓冲区大小设置为1024?难道不能一次性读取整个流吗? 还有为什么是1024?为什么不设置得更大呢? - Hoy Cheung

3

这里有一些好的解决方案,但它们似乎都在尝试从内容类型字符串中解析字符集。下面是一个使用System.Net.Mime.ContentType的解决方案,它应该更可靠,也更短。

 var client = new System.Net.WebClient();
 var data = client.DownloadData(url);
 var encoding = System.Text.Encoding.Default;
 var contentType = new System.Net.Mime.ContentType(client.ResponseHeaders[HttpResponseHeader.ContentType]);
 if (!String.IsNullOrEmpty(contentType.CharSet))
 {
      encoding = System.Text.Encoding.GetEncoding(contentType.CharSet);
 }
 string result = encoding.GetString(data);

1
这是一段只下载一次的代码。
String FinalResult = "";
HttpWebRequest Request = (HttpWebRequest)System.Net.WebRequest.Create( URL );
HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();
Stream ResponseStream = Response.GetResponseStream();
StreamReader Reader = new StreamReader( ResponseStream );

bool NeedEncodingCheck = true;

while( true )
{
    string NewLine = Reader.ReadLine(); // it may not working for zipped HTML.
    if( NewLine == null )
    {
        break;
    }

    FinalResult += NewLine;
    FinalResult += Environment.NewLine;

    if( NeedEncodingCheck )
    {
        int Start = NewLine.IndexOf( "charset=" );
        if( Start > 0 )
        {
            Start += "charset=\"".Length;   
            int End = NewLine.IndexOfAny( new[] { ' ', '\"', ';' }, Start );

            Reader = new StreamReader( ResponseStream, Encoding.GetEncoding(
                NewLine.Substring( Start, End - Start ) ) ); // Replace Reader with new encoding.

            NeedEncodingCheck = false;
        }
    }
}

Reader.Close();
Response.Close();

0

使用 WebRequest 请求网页“www.google.fr”时仍存在一些问题。

我使用 Fiddler 检查了原始请求和响应。问题来自 Google 服务器。响应的 HTTP 标头设置为 charset=ISO-8859-1,文本本身使用 ISO-8859-1 编码,而 HTML 则指定 charset=UTF-8。这是不一致的,会导致编码错误。

经过多次测试,我找到了一个解决方法。只需添加:

myHttpWebRequest.UserAgent = "Mozilla/5.0";

将此代码添加到您的程序中,Google响应将神奇地完全变成UTF-8。

0

我使用了一个很好的协议分析器WireShark来研究同样的问题。我认为httpWebResponse类存在一些设计缺陷。事实上,整个消息实体在第一次调用HttpWebRequest类的GetResponse()方法时就已经被下载了,但是框架没有地方将数据存储在HttpWebResponse类或其他地方,导致你必须第二次获取响应流。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接