Как прочитать защищенный RSS-канал в SyndicationFeed без предоставления учетных данных? - PullRequest
3 голосов
/ 11 мая 2010

По какой-то причине IBM использует https (не требуя учетных данных) для своих RSS-каналов. Я пытаюсь потреблять https://www.ibm.com/developerworks/mydeveloperworks/blogs/roller-ui/rendering/feed/gradybooch/entries/rss?lang=en с .NET 4 SyndicationFeed. Я могу открыть этот канал в браузере, и он загружается просто отлично. Вот код:

        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?

1 Ответ

9 голосов
/ 12 мая 2010

Я не думаю, что это имеет отношение к безопасности. Ошибка 500 - это ошибка на стороне сервера. Что-то в запросе, сгенерированном XmlReader.Create (url), сбивает с толку веб-сайт ibm. Если бы это была просто проблема безопасности, как предложено в вашем вопросе, то вы ожидаете получить ошибку 403 или «Отказ в авторизации». Но вы получили 500, что является ошибкой приложения.

Несмотря на это, может быть, клиентское приложение может что-то сделать, чтобы не запутать сервер.

Я посмотрел заголовки исходящих HTTP-запросов, используя Fiddler . Для запроса, сгенерированного 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. Мне нужен постоянный файл cookie для ibm.com. Для этого мне пришлось прибегнуть к p / invoke Win32 InternetGetCookie . Чтобы узнать, как это сделать, см. Класс PersistentCookies, прикрепленный к пользовательскому контенту внизу страницы документа для WebRequest . После прикрепления файла cookie я больше не получал 500 ошибок. Ура!

  2. Но результирующий поток не может быть прочитан XmlReader.Create (). Это выглядело двоично для меня. Я понял, что мне нужно распаковать gzip или дефлированный контент. Для этого мне пришлось обернуть GZipStream или DeflateStream вокруг полученного потока ответов и использовать поток распаковки для XmlReader. установить свойство AutomaticDecompression для HttpWebRequest. Я мог бы избежать необходимости в этом, не включив в исходящий запрос «gzip, deflate» в заголовке Accept-Encoding. Фактически, после установки свойства 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.

    Я посмотрел и обнаружил небольшой блок <script> в этом месте внутри элемента <copyright> в документе rss. Похоже, что IBM пытается заставить браузер «локализовать» дату авторского права, добавив логику, которая запускается в браузере для форматирования даты. Для меня это выглядит излишним, или даже ошибкой со стороны IBM. Но поскольку угловая скобка внутри текстового узла элемента беспокоит XmlReader, я удалил блок скрипта с заменой Regex.


После устранения этих препятствий все заработало. Приложение .NET смогло прочитать поток RSS с этого URL https.

Дальнейшего тестирования я не проводил - чтобы увидеть, изменит ли поведение заголовок Accept или Accept-Encoding. Это вам нужно выяснить, если вам все равно.

Полученный код приведен ниже. Это намного хуже, чем ваш простой 3-х линейный. Я не знаю, как это сделать проще.

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;
                }
            }
        }
    }
}
...