HttpHandler中的GZipStream:我在做什么错?

GZipStream in HttpHandler: What am I doing wrong?
2021-02-23
  •  译文(汉语)
  •  原文(英语)

我正在编写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);
}

请告诉我我在做什么错?

解决过程1

您可以尝试以下方法:

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(见备注HERE),但大多数减压programms的应该能够阅读它.

速聊1:
很好地指出*.gz扩展名是正确的.我认为这DeflateStream是处理.zip文件的正确方法.
速聊2:
据MSDNDeflateStream使用相同的算法GZipStream,ZipArchive应该用于*.zip...
速聊3:
有用的信息,我似乎已经弄错了.谢谢!
解决过程2

您可能需要刷新并显式关闭压缩流,如下所示:

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()将导致流将所有缓冲的数据写入目标流,并清空缓冲区.

请注意,处置资源是一种好习惯.通过usingstr变量周围添加指令,您还将配置压缩流及其内部缓冲区.


1通过包装流,我指的是Stream内部实现到另一个Stream实例的实现.当在包装流上调用相应的Write方法时,有可能内部流不会立即被写入.而且,内部流本身可以是缓冲流(这意味着在调用write方法时,数据实际上并不写入流中,而是写入缓冲区中).因此,需要在关闭之前冲洗包装器流,以确保内部流将所有内容写入其中.

I'm writing a HttpHandler, which sends zipped file to the client on GET requests.

This code works well and sends unzipped data

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);
}

but this code sends broken zip-files.

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);
}

Please, tell me what am I doing wrong?

Solutions1

You could try the following:

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);
   }
}

Now everything should be flushed to the MemoryStream and then forwarded to the OutputStream and then disposed.

SIDE NOTE:
GZipStream does NOT generate a *.zip file as you seem to expect it to. The correct extension would be *.gz (see Remarks HERE), but most decompression programms should be able to read it.

Talk1:
Good for pointing out the *.gz extension being the correct one. I think DeflateStream is the proper way to address .zip files.
Talk2:
According to MSDN DeflateStream uses the same algorithem as GZipStream and ZipArchive should be used für *.zip...
Talk3:
Useful information, I seem to have been mistaken. Thanks!
Solutions2

You may need to flush and explicitly close the compressing stream, like this:

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);
}

The problem is that wrapper streams1 may use internal buffering of the data (like the GZipStream does). This means the data passed to the buffering stream is written to its inner buffer, but is not transferred to the main stream yet. Calling Flush() would cause the stream to write all buffered data to the target stream and empty the buffer.

Note, that it is a good practice to dispose of your resources. By adding a using directive around the str variable, you will also dispose the compression stream, and its inner buffer.


1 By wrapper streams I am referring to Stream implementations, which internally delegate to another Stream instance. It is possible, that the inner Stream is not immediately written to, when a corresponding Write method is called on the wrapper stream. Also, the inner stream could be a buffered stream itself (meaning the data is not actually written to the stream when a write method is called, but to a buffer). Therefore, flushing the wrapper stream prior closing it is needed to ensure the inner stream will have all the content written to it.

转载于:https://stackoverflow.com/questions/23733654/gzipstream-in-httphandler-what-am-i-doing-wrong

本人是.net程序员,因为英语不行,使用工具翻译,希望对有需要的人有所帮助
如果本文质量不好,还请谅解,毕竟这些操作还是比较费时的,英语较好的可以看原文

留言回复
我们只提供高质量资源,素材,源码,坚持 下了就能用 原则,让客户花了钱觉得值
上班时间 : 周一至周五9:00-17:30 期待您的加入