手写一套迷你版HTTP服务器(下)

简介: 手写一套迷你版HTTP服务器(下)

那么又该怎么来进行这样的一套框架设计和编写呢?


首先从整体设计方面,核心内容是分为了netty的server和serverHandler处理器:

首先是接受数据的server端:


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
 * @author idea
 * @data 2019/4/26
 */
public class NettyHttpServer {
    private int inetPort;
    public NettyHttpServer(int inetPort) {
        this.inetPort = inetPort;
    }
    public int getInetPort() {
        return inetPort;
    }
    public void init() throws Exception {
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap server = new ServerBootstrap();
            // 1. 绑定两个线程组分别用来处理客户端通道的accept和读写时间
            server.group(parentGroup, childGroup)
                    // 2. 绑定服务端通道NioServerSocketChannel
                    .channel(NioServerSocketChannel.class)
                    // 3. 给读写事件的线程通道绑定handler去真正处理读写
                    // ChannelInitializer初始化通道SocketChannel
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 请求解码器
                            socketChannel.pipeline().addLast("http-decoder", new HttpRequestDecoder());
                            // 将HTTP消息的多个部分合成一条完整的HTTP消息
                            socketChannel.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65535));
                            // 响应转码器
                            socketChannel.pipeline().addLast("http-encoder", new HttpResponseEncoder());
                            // 解决大码流的问题,ChunkedWriteHandler:向客户端发送HTML5文件
                            socketChannel.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
                            // 自定义处理handler
                            socketChannel.pipeline().addLast("http-server", new NettyHttpServerHandler());
                        }
                    });
            // 4. 监听端口(服务器host和port端口),同步返回
            ChannelFuture future = server.bind(this.inetPort).sync();
            System.out.println("[server] opening in "+this.inetPort);
            // 当通道关闭时继续向后执行,这是一个阻塞方法
            future.channel().closeFuture().sync();
        } finally {
            childGroup.shutdownGracefully();
            parentGroup.shutdownGracefully();
        }
    }
}
复制代码


Netty接收数据的处理器NettyHttpServerHandler 代码如下:


import com.alibaba.fastjson.JSON;
import com.sise.itree.common.BaseController;
import com.sise.itree.model.ControllerRequest;
import com.sise.itree.model.PicModel;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.CharsetUtil;
import com.sise.itree.core.handle.StaticFileHandler;
import com.sise.itree.core.handle.response.BaseResponse;
import com.sise.itree.core.handle.response.ResponCoreHandle;
import com.sise.itree.core.invoke.ControllerCglib;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import static io.netty.buffer.Unpooled.copiedBuffer;
import static com.sise.itree.core.ParameterHandler.getHeaderData;
import static com.sise.itree.core.handle.ControllerReactor.getClazzFromList;
import static com.sise.itree.core.handle.FilterReactor.aftHandler;
import static com.sise.itree.core.handle.FilterReactor.preHandler;
import static com.sise.itree.util.CommonUtil.*;
/**
 * @author idea
 * @data 2019/4/26
 */
