Springboot2(25)集成netty实现文件传输

源码地址

springboot2教程系列
其它netty文件有博客

Springboot2(24)集成netty实现http服务(类似SpingMvc的contoller层实现)

Springboot2(25)集成netty实现文件传输

Springboot2(26)集成netty实现websocket通讯

Springboot2(27)集成netty实现反向代理(内网穿透)

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

添加依赖


    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><span class="token punctuation">)</span><span class="token punctuation">;</span>
        buf<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>dirPath<span class="token punctuation">)</span><span class="token punctuation">;</span>
        buf<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"目录:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        buf<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"\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<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/

Springboot2(25)集成netty实现文件传输_第1张图片

Springboot2(25)集成netty实现文件传输_第2张图片

你可能感兴趣的:(Springboot2,集成netty实现文件传输,springboot2,springboot2)