动态创建一堆URL的动态zip

时间:2013-11-29 20:14:40

标签: c# asp.net .net .net-4.5

我正在尝试创建任意大小的zip文件。 zip存档的来源是一堆URL,可能很大(列表中有500个4MB JPG)。我希望能够在请求中执行所有操作并立即开始下载,并在构建时创建zip并进行流式处理。它不应该驻留在服务器的内存或磁盘上。

我最接近的是: 注意:url是文件名URL的键值对,因为它们应该存在于创建的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();

这段代码给了我一个zip文件,但zip中的每个JPG文件只是一个文本文件,上面写着&#34; System.Net.ConnectStream&#34;我还有其他的尝试,建立一个带有适当文件的zip文件,但你可以从开头的延迟告诉服务器完全在内存中构建zip,然后在最后将其压缩。当文件数量接近50时,它根本没有响应。评论中的部分给出了我尝试Ionic.Zip的相同结果。

这是IIS8上的.NET 4.5。我正在使用VS2013构建并尝试在AWS Elastic Beanstalk上运行它。

4 个答案:

答案 0 :(得分:0)

您正在尝试创建一个zip文件,并在创建时将其流式传输。事实证明这非常困难。

您需要了解Zip file format。特别注意,本地文件条目具有无法更新的头字段(CRC,压缩和未压缩文件大小),直到整个文件被压缩为止。因此,在将文件发送到响应流之前,至少需要缓冲至少一个整个文件。

所以最多你可以这样做:

open archive
for each file
    create entry
    write file to entry
    read entry raw data and send to the response output stream

您将遇到的问题是,没有记录的方式(并且没有我记得的未记录方式)来读取原始数据。唯一的读取方法最终解压缩数据并丢弃标题。

可能还有其他一些zip库可以满足您的需求。我不建议尝试使用ZipArchive

答案 1 :(得分:0)

所以回答我自己的问题 - 这是适合我的解决方案:

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

答案 2 :(得分:0)

您正在使用的zip组件中必须有一种方法可以将条目延迟添加到存档中,即。在调用zip.Save()之后添加它们。我使用延迟技术使用IonicZip,下载flickr专辑的代码如下所示:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsLoggedIn())
        Response.Redirect("/login.aspx");
    else
    {
        // this is dco album id, find out what photosetId it maps to
        string albumId = Request.Params["id"];
        Album album = findAlbum(new Guid(albumId));
        Flickr flickr = FlickrInstance();
        PhotosetPhotoCollection photos = flickr.PhotosetsGetPhotos(album.PhotosetId, PhotoSearchExtras.OriginalUrl | PhotoSearchExtras.Large2048Url | PhotoSearchExtras.Large1600Url);

        Response.Clear();
        Response.BufferOutput = false;

        // ascii only
        //string archiveName = album.Title + ".zip";
        string archiveName = "photos.zip";
        Response.ContentType = "application/zip";
        Response.AddHeader("content-disposition", "attachment; filename=" + archiveName);
        int picCount = 0;
        string picNamePref = album.PhotosetId.Substring(album.PhotosetId.Length - 6);
        using (ZipFile zip = new ZipFile())
        {
            zip.CompressionMethod = CompressionMethod.None;
            zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
            zip.ParallelDeflateThreshold = -1;
            _map = new Dictionary<string, string>();
            foreach (Photo p in photos)
            {
                string pictureUrl = p.Large2048Url;
                if (string.IsNullOrEmpty(pictureUrl))
                    pictureUrl = p.Large1600Url;
                if (string.IsNullOrEmpty(pictureUrl))
                    pictureUrl = p.LargeUrl;

                string pictureName = picNamePref + "_" + (++picCount).ToString("000") + ".jpg";
                _map.Add(pictureName, pictureUrl);
                zip.AddEntry(pictureName, processPicture);
            }
            zip.Save(Response.OutputStream);
        }
        Response.Close();
    }
}
private volatile Dictionary<string, string> _map;
protected void processPicture(string pictureName, Stream output)
{
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(_map[pictureName]);
    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        using (Stream input = response.GetResponseStream())
        {
            byte[] buf = new byte[8092];
            int len;
            while ( (len = input.Read(buf, 0, buf.Length)) > 0)
                output.Write(buf, 0, len);
        }
        output.Flush();
    }
}

这样,Page_Load中的代码立即变为zip.Save(),下载开始(客户端显示“另存为”框,然后才从flickr中提取图像。

答案 3 :(得分:0)

此代码工作正常,但当我在Windows Azure上托管我的代码作为云服务时,它会破坏我的zip文件抛出消息无效文件

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

此代码在本地计算机上运行正常,但在服务器上部署后却没有。如果我的zip文件很大,它会破坏我的zip文件。