@Slf4j
public class NettyHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
        String uri = getUri(fullHttpRequest.getUri());
        Object object = getClazzFromList(uri);
        String result = "recive msg";
        Object response = null;
        //静态文件处理
        response = StaticFileHandler.responseHandle(object, ctx, fullHttpRequest);
        if (!(response instanceof FullHttpResponse) && !(response instanceof PicModel)) {
            //接口处理
            if (isContaionInterFace(object, BaseController.class)) {
                ControllerCglib cc = new ControllerCglib();
                Object proxyObj = cc.getTarget(object);
                Method[] methodArr = null;
                Method aimMethod = null;
                if (fullHttpRequest.method().equals(HttpMethod.GET)) {
                    methodArr = proxyObj.getClass().getMethods();
                    aimMethod = getMethodByName(methodArr, "doGet");
                } else if (fullHttpRequest.method().equals(HttpMethod.POST)) {
                    methodArr = proxyObj.getClass().getMethods();
                    aimMethod = getMethodByName(methodArr, "doPost");
                }
                //代理执行method
                if (aimMethod != null) {
                    ControllerRequest controllerRequest=paramterHandler(fullHttpRequest);
                    preHandler(controllerRequest);
                    BaseResponse baseResponse = (BaseResponse) aimMethod.invoke(proxyObj, controllerRequest);
                    aftHandler(controllerRequest);
                    result = JSON.toJSONString(baseResponse);
                }
            }
            response = ResponCoreHandle.responseHtml(HttpResponseStatus.OK, result);
        }
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
    /**
     * 处理请求的参数内容
     *
     * @param fullHttpRequest
     * @return
     */
    private ControllerRequest paramterHandler(FullHttpRequest fullHttpRequest) {
        //参数处理部分内容
        Map<String, Object> paramMap = new HashMap<>(60);
        if (fullHttpRequest.method() == HttpMethod.GET) {
            paramMap = ParameterHandler.getGetParamsFromChannel(fullHttpRequest);
        } else if (fullHttpRequest.getMethod() == HttpMethod.POST) {
            paramMap = ParameterHandler.getPostParamsFromChannel(fullHttpRequest);
        }
        Map<String, String> headers = getHeaderData(fullHttpRequest);
        ControllerRequest ctr = new ControllerRequest();
        ctr.setParams(paramMap);
        ctr.setHeader(headers);
        return ctr;
    }
}
复制代码


这里面的核心模块我大致分成了:


url匹配

从容器获取响应数据

静态文件响应处理

接口请求响应处理四个步骤

url匹配处理:


我们的客户端发送的url请求进入server端之后,需要快速的进行url路径的格式处理。例如将http://localhost:8080/xxx-1/xxx-2?username=test转换为/xxx-1/xxx-2的格式,这样方便和controller顶部设计的注解的url信息进行关键字匹配。


/**
     * 截取url里面的路径字段信息
     *
     * @param uri
     * @return
     */
    public static String getUri(String uri) {
        int pathIndex = uri.indexOf("/");
        int requestIndex = uri.indexOf("?");
        String result;
        if (requestIndex < 0) {
            result = uri.trim().substring(pathIndex);
        } else {
            result = uri.trim().substring(pathIndex, requestIndex);
        }
        return result;
    }
复制代码


从容器获取匹配响应数据:


经过了前一段的url格式处理之后,我们需要根据url的后缀来预先判断是否是数据静态文件的请求:


对于不同后缀格式来返回不同的model对象(每个model对象都是共同的属性url),之所以设计成不同的对象是因为针对不同格式的数据,response的header里面需要设置不同的属性值。


/**
     * 匹配响应信息
     *
     * @param uri
     * @return
     */
    public static Object getClazzFromList(String uri) {
        if (uri.equals("/") || uri.equalsIgnoreCase("/index")) {
            PageModel pageModel;
            if(ITreeConfig.INDEX_CHANGE){
                pageModel= new PageModel();
                pageModel.setPagePath(ITreeConfig.INDEX_PAGE);
            }
            return new PageModel();
        }
        if (uri.endsWith(RequestConstants.HTML_TYPE)) {
            return new PageModel(uri);
        }
        if (uri.endsWith(RequestConstants.JS_TYPE)) {
            return new JsModel(uri);
        }
        if (uri.endsWith(RequestConstants.CSS_TYPE)) {
            return new CssModel(uri);
        }
        if (isPicTypeMatch(uri)) {
            return new PicModel(uri);
        }
        //查看是否是匹配json格式
        Optional<ControllerMapping> cmOpt = CONTROLLER_LIST.stream().filter((p) -> p.getUrl().equals(uri)).findFirst();
        if (cmOpt.isPresent()) {
            String className = cmOpt.get().getClazz();
            try {
                Class clazz = Class.forName(className);
                Object object = clazz.newInstance();
                return object;
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                LOGGER.error("[MockController] 类加载异常,{}", e);
            }
        }
        //没有匹配到html,js,css,图片资源或者接口路径
        return null;
    }
