【问题标题】:HttpClient: Correct order to detect encodingHttpClient:检测编码的正确顺序
【发布时间】:2016-03-19 21:48:57
【问题描述】:

我正在使用 HttpClient 来获取一些文件。我将内容放入一个字节数组(字节)中。现在我需要检测编码。内容类型可以是 html、css、JavaScript 或 XML 内容类型。

目前我从标题中检查字符集,然后在最后检查文件的第一部分是否有字符集元标记之前检查 BOM(字节顺序标记)。 通常这可以正常工作,因为没有冲突。

但是:该顺序是否正确(以防发生冲突)?

我目前使用的代码:

Encoding encoding;
try
{
    encoding = Encoding.GetEncoding(responseMessage.Content.Headers.ContentType.CharSet);
}
catch
{
    using (MemoryStream ms = new MemoryStream(bytes))
    {
        using (StreamReader sr = new StreamReader(ms, Encoding.Default, true))
        {
            char[] chars = new char[1024];
            sr.Read(chars, 0, 1024);
            string textDefault = new string(chars);
            if (sr.CurrentEncoding == Encoding.Default)
            {
                encoding = Global.EncodingFraContentType(textDefault);
            }
            else
            {
                encoding = sr.CurrentEncoding;
            }
        }
    }
}
responseInfo.Text = encoding.GetString(bytes);
Global.EncodingFraContentType 是一个正则表达式,用于查找在 XML 声明或元标记中定义的字符集。

检测字符集/编码的正确顺序是什么?

