通过netty实现http服务器

本文描述了如何在SpringBoot中因jar包过大而选择自定义实现一个HTTP服务器,通过扫描注解生成接口,使用Netty框架配置服务器,以及参数解析的过程。
摘要由CSDN通过智能技术生成

springboot自带的http服务功能由于功能较多,导致打包后的jar包比较大,由于我们业务以及环境的特殊性因此决定自己实现一个http服务器,主要原理是由一个启动器去扫描系统包下的类注解、方法注解等,生成http接口容器,然后通过url等方式定位到接口,最后进行反射调用

最终的效果:
通过指定包名启动http服务、初始化配置信息以及容器信息

在这里插入图片描述
定义接口方式如下:
在这里插入图片描述

1、maven依赖

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-codec-http</artifactId>            
        <version>4.1.86.Final</version>
    </dependency>

2、HttpServerStarter.class

package com.ctrip.hotel.octopus.crawler.http.starter;

import com.ctrip.hotel.octopus.commons.base.utils.JsonUtils;
import com.ctrip.hotel.octopus.commons.base.utils.RetryMonitor;
import com.ctrip.hotel.octopus.crawler.common.config.PropertiesLoader;
import com.ctrip.hotel.octopus.crawler.http.container.HttpServerContainer;
import com.ctrip.hotel.octopus.crawler.http.initializer.HttpInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.util.Properties;

/**
 * @author ZhaoXu
 * @date 2024/1/10 19:09
 */
@Slf4j
public class HttpServerStarter {
    private final EventLoopGroup bossGroup = new NioEventLoopGroup();

    private final EventLoopGroup workerGroup = new NioEventLoopGroup();

    private final ServerBootstrap serverBootstrap = new ServerBootstrap();

    private final String[] basePackages;

    public HttpServerStarter(String[] basePackages) {
        this.basePackages = basePackages;
    }

    public static void start(String... basePackages) {
        // 失败自动重试
        RetryMonitor.registry(() -> {
            try {
                HttpServerStarter httpServerStarter = new HttpServerStarter(basePackages);
                httpServerStarter.init();
                httpServerStarter.start();
            } catch (Exception e) {
                log.error("服务启动失败!", e);
                throw new RuntimeException(e);
            }
        }, Integer.MAX_VALUE);
    }

    private void init() {
        // 初始化http服务
        initHttpServer();

        // 初始化http容器
        initHttpContainer(basePackages);
    }

    private void start() throws InterruptedException {
        String port = PropertiesLoader.getProperty("server.port", "8080");
        ChannelFuture channelFuture = serverBootstrap.bind("0.0.0.0", Integer.parseInt(port)).sync();
        log.info("http服务启动成功,监听本地端口:" + port);
        channelFuture.channel().closeFuture().sync();
    }

    private void initHttpContainer(String[] basePackages) {
        log.info("初始化http容器");
        HttpServerContainer httpServerContainer = new HttpServerContainer(basePackages);
        httpServerContainer.start();
    }

    private void initHttpServer() {
        log.info("初始化http服务信息");
        serverBootstrap.group(bossGroup, workerGroup);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
        //开启SO_KEEPALIVE
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
        //配置receiveBuf使用混合型
        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator());
        //开启TCP_NODELAY
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
        //配置初始化处理器队列
        serverBootstrap.childHandler(new HttpInitializer());
    }
}

3、定义channel以及handler等配置类HttpInitializer

package com.ctrip.hotel.octopus.crawler.http.initializer;

import com.ctrip.hotel.octopus.crawler.http.handler.HttpCustomHandler;
import io.netty.channel.ChannelHandlerContext;
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.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * @author ZhaoXu
 * @date 2024/1/10 19:55
 */
@Slf4j
public class HttpInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("http编解码器", new HttpServerCodec());
        pipeline.addLast("http大数据包处理器", new ChunkedWriteHandler());
        pipeline.addLast("http报文聚合器", new HttpObjectAggregator(64 * 1024));
        pipeline.addLast("自定义http请求分发器", new HttpCustomHandler());
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("http服务发生异常!", cause);
        ctx.close();
    }
}

4、HttpCustomHandler

package com.ctrip.hotel.octopus.crawler.http.handler;

import com.ctrip.hotel.octopus.commons.base.enums.ErrorCode;
import com.ctrip.hotel.octopus.commons.base.exception.BaseException;
import com.ctrip.hotel.octopus.commons.base.model.BaseResponse;
import com.ctrip.hotel.octopus.commons.base.utils.JsonUtils;
import com.ctrip.hotel.octopus.crawler.common.config.ThreadPoolConfig;
import com.ctrip.hotel.octopus.crawler.http.container.HttpServerContainer;
import com.ctrip.hotel.octopus.crawler.http.enums.HttpRequestType;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author ZhaoXu
 * @date 2024/1/10 19:52
 */
