0

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?

Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108
skaeff
  • 753
  • 2
  • 13
  • 25

2 Answers2

3

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.

Christoph Fink
  • 22,727
  • 9
  • 68
  • 113
1

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.

Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108