复制代码


网络异常,图片无法展示
|


针对静态文件的处理模块,这里面主要是由responseHandle函数处理。


代码如下:


/**
     * 静态文件处理器
     *
     * @param object
     * @return
     * @throws IOException
     */
    public static Object responseHandle(Object object, ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
        String result;
        FullHttpResponse response = null;
        //接口的404处理模块
        if (object == null) {
            result = CommonUtil.read404Html();
            return ResponCoreHandle.responseHtml(HttpResponseStatus.OK, result);
        } else if (object instanceof JsModel) {
            JsModel jsModel = (JsModel) object;
            result = CommonUtil.readFileFromResource(jsModel.getUrl());
            response = notFoundHandler(result);
            return (response == null) ? ResponCoreHandle.responseJs(HttpResponseStatus.OK, result) : response;
        } else if (object instanceof CssModel) {
            CssModel cssModel = (CssModel) object;
            result = CommonUtil.readFileFromResource(cssModel.getUrl());
            response = notFoundHandler(result);
            return (response == null) ? ResponCoreHandle.responseCss(HttpResponseStatus.OK, result) : response;
        }//初始化页面
        else if (object instanceof PageModel) {
            PageModel pageModel = (PageModel) object;
            if (pageModel.getCode() == RequestConstants.INDEX_CODE) {
                result = CommonUtil.readIndexHtml(pageModel.getPagePath());
            } else {
                result = CommonUtil.readFileFromResource(pageModel.getPagePath());
            }
            return ResponCoreHandle.responseHtml(HttpResponseStatus.OK, result);
        } else if (object instanceof PicModel) {
            PicModel picModel = (PicModel) object;
            ResponCoreHandle.writePic(picModel.getUrl(), ctx, fullHttpRequest);
            return picModel;
        }
        return null;
    }
复制代码


对于接口类型的数据请求,主要是在handler里面完成


网络异常,图片无法展示
|


代码为:


if (!(response instanceof FullHttpResponse) && !(response instanceof PicModel)) {
            //接口处理
            if (isContaionInterFace(object, BaseController.class)) {
                ControllerCglib cc = new ControllerCglib();
                Object proxyObj = cc.getTarget(object);
                Method[] methodArr = null;
                Method aimMethod = null;
                if (fullHttpRequest.method().equals(HttpMethod.GET)) {
                    methodArr = proxyObj.getClass().getMethods();
                    aimMethod = getMethodByName(methodArr, "doGet");
                } else if (fullHttpRequest.method().equals(HttpMethod.POST)) {
                    methodArr = proxyObj.getClass().getMethods();
                    aimMethod = getMethodByName(methodArr, "doPost");
                }
                //代理执行method
                if (aimMethod != null) {
                    ControllerRequest controllerRequest=paramterHandler(fullHttpRequest);
                    preHandler(controllerRequest);
                    BaseResponse baseResponse = (BaseResponse) aimMethod.invoke(proxyObj, controllerRequest);
                    aftHandler(controllerRequest);
                    result = JSON.toJSONString(baseResponse);
                }
            }
            response = ResponCoreHandle.responseHtml(HttpResponseStatus.OK, result);
        }
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
复制代码


这里面主要是借用了cglib来进行一些相关的代理编写,通过url找到匹配的controller,然后根据请求的类型来执行doget或者dopost功能。而preHandler和afterHandler主要是用于进行相关过滤器的执行操作。这里面用到了责任链的模式来进行编写。


过滤链在程序初始化的时候便有进行相应的扫描和排序操作,核心代码思路如下所示:


