3

Есть следующий код:

                static readonly HttpClient client = new HttpClient(handler);
        private async Task RequestAsync(Guid ActId, string FileId, string FileName, string FileFolderName, string Description, string UploaderName, string UploadDate)
        {
            client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.5");
            client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
            client.DefaultRequestHeaders.Add("Accept", "text/html, application/xhtml+xml, image/jxr, */*");
            try
            {
                var url = FileLinkIBMFileNet.Replace("{file_id}", FileId).Replace("{file_name}", FileName);
                var authToken = Encoding.ASCII.GetBytes($"{LoginIBMFilenet}:{PasswordIBMFileNet}");
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
                Convert.ToBase64String(authToken));
                byte[] tmpFileByte = await client.GetByteArrayAsync(url);
                InsertFile(ActId, FileName, FileFolderName, tmpFileByte, Description, UploaderName, UploadDate, FileId);
        }
        catch (Exception ex) {
            string message = string.Format("Error in RequestAsync\nMessage ---\n{0} \nSource ---\n{1} \nStackTrace ---\n{2} \nActId ---\n{3} \nFileId ---\n{4} \nFilename ---\n{5}", ex.Message, ex.Source, ex.StackTrace, ActId, FileId, FileName);
            _logger.Error(message, ex);
            throw ex;
        }
        }

Который через Fiddler при запросах всегда получает 200 ОК. введите сюда описание изображения Но в коде у большинство файлов при получении ответа происходит следующая ошибка в методе

await client.GetByteArrayAsync(url);

Ошибка:

Сервер нарушил протокол. Section=ResponseHeader Detail=За возвратом каретки должен следовать перевод строки
Specified value has invalid Control characters. (Parameter 'value') 
Source ---
System.Net.WebHeaderCollection 
StackTrace ---
   at System.Net.HttpValidationHelpers.CheckBadHeaderValueChars(String value)
   at System.Net.WebHeaderCollection.Set(String name, String value)
   at System.Net.HttpWebResponse.get_Headers()
   at ITRusPostISO.ITMetasonicMigrationFile.ITMetasonicMigrationFileService.RequestAsync(Guid ActId, String FileId, String FileName, String FileFolderName, String Description, String UploaderName, String UploadDate) 

Также прикрепляю скриншот Request/Response из Fiddler файла который нормально скачивается и которые падает в ошибку. введите сюда описание изображения

Response InternetExplorer при переходе по ссылке и скачивании файлов. введите сюда описание изображения

