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 для потока-оболочки. Кроме того, внутренний поток может быть самим буферным потоком (то есть данные фактически не записываются в поток при вызове метода записи, а в буфер). Следовательно, очистка потока-обертки перед его закрытием необходима, чтобы гарантировать, что во внутренний поток будет записан весь контент.