Netty 是一个高性能的异步事件驱动的网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。要使用 Netty 搭建一个支持 HTTP 方法(GET, POST, PUT, DELETE)的 HTTP 服务器,可以按照以下步骤进行操作。
准备工作
- 添加依赖:确保你的项目中包含了 Netty 的相关依赖。
- Java版本:确保你使用的 Java 版本支持 Netty,一般推荐使用 Java 8 或更高版本。
添加 Maven 依赖
在你的 pom.xml
文件中添加以下依赖:
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.95.Final</version>
</dependency>
</dependencies>
创建 HTTP 服务器
下面是一个简单的示例,展示了如何创建一个支持 GET, POST, PUT, DELETE 方法的 HTTP 服务器。
1. 定义 HTTP 请求处理器
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
if (!req.decoderResult().isSuccess()) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
// 根据请求类型处理
switch (req.method()) {
case GET:
handleGet(ctx, req);
break;
case POST:
handlePost(ctx, req);
break;
case PUT:
handlePut(ctx, req);
break;
case DELETE:
handleDelete(ctx, req);
break;
default:
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.METHOD_NOT_ALLOWED));
break;
}
}
private void handleGet(ChannelHandlerContext ctx, FullHttpRequest req) {
// 处理 GET 请求
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("This is a GET response", CharsetUtil.UTF_8));
sendHttpResponse(ctx, req, response);
}
private void handlePost(ChannelHandlerContext ctx, FullHttpRequest req) {
// 处理 POST 请求
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("This is a POST response", CharsetUtil.UTF_8));
sendHttpResponse(ctx, req, response);
}
private void handlePut(ChannelHandlerContext ctx, FullHttpRequest req) {
// 处理 PUT 请求
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("This is a PUT response", CharsetUtil.UTF_8));
sendHttpResponse(ctx, req, response);
}
private void handleDelete(ChannelHandlerContext ctx, FullHttpRequest req) {
// 处理 DELETE 请求
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("This is a DELETE response", CharsetUtil.UTF_8));
sendHttpResponse(ctx, req, response);
}
private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
if (res.status().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
HttpHeaders.setContentLength(res, res.content().readableBytes());
}
// Generate an error page if response status code is not 200 (OK).
if (res.status().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
}
// Send the response and close the connection if necessary.
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!HttpHeaders.isKeepAlive(req) || res.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2. 启动服务器
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class HttpServer {
public static void main(String[] args) throws InterruptedException {
int port = 8080; // 选择一个端口
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer());
ChannelFuture f = b.bind(port).sync();
System.out.println("HTTP server started on port " + port);
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
3. 初始化 Channel
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new HttpResponseEncoder());
p.addLast(new HttpRequestDecoder());
p.addLast(new HttpObjectAggregator(65536));
p.addLast(new HttpServerHandler());
}
}
以上代码提供了一个基本的 HTTP 服务器框架,你可以根据需要添加具体的业务逻辑处理,如错误处理、日志记录、文件上传等高级功能。
在 Netty 中处理文件上传通常涉及到对 HTTP 请求中的 multipart/form-data
类型的解析。这种类型的请求通常用于上传文件和其他表单数据。下面介绍如何使用 Netty 和第三方库来处理文件上传。
第三方库
对于文件上传的支持,我们可以使用 netty-http2
项目中的 netty-codec-http2
依赖或者使用 netty-file-upload
项目中的 netty-codec-http
依赖,后者提供了更直接的方式来处理文件上传。我们将使用 netty-codec-http
依赖。
首先,在你的 pom.xml
文件中添加如下依赖:
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.95.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>4.1.95.Final</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
文件上传处理
- HTTP 请求解析器:使用
HttpDataFactory
和HttpPostRequestDecoder
解析上传的数据。 - 文件保存:定义一个方法来保存上传的文件。
更新 HttpServerHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
if (!req.decoderResult().isSuccess()) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
switch (req.method()) {
case GET:
handleGet(ctx, req);
break;
case POST:
handlePost(ctx, req);
break;
case PUT:
handlePut(ctx, req);
break;
case DELETE:
handleDelete(ctx, req);
break;
default:
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.METHOD_NOT_ALLOWED));
break;
}
}
private void handlePost(ChannelHandlerContext ctx, FullHttpRequest req) {
if (!req.headers().contains(HttpHeaderNames.CONTENT_TYPE)) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
String contentType = req.headers().get(HttpHeaderNames.CONTENT_TYPE);
if (contentType.contains("multipart/form-data")) {
handleFileUpload(ctx, req);
} else {
handleRegularPost(ctx, req);
}
}
private void handleFileUpload(ChannelHandlerContext ctx, FullHttpRequest req) {
FileUpload fileUpload = new FileUpload(new DiskFileItemFactory());
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(fileUpload, req);
while (true) {
try {
HttpPostRequestDecoder.State state = decoder.decode(ctx.channel(), req, ctx.alloc().buffer());
if (state == HttpPostRequestDecoder.State.END_OF_MESSAGE) {
break;
}
if (state == HttpPostRequestDecoder.State.CHUNKED_INPUT) {
decoder.offer(req);
}
} catch (HttpPostRequestDecoder.EndOfDataDecoderException e) {
break;
} catch (HttpPostRequestDecoder.ErrorDataDecoderException e) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
}
FileItemIterator iterator = decoder.getBodyHttpData();
while (iterator.hasNext()) {
FileItemStream item = iterator.next();
if (item.isFormField()) {
// Handle form fields here
} else {
saveUploadedFile(item, "/tmp/uploaded"); // Save the file to disk
}
}
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("File uploaded successfully.", CharsetUtil.UTF_8));
sendHttpResponse(ctx, req, response);
}
private void saveUploadedFile(FileItemStream item, String uploadDir) throws IOException {
Path targetPath = Path.of(uploadDir, item.getName());
Files.createDirectories(targetPath.getParent());
try (FileItemStream.ItemStream stream = item.openStream()) {
Files.copy(stream, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
}
private void handleRegularPost(ChannelHandlerContext ctx, FullHttpRequest req) {
// Handle regular POST data here
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("This is a POST response", CharsetUtil.UTF_8));
sendHttpResponse(ctx, req, response);
}
private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
// ... [same as before]
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
注意事项
- 在这个示例中,我们使用了
commons-fileupload
库来处理multipart/form-data
数据。 - 上传的文件被保存到
/tmp/uploaded
目录下。你需要确保这个目录存在并且有适当的权限。 - 我们使用了
DiskFileItemFactory
和FileUpload
来处理文件上传。 - 如果上传的文件非常大,你可能需要调整
DiskFileItemFactory
的配置以适应你的需求。
以上代码提供了一个基本的文件上传处理机制。你可以根据实际需要进一步扩展和优化。例如,可以添加文件大小限制、文件类型检查等功能。