使用Servlet 3.0 提供文件下载,当然了任何Servlet版本,都可以做到,这里仅仅作为知识的积累。下面贴出代码,防止忘却。
   一。常规方式文件下载示范
很普通,简单易用,不多说。一般情况下,够用了。
   二。伪零拷贝(zero copy)方式文件下载示范
变化不大,关键代码在于:利用
FileChannel.transferTo(long position, long count, WritableByteChannel target)
方法达到零拷贝(zero copy)目的。把HttpServletResponse的输出对象(ServletOutputStream)利用Channels.newChannel(OutputStream out)工具,构建一个WritableByteChannel对象而已。
OutputStream out = response.getOutputStream();
WritableByteChannel outChannel = Channels.newChannel(out);
测试代码:
心存疑虑的是,这个是伪零拷贝方式实现。查看一下Channels.newChannel的源码:
public static WritableByteChannel newChannel(final OutputStream out) {
 if (out == null) {
     throw new NullPointerException();
 }
        if (out instanceof FileOutputStream &&
     FileOutputStream.class.equals(out.getClass())) {
                return ((FileOutputStream)out).getChannel();
        }
 return new WritableByteChannelImpl(out);
    }
因为输入的方法参数为ServletOutputStream类型实例,因此只能返回一个新构建的WritableByteChannelImpl对象。具体构建:
private static class WritableByteChannelImpl
        extends AbstractInterruptibleChannel // Not really interruptible
        implements WritableByteChannel
    {
        OutputStream out;
        private static final int TRANSFER_SIZE = 8192;
        private byte buf[] = new byte[0];
        private boolean open = true;
        private Object writeLock = new Object();
        WritableByteChannelImpl(OutputStream out) {
            this.out = out;
        }
        public int write(ByteBuffer src) throws IOException {
            int len = src.remaining();
            int totalWritten = 0;
            synchronized (writeLock) {
                while (totalWritten < len) {
                    int bytesToWrite = Math.min((len - totalWritten),
                                                TRANSFER_SIZE);
                    if (buf.length < bytesToWrite)
                        buf = new byte[bytesToWrite];
                    src.get(buf, 0, bytesToWrite);
                    try {
                        begin();
                        out.write(buf, 0, bytesToWrite);
                    } finally {
                        end(bytesToWrite > 0);
                    }
      totalWritten += bytesToWrite;
                }
                return totalWritten;
            }
        }
        protected void implCloseChannel() throws IOException {
            out.close();
            open = false;
        }
    }
很显然,也是属于内存类型的拷贝了,只能算作伪零拷贝实现了。
  三。转发到文件服务器上
一般常识为,让最擅长的人来做最擅长的事情,是为高效。使用类如Nginx高效的Web服务器专门处理文件下载业务,达到零拷贝的目的,也是最佳搭配组合。Nginx可以利用header元数据X-Accel-Redirect来控制文件下载行为,甚是不错。利用JAVA进行业务逻辑判断,若符合规则,则提交给Nginx进行处理文件的下载,否则,返回给终端用户权限不够等信息。
用于控制用户是否具有资格进行文件下载业务的控制器:
当然,这个仅仅用于演示,逻辑简单。因为需要和nginx服务器进行配合,构建一个Server,其配置文件:
我们在nginx配置文件中,设置/dowloads/目录是不允许直接访问的,必须经由/download/控制器进行转发方可。经测试,中文名不会出现乱码问题,保存的文件也是我们所请求的文件,同名,也不会出现乱码问题。但是,若在后台获取文件名,用于显示/输出,则需要从ISO-8859-1解码成GBK编码方可。
但这样做,可能被绑定到某个类型的服务器,但也值得。实际上切换到Apache也是很简单的。
PS : Apache服务器诶则对应X-Sendfile头部属性,因很长时间不再使用Apache,这里不再测试。
源码下载