netty构建http服务器

Netty 是一个高性能的异步事件驱动的网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。要使用 Netty 搭建一个支持 HTTP 方法(GET, POST, PUT, DELETE)的 HTTP 服务器,可以按照以下步骤进行操作。

准备工作

  1. 添加依赖:确保你的项目中包含了 Netty 的相关依赖。
  2. 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>

文件上传处理

  1. HTTP 请求解析器:使用 HttpDataFactoryHttpPostRequestDecoder 解析上传的数据。
  2. 文件保存:定义一个方法来保存上传的文件。
更新 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 目录下。你需要确保这个目录存在并且有适当的权限。
  • 我们使用了 DiskFileItemFactoryFileUpload 来处理文件上传。
  • 如果上传的文件非常大,你可能需要调整 DiskFileItemFactory 的配置以适应你的需求。

以上代码提供了一个基本的文件上传处理机制。你可以根据实际需要进一步扩展和优化。例如,可以添加文件大小限制、文件类型检查等功能。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

svygh123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值