• SpringBoot集成Netty实现文件传输


    实现浏览本地文件目录,实现文件夹目录的跳转和文件的下载

    添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.1.Final</version>
    </dependency>
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>${commons.lang.version}</version>
    </dependency>

    排除tomcat的依赖

    Netty Http服务端编写:

    handler 处理类

    @Component
    @Slf4j
    @ChannelHandler.Sharable //@Sharable 注解用来说明ChannelHandler是否可以在多个channel直接共享使用
    public class FileServerHandler extends ChannelInboundHandlerAdapter {
    
       // private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\.]*");
    
        //文件存放路径
        @Value("${netty.file.path:}")
        String path;
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            try{
                if (msg instanceof FullHttpRequest) {
                    FullHttpRequest req = (FullHttpRequest) msg;
                    if(req.method() != HttpMethod.GET) {
                        sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
                        return;
                    }
                    String url = req.uri();
                    File file = new File(path + url);
                    if(file.exists()){
                        if(file.isDirectory()){
                            if(url.endsWith("/")) {
                                sendListing(ctx, file);
                            }else{
                                sendRedirect(ctx, url + "/");
                            }
                            return;
                        }else {
                            transferFile( file,  ctx);
                        }
                    }else{
                        sendError(ctx, HttpResponseStatus.NOT_FOUND);
                    }
                }
            }catch(Exception e){
                log.error("Exception:{}",e);
                sendError(ctx, HttpResponseStatus.BAD_REQUEST);
            }
        }
    
        /**
         * 传输文件
         * @param file
         * @param ctx
         */
        private void transferFile(File file, ChannelHandlerContext ctx){
            try{
                RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
                long fileLength = randomAccessFile.length();
                HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
                ctx.write(response);
                ChannelFuture sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
                addListener( sendFileFuture);
                ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
                lastContentFuture.addListener(ChannelFutureListener.CLOSE);
            }catch (Exception e){
                log.error("Exception:{}",e);
            }
        }
    
        /**
         * 监听传输状态
         * @param sendFileFuture
         */
        private void addListener( ChannelFuture sendFileFuture){
            sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
                    @Override
                    public void operationComplete(ChannelProgressiveFuture future)
                            throws Exception {
                        log.debug("Transfer complete.");
                    }
                    @Override
                    public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception {
                        if(total < 0){
                            log.debug("Transfer progress: " + progress);
                        }else{
                            log.debug("Transfer progress: " + progress + "/" + total);
                        }
                    }
            });
        }
    
    
        /**
         * 请求为目录时,显示文件列表
         * @param ctx
         * @param dir
         */
        private static void sendListing(ChannelHandlerContext ctx, File dir){
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
    
            String dirPath = dir.getPath();
            StringBuilder buf = new StringBuilder();
    
            buf.append("<!DOCTYPE html>
    ");
            buf.append("<html><head><title>");
            buf.append(dirPath);
            buf.append("目录:");
            buf.append("</title></head><body>
    ");
    
            buf.append("<h3>");
            buf.append(dirPath).append(" 目录:");
            buf.append("</h3>
    ");
            buf.append("<ul>");
            buf.append("<li>链接:<a href=" ../")..</a></li>
    ");
            for (File f : dir.listFiles()) {
                if(f.isHidden() || !f.canRead()) {
                    continue;
                }
                String name = f.getName();
                /*if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
                    continue;
                }*/
                buf.append("<li>链接:<a href="");
                buf.append(name);
                buf.append("">");
                buf.append(name);
                buf.append("</a></li>
    ");
            }
            buf.append("</ul></body></html>
    ");
            ByteBuf buffer = Unpooled.copiedBuffer(buf,CharsetUtil.UTF_8);
            response.content().writeBytes(buffer);
            buffer.release();
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    
        /**
         * 跳转链接
         * @param ctx
         * @param newUri
         */
        private static void sendRedirect(ChannelHandlerContext ctx, String newUri){
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND);
            response.headers().set(HttpHeaderNames.LOCATION, newUri);
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    
        /**
         * 失败响应
         * @param ctx
         * @param status
         */
        private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status){
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                    Unpooled.copiedBuffer("Failure: " + status.toString() + "
    ", CharsetUtil.UTF_8));
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    
    
    }

    ChannelPipeline 实现:

    @Component
    @ConditionalOnProperty(  //配置文件属性是否为true
            value = {"netty.file.enabled"},
            matchIfMissing = false
    )
    public class FilePipeline extends ChannelInitializer<SocketChannel> {
    
        @Autowired
        FileServerHandler fleServerHandler;
    
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline p = socketChannel.pipeline();
            p.addLast("http-decoder", new HttpRequestDecoder());
            p.addLast("http-aggregator", new HttpObjectAggregator(65536));
            p.addLast("http-encoder", new HttpResponseEncoder());
            p.addLast("http-chunked", new ChunkedWriteHandler());
            p.addLast("fileServerHandler",fleServerHandler);
        }
    }

    服务实现:

    @Configuration
    @EnableConfigurationProperties({NettyFileProperties.class})
    @ConditionalOnProperty(  //配置文件属性是否为true
            value = {"netty.file.enabled"},
            matchIfMissing = false
    )
    @Slf4j
    public class FileServer {
        @Autowired
        FilePipeline filePipeline;
    
        @Autowired
        NettyFileProperties nettyFileProperties;
    
        @Bean("starFileServer")
        public String start() {
            Thread thread =  new Thread(() -> {
                NioEventLoopGroup bossGroup = new NioEventLoopGroup(nettyFileProperties.getBossThreads());
                NioEventLoopGroup workerGroup = new NioEventLoopGroup(nettyFileProperties.getWorkThreads());
                try {
                    log.info("start netty [FileServer] server ,port: " + nettyFileProperties.getPort());
                    ServerBootstrap boot = new ServerBootstrap();
                    options(boot).group(bossGroup, workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .handler(new LoggingHandler(LogLevel.INFO))
                            .childHandler(filePipeline);
                    Channel ch = null;
                  //是否绑定IP
                    if(StringUtils.isNotEmpty(nettyFileProperties.getBindIp())){
                        ch = boot.bind(nettyFileProperties.getBindIp(),nettyFileProperties.getPort()).sync().channel();
                    }else{
                        ch = boot.bind(nettyFileProperties.getPort()).sync().channel();
                    }
                    ch.closeFuture().sync();
                } catch (InterruptedException e) {
                    log.error("启动NettyServer错误", e);
                } finally {
                    bossGroup.shutdownGracefully();
                    workerGroup.shutdownGracefully();
                }
            });
            thread.setName("File_Server");
            thread.start();
            return "file start";
        }
    
    
        private ServerBootstrap options(ServerBootstrap boot) {
     /*       boot.option(ChannelOption.SO_BACKLOG, 1024)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);*/
            return boot;
        }
    }

    启动配置:

    ---application.yml
    spring.profiles.active: file
    
    ---application-file.yml
    netty:
       file:
         enabled: true
         path: d:
         port: 3456

    测试

    在浏览器打开http://127.0.0.1:3456/

  • 相关阅读:
    javascript 函数介绍
    javascript 日期对象(date)详解
    js 计算过去和未来的时间距离现在多少天?
    phpcms 模板常用标签指南
    checkbox radio 样式重写
    datatable-固定行固定列
    表格-固定列 固定行
    axios 请求数据 入门级介绍
    图片上传的问题-偶现base64图片 小黑块问题
    cropper.js图片裁剪——第二弹
  • 原文地址:https://www.cnblogs.com/47Gamer/p/13853126.html
Copyright © 2020-2023  润新知