@Slf4j
public class HttpCustomHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("处理http请求服务异常!", cause);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) {
        ThreadPoolExecutor executor = ThreadPoolConfig.getExecutor();
        fullHttpRequest.content().retain();
        executor.execute(() -> {
            try {
                String name = fullHttpRequest.method().name();
                HttpRequestType requestType = HttpRequestType.type2EnumMap.get(name);
                // 解析请求类型
                if (ObjectUtils.isEmpty(requestType)) {
                    sendResponse(channelHandlerContext, BaseResponse.fail("不支持的请求类型", -1));
                    return;
                }
                String url = fullHttpRequest.uri();
                int indexOf = url.indexOf("?");
                if (indexOf != -1) {
                    url = url.substring(0, indexOf);
                }
                // 定位接口位置
                Method targetMethod = HttpServerContainer.getTargetMethod(url, requestType);
                if (ObjectUtils.isEmpty(targetMethod)) {
                    sendResponse(channelHandlerContext, BaseResponse.fail("资源不存在!", 404));
                    return;
                }
                Object instance = HttpServerContainer.getTargetInstanceByMethod(targetMethod);

                // 解析属性并注入至方法
                Object[] realParams = ParameterHandler.resolveRequestParam(fullHttpRequest, targetMethod);
                Object result = targetMethod.invoke(instance, realParams);
                if (!(result instanceof BaseResponse)) {
                    result = BaseResponse.success(result);
                }
                sendResponse(channelHandlerContext, result);
            } catch (Exception e) {
                if (BaseException.class.isAssignableFrom(e.getCause().getClass())) {
                    BaseException baseException = (BaseException) e.getCause();
                    log.info("发生业务异常!", e);
                    sendResponse(channelHandlerContext, new BaseResponse<>(baseException.getCode(), baseException.getMessage(), e.getCause()));
                } else {
                    log.error("发生系统异常!", e);
                    sendResponse(channelHandlerContext, new BaseResponse<>(500, e.getMessage(), e.getCause()));
                }
            }
        });
    }

    private void sendResponse(ChannelHandlerContext channelHandlerContext, Object response) {
        String json = JsonUtils.toJson(response);
        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(json.getBytes(StandardCharsets.UTF_8)));
        fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
        channelHandlerContext.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
    }
}

5、http容器:HttpServerContainer

package com.ctrip.hotel.octopus.crawler.http.container;

import com.ctrip.hotel.octopus.commons.base.utils.ClassUtils;
import com.ctrip.hotel.octopus.crawler.http.annotation.RequestMapping;
import com.ctrip.hotel.octopus.crawler.http.enums.HttpRequestType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author ZhaoXu
 * @date 2024/1/10 20:09
 */
@Slf4j
public class HttpServerContainer {
    private final String[] basePackages;
    /**
     * 通过url + 请求方式定位某个方法
     */
    private static final Map<Pair<String, HttpRequestType>, Method> URL_AND_TYPE2_METHOD = new ConcurrentHashMap<>();

    /**
     * 通过方法定位class
     */
    private static final Map<Method, Object> METHOD_2_CLASS = new ConcurrentHashMap<>();

    public HttpServerContainer(String... basePackages) {
        if (ObjectUtils.isEmpty(basePackages)) {
            throw new RuntimeException("包路径为空,无法解析!");
        }
        this.basePackages = basePackages;
    }

    public void start() {
        // 扫描包路径下的所有类
        Set<Class<?>> allClass = Arrays.stream(basePackages)
                .map(ClassUtils::scanClass)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());

