技术点描述
上传大文件并且没有内存压力的文件服务。
实现方案
Netty利用块(HttpChunk)对开发文件服务器端做了很好的实现:
内容存储方式:
内存、硬盘、混合存储。
参考源码包
- 源码包:
- 接口/抽象类/类说明:
- InterfaceHttpData(interface):实现该接口的对象可以用HttpPostRequestEncoder/Decoder编解码
- HttpDataFactory(interface):创建InterfaceHttpData对象
- HttpData(interface):继承InterfaceHttpData
- Attribute(interface):属性接口,继承HttpData
- FileUpLoad(interface):可存放在内存、临时文件或一些其他实现
DefaultHttpDataFactory
根据构造函数给FileUpload和Attribute提供默认的工厂。
- 重要方法:
- public DefaultHttpDataFactory(boolean useDisk) { this.useDisk = useDisk;
checkSize = false;
}
参数为true时设置为硬盘存储,为false内存存储 - public DefaultHttpDataFactory(long minSize) { useDisk = false;
checkSize = true;
this.minSize = minSize;
}
为混合存储模式,如果HttpData大于最小值将会存储在硬盘,否则存储在内存
- 重要属性 private final ConcurrentHashMap<HttpRequest, List<HttpData>> requestFileDeleteMap =
new ConcurrentHashMap<HttpRequest, List<HttpData>>();
保存所有HttpRequest的数据,直到调用cleanAllHttpDatas()
HttpPostRequestEncoder
说明:implements ChunkedInput(通过ChunkedWriteHandler传输)。将请求表单编码成POST。
主要方法:
HttpRequest finalizeRequest()
结束request头准备并返回将被发送的request.如果请求不需要分块,只有这一个请求被发送到服务端。
if (isMultipart) {
String value = HttpHeaders.Values.MULTIPART_FORM_DATA + "; " +
HttpHeaders.Values.BOUNDARY + "=" + multipartDataBoundary;
request.addHeader(HttpHeaders.Names.CONTENT_TYPE, value);
} else {
// Not multipart
request.addHeader(HttpHeaders.Names.CONTENT_TYPE,
HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED);
}
判断需不需要上传文件并进行设置。
DiskFileUpload
将FileUpload存储到真实文件。
重要属性:
baseDirectory 存储上传文件的目录
deleteOnExitTemporaryFile 在虚拟机推出访问是是否删除文件,默认值为true
Demo实现
HttpUploadClient
bodyRequestEncoder.addBodyAttribute("getform", "POST");
bodyRequestEncoder.addBodyAttribute("info", "first value");
bodyRequestEncoder.addBodyAttribute("secondinfo", "secondvalue&");
bodyRequestEncoder.addBodyAttribute("thirdinfo", textAreaLong);
bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", true);//false 以二进制方式传输,true以文本方式传输
bodyRequestEncoder.addBodyAttribute("Send", "Send");
添加HttpPostRequestEncoder上传的属性和文件
HttpUploadServerHandler
DiskFileUpload.deleteOnExitTemporaryFilefalse;
DiskFileUpload.baseDirectory"D:\\"; DiskAttribute.deleteOnExitTemporaryFiletrue; DiskAttribute.baseDirectory"D:\\";
设置退出时是否删除临时生成的属性文件和上传的文件。
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
if (decoder != null) {
decoder.cleanFiles();
}
}
连接关闭时同时清除与此请求相关的文件。
if (fileUpload.isCompleted()) {
fileUpload.renameTo(File dest);//当上传结束时可以将文件写到指定目录,但不建议这样做。
}
以下是renameTo方法的实现
FileInputStream inputStream = newfile);
FileOutputStream outputStream = new FileOutputStream(dest);
FileChannel in = inputStream.getChannel();
FileChannel out = outputStream.getChannel();
int chunkSize = 8196;
long position = 0;
whilesize) {
ifsize - position) {
int) (size - position);
}
position += in.transferTo(position, chunkSize , out);
}
在测试过程中发现这个方法很耗内存。
可以使用生成的临时文件,要清除requestFileDeleteMap中保存的数据。否则decoder.cleanFiles();会将临时文件删除
decoder.removeHttpDataFromClean(fileUpload);