源码地址
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的依赖
@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("" );
buf.append(dirPath);
buf.append("目录:");
buf.append("\r\n");
buf.append(""
);
buf.append(dirPath).append(" 目录:");
buf.append("\r\n");
buf.append(""
);
buf.append("链接: \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("链接:);
buf.append(name);
buf.append("\">");
buf.append(name);
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);
}
}
@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/