当前位置: 首页 > article >正文

【Netty系列】实现HTTP文件服务器

目录

一、完整代码实现

1. Maven依赖 (pom.xml)

2. 主启动类 (FileServer.java)

3. 通道初始化类 (FileServerInitializer.java)

4. 核心业务处理器 (FileServerHandler.java)

二、代码关键解释

1. 架构分层

2. 安全防护机制

3. 文件传输优化

4. 目录列表生成

三、运行与测试

1. 准备测试环境

2. 启动服务器

3. 测试用例

四、扩展建议

1. 添加HTTPS支持

2. 实现断点续传

3. 添加缓存控制


以下是基于Netty 4.1的完整HTTP文件服务器实现,包含详细注释和关键功能说明:

一、完整代码实现

1. Maven依赖 (pom.xml)
<dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.86.Final</version></dependency>
</dependencies>
2. 主启动类 (FileServer.java)
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;public class FileServer {private static final int PORT = 8080;private static final String BASE_DIR = "server_files";public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new FileServerInitializer(BASE_DIR)).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);ChannelFuture f = b.bind(PORT).sync();System.out.println("File server started at port: " + PORT);f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}
}
3. 通道初始化类 (FileServerInitializer.java)
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.stream.ChunkedWriteHandler;public class FileServerInitializer extends ChannelInitializer<SocketChannel> {private final String baseDir;public FileServerInitializer(String baseDir) {this.baseDir = baseDir;}@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline pipeline = ch.pipeline();// HTTP协议编解码器pipeline.addLast(new HttpServerCodec());// 聚合HTTP完整请求(最大支持64KB)pipeline.addLast(new HttpObjectAggregator(65536));// 支持分块大文件传输pipeline.addLast(new ChunkedWriteHandler());// 自定义业务处理器pipeline.addLast(new FileServerHandler(baseDir));}
}
4. 核心业务处理器 (FileServerHandler.java)
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;import java.io.File;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;public class FileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {private final String baseDir;public FileServerHandler(String baseDir) {this.baseDir = baseDir;}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {try {// 1. 验证HTTP方法if (!HttpMethod.GET.equals(request.method())) {sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);return;}// 2. 路径安全处理String uri = request.uri();if (uri.contains("..") || uri.contains("/.")) {sendError(ctx, HttpResponseStatus.FORBIDDEN);return;}// 3. 获取物理文件路径Path filePath = Paths.get(baseDir, uri).normalize().toAbsolutePath();// 4. 验证文件是否存在if (!Files.exists(filePath)) {sendError(ctx, HttpResponseStatus.NOT_FOUND);return;}// 5. 处理目录请求if (Files.isDirectory(filePath)) {sendDirectoryListing(ctx, filePath);return;}// 6. 发送文件内容sendFile(ctx, filePath.toFile());} catch (Exception e) {sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);}}private void sendFile(ChannelHandlerContext ctx, File file) throws Exception {RandomAccessFile raf = new RandomAccessFile(file, "r");long fileLength = raf.length();// 构建HTTP响应头HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);HttpUtil.setContentLength(response, fileLength);response.headers().set(HttpHeaderNames.CONTENT_TYPE, Files.probeContentType(file.toPath()));// 发送响应头ctx.write(response);// 零拷贝传输文件内容(SSL和非SSL场景处理不同)if (ctx.pipeline().get(SslHandler.class) == null) {ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength));} else {ctx.write(new ChunkedFile(raf));}// 结束传输ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);future.addListener(f -> raf.close());}private void sendDirectoryListing(ChannelHandlerContext ctx, Path dir) {StringBuilder sb = new StringBuilder().append("<html><head><title>Index of ").append(dir.getFileName()).append("</title></head><body><h1>Index of ").append(dir.getFileName()).append("</h1><hr><pre>");try {Files.list(dir).forEach(path -> {String name = path.getFileName().toString();sb.append("<a href=\"").append(name).append("\">").append(name).append("</a>").append("        ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date(path.toFile().lastModified()))).append("        ").append(Files.isDirectory(path) ? "-" : path.toFile().length()).append("\n");});} catch (Exception e) {sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);return;}sb.append("</pre><hr></body></html>");FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,Unpooled.copiedBuffer(sb.toString(), StandardCharsets.UTF_8));response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,status,Unpooled.copiedBuffer("Error: " + status + "\r\n", StandardCharsets.UTF_8));response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

二、代码关键解释

1. 架构分层
  • 主启动类:负责Netty服务端的启动配置
  • 初始化器:配置管道(Pipeline)的处理链
  • 业务处理器:实现核心文件服务逻辑
2. 安全防护机制
// 路径注入防护
if (uri.contains("..") || uri.contains("/.")) {sendError(ctx, HttpResponseStatus.FORBIDDEN);return;
}// 文件路径标准化处理
Path filePath = Paths.get(baseDir, uri).normalize().toAbsolutePath();
3. 文件传输优化
// 零拷贝技术(DefaultFileRegion)
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength));// SSL环境使用分块传输(ChunkedFile)
ctx.write(new ChunkedFile(raf));
4. 目录列表生成
// 动态生成HTML目录索引
Files.list(dir).forEach(path -> {// 构建带文件属性的超链接
});

三、运行与测试

1. 准备测试环境
mkdir -p server_files/test
echo "Hello Netty" > server_files/test.txt
dd if=/dev/urandom of=server_files/largefile.bin bs=1M count=100
2. 启动服务器
mvn clean package exec:java -Dexec.mainClass="FileServer"
3. 测试用例
# 获取文本文件
curl http://localhost:8080/test.txt# 列出目录内容
curl http://localhost:8080/test/# 下载大文件
wget http://localhost:8080/largefile.bin# 错误请求测试
curl -v http://localhost:8080/../etc/passwd

四、扩展建议

1. 添加HTTPS支持
// 在初始化器中添加SSL处理器
SslContext sslCtx = SslContextBuilder.forServer(cert, key).build();
pipeline.addFirst("ssl", sslCtx.newHandler(ch.alloc()));
2. 实现断点续传
// 解析Range请求头
String rangeHeader = request.headers().get(HttpHeaderNames.RANGE);
if (rangeHeader != null) {// 处理形如"bytes=0-100"的请求// 设置206 Partial Content状态// 使用FileRegion指定传输范围
}
3. 添加缓存控制
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "max-age=3600").set(HttpHeaderNames.EXPIRES, new Date(System.currentTimeMillis() + 3600000));