【问题讨论】:

    标签: c# http encoding dotnet-httpclient


    【解决方案1】:

    正确答案不取决于顺序,而是取决于实际给出正确结果的顺序,这里没有完美的答案。

    如果有冲突,那么服务器给你的东西不正确。由于它不正确,因此不可能有“正确”的顺序,因为没有正确的错误方式。而且,可能标头和嵌入的元数据都是错误的!

    即使是稍微常用的编码也不能有一些看起来像 BOM 开头的 UTF-8 或 UTF-16 的东西,并且仍然是您提到的内容类型的有效示例,所以如果有 BOM那么就赢了。

    (唯一的例外是,如果文档编辑得非常糟糕,以至于中途切换编码,这并非闻所未闻,但错误的内容非常错误,没有真正的意义)。

    如果内容不包含大于 0x7F 的八位字节,则无关紧要,并且标头和元数据都将其声明为 US-ASCII、UTF-8、任何 ISO-8859 系列编码的不同示例,或这些八位位组都映射到相同代码点的任何其他编码,那么您认为它是什么并不重要,因为净结果是相同的。认为它是元数据所说的任何内容,因为这样您就不需要重写它来正确匹配。

    如果它在没有 BOM 的 UTF-16 中,很可能很快就会清楚,因为所有这些格式都有很多在 U+0000 到 U+00FF 范围内具有特殊含义的字符(实际上,通常U+0020 到 U+007F),所以你会有很多范围,每个其他字符都有一个零字节。

    如果它具有高于 0x7F 的八位字节并且是有效的 UTF-8,那么它几乎肯定是 UTF-8。 (同理,如果它不是 UTF-8 并且八位字节数高于 0x7F,那么几乎可以肯定它不会被误认为是 UTF-8)。

    最棘手的常见情况是,如果您对它采用两种不同的编码有冲突的说法,这两种编码都是每字符一个八位字节的编码,并且存在 0x80-0xFF 范围内的八位字节。这是您无法确定的情况。如果一种编码是另一种编码的子集(尤其是在排除 C1 控件时),那么您可以选择超集,但这需要存储有关这些编码的知识以及大量工作。大多数时候我倾向于只抛出一个异常,当它在日志中找到时,看看我是否可以获得修复他们的错误的源,或者特殊情况下的源,但如果你是处理您可能没有关系的大量不同来源。唉,这里没有完美的答案。

    还值得注意的是,有时标头和嵌入的元数据会错误地相互一致。一个常见的情况是 CP-1252 中的内容,但声称在 ISO-8859-1 中。

    【讨论】:

      【解决方案2】:

      根据W3C Faq

      如果您的文件开头有 UTF-8 字节顺序标记 (BOM),则 Internet Explorer 10 或 11 以外的最新浏览器版本将使用它来确定您的页面编码是 UTF-8。它的优先级高于任何其他声明,包括 HTTP 标头。

      当涉及到 http-header 与 meta BOM 优先时,只要 meta 在前 1024 内,它就可以优先,尽管对此没有严格的规定。

      【讨论】:

      • 你的答案看起来有点乱。一个句子开始但没有结束。一些未引用的部分似乎来自链接页面。
      • @Frédéric - 感谢您的反馈 - 少即是多,相应修改
      【解决方案3】:

      结论 - 按重要性排序:

      1. 字节顺序标记 (BOM):如果存在,这是 AUTHORATIVE,因为它是 由实际保存文件的编辑器添加(这只能是 存在于unicode 编码上)。
      2. Content-Type charset(在服务器设置的标头中):对于动态创建/处理的文件,它应该存在(因为 服务器知道),但可能不适用于静态文件(服务器只是 发送那些)。
      3. 内联字符集:对于 xml、html 和 css,可以在文档中指定编码,xml prologhtml meta tag@charset in css。要阅读,您需要先解码 文档的一部分使用例如 'Windows-1252' 编码
      4. 假设 utf-8。这是standard of the web,是目前使用最多的。
      5. 如果找到的编码等于“ISO-8859-1”,请改用“Windows-1252”(在 html5 中是必需的 - 更多信息请参阅 Wikipedia

      现在尝试使用找到的编码解码文档。如果error handling 被打开,那可能会失败!在这种情况下:

      1. 使用“Windows-1252”。这是旧 Windows 文件中的标准,并且在最后一次尝试时工作正常(那里仍然有很多旧文件)。 这将出现never throw 错误。但是,这当然可能是错误的。

      我已经制作了一个实现这一点的方法。我使用的regex 能够找到指定为的编码:

      Xml<?xml version="1.0" encoding="utf-8"?><?xml encoding="utf-8"?>

      html<meta charset="utf-8" /><meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      css@charset "utf-8";

      (它适用于单引号和双引号)。

      你需要:

      using System;
      using System.IO;
      using System.Net.Http;
      using System.Text;
      using System.Text.RegularExpressions;
      using System.Threading.Tasks;
      

      这里是返回解码字符串的方法(参数是HttpClientUri):

      public static async Task<string> GetString(HttpClient httpClient, Uri url)
      {
          byte[] bytes;
          Encoding encoding = null;
          Regex charsetRegex = new Regex(@"(?<=(<meta.*?charset=|^\<\?xml.*?encoding=|^@charset[ ]?)[""']?)[\w-]+?(?=[""';\r\n])",
              RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
      
          using (HttpResponseMessage responseMessage = await httpClient.GetAsync(url).ConfigureAwait(false))
          {
              responseMessage.EnsureSuccessStatusCode();
              bytes = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
              string headerCharset = responseMessage?.Content?.Headers?.ContentType?.CharSet;
      
              byte[] buffer = new byte[0x1000];
              Array.Copy(bytes, buffer, Math.Min(bytes.Length, buffer.Length));
              using (MemoryStream ms = new MemoryStream(buffer))
              {
                  using (StreamReader sr = new StreamReader(ms, Encoding.GetEncoding("Windows-1252"), true, buffer.Length, true))
                  {
                      string testString = await sr.ReadToEndAsync().ConfigureAwait(false);
                      if (!sr.CurrentEncoding.Equals(Encoding.GetEncoding("Windows-1252")))
                      {
                          encoding = sr.CurrentEncoding;
                      }
                      else if (headerCharset != null)
                      {
                          encoding = Encoding.GetEncoding(headerCharset, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
                      }
                      else
                      {
                          string inlineCharset = charsetRegex.Match(testString).Value;
                          if (!string.IsNullOrEmpty(inlineCharset))
                          {
                              encoding = Encoding.GetEncoding(inlineCharset, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
                          }
                          else
                          {
                              encoding = new UTF8Encoding(false, true);
                          }
                      }
                      if (encoding.Equals(Encoding.GetEncoding("iso-8859-1")))
                      {
                          encoding = Encoding.GetEncoding("Windows-1252", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
                      }
                  }
              }
              using (MemoryStream ms = new MemoryStream(bytes))
              {
                  try
                  {
                      using (StreamReader sr = new StreamReader(ms, encoding, false, 0x8000, true))
                      {
                          return await sr.ReadToEndAsync().ConfigureAwait(false);
                      }
                  }
                  catch (DecoderFallbackException)
                  {
                      ms.Position = 0;
                      using (StreamReader sr = new StreamReader(ms, Encoding.GetEncoding("Windows-1252"), false, 0x8000, true))
                      {
                          return await sr.ReadToEndAsync().ConfigureAwait(false);
                      }
                  }
              }
          }
      }
      

      您应该将方法调用包装在 try/catch 中,因为如果请求失败,HttpClient 会抛出错误。

      更新

      .Net Core 中,您没有“Windows-1252”编码(恕我直言,大错特错),所以在这里您必须使用“ISO-8859-1”。

      【讨论】:

        猜你喜欢
        • 2011-06-15
        • 1970-01-01
        • 1970-01-01
        • 2021-03-26
        • 1970-01-01
        • 2018-12-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多