Netty接收HTTP文件上传及文件下载
文件上传
这个处理器的原理是接收HttpObject对象,按照HttpRequest,HttpContent来做处理,文件内容是在HttpContent消息带来的。
然后在HttpContent中一个chunk一个chunk读,chunk大小可以在初始化HttpServerCodec时设置。将每个chunk交个httpDecoder复制一份,当读到LastHttpContent对象时,表明上传结束,可以将httpDecoder中缓存的文件通过HttpDataFactory写到磁盘上,然后在删除缓存的HttpContent对象。
@Slf4j
public class HttUploadHandler extends SimpleChannelInboundHandler<HttpObject> {
public HttUploadHandler() {
super(false);
}
private static final HttpDataFactory factory = new DefaultHttpDataFactory(true);
private static final String FILE_UPLOAD = "/data/";
private static final String URI = "/upload";
private HttpPostRequestDecoder httpDecoder;
HttpRequest request;
@Override
protected void channelRead0(final ChannelHandlerContext ctx, final HttpObject httpObject)
throws Exception {
if (httpObject instanceof HttpRequest) {
request = (HttpRequest) httpObject;
if (request.uri().startsWith(URI) && request.method().equals(HttpMethod.POST)) {
httpDecoder = new HttpPostRequestDecoder(factory, request);
httpDecoder.setDiscardThreshold(0);
} else {
//传递给下一个Handler
ctx.fireChannelRead(httpObject);
}
}
if (httpObject instanceof HttpContent) {
if (httpDecoder != null) {
final HttpContent chunk = (HttpContent) httpObject;
httpDecoder.offer(chunk);
if (chunk instanceof LastHttpContent) {
writeChunk(ctx);
//关闭httpDecoder
httpDecoder.destroy();
httpDecoder = null;
}
ReferenceCountUtil.release(httpObject);
} else {
ctx.fireChannelRead(httpObject);
}
}
}
private void writeChunk(ChannelHandlerContext ctx) throws IOException {
while (httpDecoder.hasNext()) {
InterfaceHttpData data = httpDecoder.next();
if (data != null && HttpDataType.FileUpload.equals(data.getHttpDataType())) {
final FileUpload fileUpload = (FileUpload) data;
final File file = new File(FILE_UPLOAD + fileUpload.getFilename());
log.info("upload file: {}", file);
try (FileChannel inputChannel = new FileInputStream(fileUpload.getFile()).getChannel();
FileChannel outputChannel = new FileOutputStream(file).getChannel()) {
outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
ResponseUtil.response(ctx, request, new GeneralResponse(HttpResponseStatus.OK, "SUCCESS", null));
}
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.warn("{}", cause);
ctx.channel().close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (httpDecoder != null) {
httpDecoder.cleanFiles();
}
}
}
文件下载
做了改动:
- 为了更高效的传输大数据,实例中用到了ChunkedWriteHandler编码器,它提供了以zero-memory-copy方式写文件。
- 通过ChannelProgressiveFutureListener对文件下载过程进行监听。
// 新增ChunkedHandler,主要作用是支持异步发送大的码流(例如大文件传输),但是不占用过多的内存,防止发生java内存溢出错误
ch.pipeline().addLast(new ChunkedWriteHandler());
// 用于下载文件
ch.pipeline().addLast(new HttpDownloadHandler());
@Slf4j
public class HttpDownloadHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
public HttpDownloadHandler() {
super(false);
}
private String filePath = "/data/body.csv";
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
String uri = request.uri();
if (uri.startsWith("/download") && request.method().equals(HttpMethod.GET)) {
GeneralResponse generalResponse = null;
File file = new File(filePath);
try {
final RandomAccessFile raf = new RandomAccessFile(file, "r");
long fileLength = raf.length();
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK);
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/octet-stream");
response.headers().add(HttpHeaderNames.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", file.getName()));
ctx.write(response);
ChannelFuture sendFileFuture = ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
@Override
public void operationComplete(ChannelProgressiveFuture future)
throws Exception {
log.info("file {} transfer complete.", file.getName());
raf.close();
}
@Override
public void operationProgressed(ChannelProgressiveFuture future,
long progress, long total) throws Exception {
if (total < 0) {
log.warn("file {} transfer progress: {}", file.getName(), progress);
} else {
log.debug("file {} transfer progress: {}/{}", file.getName(), progress, total);
}
}
});
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
} catch (FileNotFoundException e) {
log.warn("file {} not found", file.getPath());
generalResponse = new GeneralResponse(HttpResponseStatus.NOT_FOUND, String.format("file %s not found", file.getPath()), null);
ResponseUtil.response(ctx, request, generalResponse);
} catch (IOException e) {
log.warn("file {} has a IOException: {}", file.getName(), e.getMessage());
generalResponse = new GeneralResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, String.format("读取 file %s 发生异常", filePath), null);
ResponseUtil.response(ctx, request, generalResponse);
}
} else {
ctx.fireChannelRead(request);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
log.warn("{}", e);
ctx.close();
}
}
下载文件遇到的坑
由于RandomAccessFile
是一种文件资源,所以我习惯性的在最后关闭文件资源,采用的是Java7的 try-with-resources
语法,于是问题就出现了,由于 ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
是异步的,在我关闭RandomAccessFile
时,文件还未传输完毕,就会导致下载文件停止。
代码放在: https://github.com/morethink/code/tree/master/java/netty-example
Netty接收HTTP文件上传及文件下载的更多相关文章
- struts2的文件上传和文件下载
实现使用Struts2文件上传和文件下载: 注意点: (1)对应表单的file1和私有成员变量的名称必须一致 <input type="file" name="fi ...
- 分享知识-快乐自己:SpringMvc中的单多文件上传及文件下载
摘要:SpringMvc中的单多文件上传及文件下载:(以下是核心代码(拿过去直接能用)不谢) <!--设置文件上传需要的jar--> <dependency> <grou ...
- Struts2文件上传和文件下载
一.单个文件上传 文件上传需要两个jar包: 首先制作一个简单的页面,用于实现文件上传 <h1>单个文件上传</h1> <s:form action="uplo ...
- Struts2 文件上传和文件下载
一.单个文件上传 文件上传需要两个jar包: 首先制作一个简单的页面,用于实现文件上传 <h1>单个文件上传</h1> <s:form action="uplo ...
- struts文件上传、文件下载
文件上传 如果在表单中上传文件,表单的enctype属性为multipart/form-data struts默认上传文件大小为2M,如果需要修改,在配置文件中设置 <constant name ...
- struts2实现文件上传、多文件上传和文件下载
总结的两个问题,就是struts2上传下载的时候对属性名配置要求非常严格: 第一:上传的时候 private File file; private String fileContentType; pr ...
- struts2 笔记02 文件上传、文件下载、类型转换器、国际化的支持
Struts2的上传 1. Struts2默认采用了apache commons-fileupload 2. Struts2支持三种类型的上传组件 3. 需要引入commons-fileupload ...
- Web---演示Servlet的相关类、表单多参数接收、文件上传简单入门
说明: Servlet的其他相关类: ServletConfig – 代表Servlet的初始化配置参数. ServletContext – 代表整个Web项目. ServletRequest – 代 ...
- struts2中的文件上传,文件下载
文件上传: Servlet中的文件上传回顾 前台页面 1.提交方式post 2.表单类型 multipart/form-data 3.input type=file 表单输入项 后台 apache提交 ...
随机推荐
- DispatcherServlet的作用
DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好 ...
- 定时任务中的备份不同的数据库中的所有的表,每个表使用单独的sql备份文件
#! /bin/bash # 指定用户 USER=root # 指定密码 PASS=123456 # 指定主机地址 HOST=localhost # 指定备份的目录 BACKUP=/backup/sq ...
- 更改数据库字符集编码引起的问题、textarea标签输出内容时不能顶格(左对齐)输出
用svn拉下来的项目,部署好的Oracle数据库(gbk编码),用tomcat部署好并发布项目,当访问相关网页时,出现乱码.于是把Oracle的字符编码改成utf8,tomcat也改成UTF-8,重新 ...
- LeetCode题解:(114) Flatten Binary Tree to Linked List
题目说明 Given a binary tree, flatten it to a linked list in-place. For example, Given 1 / \ 2 5 / \ \ 3 ...
- vm的三种网络模式
Vm网卡的模式:网络地址转换模式(nat),仅主机(host-only),桥接模式(Brideged) VMware 的几个虚拟设备: ■ VMnet0:这是 VMware 用于虚拟桥接网络下的虚拟交 ...
- 笔记之分布式文件系统(DFS)
不知何故,老外都挺喜欢使用DFS,但是国内公司用这个的不多.一个具体的需求就是,备份服务器在国外,所以启用DFS把国内的数据同步一份到国外进行备份.最近有机会接触DFS,把一些心得体会记录一下. 1. ...
- 题解 P2026 【求一次函数解析式】
高中方式轻松解决这个模拟题. 首先我们了解斜率的简单求法: \[k= {y2-y1 \over x2-x1}{=}{\Delta y \over \Delta x}\] 然后我们了解到让我们求解一次函 ...
- 【模板】ISAP最大流
题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...
- iptables之NAT代理-内网访问外网
1.前言 本文使用NAT功能:内网服务器,想上网又不想被攻击. 工作原理:内网主机向公网发送数据包时,由于目的主机跟源主机不在同一网段,所以数据包暂时发往内网默认网关处理,而本网段的主机对此数据包不做 ...
- 解题:AHOI2017/HNOI2017 礼物
题面 先不管旋转操作,只考虑增加亮度这个操作.显然这个玩意的影响是相对于$x,y$固定的,所以可以枚举增加的亮度然后O(1)算出来.为了方便我们把这个操作换种方法表示,只让一个手环改变$[-m,m]$ ...