2012-06-08 61 views
0

我有一个允许文件上传的Yesod应用程序(但问题比这个更普遍)。我也允许文件dowloads。我想让用户用单个链接下载多个文件。根据这个问题:How to download multiple files with one HTTP request?唯一的解决方案似乎是创建文件存档与所有的文件里面。在Web服务器上动态创建流式文件存档

我想在Haskell的常量内存中使用Hackage的库来完成它,而不写入磁盘或执行外部程序。

尤其是以下的非解决方案:

  • 调用外部程序创建存档:该文件可能是在磁盘或通过一些偏远的网址上访问某些数据库。该文件系统可能是“只读”的。由于安全原因,执行外部程序可能不可能。外部程序使部署复杂化。

  • 从源文件在磁盘上创建临时存档:请参阅上面的“只读”文件系统。实际上写入磁盘的效率也非常低。

  • 在内存中创建完整的存档并在之后进行提供:文件可能非常大(可能是CD映像)和多个文件。需要的内存太大了。

+0

如果您在内存中执行此操作,并且有10个用户每个都下载5x 100MB文件,则您需要5GB的RAM才能进行归档。似乎没有特别的可扩展性。 – Polynomial

+2

@Polynomial,如果你阅读这个问题,@Tener明确不想将整个档案保存在内存中。有很多'gzip'和'zip'的实现可以实时压缩内容并对其进行流式处理。 – dflemstr

+0

@dflemstr哎呀,错过了问题的最后部分。尽管如此,这似乎是在即使是中等负载的情况下也会摧毁服务器的CPU。 – Polynomial

回答

1

这很大程度上取决于哪些文件要支持格式文件(.zip,.tar.gz以及TAR.BZ2是最常见的),但你可以使用zip-archive库创建的.zip档案。这些档案以懒惰字节字符串的形式生成,这意味着它们将被即时生成。唯一棘手的部分是产生一个类型为Archive的值与正确的内容。它可能例如是这样的:

import Codec.Archive.Zip 

-- ... and in your code: 
let archiveTemplate = 
    Archive 
    { zComment = ByteString.pack "Downloaded from mysite.com" 
    , zSignature = Nothing 
    , zEntries = [] 
    } 

let filesIWantToInclude = ["foo.png", "bar.iso"] 
entries <- forM filesIWantToInclude $ readEntry [] 
let archive = foldr addEntryToArchive archiveTemplate entries 

let byteString = fromArchive archive 
-- Now you can send the byteString over the network, or something. 

如果你没有在数据库或东西,你要压缩的文件系统上的文件,而是文件,你可以手动建立Entry类型的值填写正确的字段。您只需要表示要压缩的数据的懒惰ByteString,仅此而已;那么您可以使用toEntry函数生成一个条目。值得一提的是,Entry中的eRelativePath字段是.zip存档内文件的相对路径,而不是文件系统中的实际相对路径。

+0

我确实看过这个图书馆。乍看起来,它似乎是一个非解决方案。我没有测试它,但从源文件中查找readEntry使用toEntry,它试图变得聪明,只在需要时才进行压缩。测试是通过尝试压缩整个文件并查看是否有帮助。我认为这会使整个文件存储在内存中,因此整个库会消耗太多内存。虽然可以手动创建条目。我需要用于crc32计算的代码。 – Tener

+0

您可以复制内部压缩方法并省略大小比较... – dflemstr

相关问题