【问题标题】:Can I do direct streaming of ZipFile in vaadin?我可以在 vaadin 中直接流式传输 ZipFile 吗?
【发布时间】:2016-08-07 06:15:27
【问题描述】:

我的应用程序的当前架构不允许我在服务器端存储文件并创建指向该存储文件的链接。那么是否有任何其他选项(或代码 sn-p)可以直接流式传输 ZipFile 并将其存储在客户端?

编辑: 我想我的问题被误解了。我收到了压缩文件并将其存储在客户端的答案。我已经做到了。以下是示例用例的主要关注点:

场景: 用户有大约 5000 条记录(每条大约 1 MB 大小),用户想要下载以 ZIP 格式压缩的每 5000 条记录的子记录(CSV 格式)。所有 CSV 文件都是即时生成的。

方法: 由于 ZIP 文件的大小可以达到 5 GB,所以我采取了将文件内容直接流式传输到客户端创建的 ZIP 文件的方法。我为此使用了 PipeInputStream 和 PipeOutputStream。

结果: 由于我是 vaadin 的新手,因此我在上述方法中没有成功,因此寻找任何支持将 ZIP 文件(无论大小如何)直接流式传输到客户端的建议/代码 sn-ps。

我想我现在很清楚了。

【问题讨论】:

  • 在链接上:vaadin.com/forum#!/thread/2824570你可以找到关于这个话题的讨论。
  • thisZipOutputStream 一起使用有帮助吗?
  • @DraganRadevic 感谢分享我已经探索过该链接的链接,但解决方案说我需要将文件存储在我的服务器中的某个位置并创建链接,以便用户可以使用该链接下载。跨度>
  • @SteffenHarbich 不。
  • 您可以在内存中创建ZipOutputStream,然后让用户下载文件。你有更多的选择。创建 BinaryInputStream(静态内容)或 PipedInputStream 如果您想动态下载 zip(同时为大文件创建)

标签: java vaadin


【解决方案1】:

您无需创建 ZIP 包即可通过 HTTP 压缩单个文件。所有体面的浏览器都支持读取压缩的 HTTP 响应[1],所以让我们利用它:

@Theme("mytheme")
@Widgetset("org.test.MyAppWidgetset")
public class MyUI extends UI {

    @Override
    protected void init(VaadinRequest vaadinRequest) {
        Button downloadButton = new Button("Download File");

        StreamResource myResource = createResource();
        FileDownloader fileDownloader = new FileDownloader(myResource);
        fileDownloader.extend(downloadButton);

        setContent(downloadButton);
    }

    private StreamResource createResource() {
        return new StreamResource(new StreamResource.StreamSource() {
            @Override
            public InputStream getStream() {
                InputStream s = null;
                try {
                    s = new DeflaterInputStream(new FileInputStream("/tmp/some.csv"));
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                return s;

            }
        }, "some.csv") {
            @Override
            public DownloadStream getStream() {
                DownloadStream ds =  super.getStream();
                ds.setParameter("Content-Encoding", "deflate");
                return ds;
            }
        };
    }

    @WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
    @VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
    public static class MyUIServlet extends VaadinServlet {
    }
}

浏览器会下载压缩流,但会保存原始文件,即时解压。

优点:这非常节省内存。

缺点:您不能将多个文件合并到一个 ZIP 存档中。

[1]https://en.wikipedia.org/wiki/HTTP_compression

【讨论】:

  • 感谢您的回复。但我猜当只有一个文件时,上述功能有效,这里我有多个文件。
【解决方案2】:

您的问题不清楚。总是,在发布问题时提供一些研究。现在,问题是您要从哪里流式传输一些 URL、文件共享、数据库。您使用的是什么 Vaadin 版本 66?

已编辑问题有两部分。

  1. 直接从网站下载 Zip 文件。下面是下载any文件的示例代码sn-p。

    import java.io.BufferedInputStream;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.InputStream;
    import java.net.URL;
    
    import javax.servlet.annotation.WebServlet;
    
    import com.vaadin.annotations.Theme;
    import com.vaadin.annotations.VaadinServletConfiguration;
    import com.vaadin.server.FileDownloader;
    import com.vaadin.server.StreamResource;
    import com.vaadin.server.StreamResource.StreamSource;
    import com.vaadin.server.VaadinRequest;
    import com.vaadin.server.VaadinServlet;
    import com.vaadin.ui.Button;
    import com.vaadin.ui.UI;
    
    @SuppressWarnings("serial")
    @Theme("vaadintest")
    public class VaadintestUI extends UI {
    
        @WebServlet(value = "/*", asyncSupported = true)
        @VaadinServletConfiguration(productionMode = false, ui = VaadintestUI.class)
        public static class Servlet extends VaadinServlet {
        }
    
        protected void init(VaadinRequest request) {
            Button downloadButton = new Button("Download Zip File");
    
            StreamResource myResource = createResource();
            FileDownloader fileDownloader = new FileDownloader(myResource);
            fileDownloader.extend(downloadButton);
    
            setContent(downloadButton);
        }
    
