1. 程式人生 > >Springboot2(25)整合netty實現檔案傳輸

Springboot2(25)整合netty實現檔案傳輸

原始碼地址

springboot2教程系列
其它netty檔案有部落格

Springboot2(24)整合netty實現http服務(類似SpingMvc的contoller層實現)

Springboot2(25)整合netty實現檔案傳輸

Springboot2(26)整合netty實現websocket通訊

Springboot2(27)整合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>\r\n"); buf.append("<html><head><title>"); buf.append(dirPath); buf.append("目錄:"); buf.append("</title></head><body>\r\n"); buf.append("<h3>"); buf.append(dirPath).append(" 目錄:"); buf.append("</h3>\r\n"); buf.append("<ul>"); buf.append("<li>連結:<a href=\" ../\")..</a></li>\r\n"); 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>\r\n"); } buf.append("</ul></body></html>\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/

在這裡插入圖片描述

在這裡插入圖片描述