我正在尝试创建任意大小的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上运行它。
答案 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文件。