Servlet 3.0笔记之文件下载的那点事情

Servlet 3.0笔记之文件下载的那点事情

使用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,这里不再测试。
源码下载
参考资料:
  1. 通过零拷贝实现有效数据传输

  2. Most effective way to write File to ServletOutputStream
  3. Java NIO FileChannel versus FileOutputstream performance / usefulness
  4. NginxChsXSendfile

你可能感兴趣的:(Servlet 3.0笔记之文件下载的那点事情)