` Вот старый код на HttpWebRequest

private async Task RequestAsync(Guid ActId, string FileId, string FileName, string FileFolderName, string Description, string UploaderName, string UploadDate)
        {
            try{
                string downloadLink = FileLinkIBMFileNet.Replace("{file_id}", FileId).Replace("{file_name}", FileName);
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downloadLink);
                request.Credentials = new NetworkCredential(LoginIBMFilenet, PasswordIBMFileNet);
                request.Timeout = 180000;
                request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
            WebHeaderCollection headers = response.Headers;
            int streamSize = int.Parse(response.Headers["Content-Disposition"].Split(';')[2].Replace(" size=", ""));
            byte[] tmpFileByte = new byte[streamSize];
            using (Stream stream = response.GetResponseStream())
            {
                using (BinaryReader reader = new BinaryReader(stream))
                {
                    reader.Read(tmpFileByte, 0, streamSize);
                }
            }
            InsertFile(ActId, FileName, FileFolderName, tmpFileByte, Description, UploaderName, UploadDate, FileId);
            response.Close();
        }
        catch(Exception ex) {
            string message = string.Format("Error in RequestAsync\nMessage ---\n{0} \nSource ---\n{1} \nStackTrace ---\n{2} \nActId ---\n{3} \nFileId ---\n{4} \nFilename ---\n{5}", ex.Message, ex.Source, ex.StackTrace, ActId, FileId, FileName);
            _logger.Error(message, ex);
            throw ex;
        }
    }

Решение использовать tcpClient так как сервер шлет всегда битый response

  • Не ответ, но подсказка: var client = new HttpClient(handler); не создавайте HttpClient на каждый запрос, это ошибка, вынесите его в private static readonly поле. – aepot Oct 08 '20 at 09:10
  • Как-то так – aepot Oct 08 '20 at 09:38
  • Если все еще не работает, обновите код в вопросе, чтобы было понятно, что случайный фикс не случился. – aepot Oct 08 '20 at 09:55
  • Ну, суть ошибки что, клиент ожидает в конце строки \r\n, а получает только \r. Так же убедитесь, что у вас внутри заголовка этот \r не встречается там, где не нужно, его hex код 0x0d. Я в фидлере вижу какие-то непечатаемые квадратики в заголовке, может дело в них? – aepot Oct 08 '20 at 10:19
  • Кажется, нашел, но это было не легко. – aepot Oct 08 '20 at 10:39
  • Значит надо ковырять SocketsHttpHandler на предмет такой настройки. Вы уверены, что оно не сработает, вы пробовали? – aepot Oct 08 '20 at 11:18
  • У меня другая идея, попробуйте передать Accept-Language и Accept-Encoding заголовки серверу явным образом в GET запросе, можно в DefaultRequestHeaders даже. Мне кажется, там дата, и дата возможно не в ASCII формате. – aepot Oct 08 '20 at 11:31
  • Меня смущает кодировка ASCII. Логин и пароль всегда попадают в её диапазон? Может попробовать заменить на utf-8? – Alexander Petrov Oct 08 '20 at 11:32
  • @АндрейКазанцев Accept-Language и я бы убрал из него русский. – aepot Oct 08 '20 at 12:06
  • @АндрейКазанцев ну тогда остается либо переезжать на WebRequestHandler (вместо HttpClientHandler) и использовать useUnsafeHeaderParsing, либо писать админам сервера, чтобы чинили сервер, потому что эта проблема вызвана именно кривым ответом от него, а не чем-либо другим. – aepot Oct 08 '20 at 12:29
  • @АндрейКазанцев эта настройка не относится к Framework или Core, она относится к HttpWebRequest. А он есть и там и там. – aepot Oct 08 '20 at 13:01
  • Вот что нашел. То есть, разрабы знают, но пока ничего не делали в данном направлении. Возможно, вам стоит поискать какой-нибудь NuGet пакет, который умеет проглатывать подобные косяки в заголовках – aepot Oct 08 '20 at 13:48
  • 1
    @АндрейКазанцев это к серверу вопрос, есть ли что-то, что заставит его поменять формат ответа. У меня еще одна (бредовая) идея, есть же великий и ужасный WebBrowser, а так же WebView/WebView2, или даже CefSharp. Можно попробовать приручить один из них. Ну и если совсем упороться, то можно гонять трафик через прокси-сервис, который будет парсить входящий трафик и фиксить ответ от сервера (типа MITM-подхода). А еще можно написать свою собственную простую реализацию HTTP с использованием TcpClient, это самое легковесное из всего перечисленного. – aepot Oct 08 '20 at 14:29
  • 1
  • @АндрейКазанцев никаких портов открывать не нужно, зачем сервер? Нужно просто отправить GET запрос, получить и распарсить данные, все как обычно, только на более низком уровне. Просто при обращении к серверу, нужно указать удаленный порт, 80 или 443 в зависимости от того, HTTP нужен или HTTPS – aepot Oct 08 '20 at 15:00
  • Пинг без указания порта делается, и что за исключение, try-catch используйте. В целом, дальше я вам уже не помощник, знаю об этом еще меньше, чем гугл. :) – aepot Oct 08 '20 at 15:34
  • А почему вы решили, что порт 80? Посмотрите внимательно на скриншот fiddler. – aepot Oct 08 '20 at 17:33
  • Комментарии не предназначены для расширенной дискуссии; разговор перемещён в чат. – Barmaley Oct 09 '20 at 05:45
  • Попробуйте скачать файл браузером и сохранить, затем скачать этот же файл с помощью нового метода и сохранить, затем сравнить оба файла. – aepot Oct 09 '20 at 09:38
  • @АндрейКазанцев размеры понятно, что совпадают, сравните содержимое. – aepot Oct 09 '20 at 10:48
  • Вы уверены, что сервер вам gzip возвращает? Content-Encoding: gzip заголовок в ответе присутствует? Если нет, значит ответ не запакован. – aepot Oct 09 '20 at 14:05
  • @АндрейКазанцев вы просто что-то не так делаете, попробуйте мой вариант, ниже. – aepot Oct 09 '20 at 15:27
  • Решение мы ушли от httpClient и сделали код на tcpClient так как сервер слал битый response – Андрей Казанцев Oct 10 '20 at 15:48

1 Answers1

1

Я читал бы ответ от сервера примерно вот так:

private readonly char[] _colon = new[] { ':' };

public async Task RequestAsync(Guid ActId, string FileId, string FileName, string FileFolderName, string Description, string UploaderName, string UploadDate) { try { byte[] body; Uri url = new Uri(FileLinkIBMFileNet.Replace("{file_id}", FileId).Replace("{file_name}", FileName)); using (var tcp = new TcpClient(url.Host, 9080)) using (var tcpStream = tcp.GetStream()) { tcp.SendTimeout = 1000; tcp.ReceiveTimeout = 1800; StringBuilder builder = new StringBuilder(); builder.Append("GET ").Append(url.AbsoluteUri).AppendLine(" HTTP/1.1"); builder.Append("Host: ").AppendLine(url.Authority); builder.AppendLine("Connection: Keep-Alive"); builder.AppendLine("Accept-Encoding: gzip"); builder.Append("Authorization: Basic ").AppendLine(Convert.ToBase64String(Encoding.ASCII.GetBytes($"{LoginIBMFilenet}:{PasswordIBMFileNet}"))); builder.AppendLine(); byte[] header = Encoding.ASCII.GetBytes(builder.ToString()); await tcpStream.WriteAsync(header, 0, header.Length).ConfigureAwait(false);

        Dictionary<string, string> headers = new Dictionary<string, string>();
        string line;
        while ((line = tcpStream.ReadLine())?.Length > 0)
        {
            string[] tokens = line.Split(_colon, 2);
            if (tokens.Length > 1)
                headers.Add(tokens[0], tokens[1].Trim());
            else
                headers.Add("Response", tokens[0]);
        }

        using (MemoryStream memory = new MemoryStream())
        {
            bool FirstChunk = true;
            if (headers.TryGetValue("Transfer-Encoding", out string chk) && chk.Contains("chunked"))
            {
                int chunkSize = 0;
                while (true)
                {
                    if (!FirstChunk)
                        tcpStream.ReadLine();
                    else
                        FirstChunk = false;
                    chunkSize = int.Parse(tcpStream.ReadLine(), NumberStyles.HexNumber);
                    if (chunkSize == 0) break;
                    await tcpStream.CopyBytesAsync(memory, chunkSize).ConfigureAwait(false);
                }
            }
            else
                await tcpStream.CopyToAsync(memory).ConfigureAwait(false);

            memory.Position = 0;

            if (headers.TryGetValue("Content-Encoding", out string enc) && enc.Contains("gzip"))
                using (Stream gzipStream = new GZipStream(memory, CompressionMode.Decompress, true))
                using (MemoryStream ms = new MemoryStream())
                {
                    await gzipStream.CopyToAsync(ms).ConfigureAwait(false);
                    body = ms.ToArray();
                }
            else
                body = memory.ToArray();
        }
    }
    InsertFile(ActId, FileName, FileFolderName, body, Description, UploaderName, UploadDate, FileId);
}
catch (Exception ex)
{
    throw ex;
}

}

И вот такие экстеншн методы для NetworkStream понадобятся.

public static class NetworkStreamExtensions
{
    public static string ReadLine(this NetworkStream stream)
    {
        List<byte> bytes = new List<byte>();
        int b;
        while ((b = stream.ReadByte()) != -1 && b != '\n')
            bytes.Add((byte)b);
        if (bytes.Count > 0 && bytes.Last() == '\r')
            bytes.RemoveAt(bytes.Count - 1);
        return b == -1 && bytes.Count == 0 ? null : Encoding.ASCII.GetString(bytes.ToArray());
    }
public static async Task CopyBytesAsync(this NetworkStream stream, Stream outputStream, int bytesToCopy)
{
    int toCopy = bytesToCopy;
    const int bufferSize = 32768;
    byte[] buffer = new byte[bufferSize];
    while (true)
    {
        int toRead = toCopy &gt; bufferSize ? bufferSize : toCopy;
        if (toRead == 0) break;
        int bytesRead = await stream.ReadAsync(buffer, 0, toRead).ConfigureAwait(false);
        if (bytesRead == 0) break;
        await outputStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
        toCopy -= bytesRead;
    }
}

}

aepot
  • 49,560