Netty案例:HTTP服务器开发方案

目录

1、需求

2、核心设计思路

3、代码实现

4、部署与测试

5、关键功能说明


1、需求

Netty服务器监听8080端口,支持浏览器访问、信息恢复和资源过滤功能

Netty案例:HTTP服务器开发方案_第1张图片

2、核心设计思路

  • HTTP协议处理:使用Netty的HTTP编解码器

  • 资源过滤:通过URI分析实现黑白名单

  • 状态恢复:利用ChannelHandlerContext维护请求状态

  • 安全控制:过滤危险资源类型和路径遍历攻击

3、代码实现

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class SecureHttpServer {
    private static final int PORT = 8080;
    // 禁止访问的资源扩展名
    private static final String[] FORBIDDEN_EXTENSIONS = {".conf", ".env", ".key"};
    // 白名单路径
    private static final String[] ALLOWED_PATHS = {"/public/", "/images/"};

    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)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
             .childHandler(new ChannelInitializer() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline p = ch.pipeline();
                     // HTTP编解码器
                     p.addLast(new HttpServerCodec());
                     // 聚合HTTP请求内容
                     p.addLast(new HttpObjectAggregator(65536));
                     // 自定义业务处理器
                     p.addLast(new ResourceFilterHandler());
                 }
             });

            ChannelFuture f = b.bind(PORT).sync();
            System.out.println("HTTP服务器启动,监听端口: " + PORT);
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    @Sharable
    static class ResourceFilterHandler extends SimpleChannelInboundHandler {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
            // 1. 获取请求URI
            String uri = request.uri();
            System.out.println("收到请求: " + uri);

            // 2. 资源过滤检查
            if (isForbiddenResource(uri)) {
                sendErrorResponse(ctx, HttpResponseStatus.FORBIDDEN, "禁止访问该资源");
                return;
            }

            // 3. 处理正常请求
            handleRequest(ctx, request, uri);
        }

        private boolean isForbiddenResource(String uri) {
            // 检查路径遍历攻击 (e.g. /../etc/passwd)
            if (uri.contains("..") || uri.contains("~")) {
                return true;
            }

            // 检查禁止的扩展名
            for (String ext : FORBIDDEN_EXTENSIONS) {
                if (uri.endsWith(ext)) return true;
            }

            // 检查白名单路径
            boolean allowed = false;
            for (String path : ALLOWED_PATHS) {
                if (uri.startsWith(path)) {
                    allowed = true;
                    break;
                }
            }
            return !allowed && !uri.equals("/");
        }

        private void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request, String uri) {
            // 恢复上次请求状态(简单实现)
            String lastPath = ctx.channel().attr(AttributeKey.valueOf("lastPath")).get();
            String recoveryInfo = lastPath != null ? "\n上次访问: " + lastPath : "";

            // 存储当前路径
            ctx.channel().attr(AttributeKey.valueOf("lastPath")).set(uri);

            // 构建响应内容
            String content = ""
                    + "

请求资源: " + sanitize(uri) + "

" + "

时间: " + new java.util.Date() + "

" + "

" + recoveryInfo + "

" + ""; FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.OK, ctx.alloc().buffer().writeBytes(content.getBytes()) ); response.headers() .set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8") .set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()) .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); ctx.writeAndFlush(response); } private void sendErrorResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String message) { FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, status, ctx.alloc().buffer().writeBytes(("

" + message + "

").getBytes()) ); response.headers() .set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8") .set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } // 简单的HTML转义防止XSS private String sanitize(String input) { return input.replace("&", "&") .replace("<", "<") .replace(">", ">"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } }

4、部署与测试

部署运行:

mvn clean package
java -jar target/your-jar-file.jar

测试用例:

# 允许访问
curl http://localhost:8080/
curl http://localhost:8080/public/style.css

# 被拦截的请求
curl -v http://localhost:8080/../etc/passwd
curl -v http://localhost:8080/config.env
curl -v http://localhost:8080/private/data

浏览器访问效果:

  • 正常访问:显示当前资源路径和上次访问记录

  • 禁止访问:显示403错误页面

  • 保持连接状态:通过Keep-Alive复用连接

5、关键功能说明

  • 资源过滤机制

    • 路径遍历防护:检测..~字符

    • 扩展名黑名单:阻止访问.conf.env等敏感文件

    • 路径白名单:仅允许/public//images/目录

    • 根路径例外:允许访问首页(/)

  • 状态恢复实现

    • 使用Channel的Attribute存储lastPath

    • 每次请求显示上次访问路径

    • 保持连接复用(Keep-Alive)

  • 安全增强

    • HTML内容转义(防XSS)

    • 严格的资源访问控制

    • 错误请求连接自动关闭

你可能感兴趣的:(Netty案例:HTTP服务器开发方案)