GZipStream в HttpHandler: Что я делаю не так?

Я пишу HttpHandler, который отправляет заархивированный файл клиенту по запросам GET.

Этот код работает хорошо и отправляет разархивированные данные

using (var mem = new MemoryStream())
{
  WriteMyDataToStream(mem);
  context.Response.AddHeader("Content-Type", "application/octet-stream");
  context.Response.AddHeader("Content-Disposition","attachment; filename=file.csv");
  mem.WriteTo(context.Response.OutputStream);
}

но этот код отправляет испорченные zip-файлы.

using (var mem = new MemoryStream())
{
   var str = new GZipStream(mem, CompressionMode.Compress);
   WriteMyDataToStream(str);
   context.Response.AddHeader("Content-Type", "application/octet-stream");
   context.Response.AddHeader("Content-Disposition","attachment; filename=file.zip");
   mem.WriteTo(context.Response.OutputStream);
}

Пожалуйста, скажите мне, что я делаю не так?

2 ответа

Вы можете попробовать следующее:

using (var mem = new MemoryStream())
{
   using(var str = new GZipStream(mem, CompressionMode.Compress))
   {
      WriteMyDataToStream(str);
      str.Flush();
      context.Response.AddHeader("Content-Type", "application/octet-stream");
      context.Response.AddHeader("Content-Disposition","attachment; filename=file.zip");
      mem.WriteTo(context.Response.OutputStream);
   }
}

Теперь все должно быть сброшено MemoryStream а затем направить в OutputStream а затем утилизировать.

БОКОВОЕ ПРИМЕЧАНИЕ:
GZipStream НЕ генерирует *.zip файл, как вы, кажется, ожидаете. Правильное расширение будет *.gz (см. примечания ЗДЕСЬ), но большинство программ распаковки должны быть в состоянии прочитать его.

Вам может потребоваться очистить и явно закрыть поток сжатия, например так:

using (var mem = new MemoryStream())
{
    using(var str = new GZipStream(mem, CompressionMode.Compress))
    {
        WriteMyDataToStream(str);
        // force the compression stream buffer to be written to the mem stream
        str.Flush(); 

    }
    context.Response.AddHeader("Content-Type", "application/octet-stream");
    context.Response.AddHeader("Content-Disposition","attachment; filename=file.zip");
    mem.WriteTo(context.Response.OutputStream);
}

Проблема в том, что потоки-обертки1 могут использовать внутреннюю буферизацию данных (например, GZipStream делает). Это означает, что данные, передаваемые в буферный поток, записываются во его внутренний буфер, но еще не передаются в основной поток. призвание Flush() заставит поток записать все буферизованные данные в целевой поток и очистить буфер.

Обратите внимание, что это хорошая практика, чтобы избавиться от ваших ресурсов. Добавляя using директива во всем str переменная, вы также будете располагать потоком сжатия и его внутренним буфером.


1 По потокам-обёрткам я имею в виду Stream реализации, которые внутренне делегируют другому Stream пример. Возможно, что внутренний поток записывается не сразу, когда вызывается соответствующий метод Write для потока-оболочки. Кроме того, внутренний поток может быть самим буферным потоком (то есть данные фактически не записываются в поток при вызове метода записи, а в буфер). Следовательно, очистка потока-обертки перед его закрытием необходима, чтобы гарантировать, что во внутренний поток будет записан весь контент.

Другие вопросы по тегам