Как сжать http запрос на лету и без загрузки сжатого буфера в память

Мне нужно отправить объемные данные в почтовом запросе http на сервер, поддерживающий закодированные gzip-запросы.

Начиная с простого

public async Task<string> DoPost(HttpContent content)
{
  HttpClient client = new HttpClient();
  HttpResponseMessage response = await client.PostAsync("http://myUri", content);

  response.EnsureSuccessStatusCode();
  return await response.Content.ReadAsStringAsync();
}

Я только что добавил предварительное сжатие

public async Task<string> DoPost(HttpContent content, bool compress)
{
  if (compress) 
    content= await CompressAsync(content);

  return await DoPost(content);
}

private static async Task<StreamContent> CompressAsync(HttpContent content)
{
  MemoryStream ms = new MemoryStream();
  using (GZipStream gzipStream = new GZipStream(ms, CompressionMode.Compress, true))
  {
    await content.CopyToAsync(gzipStream);
    await gzipStream.FlushAsync();
  }

  ms.Position = 0;
  StreamContent compressedStreamContent = new StreamContent(ms);
  compressedStreamContent.Headers.ContentType = content.Headers.ContentType;
  compressedStreamContent.Headers.Add("Content-Encoding", "gzip");

  return compressedStreamContent;
}

Он работает отлично, но сжатые данные полностью загружаются в память перед отправкой запроса. Я хотел бы иметь возможность сжимать данные на лету во время отправки в потоковом режиме.

Для этого я пробовал следующий код:

private static async Task<HttpContent> CompressAsync2(HttpContent content)
{
  PushStreamContent pushStreamContent = new PushStreamContent(async (stream, content2, transport) =>
  {
    using (GZipStream gzipStream = new GZipStream(stream, CompressionMode.Compress, true))
    {
      try
      {
        await content.CopyToAsync(gzipStream);
        await gzipStream.FlushAsync();
      }
      catch (Exception exception)
      {
        throw;
      }
    }
  });
  pushStreamContent.Headers.ContentType = content.Headers.ContentType;
  pushStreamContent.Headers.Add("Content-Encoding", "gzip");

  return pushStreamContent;
}

но он никогда не выходит за пределы CopyToAsync(gzipStream). FlushAsync никогда не выполняется, и исключение не выдается, и Fiddler не видит никаких сообщений.

Мои вопросы:

  • Почему CompressAsync2 не работает?
  • Как сжимать на лету при отправке и без загрузки сжатого буфера в память?

Любая помощь будет принята с благодарностью.


person MuiBienCarlota    schedule 21.05.2013    source источник
comment
PushStreamContent (в настоящее время) не поддерживает async лямбда-выражений.   -  person Stephen Cleary    schedule 21.05.2013
comment
@Stephen Cleary: Вы правы, я должен был проверить! И я не могу вывести из PushStreamContent перегрузку SerializeToStreamAsync (слишком много только внутренних). ты видишь решение?   -  person MuiBienCarlota    schedule 21.05.2013
comment
Хм, проще всего просто взять PushStreamContent и измените его для поддержки async лямбда-выражений. Это было в моем списке дел в течение нескольких месяцев, просто не было времени на это.   -  person Stephen Cleary    schedule 21.05.2013


Ответы (1)


Попробуйте использовать класс CompressedContent из WebAPIContrib https://github.com/WebApiContrib/WebAPIContrib/blob/master/src/WebApiContrib/Content/CompressedContent.cs

public async Task<string> DoPost(HttpContent content)
{
  HttpClient client = new HttpClient();
  HttpResponseMessage response = await client.PostAsync("http://myUri", 
                                 new CompressedContent(content,"gzip"));

  response.EnsureSuccessStatusCode();
  return await response.Content.ReadAsStringAsync();
}

P.S. что это будет транслировать контент только на .net 4.5. Версия .net 4 HttpWebRequest всегда буферизует отправленный контент.

П.П.С. Создание нового HttpClient для каждого запроса — не лучший способ использования HttpClient. Это приведет к принудительному созданию нового TCP-соединения для каждого запроса.

person Darrel Miller    schedule 21.05.2013
comment
Действительно интересная ссылка: большое спасибо. Мне нужно остаться на .Net 4.0 (требуется XP). Как вы думаете, смогу ли я получить потоковую передачу, когда установлен .Net 4.5, даже если я скомпилирую для .Net 4.0? - person MuiBienCarlota; 21.05.2013
comment
Я создал локальный HttpClient только для простоты вопроса. - person MuiBienCarlota; 21.05.2013
comment
@MuiBienCarlota Я не уверен. Мое предположение было бы да, но вам нужно проверить. Что касается HttpClient ... круто, я просто часто его вижу, и у меня есть миссия его искоренить ;-) - person Darrel Miller; 21.05.2013
comment
У меня есть ответ: нет. При компиляции для .Net 4.0 мы не находимся в потоковом режиме (даже при наличии .Net 4.5). Попробую под .Net 4.5. - person MuiBienCarlota; 21.05.2013
comment
Он работает при нацеливании на .Net 4.5, а не на .Net 4.0, даже если присутствует .Net 4.5. Меня это удивляет. Я думал, что .Net 4.5 является полной заменой .Net 4.0, т.е. если установлена ​​4.5, моя программа, ориентированная на 4.0, должна работать под 4.5. Таинственный! - person MuiBienCarlota; 22.05.2013
comment
Ответ принят: возможен только в .Net 4.5 из-за ограничения HttpWebRequest .NET 4.0 и, не забывая ответ Стивена Клири на мой первый вопрос (нет поддержки лямбда в PushStreamContent). - person MuiBienCarlota; 28.05.2013