SpringBoot集成Netty实现文件传输

共 5398字,需浏览 11分钟

 ·

2020-10-24 05:30

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

  作者 |  47号Gamer丶

来源 |  urlify.cn/bIzUZb

66套java从入门到精通实战课程分享

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

添加依赖:


    org.springframework.boot
    spring-boot-starter-web
    
        
            org.springframework.boot
            spring-boot-starter-tomcat
        

    



    io.netty
    netty-all
    4.1.1.Final


    commons-lang
    commons-lang
    ${commons.lang.version}

排除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("\r\n");
        buf.append(""</span>);<br>        buf.append(dirPath);<br>        buf.append(<span style="color: #98c379;line-height: 26px;">"目录:"</span>);<br>        buf.append(<span style="color: #98c379;line-height: 26px;">"\r\n");

        buf.append("

");
        buf.append(dirPath).append(" 目录:");
        buf.append("

\r\n"
);
        buf.append("\r\n");
        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() + "\r\n", 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 {

    @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/

 



粉丝福利:108本java从入门到大神精选电子书领取

???

?长按上方锋哥微信二维码 2 秒
备注「1234」即可获取资料以及
可以进入java1234官方微信群



感谢点赞支持下哈 

浏览 71
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报