该实现具备完整的文件服务功能,实际生产部署时建议增加:

  1. 访问日志记录
  2. 限速控制
  3. 身份验证
  4. 病毒扫描集成
  5. 监控指标采集

可根据具体业务需求进行功能扩展和性能调优。

http://www.lryc.cn/news/2396626.html

相关文章:

  • Java开发经验——阿里巴巴编码规范实践解析7
  • 权威认证与质量保障:第三方检测在科技成果鉴定测试中的核心作用
  • 混和效应模型在医学分析中的应用
  • 架构分享|三层存储架构加速云端大模型推理
  • Perforce P4产品简介:无限扩展+全球协作+安全管控+工具集成(附下载)
  • 网络协议入门:TCP/IP五层模型如何实现全球数据传输?
  • Docker安装Redis集群(3主3从+动态扩容、缩容)保姆级教程含踩坑及安装中遇到的问题解决
  • 企业级 AI 开发新范式:Spring AI 深度解析与实践
  • 如何用docker部署ELK?
  • Redis最佳实践——安全与稳定性保障之高可用架构详解
  • 【Python 算法零基础 4.排序 ⑥ 快速排序】
  • Java面试实战:从Spring Boot到微服务与AI的全栈挑战
  • Go 即时通讯系统:日志模块重构,并从main函数开始
  • CppCon 2014 学习:Exception-Safe Coding
  • MYSQL MGR高可用
  • 阿里通义实验室突破空间音频新纪元!OmniAudio让360°全景视频“声”临其境
  • 异步上传石墨文件进度条前端展示记录(采用Redis中String数据结构实现-苏东坡版本)
  • 处理知识库文件_编写powershell脚本文件_批量转换其他格式文件到pdf文件---人工智能工作笔记0249
  • rtpmixsound:实现音频混音攻击!全参数详细教程!Kali Linux教程!
  • 【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder
  • stm与51单片机哪个更适合新手学
  • 【计算机网络】第3章:传输层—面向连接的传输:TCP
  • 从架构视角设计统一网络请求体系 —— 基于 uni-app 的前后端通信模型
  • 《信号与系统》--期末总结V1.0
  • 第32次CCF计算机软件能力认证-2-因子化简
  • mac笔记本如何快捷键截图后自动复制到粘贴板
  • 高考加油!UI界面生成器!
  • window ollama部署模型
  • 用mediamtx搭建简易rtmp,rtsp视频服务器
  • ubuntu安装devkitPro