3

无论出于何种原因,IBM 都在其 RSS 提要中使用 https(无需凭据)。我正在尝试使用.NET 4 SyndicationFeed来使用https://www.ibm.com/developerworks/mydeveloperworks/blogs/roller-ui/rendering/feed/gradybooch/entries/rss?lang=en 。我可以在浏览器中打开这个提要,它加载得很好。这是代码:

        using (XmlReader xml = XmlReader.Create("https://www.ibm.com/developerworks/mydeveloperworks/blogs/roller-ui/rendering/feed/gradybooch/entries/rss?lang=en"))
        {
            var items = from item in SyndicationFeed.Load(xml).Items
                        select item;
        }

这是一个例外:

System.Net.WebException was unhandled by user code
Message=The remote server returned an error: (500) Internal Server Error.
Source=System
StackTrace:
   at System.Net.HttpWebRequest.GetResponse()
   at System.Xml.XmlDownloadManager.GetNonFileStream(Uri uri, ICredentials credentials, IWebProxy proxy, RequestCachePolicy cachePolicy)
   at System.Xml.XmlDownloadManager.GetStream(Uri uri, ICredentials credentials, IWebProxy proxy, RequestCachePolicy cachePolicy)
   at System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn)
   at System.Xml.XmlReaderSettings.CreateReader(String inputUri, XmlParserContext inputContext)
   at System.Xml.XmlReader.Create(String inputUri, XmlReaderSettings settings, XmlParserContext inputContext)
   at System.Xml.XmlReader.Create(String inputUri)
   at EDN.Util.Test.FeedAggTest.LoadFeedInfoTest() in D:\cdn\trunk\CDN\Dev\Shared\net\EDN.Util\EDN.Util.Test\FeedAggTest.cs:line 126

如何配置阅读器以使用 https 提要?

4

1 回答 1

9

我认为这与安全无关。500 错误是服务器端错误。XmlReader.Create(url) 生成的请求中的某些内容使 ibm 网站感到困惑。如果这只是一个安全问题,如您的问题中所建议的那样,那么您可能会收到 403 错误或“授权被拒绝”。但是你得到了 500,这是一个应用程序错误。

即便如此,客户端应用程序也许可以做一些事情,以避免混淆服务器。

我使用Fiddler查看了传出的 HTTP 请求标头。对于 IE 生成的请求,标头如下所示:

GET https://www.ibm.com/developerworks/mydeveloperworks/blogs/roller-ui/rendering/feed/gradybooch/entries/rss?lang=en HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, application/x-shockwave-flash, application/x-silverlight-2-b2, */*
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0; .NET CLR 3.5.30729;)
Accept-Encoding: gzip, deflate
Host: www.ibm.com
Connection: Keep-Alive
Cookie: UnicaNIODID=Ww06gyvyPpZ-WPl6K7y; conxnsCookie=en; IBMPOLLCOOKIE=""; UnicaNIODID=QridYHCNf7M-WYM8Usr

对于来自 XmlReader.Create(url) 的请求,标头如下所示:

GET https://www.ibm.com/developerworks/mydeveloperworks/blogs/roller-ui/rendering/feed/gradybooch/entries/rss?lang=en HTTP/1.1
Host: www.ibm.com
Connection: Keep-Alive

很不一样。此外,在对后者的响应中,我Set-Cookie在 500 响应中得到了一个标头,该标头在对 IE 的响应中不存在。

基于此,我推测是请求标头(尤其是 cookie)的差异让 ibm.com 感到困惑。


我不知道如何说服 XmlReader.Create() 嵌入我想要的所有请求标头,包括 cookie。但我知道如何使用 HttpWebRequest 来做到这一点。所以我用了那个。

我必须清除一些障碍。

  1. 我需要 ibm.com 的持久 cookie。为此,我不得不求助于 Win32 InternetGetCookie的 ap/invoke 。请参阅WebRequest文档页面底部的用户贡献内容中附加的 PersistentCookies 类,了解如何执行此操作。附加 cookie 后,我不再收到 500 错误。万岁!

  2. 但是 XmlReader.Create() 无法读取结果流。它在我看来是二进制的。我意识到我需要解压缩 gzip 或压缩的内容。为此,我必须在收到的响应流周围包装 GZipStream 或 DeflateStream,并为 XmlReader 使用解压缩流。在 HttpWebRequest 上 设置AutomaticDecompression属性。Accept-Encoding我可以通过不在出站请求的标头中包含“gzip,deflate”来避免这种需要。实际上,在设置 AutomaticDecompression 属性之后,这些标头会隐式设置在出站 HTTP 请求中。

  3. 当我这样做时,我得到了实际的文本。但是一些字节码是关闭的。接下来,我需要在 TextReader 中使用正确的文本编码,如 HttpWebResponse 中所示。

  4. 之后,我得到了一个合理的字符串,但是解压后的 rss 流导致 XmlReader 阻塞,

    ReadElementString method can only be called on elements with simple or empty content. Line 11, position 25.

    我查看并在 rss 文档的元素<script>内的那个位置找到了一个小块。<copyright>似乎 IBM 正试图通过附加将在浏览器中运行以格式化日期的逻辑来让浏览器“本地化”版权日期。对我来说似乎有点矫枉过正,甚至是 IBM 的一个错误。但是因为元素文本节点中的尖括号干扰了 XmlReader,所以我用正则表达式替换删除了脚本块。


在清除了这些障碍之后,它奏效了。.NET 应用程序能够从该 https url 读取 RSS 流。

我没有做任何进一步的测试——看看改变Accept标题或Accept-Encoding标题是否会改变行为。如果你在乎,那是你自己想办法。

结果代码如下。它比你简单的 3-liner 丑得多。我不知道如何使它更简单。

public void Run()
{
    string url;
    url = "https://www.ibm.com/developerworks/mydeveloperworks/blogs/roller-ui/rendering/feed/gradybooch/entries/rss?lang=en";

    HttpWebRequest hwr = (HttpWebRequest) WebRequest.Create(url);
    // attach persistent cookies
    hwr.CookieContainer =
        PersistentCookies.GetCookieContainerForUrl(url);
    hwr.Accept = "text/xml, */*";
    hwr.Headers.Add(HttpRequestHeader.AcceptLanguage, "en-us");
    hwr.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; .NET CLR 3.5.30729;)";
    hwr.KeepAlive = true;
    hwr.AutomaticDecompression = DecompressionMethods.Deflate |
                                 DecompressionMethods.GZip;

    using (var resp = (HttpWebResponse) hwr.GetResponse())
    {
        using(Stream s = resp.GetResponseStream())
        {            
            string cs = String.IsNullOrEmpty(resp.CharacterSet) ? "UTF-8" : resp.CharacterSet;
            Encoding e = Encoding.GetEncoding(cs);

            using (StreamReader sr = new StreamReader(s, e))
            {
                var allXml = sr.ReadToEnd();

                // remove any script blocks - they confuse XmlReader
                allXml = Regex.Replace( allXml,
                                        "(.*)<script type='text/javascript'>.+?</script>(.*)",
                                        "$1$2",
                                        RegexOptions.Singleline);

                using (XmlReader xmlr = XmlReader.Create(new StringReader(allXml)))
                {
                    var items = from item in SyndicationFeed.Load(xmlr).Items
                        select item;
                }
            }
        }
    }
}
于 2010-05-12T01:31:05.690 回答