        // 自定义接口路由
        for (Class<?> aClass : allClass) {
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = declaredMethod.getAnnotation(RequestMapping.class);
                    String url = requestMapping.value();
                    HttpRequestType requestType = requestMapping.requestType();
                    if (ObjectUtils.anyNull(url, requestType)) {
                        log.error("容器接口解析异常,class;{},method:{}", aClass.getName(), declaredMethod.getName());
                    }
                    log.info("装载http接口,url:{},requestType:{},class;{},method:{}", url, requestType.getType(), aClass.getName(), declaredMethod.getName());
                    Pair<String, HttpRequestType> pair = Pair.of(url, requestType);
                    URL_AND_TYPE2_METHOD.put(pair, declaredMethod);
                    try {
                        METHOD_2_CLASS.put(declaredMethod, aClass.newInstance());
                    } catch (InstantiationException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    public static Method getTargetMethod(String url, HttpRequestType requestType) {
        Pair<String, HttpRequestType> pair = Pair.of(url, requestType);
        return URL_AND_TYPE2_METHOD.get(pair);
    }

    public static Object getTargetInstanceByMethod(Method method) {
        return METHOD_2_CLASS.get(method);
    }
}

6、参数解析器:ParameterHandler

package com.ctrip.hotel.octopus.crawler.http.handler;

import com.ctrip.hotel.octopus.commons.base.utils.JsonUtils;
import com.ctrip.hotel.octopus.crawler.http.annotation.RequestParam;
import com.ctrip.hotel.octopus.crawler.http.annotation.RequestBody;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * @author ZhaoXu
 * @date 2024/1/10 21:40
 */
@Slf4j
public class ParameterHandler {
    /**
     * 解析请求参数
     * @param fullHttpRequest
     * @param targetMethod
     * @return
     */
    public static Object[] resolveRequestParam(FullHttpRequest fullHttpRequest, Method targetMethod) {
        Parameter[] parameters = targetMethod.getParameters();
        Object[] realParams = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // 参数分为param和body类型
            if (parameter.isAnnotationPresent(RequestBody.class)) {
                Class<?> type = parameter.getType();
                ByteBuf content = fullHttpRequest.content();
                String jsonString = content.toString(StandardCharsets.UTF_8);
                realParams[i] = JsonUtils.fromJson(jsonString, type);
            } else if (parameter.isAnnotationPresent(RequestParam.class)) {
                RequestParam annotation = parameter.getAnnotation(RequestParam.class);
                String value = annotation.value();
                QueryStringDecoder queryStringDecoder = new QueryStringDecoder(fullHttpRequest.uri());
                Map<String, List<String>> params = queryStringDecoder.parameters();
                List<String> values = params.get(value);
                if (ObjectUtils.isNotEmpty(values)) {
                    Class<?> type = parameter.getType();
                    if (type == String.class) {
                        realParams[i] = values.get(0);
                    }
                    if (type == Integer.class || type == int.class) {
                        realParams[i] = Integer.parseInt(values.get(0));
                    }
                    if (type == Double.class || type == double.class) {
                        realParams[i] = Double.parseDouble(values.get(0));
                    }
                }
            }
        }
        return realParams;
    }
}
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty是一款基于NIO的网络编程框架,提供了高效、稳定、灵活的网络编程能力。使用Netty实现代理服务器可以简化开发过程,提高性能和可维护性。 以下是使用Netty实现代理服务器的示例代码: ``` import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.*; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler; public class ProxyServer { public static void main(String[] args) throws Exception { EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .option(ChannelOption.AUTO_READ, false) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpClientCodec()); ch.pipeline().addLast(new HttpObjectAggregator(65536)); ch.pipeline().addLast(new ChunkedWriteHandler()); ch.pipeline().addLast(new ProxyServerHandler()); } }); ChannelFuture future = bootstrap.connect("www.example.com", 80).sync(); future.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); } } private static class ProxyServerHandler extends ChannelInboundHandlerAdapter { private Channel remoteChannel; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { remoteChannel = ctx.channel(); ctx.read(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpRequest) { HttpRequest request = (HttpRequest) msg; String host = request.headers().get("Host"); ChannelFuture future = new Bootstrap() .group(ctx.channel().eventLoop()) .channel(ctx.channel().getClass()) .handler(new LoggingHandler(LogLevel.INFO)) .option(ChannelOption.AUTO_READ, false) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpResponseDecoder()); ch.pipeline().addLast(new HttpObjectAggregator(65536)); ch.pipeline().addLast(new ChunkedWriteHandler()); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(request); ctx.read(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; response.headers().remove("Transfer-Encoding"); response.headers().remove("Content-Length"); remoteChannel.writeAndFlush(response); remoteChannel.writeAndFlush(new ChunkedNioStream((ByteBuf) msg)); } else if (msg instanceof HttpContent) { remoteChannel.writeAndFlush(new ChunkedNioStream((ByteBuf) msg)); if (msg instanceof LastHttpContent) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } }); } }) .connect(host, 80); remoteChannel.config().setAutoRead(false); future.addListener((ChannelFutureListener) future1 -> { if (future1.isSuccess()) { remoteChannel.config().setAutoRead(true); ctx.channel().config().setAutoRead(true); } else { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } }); } else if (msg instanceof HttpContent) { remoteChannel.writeAndFlush(new ChunkedNioStream((ByteBuf) msg)); if (msg instanceof LastHttpContent) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); } } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (remoteChannel != null) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); if (remoteChannel != null) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } ctx.close(); } } } ``` 以上代码中,代理服务器连接到目标服务器的IP地址和端口号是硬编码的,你需要根据实际情况进行修改。在启动代理服务器之后,当客户端发送HTTP请求时,会在一个新的线程中处理请求,解析请求并连接到目标服务器,将请求转发给目标服务器。接收到目标服务器的响应后,将响应转发给客户端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值