    private StreamResource createResource() {
        return new StreamResource(new StreamSource() {
            @Override
            public InputStream getStream() {
    
                BufferedInputStream in = null;
                ByteArrayOutputStream bao = null;
                try {
                    in = new BufferedInputStream(
                            new URL("http://www-us.apache.org/dist/commons/io/binaries/commons-io-2.5-bin.zip" + "")
                                    .openStream());
                    byte[] buff = new byte[8000];
                    int bytesRead = 0;
                    bao = new ByteArrayOutputStream();
    
                    while ((bytesRead = in.read(buff)) != -1) {
                        bao.write(buff, 0, bytesRead);
                    }
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return new ByteArrayInputStream(bao.toByteArray());
    
            }
        }, "somefile.zip");
    }
    

    }

  2. 压缩巨大的 CSV 文件并下载

参考:Best Practices to Create and Download a huge ZIP (from several BLOBs) in a WebApp

在服务器端压缩和存储很容易。我不认为由于访问问题直接存储到客户端磁盘会更容易(可以使用 OutputStream)。您可以在服务器端将其压缩并共享到压缩文件客户端的链接(文件需要存储在 WebApp 文件夹中)。

另外,您可以使用压缩后的输出流来下载它。下面是它的简单示例。

参考:http://www.mkyong.com/java/how-to-download-file-from-website-java-jsp/

编辑问题,给出实际场景:这样就很容易分享代码sn-p

您可以参考的一些有用的链接如下:

  1. Best Practices to Create and Download a huge ZIP (from several BLOBs) in a WebApp
  2. How to split a huge zip file into multiple volumes?
  3. Zip files with Java: Is there a limit?
  4. Very large zip file (> 50GB) --> ZipException: invalid CEN header
  5. Java multithreaded file downloading performance
  6. In Java: How to zip file from byte[] array?
  7. Uncompressing a ZIP file in memory in Java
  8. Best way to detect if a stream is zipped in Java

【讨论】:

  • 感谢您的回复。对于我的问题听起来不清楚,我深表歉意。请注意,我已经成功创建了 csv 文件,将它们压缩并下载,但问题是文件的大小可能为 5 GB。根据业务需求,我必须创建多个 CSV 文件,将其压缩并下载 zip 文件。整个活动应在一个会话中涵盖。一个用例是有 5000 个 CSV 文件,每个文件的大小可能是 1 MB,并且可以在功能投入生产时增加。希望这会有所帮助。
【解决方案3】:

看起来 apache.commons.compress ZipArchiveOutputStream 是您问题的答案。但是您应该知道,您的情况有一系列限制(没有 RandomAccessFile)。简单示例(使用标准输出重定向:java -cp Test > test.zip):

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import java.util.Arrays;

public class Test {

public static void main(String[] args) throws Exception {
      ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(System.out);
      byte[] data = new byte[1024*1024];
      for(byte i=49; i<120; i++) {
          Arrays.fill(data, i);
          ArchiveEntry file1 = new ZipArchiveEntry("file" + i + ".txt");
          zipOut.putArchiveEntry(file1);
          zipOut.write(data);
          zipOut.closeArchiveEntry();
       }
       zipOut.finish();
       zipOut.close();
    }
}

【讨论】:

    【解决方案4】:

    我可以看到答案已被系统自动接受(不是我),因为它被投票了 3 次。我对此没有问题,但我不想让那些(以防万一)登陆这里寻找答案的人感到困惑。我很欣赏@Pratyush Kumar Singh 给出的答案(因为它可以帮助您探索更多)但很抱歉 StackOverFlow 我不能接受这个答案,因为我的问题没有得到回答还。我仍在努力,一旦通过 POC 解决,我将发布答案。

    【讨论】:

    • 嗨@Milesh,在深入了解您的问题后,我发现您的问题是基本上市场上所有的java ZIP实现都只支持推送式API(必须写入临时OutputStream,然后转换它到 InputStream)。本着 SequenceInputStream 的精神,您的问题可以通过拉式 API 来解决,但我找不到这样的实现。我正在考虑自己实现它:)
    • @tair 我完全同意你的看法。一旦你发现任何有用的东西,请与我分享:)
    • 嘿@Milesh 我终于明白了!看看我的第二个答案!
    【解决方案5】:

    Java JDK 和 Apache commons-compress 不允许您懒惰地流式传输 ZIP 存档,因此我实现了一个 Java ZIP 库 [1] 来处理这个问题。当前的限制是它不支持 ZIP64 扩展,这意味着它不能压缩大于 4 GiB 的文件,也不能生成大于 4 GiB 的档案。我正在努力。

    [1]https://github.com/tsabirgaliev/zip

    【讨论】:

      猜你喜欢
      • 2014-03-03
      • 2016-12-26
      • 1970-01-01
      • 2015-05-05
      • 2015-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-15
      相关资源
      最近更新 更多