Чтение "фрагментированного" ответа с помощью HttpWebResponse

У меня возникают проблемы с чтением "фрагментированного" ответа при использовании StreamReader для чтения потока, возвращаемого GetResponseStream () из HttpWebResponse:

// response is an HttpWebResponse
StreamReader reader = new StreamReader(response.GetResponseStream());
string output = reader.ReadToEnd(); // throws exception...

При reader.ReadToEnd() вызове метода возникает следующее исключение System.IO.IOException: невозможно прочитать данные из транспортного соединения: соединение было закрыто.

Приведенный выше код отлично работает, когда сервер возвращает «не фрагментированный» ответ.

Единственный способ заставить его работать - использовать HTTP / 1.0 для начального запроса (вместо HTTP / 1.1, используемого по умолчанию), но это похоже на неудачный обходной путь.

Любые идеи?


@Chuck

Ваше решение работает очень хорошо. Он по-прежнему вызывает то же исключение IOExeception при последнем Read (). Но после проверки содержимого StringBuilder похоже, что все данные были получены. Так что, возможно, мне просто нужно обернуть Read () в try-catch и проглотить «ошибку».

Ответов (4)

Решение

Не пробовали это с "фрагментированным" ответом, но сработает ли что-то подобное?

StringBuilder sb = new StringBuilder();
Byte[] buf = new byte[8192];
Stream resStream = response.GetResponseStream();
string tmpString = null;
int count = 0;
do
{
     count = resStream.Read(buf, 0, buf.Length);
     if(count != 0)
     {
          tmpString = Encoding.ASCII.GetString(buf, 0, count);
          sb.Append(tmpString);
     }
}while (count > 0);

Я работаю над подобной проблемой. .Net HttpWebRequest и HttpWebRequest обрабатывают файлы cookie и перенаправляют автоматически, но не обрабатывают фрагментированное содержимое в теле ответа автоматически.

Возможно, это связано с тем, что фрагментированное содержимое может содержать больше, чем простые данные (например, имена фрагментов, завершающие заголовки).

Простое чтение потока и игнорирование исключения EOF не сработает, поскольку поток содержит больше, чем желаемое. Поток будет содержать фрагменты, и каждый фрагмент начинается с объявления своего размера. Если поток просто читается от начала до конца, окончательные данные будут содержать метаданные фрагмента (и в случае, если это gziped-контент, он не пройдет проверку CRC при распаковке).

Чтобы решить эту проблему, необходимо вручную проанализировать поток, удалив размер фрагмента из каждого фрагмента (а также разделители CR LF), обнаружив последний фрагмент и сохранив только данные фрагмента. Вероятно, где-то есть библиотека, которая делает это, я ее еще не нашел.

Полезные ресурсы:

http://en.wikipedia.org/wiki/Chunked_transfer_encoding http://tools.ietf.org/html/rfc2616#section-3.6.1

Попробовав множество фрагментов из StackOverflow и Google, в конечном итоге я обнаружил, что это работает лучше всего (если вы знаете данные в виде строки UTF8, в противном случае вы можете просто сохранить массив байтов и обработать соответствующим образом):

byte[] data;
var responseStream = response.GetResponseStream();
var reader = new StreamReader(responseStream, Encoding.UTF8);
data = Encoding.UTF8.GetBytes(reader.ReadToEnd());
return Encoding.Default.GetString(data.ToArray());

Я обнаружил, что большую часть времени работают другие варианты, но иногда данные усекаются. Я получил этот фрагмент из:

https://social.msdn.microsoft.com/Forums/en-US/4f28d99d-9794-434b-8b78-7f9245c099c4/problems-with-httpwebrequest-and-transferencoding-chunked?forum=ncl

У меня была такая же проблема (вот как я здесь оказался :-). В конце концов отследил это до того факта, что фрагментированный поток недействителен - окончательный фрагмент нулевой длины отсутствовал. Я придумал следующий код, который обрабатывает как действительные, так и недопустимые потоки с фрагментами.

using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
    StringBuilder sb = new StringBuilder();

    try
    {
        while (!sr.EndOfStream)
        {
            sb.Append((char)sr.Read());
        }
    }
    catch (System.IO.IOException)
    { }

    string content = sb.ToString();
}