本文通过netty实现一个简单的文件服务器,能够像nginx一样将目录结构列举出来并且支持下载
1、 对应目录: 点击文件目录可以进行下钻, 而且可以通过 ".." 进行回退到上一个目录。
2、对应文件: 支持下载。
一、首先先编写服务启动类,配置好netty的启动参数及pipeline。相关代码如下:
public class HttpServer {
//绑定端口
private int port;
//boss线程
private NioEventLoopGroup boss;
//工作线程
private NioEventLoopGroup work;
public HttpServer(int port) {
this.port = port;
}
/**
* 开启服务
*/
public void start() {
//boss线程一般设置为1
boss = new NioEventLoopGroup(1);
/**
* 工作线程数使用默认值,cup核数*2
*/
work = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, work)
.option(ChannelOption.SO_BACKLOG, 100)
.childOption(ChannelOption.SO_KEEPALIVE, true) //保持长连接
.channel(NioServerSocketChannel.class)
//打印日志
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//设置 http请求编码解码器,使得系统支持http协议的请求
ch.pipeline().addLast("http-codec",new HttpServerCodec());
//在http请求的编解码器后设置HttpObjectAggregator,其配合HttpServerCodec完成封装http请求。因为一个完整
//的请求会被解码成多个http message。
ch.pipeline().addLast("http-aggregator",new HttpObjectAggregator(65535));
//ChunkedWriteHandler它增加了对异步写入大数据流的支持,既不占用大量内存
ch.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
//自己的业务处理类,文件服务器。
ch.pipeline().addLast("http-file",new HttpFileServerHandler());
}
});
try {
ChannelFuture c = serverBootstrap.bind(port).sync();
c.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
/**
* 停止服务
*/
public void stop(){
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
二、写文件服务实现逻辑:
public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private final File root = new File("D:/");
private final String charsetName = "utf-8";
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String uri = URLDecoder.decode(msg.uri(), "utf-8");
if(uri.contains("favicon.ico")) {
sendError(ctx);
return ;
}
File file;
if(uri.length()==0 || uri.equals("/")) {
file = root;
} else {
file = new File(root, uri);
}
if(file.isDirectory()) {
//响应目录结构
responseDirectory(file,msg,ctx);
return ;
} else if(file.isFile()) {
//文件下载
responseFile(file,msg,ctx);
return ;
}
sendError(ctx);
}
private void sendError(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
response.headers().set(HttpHeaderNames.CONNECTION, "keep-alive");
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
ctx.writeAndFlush(response);
}
private void responseFile(File file, FullHttpRequest msg, ChannelHandlerContext ctx) {
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
//设置请求头方面的信息
response.headers().set(HttpHeaderNames.CONNECTION, "keep-alive");
response.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, file.length());
ctx.write(response);
//文件下载,下载完成后输出下载完成提示信息
DefaultFileRegion fileRegin = new DefaultFileRegion(file, 0, file.length());
ctx.write(fileRegin).addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
System.out.println("下载完成");
}
});
//文件下载结束标记
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
}
private void responseDirectory(File root, FullHttpRequest msg, ChannelHandlerContext ctx) throws IOException {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().set(HttpHeaderNames.CONNECTION, "keep-alive");
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8");
File[] files = root.listFiles();
//构建文件目录结构,
StringBuilder b = new StringBuilder();
if(root != this.root) {
b.append("<a href='/");
b.append(root.getParent().substring(3));
b.append("'>..</a></br>");
}
for (File file : files) {
b.append("<a ");
b.append("href='");
b.append("/"+file.getCanonicalPath().substring(3));
b.append("'>");
b.append(file.getName());
b.append("</a><br/>");
}
byte[] u = b.toString().getBytes(charsetName);
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, u.length);
response.content().writeBytes(u);
ctx.writeAndFlush(response);
}
}
三、 运行服务
public class Start {
public static void main(String[] args) {
HttpServer server = new HttpServer(8899);
server.start();
}
}
四、通过 http://127.0.0.1:8899 进行访问 ;实现效果如下