Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a dynamic zip of a bunch of URLs on the fly

I am trying to create a zip file of any size on the fly. The source of the zip archive is a bunch of URLs and could be potentially large (500 4MB JPGs in the list). I want to be able to do everything inside the request and have the download start right away and have the zip created and streamed as it is built. It should not have to reside in memory or on disk on the server.

The closest I have come is this: Note: urls is a keyvaluepair of URLs to the file names as they should exist in the created zip

Response.ClearContent();
Response.ClearHeaders();
Response.ContentType = "application/zip";
Response.AddHeader("Content-Disposition", "attachment; filename=DyanmicZipFile.zip");

using (var memoryStream = new MemoryStream())
{
    using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
    {
        foreach (KeyValuePair<string, string> fileNamePair in urls)
        {
            var zipEntry = archive.CreateEntry(fileNamePair.Key);

            using (var entryStream = zipEntry.Open())
                using (WebClient wc = new WebClient())
                    wc.OpenRead(GetUrlForEntryName(fileNamePair.Key)).CopyTo(entryStream);

                //this doesn't work either
                //using (var streamWriter = new StreamWriter(entryStream))
                //  using (WebClient wc = new WebClient())
                //      streamWriter.Write(wc.OpenRead(GetUrlForEntryName(fileNamePair.Key)));
        }
    }

    memoryStream.WriteTo(Response.OutputStream);
}
HttpContext.Current.ApplicationInstance.CompleteRequest();

This code gives me a zip file, but each JPG file inside the zip is just a text file that says "System.Net.ConnectStream" I have other attempts on this that do build a zip file with the proper files inside, but you can tell by the delay at the beginning that the server is completely building the zip in memory and then blasting it down at the end. It doesn't respond at all when the file count gets near 50. The part in comments gives me the same result I have tried Ionic.Zip as well.

This is .NET 4.5 on IIS8. I am building with VS2013 and trying to run this on AWS Elastic Beanstalk.

like image 320
Brad Murray Avatar asked Nov 23 '22 09:11

Brad Murray


1 Answers

So to answer my own question - here is the solution that works for me:

private void ProcessWithSharpZipLib()
{
    byte[] buffer = new byte[4096];

    ICSharpCode.SharpZipLib.Zip.ZipOutputStream zipOutputStream = new ICSharpCode.SharpZipLib.Zip.ZipOutputStream(Response.OutputStream);
    zipOutputStream.SetLevel(0); //0-9, 9 being the highest level of compression
    zipOutputStream.UseZip64 = ICSharpCode.SharpZipLib.Zip.UseZip64.Off;

    foreach (KeyValuePair<string, string> fileNamePair in urls)
    {
        using (WebClient wc = new WebClient())
        {
            using (Stream wcStream = wc.OpenRead(GetUrlForEntryName(fileNamePair.Key)))
            {
                ICSharpCode.SharpZipLib.Zip.ZipEntry entry = new ICSharpCode.SharpZipLib.Zip.ZipEntry(ICSharpCode.SharpZipLib.Zip.ZipEntry.CleanName(fileNamePair.Key));

                zipOutputStream.PutNextEntry(entry);

                int count = wcStream.Read(buffer, 0, buffer.Length);
                while (count > 0)
                {
                    zipOutputStream.Write(buffer, 0, count);
                    count = wcStream.Read(buffer, 0, buffer.Length);
                    if (!Response.IsClientConnected)
                    {
                        break;
                    }
                    Response.Flush();
                }
            }
        }
    }
    zipOutputStream.Close();

    Response.Flush();
    Response.End();
}
like image 84
Brad Murray Avatar answered Dec 18 '22 04:12

Brad Murray