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

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

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

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 не работает?
  • Как сжимать "на лету" во время отправки и без загрузки сжатого буфера в память?

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

Ответ 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 всегда буферизует отправленное содержимое.

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