那么又该怎么来进行这样的一套框架设计和编写呢?
首先从整体设计方面,核心内容是分为了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