/**
     * 扫描过滤器
     *
     * @param path
     * @return
     */
    public static List<FilterModel> scanFilter(String path) throws IllegalAccessException, InstantiationException {
        Map<String, Object> result = new HashMap<>(60);
        Set<Class<?>> clazz = ClassUtil.getClzFromPkg(path);
        List<FilterModel> filterModelList = new ArrayList<>();
        for (Class<?> aClass : clazz) {
            if (aClass.isAnnotationPresent(Filter.class)) {
                Filter filter = aClass.getAnnotation(Filter.class);
                FilterModel filterModel = new FilterModel(filter.order(), filter.name(), aClass.newInstance());
                filterModelList.add(filterModel);
            }
        }
        FilterModel[] tempArr = new FilterModel[filterModelList.size()];
        int index = 0;
        for (FilterModel filterModel : filterModelList) {
            tempArr[index] = filterModel;
            System.out.println("[Filter] " + filterModel.toString());
            index++;
        }
        return sortFilterModel(tempArr);
    }
    /**
     * 对加载的filter进行优先级排序
     *
     * @return
     */
    private static List<FilterModel> sortFilterModel(FilterModel[] filterModels) {
        for (int i = 0; i < filterModels.length; i++) {
            int minOrder = filterModels[i].getOrder();
            int minIndex = i;
            for (int j = i; j < filterModels.length; j++) {
                if (minOrder > filterModels[j].getOrder()) {
                    minOrder = filterModels[j].getOrder();
                    minIndex = j;
                }
            }
            FilterModel temp = filterModels[minIndex];
            filterModels[minIndex] = filterModels[i];
            filterModels[i] = temp;
        }
        return Arrays.asList(filterModels);
    }
复制代码


最后附上本框架的码云地址:gitee.com/IdeaHome_ad…

内部附属有对应的源代码,jar包,以及主够让人理解思路的代码注释,希望喜欢的朋友可以给个star

目录
相关文章
|
26天前
|
搜索推荐 安全 网络安全
服务器支持HTTPS的时机和条件
【10月更文挑战第23天】服务器支持HTTPS的时机和条件
18 5
|
1月前
使用Netty实现文件传输的HTTP服务器和客户端
本文通过详细的代码示例,展示了如何使用Netty框架实现一个文件传输的HTTP服务器和客户端,包括服务端的文件处理和客户端的文件请求与接收。
44 1
使用Netty实现文件传输的HTTP服务器和客户端
|
20天前
|
存储 Oracle 关系型数据库
oracle服务器存储过程中调用http
通过配置权限、创建和调用存储过程,您可以在Oracle数据库中使用UTL_HTTP包发起HTTP请求。这使得Oracle存储过程可以与外部HTTP服务进行交互,从而实现更复杂的数据处理和集成。在实际应用中,根据具体需求调整请求类型和错误处理逻辑,以确保系统的稳定性和可靠性。
25 0
|
2月前
HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
文章介绍了如何配置HAProxy以支持HTTPS协议和实现服务器的动态上下线。
145 8
HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
|
2月前
|
开发者
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
32 1
|
3月前
|
移动开发 网络协议 编译器
实战案例3:C语言实现的HTTP服务器
实战案例3:C语言实现的HTTP服务器
210 0
|
Web App开发 前端开发
|
Web App开发 前端开发
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
zookeeper watch的定义如下:watch事件是一次性触发器,当watch监视的数据发生变化时,通知设置了该watch的client,即watcher。
936 0
|
Web App开发 前端开发 测试技术
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
一、迁移步骤 1.首先安装最新版本gitlab(gitlab7.2安装) 2.停止旧版本gitlab服务 3.将旧的项目文件完整导入新的gitlab   bundle exec rake gitlab:import:r...
715 0
|
Web App开发 前端开发 Linux
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
 频繁的文件访问会导致系统的Cache使用量大增   $ free -m   total used free shared buffers cached   Mem: 3955 3926 28 0 55 3459   -...
648 0
下一篇
无影云桌面