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();
        }
    }

}

文件下载

参考官方Demo: https://github.com/netty/netty/blob/4.1/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java

做了改动:

  • 为了更高效的传输大数据,实例中用到了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

转载于:https://www.cnblogs.com/morethink/p/9787313.html

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个 Netty 传输文件的示例代码,包括发送和接收。这个示例代码使用 Java 语言编写,可以在 Windows、Linux 等各种操作系统上运行。 发送端的代码: ```java import io.netty.bootstrap.Bootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.io.File; import java.io.FileInputStream; import java.util.concurrent.TimeUnit; public class FileSender { private String host; private int port; public FileSender(String host, int port) { this.host = host; this.port = port; } public void sendFile(String filePath) throws Exception { final File file = new File(filePath); Bootstrap b = new Bootstrap(); b.group(new NioEventLoopGroup()) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringEncoder(), new StringDecoder(), new FileSenderHandler(file)); } }); ChannelFuture future = b.connect(host, port).sync(); future.channel().closeFuture().sync(); } private class FileSenderHandler extends ChannelInboundHandlerAdapter { private File file; private FileInputStream fis; public FileSenderHandler(File file) { this.file = file; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { String fileName = file.getName() + "\n"; String fileSize = String.valueOf(file.length()) + "\n"; ctx.writeAndFlush(Unpooled.copiedBuffer(fileName.getBytes())); ctx.writeAndFlush(Unpooled.copiedBuffer(fileSize.getBytes())); fis = new FileInputStream(file); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof String) { String response = (String) msg; if ("OK\n".equals(response)) { // 服务端已经准备好接收文件 byte[] buf = new byte[1024]; int len = 0; while ((len = fis.read(buf)) != -1) { ctx.writeAndFlush(Unpooled.copiedBuffer(buf, 0, len)); } TimeUnit.MILLISECONDS.sleep(500); // 等待一段时间确保数据已经被发送完毕 ctx.close(); // 发送完毕后关闭 channel } else { // 服务端出现错误 System.out.println("Server error: " + response); ctx.close(); } } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { fis.close(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } public static void main(String[] args) throws Exception { FileSender sender = new FileSender("127.0.0.1", 9090); sender.sendFile("D:/test.zip"); } } ``` 接收端的代码: ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.io.File; import java.io.FileOutputStream; public class FileReceiver { private int port; public FileReceiver(int port) { this.port = port; } public void start() throws Exception { ServerBootstrap b = new ServerBootstrap(); b.group(new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringEncoder(), new StringDecoder(), new FileReceiveHandler()); } }); ChannelFuture future = b.bind(port).sync(); System.out.println("FileReceiver started and listening on " + port); future.channel().closeFuture().sync(); } private class FileReceiveHandler extends ChannelInboundHandlerAdapter { private FileOutputStream fos; private String fileName; private long fileSize; private long receivedSize; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof String) { String response = (String) msg; if (fileName == null) { fileName = response.trim(); return; } if (fileSize <= 0) { fileSize = Long.parseLong(response.trim()); File file = new File("./" + fileName); fos = new FileOutputStream(file); ctx.writeAndFlush(Unpooled.copiedBuffer("OK\n".getBytes())); return; } } if (fos == null) { System.out.println("FileReceiver error: invalid message received"); return; } byte[] data = (byte[]) msg; fos.write(data); // 写入文件 receivedSize += data.length; if (receivedSize == fileSize) { // 文件接收完成 fos.close(); System.out.println("FileReceiver: file " + fileName + " received, size=" + fileSize); ctx.close(); // 关闭 channel } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } public static void main(String[] args) throws Exception { FileReceiver receiver = new FileReceiver(9090); receiver.start(); } } ``` 这个示例代码中实现了一个简单的客户端和服务端,可以在两者之间传输文件。通过该示例代码,你可以学习到如何使用 Netty 实现文件传输。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值