├── .gitignore ├── LICENSE ├── README.md ├── logo.jpg ├── md ├── interceptor.md ├── interceptor │ ├── block-interceptor.png │ ├── block-request-effect.png │ ├── block-request.png │ ├── common-request-effect.png │ ├── common-request.png │ ├── custom-interceptor-builder.png │ ├── get-interceptor.png │ ├── interceptor-builder.png │ ├── interceptor-call.png │ ├── interceptor-handle.png │ ├── interceptor-provider-with-builder.png │ ├── interceptor-provider.png │ ├── interceptor.png │ └── performance-interceptor.png ├── overview.md ├── overview │ ├── after-interceptor.png │ ├── base-controller.png │ ├── bean-context-aware.png │ ├── default-routers.png │ ├── features.png │ ├── field-inject.png │ ├── init-bean.png │ ├── init-channel-handler.png │ ├── pre-interceptor.png │ ├── setter-inject.png │ ├── start-up.png │ ├── user-controller.png │ ├── user-list.png │ └── welcome-to-redant.png ├── processor.md └── processor │ ├── abstract-linked-processor.png │ ├── auth-processor.png │ ├── fixed-abstract-linked-processor.png │ ├── fixed-auth-processor.png │ ├── fixed-linked-processor-chain.png │ ├── linked-processor-chain-test-result.png │ ├── linked-processor-chain-test.png │ ├── linked-processor-chain.png │ └── processor.png ├── pom.xml ├── redant-cluster ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── redant │ └── cluster │ ├── bootstrap │ ├── MasterServerBootstrap.java │ ├── SlaveServerBootstrap.java │ └── ZkBootstrap.java │ ├── master │ ├── MasterServer.java │ ├── MasterServerBackendHandler.java │ └── MasterServerHandler.java │ ├── node │ └── Node.java │ ├── service │ ├── discover │ │ ├── ServiceDiscover.java │ │ └── ZkServiceDiscover.java │ └── register │ │ ├── ServiceRegister.java │ │ └── ZkServiceRegister.java │ ├── slave │ └── SlaveServer.java │ └── zk │ ├── ZkClient.java │ ├── ZkConfig.java │ ├── ZkNode.java │ └── ZkServer.java ├── redant-core ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── redant │ │ │ └── core │ │ │ ├── ServerBootstrap.java │ │ │ ├── anno │ │ │ └── Order.java │ │ │ ├── aware │ │ │ ├── Aware.java │ │ │ └── BeanContextAware.java │ │ │ ├── bean │ │ │ ├── BaseBean.java │ │ │ ├── annotation │ │ │ │ ├── Autowired.java │ │ │ │ └── Bean.java │ │ │ └── context │ │ │ │ ├── BeanContext.java │ │ │ │ └── DefaultBeanContext.java │ │ │ ├── common │ │ │ ├── constants │ │ │ │ └── CommonConstants.java │ │ │ ├── enums │ │ │ │ ├── ContentType.java │ │ │ │ └── RequestMethod.java │ │ │ ├── exception │ │ │ │ ├── InvalidSessionException.java │ │ │ │ ├── InvocationException.java │ │ │ │ └── ValidationException.java │ │ │ ├── html │ │ │ │ ├── DefaultHtmlMaker.java │ │ │ │ ├── HtmlMaker.java │ │ │ │ ├── HtmlMakerEnum.java │ │ │ │ └── HtmlMakerFactory.java │ │ │ ├── util │ │ │ │ ├── GenericsUtil.java │ │ │ │ ├── HtmlContentUtil.java │ │ │ │ ├── HttpRenderUtil.java │ │ │ │ ├── HttpRequestUtil.java │ │ │ │ ├── PropertiesUtil.java │ │ │ │ ├── TagUtil.java │ │ │ │ └── ThreadUtil.java │ │ │ └── view │ │ │ │ ├── HtmlKeyHolder.java │ │ │ │ ├── Page404.java │ │ │ │ ├── Page500.java │ │ │ │ ├── PageError.java │ │ │ │ └── PageIndex.java │ │ │ ├── context │ │ │ └── RedantContext.java │ │ │ ├── controller │ │ │ ├── ControllerProxy.java │ │ │ ├── ProxyInvocation.java │ │ │ ├── annotation │ │ │ │ ├── Controller.java │ │ │ │ ├── Mapping.java │ │ │ │ └── Param.java │ │ │ └── context │ │ │ │ ├── ControllerContext.java │ │ │ │ └── DefaultControllerContext.java │ │ │ ├── converter │ │ │ ├── AbstractConverter.java │ │ │ ├── Converter.java │ │ │ ├── PrimitiveConverter.java │ │ │ └── PrimitiveTypeUtil.java │ │ │ ├── cookie │ │ │ ├── CookieManager.java │ │ │ └── DefaultCookieManager.java │ │ │ ├── executor │ │ │ ├── AbstractExecutor.java │ │ │ ├── Executor.java │ │ │ └── HttpResponseExecutor.java │ │ │ ├── handler │ │ │ ├── ControllerDispatcher.java │ │ │ └── ssl │ │ │ │ └── SslContextHelper.java │ │ │ ├── init │ │ │ ├── InitExecutor.java │ │ │ ├── InitFunc.java │ │ │ └── InitOrder.java │ │ │ ├── interceptor │ │ │ ├── Interceptor.java │ │ │ ├── InterceptorBuilder.java │ │ │ ├── InterceptorHandler.java │ │ │ └── InterceptorProvider.java │ │ │ ├── render │ │ │ └── RenderType.java │ │ │ ├── router │ │ │ ├── BadClientSilencer.java │ │ │ ├── MethodlessRouter.java │ │ │ ├── OrderlessRouter.java │ │ │ ├── PathPattern.java │ │ │ ├── RouteResult.java │ │ │ ├── Router.java │ │ │ └── context │ │ │ │ ├── DefaultRouterContext.java │ │ │ │ └── RouterContext.java │ │ │ ├── server │ │ │ ├── NettyHttpServer.java │ │ │ ├── NettyHttpServerInitializer.java │ │ │ └── Server.java │ │ │ └── session │ │ │ ├── HttpSession.java │ │ │ ├── SessionConfig.java │ │ │ ├── SessionHelper.java │ │ │ └── SessionManager.java │ └── resources │ │ ├── logback.xml │ │ ├── redant.properties │ │ └── zk.cfg │ └── test │ └── java │ └── com │ └── redant │ └── core │ └── context │ └── RedantContextTest.java └── redant-example ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── redant │ │ └── example │ │ ├── bootstrap │ │ ├── cluster │ │ │ ├── MasterServerBootstrap.java │ │ │ ├── SlaveServerBootstrap.java │ │ │ └── ZkBootstrap.java │ │ └── standalone │ │ │ └── ServerBootstrap.java │ │ ├── controller │ │ ├── BaseController.java │ │ ├── CookieController.java │ │ └── UserController.java │ │ ├── interceptor │ │ ├── BlockInterceptor.java │ │ ├── CustomInterceptorBuilder.java │ │ └── PerformanceInterceptor.java │ │ └── service │ │ ├── UserBean.java │ │ ├── UserService.java │ │ └── UserServiceImpl.java └── resources │ └── logback.xml └── test └── java └── com └── lememo └── core └── interceptor └── InterceptorProviderTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | 34 | # Editor Files # 35 | ################ 36 | *~ 37 | *.swp 38 | 39 | # Gradle Files # 40 | ################ 41 | .gradle 42 | .m2 43 | 44 | # Build output directies 45 | target/ 46 | build/ 47 | 48 | ### STS ### 49 | .apt_generated 50 | .classpath 51 | .factorypath 52 | .project 53 | .settings 54 | .springBeans 55 | 56 | # IntelliJ specific files/directories 57 | out 58 | .idea 59 | *.ipr 60 | *.iws 61 | *.iml 62 | atlassian-ide-plugin.xml 63 | 64 | # Eclipse specific files/directories 65 | .classpath 66 | .project 67 | .settings 68 | .metadata 69 | 70 | # NetBeans specific files/directories 71 | .nbattrs 72 | nbproject/private/ 73 | build/ 74 | nbbuild/ 75 | dist/ 76 | nbdist/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedAnt 项目 2 | 3 | **RedAnt** 是一个基于 Netty 的轻量级 Web 容器 4 | 5 | **特性:** 6 | 7 | - [x] **IOC容器** : 通过 @Bean 注解可以管理所有对象,通过 @Autowired 注解进行对象注入 8 | - [x] **自定义路由** : 通过 @Controller @Mapping @Param 注解可以自定义路由 9 | - [x] **自动参数转换** : 通过 TypeConverter 接口,http 参数会被转成对象(支持基本类型,Map,List,JavaBean) 10 | - [x] **结果渲染** : 支持对结果进行渲染,支持 html, xml, plain, json 格式 11 | - [x] **Cookie管理** : 内置一个 Cookie 管理器 12 | - [x] **前置后置拦截器** :支持前置拦截器与后置拦截器 13 | - [x] **单机模式** : 支持单机模式 14 | - [x] **集群模式** : 支持集群模式 15 | - [x] **服务注册与发现** :实现了一个基于 Zk 的服务注册与发现,来支持多节点模式 16 | - [ ] **Session管理** : 因为涉及到多节点模式,分布式 session 暂未实现 17 | 18 | 19 | 20 | 21 | 22 | ## 快速启动 23 | 24 | ### 1.单机模式 25 | 26 | Redant 是一个基于 Netty 的 Web 容器,类似 Tomcat 和 WebLogic 等容器 27 | 28 | 只需要启动一个 Server,默认的实现类是 NettyHttpServer 就能快速启动一个 web 容器了,如下所示: 29 | 30 | ``` java 31 | public final class ServerBootstrap { 32 | public static void main(String[] args) { 33 | Server nettyServer = new NettyHttpServer(); 34 | // 各种初始化工作 35 | nettyServer.preStart(); 36 | // 启动服务器 37 | nettyServer.start(); 38 | } 39 | } 40 | ``` 41 | 42 | 43 | 44 | ### 2.集群模式 45 | 46 | 到目前为止,我描述的都是单节点模式,如果哪一天单节点的性能无法满足了,那就需要使用集群了,所以我也实现了集群模式。 47 | 48 | 集群模式是由一个主节点和若干个从节点构成的。主节点接收到请求后,将请求转发给从节点来处理,从节点把处理好的结果返回给主节点,由主节点把结果响应给请求。 49 | 50 | 要想实现集群模式需要有一个服务注册和发现的功能,目前是借助于 Zk 来做的服务注册与发现。 51 | 52 | 53 | #### 准备一个 Zk 服务端 54 | 55 | 因为主节点需要把请求转发给从节点,所以主节点需要知道目前有哪些从节点,我通过 ZooKeeper 来实现服务注册与发现。 56 | 57 | 如果你没有可用的 Zk 服务端的话,那你可以通过运行下面的 Main 方法来启动一个 ZooKeeper 服务端: 58 | 59 | ``` java 60 | public final class ZkBootstrap { 61 | private static final Logger LOGGER = LoggerFactory.getLogger(ZkBootstrap.class); 62 | 63 | public static void main(String[] args) { 64 | try { 65 | ZkServer zkServer = new ZkServer(); 66 | zkServer.startStandalone(ZkConfig.DEFAULT); 67 | }catch (Exception e){ 68 | LOGGER.error("ZkBootstrap start failed,cause:",e); 69 | System.exit(1); 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | 这样你就可以在后面启动主从节点的时候使用这个 Zk 了。但是这并不是必须的,如果你已经有一个正在运行的 Zk 的服务端,那么你可以在启动主从节点的时候直接使用它,通过在 main 方法的参数中指定 Zk 的地址即可。 76 | 77 | 78 | 79 | #### 启动主节点 80 | 81 | 只需要运行下面的代码,就可以启动一个主节点了: 82 | 83 | ``` java 84 | public class MasterServerBootstrap { 85 | public static void main(String[] args) { 86 | String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT); 87 | 88 | // 启动MasterServer 89 | Server masterServer = new MasterServer(zkAddress); 90 | masterServer.preStart(); 91 | masterServer.start(); 92 | } 93 | } 94 | ``` 95 | 96 | 如果在 main 方法的参数中指定了 Zk 的地址,就通过该地址去进行服务发现,否则会使用默认的 Zk 地址 97 | 98 | 99 | 100 | #### 启动从节点 101 | 102 | 只需要运行下面的代码,就可以启动一个从节点了: 103 | 104 | ``` java 105 | public class SlaveServerBootstrap { 106 | 107 | public static void main(String[] args) { 108 | String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT); 109 | Node node = Node.getNodeWithArgs(args); 110 | 111 | // 启动SlaveServer 112 | Server slaveServer = new SlaveServer(zkAddress,node); 113 | slaveServer.preStart(); 114 | slaveServer.start(); 115 | } 116 | 117 | } 118 | ``` 119 | 120 | 如果在 main 方法的参数中指定了 Zk 的地址,就通过该地址去进行服务注册,否则会使用默认的 Zk 地址 121 | 122 | 123 | 124 | ## 例子 125 | 126 | 你可以运行 redant-example 模块中提供的例子来体验一下,example 模块中内置了两个 Controller 。 127 | 128 | 启动完之后,你可以在浏览器中访问 http://127.0.0.1:8888 来查看具体的效果 (默认的端口可以在 redant.properties 中修改) 129 | 130 | 如果你看到了这样的消息:"Welcome to redant!" 这就意味着你已经启动成功了。 131 | 132 | 在 redant-example 模块中,内置了以下几个默认的路由: 133 | 134 | | 方法类型 | URL | 响应类型 | 135 | | ----------------- | ---------------------------- | ----------------------------- | 136 | | GET | / | HTML | 137 | | \* | \* | HTML | 138 | | GET | /user/count | JSON | 139 | | GET | /user/list | JSON | 140 | | GET | /user/info | JSON | 141 | 142 | 143 | 144 | 145 | ## Bean 管理器 146 | 147 | 跟 Spring 一样,你可以通过 @Bean 注解来管理所有的对象,通过 @Autowired 来自动注入 148 | 149 | **Tips:** 更多信息请查看wiki: [Bean][1] 150 | 151 | 152 | 153 | ## 自定义路由 154 | 155 | 跟 Spring 一样,你可以通过 @Controller 来自定义一个 Controller. 156 | 157 | @Mapping 注解用在方法级别,@Controller + @Mapping 唯一定义一个 http 请求。 158 | 159 | @Param 注解用在方法的参数上。通过该注解可以自动将基本类型转成 POJO 对象。 160 | 161 | **Tips:** 更多信息请查看wiki: [Router][2] 162 | 163 | 164 | 165 | ## Cookie 管理器 166 | 167 | Cookie 管理器可以管理用户自定义的 Cookie。 168 | 169 | **Tips:** 更多信息请查看wiki: [Cookie][4] 170 | 171 | 172 | 173 | ## 联系我 174 | 175 | > wh_all4you#hotmail.com 176 | 177 | ![contact-me](./logo.jpg) 178 | 179 | 180 | 181 | 182 | [1]: https://github.com/all4you/redant/wiki/1:Bean 183 | [2]: https://github.com/all4you/redant/wiki/2:Router 184 | [3]: https://github.com/all4you/redant/wiki/3:Session 185 | [4]: https://github.com/all4you/redant/wiki/4:Cookie 186 | 187 | 188 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/logo.jpg -------------------------------------------------------------------------------- /md/interceptor.md: -------------------------------------------------------------------------------- 1 | # 用责任链模式设计拦截器 2 | 3 | 我在 Redant(https://github.com/all4you/redant) 中通过继承 ChannelHandler 实现了拦截器的功能,并且 pipeline 就是一种责任链模式的应用。但是我后面对原本的拦截器进行了重新设计,为什么这样做呢,因为原本的方式是在 ChannelHandler 的基础上操作的,而我们知道 Netty 的数据处理都是基于 ByteBuf 的,这就涉及到引用计数释放的问题,前面的 ChannelHandler 在处理时可以不关心引用计数的问题,而交给最后一个 ChannelHandler 去释放。 4 | 5 | 但是拦截器的一大特性就是当某个条件不满足时需要中断后面的操作直接返回,所以这就造成了在 pipeline 中某个节点需要释放引用计数,另外一个方面就是原先的设计使用了很多自定义的 ChannelHandler,有的只做了一些简单的工作,所以完全可以对他们进行合并,使代码变得更加精简紧凑。 6 | 7 | 合并多个 ChannelHandler 是比较简单的,重新设计拦截器相对就复杂一些了。 8 | 9 | ## 重新设计拦截器 10 | 11 | 首先我把原本的前置拦截器和后置拦截器统一成一个拦截器,然后抽象出两个方法,分别表示:前置处理,后置处理,如下图所示: 12 | 13 | ![interceptor](interceptor/interceptor.png) 14 | 15 | 默认前置处理的方法返回 true,用户可以根据他们的业务进行覆盖。 16 | 17 | 这里是定义了一个抽象类,也可以用接口,java 8 开始接口中可以有默认方法实现。 18 | 19 | 拦截器定义好之后,现在就可以在 ChannelHandler 中加入拦截器的方法调用了,如下图所示: 20 | 21 | ![interceptor-handle](interceptor/interceptor-handle.png) 22 | 23 | 当前置方法返回 false 时,直接返回,中断后面的业务逻辑处理,最终会到 finally 中将结果写入 response 中返回给前端。 24 | 25 | 现在只要实现 InterceptorHandler 中的两个方法就可以了,其实这也很简单,只要获取到所有的 Interceptor 的实现类,然后依次调用这些实现类的前置方法和后置方法就好了,如下图所示: 26 | 27 | ![interceptor-call](interceptor/interceptor-call.png) 28 | 29 | ## 获取拦截器 30 | 31 | 现在的重点就是怎样获取到所有的拦截器,首先可以想到的是通过扫描的方法,找到所有 Interceptor 的实现类,然后将这些实现类加入到一个 List 中即可。 32 | 33 | 那怎么保证拦截器的执行顺序呢,很简单,只要在加入 List 之前对他们进行排序就可以了。再定义一个 @Order 注解来表示排序的顺序,然后用一个 Wrapper 包装类将 Interceptor 和 Order 包装起来,排序到包装类的 List 中,最后再从包装类的 List 中依次取出所有的 Interceptor 就完成了 Interceptor 的排序了。 34 | 35 | 知道了大致的原理之后,实现起来就很简单了,如下图所示: 36 | 37 | ![get-interceptor](interceptor/get-interceptor.png) 38 | 39 | 但是我们不能每次都通过调用 scanInterceptors() 方法来获取所有的拦截器,如果这样每次都扫描一次的话性能会有影响,所以我们只需要第一次调用一下该方法,然后把结果保存在一个私有的变量中,获取的时候直接读取该变量的值即可,如下图所示: 40 | 41 | ![interceptor-provider](interceptor/interceptor-provider.png) 42 | 43 | ## 自定义拦截器实现类 44 | 45 | 下面让我们来自定义两个拦截器实现类,来验证下具体的效果。 46 | 47 | 第一个拦截器,在前置方法中对请求参数进行判断,如果请求参数中有 block=true 的参数,则进行拦截,如下图所示: 48 | 49 | ![block-interceptor](interceptor/block-interceptor.png) 50 | 51 | 第二个拦截器,在后置方法中打印出每次请求的耗时,如下图所示: 52 | 53 | ![performance-interceptor](interceptor/performance-interceptor.png) 54 | 55 | 通过 @Order 注解来指定执行的顺序,先执行 BlockInterceptor 再执行 PerformanceInterceptor。 56 | 57 | ## 查看效果 58 | 59 | 现在我们请求 /user/info 这个接口,查看下效果。 60 | 61 | 首先我们只提交正常的参数,如下图所示: 62 | 63 | ![common-request](interceptor/common-request.png) 64 | 65 | 打印的结果如下图所示: 66 | 67 | ![common-request-effect](interceptor/common-request-effect.png) 68 | 69 | 从打印的结果中可以看到依次执行了: 70 | 71 | - BlockInterceptor 的 preHandle 方法 72 | - PerformanceInterceptor 的 preHandle方法 73 | - BlockInterceptor 的 postHandle 方法 74 | - PerformanceInterceptor 的 postHandle方法 75 | 76 | 这说明拦截器是按照 @Order 注解进行了排序,然后依次执行的。 77 | 78 | 然后我们再提交一个 block=true 的参数,再次请求该接口,如下图所示: 79 | 80 | ![block-request](interceptor/block-request.png) 81 | 82 | 可以看到该请求已经被拦截器的前置方法给拦截了,再看下打印的日志,如下图所示: 83 | 84 | ![block-request-effect](interceptor/block-request-effect.png) 85 | 86 | 只打印了 BlockInterceptor 的 preHandler 方法中的部分日志,后面的方法都没有执行,因为被拦截了直接返回了。 87 | 88 | ## 存在的问题 89 | 90 | 到这里已经对拦截器完成了改造,并且也验证了效果,看上去效果还可以。但是有没有什么问题呢? 91 | 92 | 还真有一个问题:所有的 Interceptor 实现类只要被扫描到了,就会被加入到 List 中去,如果不想应用某一个拦截器这时就做不到了,因为无法对 list 中的值进行动态的更改。 93 | 94 | 如果我们可以动态的获取一个保存了 Interceptor 的 list ,如果该 list 中没有获取到值,再通过扫描的方式去拿到所有的 Interceptor 这样就完美了。 95 | 96 | 动态获取 Interceptor 的 list 的方法,可以由用户自定义实现,根据某些规则来确定要不要将某个 Interceptor 加入到 list 中去,这样就把 Interceptor 的实现和使用进行了解耦了。用户可以实现任意多的 Interceptor,但是只根据规则去使用其中的某些 Interceptor。 97 | 98 | 理清楚了原理之后,就很好实现了,首先定义一个接口,用来构造 Interceptor 的 List,如下图所示: 99 | 100 | ![interceptor-builder](interceptor/interceptor-builder.png) 101 | 102 | 有了 InterceptorBuilder 之后,在获取 Interceptor 的时候,就可以先根据 InterceptorBuilder 来获取了,如下图所示: 103 | 104 | ![interceptor-provider-with-builder](interceptor/interceptor-provider-with-builder.png) 105 | 106 | 以下是一个示例的 InterceptorBuilder,具体的可以用户自行扩展设计,如下图所示: 107 | 108 | ![custom-interceptor-builder](interceptor/custom-interceptor-builder.png) 109 | 110 | 这样用户只要实现一个 InterceptorBuilder 接口,即可按照自己的意图去组装所有的拦截器。 111 | -------------------------------------------------------------------------------- /md/interceptor/block-interceptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/block-interceptor.png -------------------------------------------------------------------------------- /md/interceptor/block-request-effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/block-request-effect.png -------------------------------------------------------------------------------- /md/interceptor/block-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/block-request.png -------------------------------------------------------------------------------- /md/interceptor/common-request-effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/common-request-effect.png -------------------------------------------------------------------------------- /md/interceptor/common-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/common-request.png -------------------------------------------------------------------------------- /md/interceptor/custom-interceptor-builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/custom-interceptor-builder.png -------------------------------------------------------------------------------- /md/interceptor/get-interceptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/get-interceptor.png -------------------------------------------------------------------------------- /md/interceptor/interceptor-builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/interceptor-builder.png -------------------------------------------------------------------------------- /md/interceptor/interceptor-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/interceptor-call.png -------------------------------------------------------------------------------- /md/interceptor/interceptor-handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/interceptor-handle.png -------------------------------------------------------------------------------- /md/interceptor/interceptor-provider-with-builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/interceptor-provider-with-builder.png -------------------------------------------------------------------------------- /md/interceptor/interceptor-provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/interceptor-provider.png -------------------------------------------------------------------------------- /md/interceptor/interceptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/interceptor.png -------------------------------------------------------------------------------- /md/interceptor/performance-interceptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/interceptor/performance-interceptor.png -------------------------------------------------------------------------------- /md/overview/after-interceptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/after-interceptor.png -------------------------------------------------------------------------------- /md/overview/base-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/base-controller.png -------------------------------------------------------------------------------- /md/overview/bean-context-aware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/bean-context-aware.png -------------------------------------------------------------------------------- /md/overview/default-routers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/default-routers.png -------------------------------------------------------------------------------- /md/overview/features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/features.png -------------------------------------------------------------------------------- /md/overview/field-inject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/field-inject.png -------------------------------------------------------------------------------- /md/overview/init-bean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/init-bean.png -------------------------------------------------------------------------------- /md/overview/init-channel-handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/init-channel-handler.png -------------------------------------------------------------------------------- /md/overview/pre-interceptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/pre-interceptor.png -------------------------------------------------------------------------------- /md/overview/setter-inject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/setter-inject.png -------------------------------------------------------------------------------- /md/overview/start-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/start-up.png -------------------------------------------------------------------------------- /md/overview/user-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/user-controller.png -------------------------------------------------------------------------------- /md/overview/user-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/user-list.png -------------------------------------------------------------------------------- /md/overview/welcome-to-redant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/overview/welcome-to-redant.png -------------------------------------------------------------------------------- /md/processor.md: -------------------------------------------------------------------------------- 1 | # 责任链 2 | 3 | 我在 Redant 中实现的拦截器所使用的责任链,其实是通过了一个 List 来保存了所有的 Interceptor,那我们通常所说的责任链除了使用 List 来实现外,还可以通过真正的链表结构来实现,Netty 和 Sentinel 中都有这样的实现,下面我来实现一个简单的链式结构的责任链。 4 | 5 | 责任链的应用已经有很多了,这里不再赘述,假设我们需要对前端提交的请求做以下操作:鉴权,登录,日志记录,通过责任链来做这些处理是非常合适的。 6 | 7 | 首先定义一个处理接口,如下图所示: 8 | 9 | ![processor](processor/processor.png) 10 | 11 | 通过 List 方式的实现很简单,只需要把每个 Processor 的实现类添加到一个 List 中即可,处理的时候遍历该 List 依次处理,这里不再做具体的描述,感兴趣的可以自行实现。 12 | 13 | ## 定义节点 14 | 15 | 如果是通过链表的形式来实现的话,首先我们需要有一个类表示链表中的某个节点,并且该节点需要有一个同类型的私有变量表示该节点的下个节点,这样就可以实现一个链表了,如下图所示: 16 | 17 | ![abstract-linked-processor](processor/abstract-linked-processor.png) 18 | 19 | ## 定义容器 20 | 21 | 接着我们需要定义一个容器,在容器中有头,尾两个节点,头结点作为一个空节点,真正的节点将添加到头结点的 next 节点上去,尾节点作为一个指针,用来指向当前添加的节点,下一次添加新节点时,将从尾节点处添加。有了具体的处理逻辑之后,实现起来就很简单了,这个容器的实现如下图所示: 22 | 23 | ![linked-processor-chain](processor/linked-processor-chain.png) 24 | 25 | ## 定义实现类 26 | 27 | 下面我们可以实现具体的 Processor 来处理业务逻辑了,只要继承 AbstractLinkedProcessor 即可,如下图所示: 28 | 29 | ![auth-processor](processor/auth-processor.png) 30 | 31 | 其他两个实现类: LoginProcessor ,LogProcessor 类似,这里就不贴出来了。 32 | 33 | 然后就可以根据规则来组装所需要的 Processor 了,假设我们的规则是需要对请求依次进行:鉴权,登录,日志记录,那组装的代码如下图所示: 34 | 35 | ![linked-processor-chain-test](processor/linked-processor-chain-test.png) 36 | 37 | 执行该代码,结果如下图所示: 38 | 39 | ![linked-processor-chain-test-result](processor/linked-processor-chain-test-result.png) 40 | 41 | ## 存在的问题 42 | 43 | 看的仔细的同学可以发现了,在 AuthProcessor 的业务逻辑实现中,除了执行了具体的逻辑代码之外,还调用了一行 super.process(content) 代码,这行代码的作用是调用链表中的下一个节点的 process 方法。但是如果有一天我们在写自己的 Processor 实现类时,忘记调用这行代码的话,会是怎样的结果呢? 44 | 45 | 结果就是当前节点后面的节点不会被调用,整个链表就像断掉一样,那怎样来避免这种问题的发生呢?其实我们在 AbstractProcessor 中已经实现了 process 方法,该方法就是调用下个节点的 process 方法的。那我们在这个方法触发调用下个节点之前,再抽象出一个用以具体的业务逻辑处理的方法 doProcess ,先执行 doProcess 方法,执行完之后再触发下个节点的 process ,这样就不会出现链表断掉的情况了,具体的实现如下图所示: 46 | 47 | ![fixed-abstract-linked-processor](processor/fixed-abstract-linked-processor.png) 48 | 49 | 相应的 LinkedProcessorChain 和具体的实现类也要做响应的调整,如下图所示: 50 | 51 | ![fixed-linked-processor-chain](processor/fixed-linked-processor-chain.png) 52 | 53 | ![fixed-auth-processor](processor/fixed-auth-processor.png) 54 | 55 | 重新执行刚刚的测试类,发现结果和之前的一样,至此一个简单的链式责任链完成了。 -------------------------------------------------------------------------------- /md/processor/abstract-linked-processor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/abstract-linked-processor.png -------------------------------------------------------------------------------- /md/processor/auth-processor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/auth-processor.png -------------------------------------------------------------------------------- /md/processor/fixed-abstract-linked-processor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/fixed-abstract-linked-processor.png -------------------------------------------------------------------------------- /md/processor/fixed-auth-processor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/fixed-auth-processor.png -------------------------------------------------------------------------------- /md/processor/fixed-linked-processor-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/fixed-linked-processor-chain.png -------------------------------------------------------------------------------- /md/processor/linked-processor-chain-test-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/linked-processor-chain-test-result.png -------------------------------------------------------------------------------- /md/processor/linked-processor-chain-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/linked-processor-chain-test.png -------------------------------------------------------------------------------- /md/processor/linked-processor-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/linked-processor-chain.png -------------------------------------------------------------------------------- /md/processor/processor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/redant/38f32e1bd3109f9207c7ce606662cac7818611e7/md/processor/processor.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.redant 8 | redant 9 | 1.0.0-SNAPSHOT 10 | pom 11 | redant 12 | 13 | 14 | 1.8 15 | UTF-8 16 | redant 17 | 1.0.0-SNAPSHOT 18 | 1.9.2 19 | 1.7.7 20 | 1.1.7 21 | 1.2.54 22 | 4.1.9.Final 23 | 3.2.4 24 | 4.2.1 25 | 3.4.13 26 | 4.0.1 27 | 28 | 29 | 30 | redant-core 31 | redant-cluster 32 | redant-example 33 | 34 | 35 | 36 | 37 | 38 | commons-beanutils 39 | commons-beanutils 40 | ${common-beans.version} 41 | 42 | 43 | 44 | 45 | org.slf4j 46 | slf4j-api 47 | ${slf4j.version} 48 | 49 | 50 | org.slf4j 51 | jcl-over-slf4j 52 | ${slf4j.version} 53 | 54 | 55 | ch.qos.logback 56 | logback-classic 57 | ${logback.version} 58 | 59 | 60 | com.alibaba 61 | fastjson 62 | ${fastjson.version} 63 | 64 | 65 | io.netty 66 | netty-all 67 | ${netty.version} 68 | 69 | 70 | cglib 71 | cglib 72 | ${cglib.version} 73 | 74 | 75 | cn.hutool 76 | hutool-all 77 | ${hutool-all.version} 78 | 79 | 80 | org.apache.zookeeper 81 | zookeeper 82 | ${zookeeper.version} 83 | 84 | 85 | org.apache.curator 86 | curator-recipes 87 | ${curator.version} 88 | 89 | 90 | com.redant 91 | redant-core 92 | ${project-version} 93 | 94 | 95 | com.redant 96 | redant-cluster 97 | ${project-version} 98 | 99 | 100 | com.redant 101 | redant-example 102 | ${project-version} 103 | 104 | 105 | 106 | 107 | 108 | ${project.artifactId} 109 | 110 | 111 | org.apache.maven.plugins 112 | maven-compiler-plugin 113 | 3.0 114 | 115 | ${java.version} 116 | ${java.version} 117 | ${java.encoding} 118 | 119 | 120 | 121 | 122 | 123 | src/main/java 124 | 125 | **/*.vm 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /redant-cluster/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | redant 7 | com.redant 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | redant-cluster 13 | jar 14 | redant-cluster 15 | 1.0.0-SNAPSHOT 16 | 17 | 18 | 19 | com.redant 20 | redant-core 21 | 1.0.0-SNAPSHOT 22 | 23 | 24 | org.apache.zookeeper 25 | zookeeper 26 | 27 | 28 | org.apache.curator 29 | curator-recipes 30 | 31 | 32 | org.apache.zookeeper 33 | zookeeper 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/bootstrap/MasterServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.bootstrap; 2 | 3 | import com.redant.cluster.master.MasterServer; 4 | import com.redant.cluster.zk.ZkConfig; 5 | import com.redant.cluster.zk.ZkServer; 6 | import com.redant.core.server.Server; 7 | 8 | /** 9 | * MasterServerBootstrap 10 | * @author houyi.wh 11 | * @date 2017/11/20 12 | **/ 13 | public class MasterServerBootstrap { 14 | 15 | public static void main(String[] args) { 16 | String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT); 17 | 18 | // 启动MasterServer 19 | Server masterServer = new MasterServer(zkAddress); 20 | masterServer.preStart(); 21 | masterServer.start(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/bootstrap/SlaveServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.bootstrap; 2 | 3 | import com.redant.cluster.node.Node; 4 | import com.redant.cluster.slave.SlaveServer; 5 | import com.redant.cluster.zk.ZkConfig; 6 | import com.redant.cluster.zk.ZkServer; 7 | import com.redant.core.server.Server; 8 | 9 | /** 10 | * SlaveServerBootstrap 11 | * @author houyi.wh 12 | * @date 2017/11/20 13 | **/ 14 | public class SlaveServerBootstrap { 15 | 16 | public static void main(String[] args) { 17 | String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT); 18 | Node node = Node.getNodeWithArgs(args); 19 | 20 | // 启动SlaveServer 21 | Server slaveServer = new SlaveServer(zkAddress,node); 22 | slaveServer.preStart(); 23 | slaveServer.start(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/bootstrap/ZkBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.bootstrap; 2 | 3 | import com.redant.cluster.zk.ZkConfig; 4 | import com.redant.cluster.zk.ZkServer; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | 9 | /** 10 | * ZK启动入口 11 | * @author houyi.wh 12 | * @date 2017/11/21 13 | **/ 14 | public class ZkBootstrap { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(ZkBootstrap.class); 17 | 18 | 19 | public static void main(String[] args) { 20 | try { 21 | ZkServer zkServer = new ZkServer(); 22 | zkServer.startStandalone(ZkConfig.DEFAULT); 23 | }catch (Exception e){ 24 | LOGGER.error("ZkBootstrap start failed,cause:",e); 25 | System.exit(1); 26 | } 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/master/MasterServer.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.master; 2 | 3 | import com.redant.cluster.service.discover.ZkServiceDiscover; 4 | import com.redant.core.common.constants.CommonConstants; 5 | import com.redant.core.server.Server; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.*; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.codec.http.HttpContentCompressor; 12 | import io.netty.handler.codec.http.HttpObjectAggregator; 13 | import io.netty.handler.codec.http.HttpServerCodec; 14 | import io.netty.handler.stream.ChunkedWriteHandler; 15 | import io.netty.util.concurrent.DefaultThreadFactory; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | 20 | /** 21 | * MasterServer 22 | * @author houyi.wh 23 | * @date 2017/11/20 24 | */ 25 | public final class MasterServer implements Server { 26 | 27 | private static final Logger LOGGER = LoggerFactory.getLogger(MasterServer.class); 28 | 29 | private String zkAddress; 30 | 31 | public MasterServer(String zkAddress){ 32 | this.zkAddress = zkAddress; 33 | } 34 | 35 | @Override 36 | public void preStart() { 37 | // 监听SlaveNode的变化 38 | ZkServiceDiscover.getInstance(zkAddress).watch(); 39 | } 40 | 41 | @Override 42 | public void start() { 43 | EventLoopGroup bossGroup = new NioEventLoopGroup(CommonConstants.BOSS_GROUP_SIZE, new DefaultThreadFactory("boss", true)); 44 | EventLoopGroup workerGroup = new NioEventLoopGroup(CommonConstants.WORKER_GROUP_SIZE, new DefaultThreadFactory("worker", true)); 45 | try { 46 | long start = System.currentTimeMillis(); 47 | ServerBootstrap b = new ServerBootstrap(); 48 | b.option(ChannelOption.SO_BACKLOG, 1024); 49 | b.group(bossGroup, workerGroup) 50 | .channel(NioServerSocketChannel.class) 51 | // .handler(new LoggingHandler(LogLevel.INFO)) 52 | .childHandler(new MasterServerInitializer(zkAddress)); 53 | 54 | ChannelFuture future = b.bind(CommonConstants.SERVER_PORT).sync(); 55 | long cost = System.currentTimeMillis()-start; 56 | LOGGER.info("[MasterServer] Startup at port:{} cost:{}[ms]",CommonConstants.SERVER_PORT,cost); 57 | 58 | // 等待服务端Socket关闭 59 | future.channel().closeFuture().sync(); 60 | } catch (InterruptedException e) { 61 | LOGGER.error("InterruptedException:",e); 62 | } finally { 63 | bossGroup.shutdownGracefully(); 64 | workerGroup.shutdownGracefully(); 65 | } 66 | } 67 | 68 | private static class MasterServerInitializer extends ChannelInitializer { 69 | 70 | private String zkAddress; 71 | 72 | MasterServerInitializer(String zkAddress){ 73 | this.zkAddress = zkAddress; 74 | } 75 | 76 | @Override 77 | public void initChannel(SocketChannel ch) { 78 | ChannelPipeline pipeline = ch.pipeline(); 79 | 80 | pipeline.addLast(new HttpServerCodec()); 81 | addAdvanced(pipeline); 82 | pipeline.addLast(new ChunkedWriteHandler()); 83 | pipeline.addLast(new MasterServerHandler(zkAddress)); 84 | } 85 | 86 | /** 87 | * 可以在 HttpServerCodec 之后添加这些 ChannelHandler 进行开启高级特性 88 | */ 89 | private void addAdvanced(ChannelPipeline pipeline){ 90 | if(CommonConstants.USE_COMPRESS) { 91 | // 对 http 响应结果开启 gizp 压缩 92 | pipeline.addLast(new HttpContentCompressor()); 93 | } 94 | if(CommonConstants.USE_AGGREGATOR) { 95 | // 将多个HttpRequest组合成一个FullHttpRequest 96 | pipeline.addLast(new HttpObjectAggregator(CommonConstants.MAX_CONTENT_LENGTH)); 97 | } 98 | } 99 | 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/master/MasterServerBackendHandler.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.master; 2 | 3 | import io.netty.channel.*; 4 | 5 | /** 6 | * @author houyi.wh 7 | * @date 2018/1/18 8 | **/ 9 | public class MasterServerBackendHandler extends ChannelInboundHandlerAdapter { 10 | 11 | private final Channel inboundChannel; 12 | 13 | public MasterServerBackendHandler(Channel inboundChannel){ 14 | this.inboundChannel = inboundChannel; 15 | } 16 | 17 | @Override 18 | public void channelActive(ChannelHandlerContext ctx) { 19 | ctx.read(); 20 | } 21 | 22 | @Override 23 | public void channelRead(final ChannelHandlerContext ctx, Object msg) { 24 | inboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() { 25 | @Override 26 | public void operationComplete(ChannelFuture future) { 27 | if (future.isSuccess()) { 28 | ctx.channel().read(); 29 | } else { 30 | future.channel().close(); 31 | } 32 | } 33 | }); 34 | } 35 | 36 | @Override 37 | public void channelInactive(ChannelHandlerContext ctx) { 38 | MasterServerHandler.closeOnFlush(inboundChannel); 39 | } 40 | 41 | @Override 42 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 43 | cause.printStackTrace(); 44 | MasterServerHandler.closeOnFlush(ctx.channel()); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/master/MasterServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.master; 2 | 3 | import com.redant.cluster.service.discover.ServiceDiscover; 4 | import com.redant.cluster.service.discover.ZkServiceDiscover; 5 | import com.redant.cluster.node.Node; 6 | import com.redant.core.common.constants.CommonConstants; 7 | import io.netty.bootstrap.Bootstrap; 8 | import io.netty.buffer.Unpooled; 9 | import io.netty.channel.*; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.handler.codec.http.HttpClientCodec; 12 | import io.netty.handler.codec.http.HttpObjectAggregator; 13 | import io.netty.handler.codec.http.HttpRequest; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * MasterServerHandler is a http master which 19 | * will transfer http request to node server 20 | * @author houyi.wh 21 | * @date 2017/11/20 22 | */ 23 | public class MasterServerHandler extends ChannelInboundHandlerAdapter { 24 | 25 | private final static Logger LOGGER = LoggerFactory.getLogger(MasterServerHandler.class); 26 | 27 | private Node node; 28 | 29 | /** 30 | * Client--->Master Channel 31 | */ 32 | private Channel inboundChannel; 33 | 34 | /** 35 | * Master--->Slave Channel 36 | */ 37 | private Channel outboundChannel; 38 | 39 | private ChannelFuture outboundConnectFuture; 40 | 41 | private ServiceDiscover serviceDiscover; 42 | 43 | public MasterServerHandler(String zkAddress){ 44 | this.serviceDiscover = ZkServiceDiscover.getInstance(zkAddress); 45 | } 46 | 47 | @Override 48 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 49 | node = serviceDiscover.discover(); 50 | inboundChannel = ctx.channel(); 51 | 52 | // Start the connection attempt. 53 | Bootstrap bootstrap = new Bootstrap(); 54 | bootstrap.group(inboundChannel.eventLoop()) 55 | .channel(ctx.channel().getClass()) 56 | // use master inboundChannel to write back the response get from remote server 57 | .handler(new ChannelInitializer() { 58 | @Override 59 | public void initChannel(SocketChannel channel) throws Exception { 60 | ChannelPipeline pipeline = channel.pipeline(); 61 | pipeline.addLast(new HttpClientCodec()); 62 | pipeline.addLast(new HttpObjectAggregator(CommonConstants.MAX_CONTENT_LENGTH)); 63 | pipeline.addLast(new MasterServerBackendHandler(inboundChannel)); 64 | } 65 | }); 66 | bootstrap.option(ChannelOption.AUTO_READ, false); 67 | // connect to node 68 | outboundConnectFuture = bootstrap.connect(node.getHost(), node.getPort()); 69 | // get outboundChannel to remote server 70 | outboundChannel = outboundConnectFuture.channel(); 71 | 72 | } 73 | 74 | @Override 75 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 76 | outboundConnectFuture.addListener(new ChannelFutureListener() { 77 | @Override 78 | public void operationComplete(ChannelFuture future) { 79 | if (future.isSuccess()) { 80 | // connection complete start to read first data 81 | inboundChannel.read(); 82 | if(outboundChannel.isActive()) { 83 | if(msg instanceof HttpRequest){ 84 | HttpRequest request = (HttpRequest)msg; 85 | if(request.uri().equals(CommonConstants.FAVICON_ICO)){ 86 | return; 87 | } 88 | outboundChannel.writeAndFlush(request).addListener(new ChannelFutureListener() { 89 | @Override 90 | public void operationComplete(ChannelFuture future) { 91 | if (future.isSuccess()) { 92 | // was able to flush out data, start to read the next chunk 93 | ctx.channel().read(); 94 | } else { 95 | LOGGER.error("write to backend {}:{} error,cause:{}", node.getHost(), node.getPort(),future.cause()); 96 | future.channel().close(); 97 | } 98 | } 99 | }); 100 | }else{ 101 | closeOnFlush(ctx.channel()); 102 | } 103 | } 104 | } else { 105 | LOGGER.error("connect to backend {}:{} error,cause:{}", node.getHost(), node.getPort(),future.cause()); 106 | // Close the connection if the connection attempt has failed. 107 | inboundChannel.close(); 108 | } 109 | } 110 | }); 111 | 112 | } 113 | 114 | @Override 115 | public void channelInactive(ChannelHandlerContext ctx) { 116 | if (outboundChannel != null) { 117 | closeOnFlush(outboundChannel); 118 | } 119 | } 120 | 121 | @Override 122 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 123 | cause.printStackTrace(); 124 | closeOnFlush(ctx.channel()); 125 | } 126 | 127 | /** 128 | * Closes the specified channel after all queued write requests are flushed. 129 | */ 130 | static void closeOnFlush(Channel channel) { 131 | if (channel.isActive()) { 132 | channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); 133 | } 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/node/Node.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.node; 2 | 3 | import cn.hutool.core.util.NumberUtil; 4 | import cn.hutool.crypto.SecureUtil; 5 | import com.alibaba.fastjson.JSON; 6 | import com.alibaba.fastjson.JSONObject; 7 | import com.redant.core.common.util.GenericsUtil; 8 | 9 | /** 10 | * Node 11 | * @author houyi.wh 12 | * @date 2017/11/20 13 | **/ 14 | public class Node { 15 | 16 | private static final String DEFAULT_HOST = GenericsUtil.getLocalIpV4(); 17 | 18 | private static final int DEFAULT_PORT = 8088; 19 | 20 | public static final Node DEFAULT_PORT_NODE = new Node(DEFAULT_HOST,DEFAULT_PORT); 21 | 22 | private String id; 23 | 24 | private String host; 25 | 26 | private int port; 27 | 28 | public Node(int port){ 29 | this(DEFAULT_HOST,port); 30 | } 31 | 32 | public Node(String host, int port){ 33 | this(SecureUtil.md5(host+"&"+port),host,port); 34 | } 35 | 36 | public Node(String id, String host, int port){ 37 | this.id = id; 38 | this.host = host; 39 | this.port = port; 40 | } 41 | 42 | public static Node getNodeWithArgs(String[] args){ 43 | Node node = Node.DEFAULT_PORT_NODE; 44 | if(args.length>1 && NumberUtil.isInteger(args[1])){ 45 | node = new Node(Integer.parseInt(args[1])); 46 | } 47 | return node; 48 | } 49 | 50 | /** 51 | * 从JsonObject中解析出SlaveNode 52 | * @param object 对象 53 | * @return 节点 54 | */ 55 | public static Node parse(JSONObject object){ 56 | if(object==null){ 57 | return null; 58 | } 59 | String host = object.getString("host"); 60 | int port = object.getIntValue("port"); 61 | String id = object.getString("id"); 62 | return new Node(id,host,port); 63 | } 64 | 65 | public String getHost() { 66 | return host; 67 | } 68 | 69 | public void setHost(String host) { 70 | this.host = host; 71 | } 72 | 73 | public int getPort() { 74 | return port; 75 | } 76 | 77 | public void setPort(int port) { 78 | this.port = port; 79 | } 80 | 81 | public String getId(){ 82 | return id; 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return JSON.toJSONString(this); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/service/discover/ServiceDiscover.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.service.discover; 2 | 3 | import com.redant.cluster.node.Node; 4 | 5 | /** 6 | * 服务发现-应用级别 7 | * @author houyi.wh 8 | * @date 2017/11/20 9 | **/ 10 | public interface ServiceDiscover { 11 | 12 | /** 13 | * 监听Slave节点 14 | */ 15 | void watch(); 16 | 17 | /** 18 | * 发现可用的Slave节点 19 | * @return 可用节点 20 | */ 21 | Node discover(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/service/discover/ZkServiceDiscover.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.service.discover; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.redant.cluster.node.Node; 6 | import com.redant.cluster.zk.ZkClient; 7 | import com.redant.cluster.zk.ZkNode; 8 | import io.netty.util.CharsetUtil; 9 | import org.apache.curator.framework.CuratorFramework; 10 | import org.apache.curator.framework.recipes.cache.ChildData; 11 | import org.apache.curator.framework.recipes.cache.PathChildrenCache; 12 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; 13 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.concurrent.locks.Lock; 21 | import java.util.concurrent.locks.ReentrantLock; 22 | 23 | /** 24 | * 服务发现 25 | * @author houyi.wh 26 | * @date 2017/11/20 27 | **/ 28 | public class ZkServiceDiscover implements ServiceDiscover { 29 | 30 | private static final Logger LOGGER = LoggerFactory.getLogger(ZkServiceDiscover.class); 31 | 32 | private CuratorFramework client; 33 | 34 | private Map nodeMap; 35 | 36 | private Lock lock; 37 | 38 | private int slaveIndex = 0; 39 | 40 | private static ServiceDiscover discover; 41 | 42 | private ZkServiceDiscover(){ 43 | 44 | } 45 | 46 | private ZkServiceDiscover(String zkAddress){ 47 | client = ZkClient.getClient(zkAddress); 48 | nodeMap = new HashMap<>(); 49 | lock = new ReentrantLock(); 50 | } 51 | 52 | public static ServiceDiscover getInstance(String zkAddress){ 53 | if(discover==null) { 54 | synchronized (ZkServiceDiscover.class) { 55 | if(discover==null) { 56 | discover = new ZkServiceDiscover(zkAddress); 57 | } 58 | } 59 | } 60 | return discover; 61 | } 62 | 63 | @Override 64 | public void watch() { 65 | if(client==null){ 66 | throw new IllegalArgumentException("param illegal with client=null"); 67 | } 68 | initNodeOnFirst(); 69 | doWatch(); 70 | } 71 | 72 | @Override 73 | public Node discover() { 74 | if(client==null){ 75 | throw new IllegalArgumentException("param illegal with client=null"); 76 | } 77 | lock.lock(); 78 | try { 79 | if (nodeMap.size() == 0) { 80 | LOGGER.error("No available Node!"); 81 | return null; 82 | } 83 | Node[] nodes = new Node[]{}; 84 | nodes = nodeMap.values().toArray(nodes); 85 | // 通过CAS循环获取下一个可用服务 86 | if (slaveIndex>=nodes.length) { 87 | slaveIndex = 0; 88 | } 89 | return nodes[slaveIndex++]; 90 | }finally { 91 | lock.unlock(); 92 | } 93 | } 94 | 95 | private void initNodeOnFirst(){ 96 | try { 97 | if(client.checkExists().forPath(ZkNode.ROOT_NODE_PATH)!=null){ 98 | List children = client.getChildren().forPath(ZkNode.ROOT_NODE_PATH); 99 | for(String child : children){ 100 | String childPath = ZkNode.ROOT_NODE_PATH+"/"+child; 101 | byte[] data = client.getData().forPath(childPath); 102 | Node node = Node.parse(JSON.parseObject(data,JSONObject.class)); 103 | if(node !=null){ 104 | LOGGER.info("add slave={} to nodeMap when init", node); 105 | nodeMap.put(node.getId(), node); 106 | } 107 | } 108 | } 109 | } catch (Exception e) { 110 | LOGGER.error("initNodeOnFirst error cause:",e); 111 | } 112 | } 113 | 114 | private void doWatch(){ 115 | try { 116 | PathChildrenCache watcher = new PathChildrenCache( 117 | client, 118 | ZkNode.ROOT_NODE_PATH, 119 | true 120 | ); 121 | watcher.getListenable().addListener(new SlaveNodeWatcher()); 122 | watcher.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); 123 | }catch(Exception e){ 124 | LOGGER.error("doWatch error cause:",e); 125 | } 126 | } 127 | 128 | private class SlaveNodeWatcher implements PathChildrenCacheListener { 129 | @Override 130 | public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { 131 | ChildData data = event.getData(); 132 | if(data==null || data.getData()==null){ 133 | return; 134 | } 135 | Node node = Node.parse(JSON.parseObject(data.getData(),JSONObject.class)); 136 | if(node ==null){ 137 | LOGGER.error("get a null slave with eventType={},path={},data={}",event.getType(),data.getPath(),data.getData()); 138 | }else { 139 | switch (event.getType()) { 140 | case CHILD_ADDED: 141 | nodeMap.put(node.getId(), node); 142 | LOGGER.info("CHILD_ADDED with path={},data={},current slave size={}", data.getPath(), new String(data.getData(),CharsetUtil.UTF_8), nodeMap.size()); 143 | break; 144 | case CHILD_REMOVED: 145 | nodeMap.remove(node.getId()); 146 | LOGGER.info("CHILD_REMOVED with path={},data={},current slave size={}", data.getPath(), new String(data.getData(),CharsetUtil.UTF_8), nodeMap.size()); 147 | break; 148 | case CHILD_UPDATED: 149 | nodeMap.replace(node.getId(), node); 150 | LOGGER.info("CHILD_UPDATED with path={},data={},current slave size={}", data.getPath(), new String(data.getData(),CharsetUtil.UTF_8), nodeMap.size()); 151 | break; 152 | default: 153 | break; 154 | } 155 | } 156 | } 157 | } 158 | 159 | 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/service/register/ServiceRegister.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.service.register; 2 | 3 | import com.redant.cluster.node.Node; 4 | 5 | /** 6 | * 服务注册-应用级别 7 | * @author houyi.wh 8 | * @date 2017/11/21 9 | **/ 10 | public interface ServiceRegister { 11 | 12 | /** 13 | * 注册节点 14 | * @param node 节点 15 | */ 16 | void register(Node node); 17 | } 18 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/service/register/ZkServiceRegister.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.service.register; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.redant.cluster.node.Node; 5 | import com.redant.cluster.zk.ZkClient; 6 | import com.redant.cluster.zk.ZkNode; 7 | import org.apache.curator.framework.CuratorFramework; 8 | import org.apache.zookeeper.CreateMode; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * @author houyi.wh 14 | * @date 2017/11/21 15 | **/ 16 | public class ZkServiceRegister implements ServiceRegister { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(ZkServiceRegister.class); 19 | 20 | private CuratorFramework client; 21 | 22 | private static ZkServiceRegister register; 23 | 24 | private ZkServiceRegister(){ 25 | 26 | } 27 | 28 | private ZkServiceRegister(String zkAddress){ 29 | client = ZkClient.getClient(zkAddress); 30 | } 31 | 32 | public static ServiceRegister getInstance(String zkAddress){ 33 | if(register==null) { 34 | synchronized (ZkServiceRegister.class) { 35 | if(register==null) { 36 | register = new ZkServiceRegister(zkAddress); 37 | } 38 | } 39 | } 40 | return register; 41 | } 42 | 43 | @Override 44 | public void register(Node node) { 45 | if(client==null || node ==null){ 46 | throw new IllegalArgumentException(String.format("param illegal with client={%s},slave={%s}",client==null?null:client.toString(), node ==null?null: node.toString())); 47 | } 48 | try { 49 | if(client.checkExists().forPath(ZkNode.SLAVE_NODE_PATH)==null) { 50 | // 创建临时节点 51 | client.create() 52 | .creatingParentsIfNeeded() 53 | .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) 54 | .forPath(ZkNode.SLAVE_NODE_PATH, StrUtil.utf8Bytes(node.toString())); 55 | } 56 | } catch (Exception e) { 57 | LOGGER.error("register slave error with slave={},cause:", node,e); 58 | } 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/slave/SlaveServer.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.slave; 2 | 3 | import com.redant.cluster.node.Node; 4 | import com.redant.cluster.service.register.ZkServiceRegister; 5 | import com.redant.core.common.constants.CommonConstants; 6 | import com.redant.core.init.InitExecutor; 7 | import com.redant.core.server.NettyHttpServerInitializer; 8 | import com.redant.core.server.Server; 9 | import io.netty.bootstrap.ServerBootstrap; 10 | import io.netty.channel.ChannelFuture; 11 | import io.netty.channel.ChannelOption; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.nio.NioServerSocketChannel; 15 | import io.netty.util.concurrent.DefaultThreadFactory; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | 20 | /** 21 | * SlaveServer 22 | * @author houyi.wh 23 | * @date 2017/11/20 24 | */ 25 | public final class SlaveServer implements Server { 26 | 27 | private static final Logger LOGGER = LoggerFactory.getLogger(SlaveServer.class); 28 | 29 | private String zkAddress; 30 | private Node node; 31 | 32 | public SlaveServer(String zkAddress, Node node){ 33 | this.zkAddress = zkAddress; 34 | this.node = node; 35 | } 36 | 37 | @Override 38 | public void preStart() { 39 | InitExecutor.init(); 40 | // 注册Slave到ZK 41 | ZkServiceRegister.getInstance(zkAddress).register(node); 42 | } 43 | 44 | @Override 45 | public void start() { 46 | if(node ==null){ 47 | throw new IllegalArgumentException("slave is null"); 48 | } 49 | EventLoopGroup bossGroup = new NioEventLoopGroup(CommonConstants.BOSS_GROUP_SIZE, new DefaultThreadFactory("boss", true)); 50 | EventLoopGroup workerGroup = new NioEventLoopGroup(CommonConstants.WORKER_GROUP_SIZE, new DefaultThreadFactory("worker", true)); 51 | try { 52 | long start = System.currentTimeMillis(); 53 | ServerBootstrap b = new ServerBootstrap(); 54 | b.option(ChannelOption.SO_BACKLOG, 1024); 55 | b.group(bossGroup, workerGroup) 56 | .channel(NioServerSocketChannel.class) 57 | // .handler(new LoggingHandler(LogLevel.INFO)) 58 | .childHandler(new NettyHttpServerInitializer()); 59 | 60 | ChannelFuture future = b.bind(node.getPort()).sync(); 61 | long cost = System.currentTimeMillis()-start; 62 | LOGGER.info("SlaveServer Startup at port:{} cost:{}[ms]", node.getPort(),cost); 63 | 64 | // 等待服务端Socket关闭 65 | future.channel().closeFuture().sync(); 66 | } catch (InterruptedException e) { 67 | LOGGER.error("InterruptedException:",e); 68 | } finally { 69 | bossGroup.shutdownGracefully(); 70 | workerGroup.shutdownGracefully(); 71 | } 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/zk/ZkClient.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.zk; 2 | 3 | import org.apache.curator.framework.CuratorFramework; 4 | import org.apache.curator.framework.CuratorFrameworkFactory; 5 | import org.apache.curator.retry.RetryNTimes; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.locks.Lock; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | 12 | /** 13 | * 操作ZK的客户端 14 | * @author houyi.wh 15 | * @date 2017/11/21 16 | **/ 17 | public class ZkClient { 18 | 19 | /** 20 | * 节点的session超时时间,当Slave服务停掉后, 21 | * curator客户端需要等待该节点超时后才会触发CHILD_REMOVED事件 22 | */ 23 | private static final int DEFAULT_SESSION_TIMEOUT_MS = 5000; 24 | 25 | private static final int DEFAULT_CONNECTION_TIMEOUT_MS = 15000; 26 | 27 | /** 28 | * 操作ZK的客户端 29 | */ 30 | private static Map clients; 31 | 32 | private static Lock lock; 33 | 34 | static{ 35 | clients = new ConcurrentHashMap<>(); 36 | lock = new ReentrantLock(); 37 | } 38 | 39 | /** 40 | * 获取ZK客户端 41 | * @param zkAddress zk服务端地址 42 | * @return zk客户端 43 | */ 44 | public static CuratorFramework getClient(String zkAddress){ 45 | if(zkAddress == null || zkAddress.trim().length() == 0){ 46 | return null; 47 | } 48 | CuratorFramework client = clients.get(zkAddress); 49 | if(client==null){ 50 | lock.lock(); 51 | try { 52 | if(!clients.containsKey(zkAddress)) { 53 | client = CuratorFrameworkFactory.newClient( 54 | zkAddress, 55 | DEFAULT_SESSION_TIMEOUT_MS, 56 | DEFAULT_CONNECTION_TIMEOUT_MS, 57 | new RetryNTimes(10, 5000) 58 | ); 59 | client.start(); 60 | clients.putIfAbsent(zkAddress,client); 61 | }else{ 62 | client = clients.get(zkAddress); 63 | } 64 | }finally { 65 | lock.unlock(); 66 | } 67 | } 68 | return client; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/zk/ZkConfig.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.zk; 2 | 3 | 4 | import com.redant.core.common.util.GenericsUtil; 5 | 6 | import java.util.Properties; 7 | 8 | /** 9 | * 启动zk所需要的配置信息 10 | * @author houyi.wh 11 | * @date 2017/11/21 12 | */ 13 | public class ZkConfig { 14 | 15 | private interface ZkConstant { 16 | int CLIENT_PORT = 2181; 17 | String DATA_DIR = "/Users/houyi/zookeeper/data"; 18 | String DATA_LOG_DIR = "/Users/houyi/zookeeper/log"; 19 | } 20 | 21 | /** 22 | * 客户端连接的端口 23 | */ 24 | private int clientPort; 25 | 26 | private int tickTime = 2000; 27 | 28 | private int initLimit = 10; 29 | 30 | private int syncLimit = 5; 31 | 32 | /** 33 | * 数据存储目录,格式为: 34 | * /home/zookeeper/1/data 35 | */ 36 | private String dataDir; 37 | 38 | /** 39 | * 日志存储目录,格式为: 40 | * /home/zookeeper/1/log 41 | */ 42 | private String dataLogDir; 43 | 44 | /** 45 | * 客户端连接数上限 46 | */ 47 | private int maxClientCnxns = 60; 48 | 49 | public ZkConfig(int clientPort,String dataDir,String dataLogDir){ 50 | this.clientPort = clientPort; 51 | this.dataDir = dataDir; 52 | this.dataLogDir = dataLogDir; 53 | } 54 | 55 | public static ZkConfig DEFAULT = new ZkConfig(ZkConstant.CLIENT_PORT,ZkConstant.DATA_DIR,ZkConstant.DATA_LOG_DIR); 56 | 57 | 58 | public String generateZkAddress(){ 59 | return GenericsUtil.getLocalIpV4()+":"+this.clientPort; 60 | } 61 | 62 | public Properties toProp(){ 63 | Properties properties = new Properties(); 64 | properties.put("clientPort",this.clientPort); 65 | properties.put("clientPortAddress",GenericsUtil.getLocalIpV4()); 66 | properties.put("tickTime",this.tickTime); 67 | properties.put("initLimit",this.initLimit); 68 | properties.put("syncLimit",this.syncLimit); 69 | properties.put("dataDir",this.dataDir); 70 | properties.put("dataLogDir",this.dataLogDir); 71 | properties.put("maxClientCnxns",this.maxClientCnxns); 72 | 73 | return properties; 74 | } 75 | 76 | 77 | 78 | 79 | 80 | public int getClientPort() { 81 | return clientPort; 82 | } 83 | 84 | public void setClientPort(int clientPort) { 85 | this.clientPort = clientPort; 86 | } 87 | 88 | public int getTickTime() { 89 | return tickTime; 90 | } 91 | 92 | public void setTickTime(int tickTime) { 93 | this.tickTime = tickTime; 94 | } 95 | 96 | public int getInitLimit() { 97 | return initLimit; 98 | } 99 | 100 | public void setInitLimit(int initLimit) { 101 | this.initLimit = initLimit; 102 | } 103 | 104 | public int getSyncLimit() { 105 | return syncLimit; 106 | } 107 | 108 | public void setSyncLimit(int syncLimit) { 109 | this.syncLimit = syncLimit; 110 | } 111 | 112 | public String getDataDir() { 113 | return dataDir; 114 | } 115 | 116 | public void setDataDir(String dataDir) { 117 | this.dataDir = dataDir; 118 | } 119 | 120 | public String getDataLogDir() { 121 | return dataLogDir; 122 | } 123 | 124 | public void setDataLogDir(String dataLogDir) { 125 | this.dataLogDir = dataLogDir; 126 | } 127 | 128 | public int getMaxClientCnxns() { 129 | return maxClientCnxns; 130 | } 131 | 132 | public void setMaxClientCnxns(int maxClientCnxns) { 133 | this.maxClientCnxns = maxClientCnxns; 134 | } 135 | 136 | 137 | } 138 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/zk/ZkNode.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.zk; 2 | 3 | /** 4 | * @author houyi.wh 5 | * @date 2017/11/21 6 | **/ 7 | public class ZkNode { 8 | 9 | /** 10 | * 根节点 11 | */ 12 | public static final String ROOT_NODE_PATH = "/redant"; 13 | 14 | /** 15 | * SlaveNode注册的节点 16 | */ 17 | public static final String SLAVE_NODE_PATH = ROOT_NODE_PATH+"/slave"; 18 | 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /redant-cluster/src/main/java/com/redant/cluster/zk/ZkServer.java: -------------------------------------------------------------------------------- 1 | package com.redant.cluster.zk; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import org.apache.zookeeper.server.ServerConfig; 5 | import org.apache.zookeeper.server.ZooKeeperServerMain; 6 | import org.apache.zookeeper.server.quorum.QuorumPeerConfig; 7 | import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; 8 | import org.apache.zookeeper.server.quorum.QuorumPeerMain; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.util.Properties; 14 | 15 | /** 16 | * ZooKeeper服务端 17 | * @author houyi.wh 18 | * @date 2017/11/21 19 | * 20 | */ 21 | public class ZkServer { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(ZkServer.class); 24 | 25 | public static String getZkAddressArgs(String[] args, ZkConfig zkConfig){ 26 | String zkAddress = ZkServer.getZkAddress(zkConfig); 27 | if(args.length>0 && StrUtil.isNotBlank(args[0])){ 28 | LOGGER.info("zkAddress is read from args"); 29 | zkAddress = args[0]; 30 | } 31 | if(StrUtil.isBlank(zkAddress)){ 32 | System.exit(1); 33 | } 34 | return zkAddress; 35 | } 36 | 37 | public static String getZkAddress(ZkConfig zkConfig){ 38 | return zkConfig!=null ? zkConfig.generateZkAddress() : null; 39 | } 40 | 41 | /** 42 | * 通过官方的ZooKeeperServerMain启动类启动单机模式 43 | * @param zkConfig 配置对象 44 | * @throws ConfigException 配置异常 45 | * @throws IOException IO异常 46 | */ 47 | public void startStandalone(ZkConfig zkConfig) throws ConfigException, IOException { 48 | Properties zkProp = zkConfig.toProp(); 49 | 50 | QuorumPeerConfig config = new QuorumPeerConfig(); 51 | config.parseProperties(zkProp); 52 | 53 | ServerConfig serverConfig = new ServerConfig(); 54 | serverConfig.readFrom(config); 55 | 56 | ZooKeeperServerMain zkServer = new ZooKeeperServerMain(); 57 | zkServer.runFromConfig(serverConfig); 58 | } 59 | 60 | /** 61 | * 通过官方的QuorumPeerMain启动类启动真集群模式 62 | * 会执行quorumPeer.join(); 63 | * 需要在不同的服务器上执行 64 | * @param zkConfig 配置对象 65 | * @throws ConfigException 配置异常 66 | * @throws IOException IO异常 67 | */ 68 | public void startCluster(ZkConfig zkConfig) throws ConfigException, IOException { 69 | Properties zkProp = zkConfig.toProp(); 70 | QuorumPeerConfig config = new QuorumPeerConfig(); 71 | config.parseProperties(zkProp); 72 | 73 | QuorumPeerMain main = new QuorumPeerMain(); 74 | main.runFromConfig(config); 75 | } 76 | 77 | 78 | } -------------------------------------------------------------------------------- /redant-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | redant 7 | com.redant 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | redant-core 13 | jar 14 | redant-core 15 | 1.0.0-SNAPSHOT 16 | 17 | 18 | 19 | commons-beanutils 20 | commons-beanutils 21 | 22 | 23 | 24 | 25 | org.slf4j 26 | slf4j-api 27 | 28 | 29 | org.slf4j 30 | jcl-over-slf4j 31 | 32 | 33 | ch.qos.logback 34 | logback-classic 35 | 36 | 37 | com.alibaba 38 | fastjson 39 | 40 | 41 | io.netty 42 | netty-all 43 | 44 | 45 | cglib 46 | cglib 47 | 48 | 49 | cn.hutool 50 | hutool-all 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/ServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.core; 2 | 3 | import com.redant.core.server.NettyHttpServer; 4 | import com.redant.core.server.Server; 5 | 6 | /** 7 | * 服务端启动入口 8 | * @author houyi.wh 9 | * @date 2017-10-20 10 | */ 11 | public final class ServerBootstrap { 12 | 13 | public static void main(String[] args) { 14 | Server nettyServer = new NettyHttpServer(); 15 | // 各种初始化工作 16 | nettyServer.preStart(); 17 | // 启动服务器 18 | nettyServer.start(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/anno/Order.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.anno; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 排序规则,升序排序 7 | * @author houyi.wh 8 | * @date 2019-01-14 9 | */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.TYPE}) 12 | @Documented 13 | public @interface Order { 14 | 15 | /** 16 | * 最低优先级 17 | */ 18 | int LOWEST_PRECEDENCE = Integer.MAX_VALUE; 19 | /** 20 | * 最高优先级 21 | */ 22 | int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; 23 | 24 | /** 25 | * The order value. Lowest precedence by default. 26 | * 27 | * @return the order value 28 | */ 29 | int value() default LOWEST_PRECEDENCE; 30 | } 31 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/aware/Aware.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.aware; 2 | 3 | /** 4 | * @author houyi.wh 5 | * @date 2019-01-14 6 | */ 7 | public interface Aware { 8 | 9 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/aware/BeanContextAware.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.aware; 2 | 3 | import com.redant.core.bean.context.BeanContext; 4 | 5 | /** 6 | * @author houyi.wh 7 | * @date 2019-01-14 8 | */ 9 | public interface BeanContextAware extends Aware{ 10 | 11 | /** 12 | * 设置BeanContext 13 | * @param beanContext BeanContext对象 14 | */ 15 | void setBeanContext(BeanContext beanContext); 16 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/bean/BaseBean.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.bean; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | 6 | import java.io.Serializable; 7 | 8 | 9 | /** 10 | * BaseBean 所有bean都继承该类 11 | * @author houyi.wh 12 | * @date 2017-10-20 13 | */ 14 | public class BaseBean implements Serializable { 15 | private static final long serialVersionUID = -4976516540408695147L; 16 | 17 | public BaseBean() { 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return JSON.toJSONString(this); 23 | } 24 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/bean/annotation/Autowired.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.bean.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD, ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Autowired { 11 | 12 | String name() default ""; 13 | 14 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/bean/annotation/Bean.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.bean.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Bean { 11 | 12 | String name() default ""; 13 | 14 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/bean/context/BeanContext.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.bean.context; 2 | 3 | /** 4 | * Bean上下文 5 | * @author houyi.wh 6 | * @date 2017-10-20 7 | */ 8 | public interface BeanContext { 9 | 10 | /** 11 | * 获得Bean 12 | * @param name Bean的名称 13 | * @return Bean 14 | */ 15 | Object getBean(String name); 16 | 17 | /** 18 | * 获得Bean 19 | * @param name Bean的名称 20 | * @param clazz Bean的类 21 | * @param 泛型 22 | * @return Bean 23 | */ 24 | T getBean(String name,Class clazz); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/constants/CommonConstants.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.constants; 2 | 3 | import com.redant.core.common.util.PropertiesUtil; 4 | 5 | /** 6 | * 公共常量 7 | * @author houyi.wh 8 | * @date 2017-10-20 9 | */ 10 | public class CommonConstants { 11 | 12 | 13 | private static final String REDANT_PROPERTIES_PATH = "/redant.properties"; 14 | 15 | private static PropertiesUtil propertiesUtil = PropertiesUtil.getInstance(REDANT_PROPERTIES_PATH); 16 | 17 | /** 18 | * 服务端口号 19 | */ 20 | public static final int SERVER_PORT = propertiesUtil.getInt("netty.server.port",8888); 21 | 22 | /** 23 | * BossGroup Size 24 | * 先从启动参数中获取:-Dnetty.server.bossGroup.size=2 25 | * 如果获取不到从配置文件中获取 26 | * 如果再获取不到则取默认值 27 | */ 28 | public static final int BOSS_GROUP_SIZE = null!=Integer.getInteger("netty.server.bossGroup.size")?Integer.getInteger("netty.server.bossGroup.size"):propertiesUtil.getInt("netty.server.bossGroup.size",2); 29 | 30 | /** 31 | * WorkerGroup Size 32 | * 先从启动参数中获取:-Dnetty.server.workerGroup.size=4 33 | * 如果获取不到从配置文件中获取 34 | * 如果再获取不到则取默认值 35 | */ 36 | public static final int WORKER_GROUP_SIZE = null!=Integer.getInteger("netty.server.workerGroup.size")?Integer.getInteger("netty.server.workerGroup.size"):propertiesUtil.getInt("netty.server.workerGroup.size",4); 37 | 38 | /** 39 | * 能处理的最大数据的字节数 40 | */ 41 | public static final int MAX_CONTENT_LENGTH = propertiesUtil.getInt("netty.maxContentLength",10485760); 42 | 43 | /** 44 | * 是否开启ssl 45 | */ 46 | public static final boolean USE_SSL = propertiesUtil.getBoolean("netty.server.use.ssl"); 47 | 48 | /** 49 | * 是否开启压缩 50 | */ 51 | public static final boolean USE_COMPRESS = propertiesUtil.getBoolean("netty.server.use.compress"); 52 | 53 | /** 54 | * 是否开启http对象聚合 55 | */ 56 | public static final boolean USE_AGGREGATOR = propertiesUtil.getBoolean("netty.server.use.aggregator"); 57 | 58 | /** 59 | * KeyStore path 60 | */ 61 | public static final String KEY_STORE_PATH = propertiesUtil.getString("ssl.keyStore.path"); 62 | 63 | /** 64 | * KeyStore password 65 | */ 66 | public static final String KEY_STORE_PASSWORD = propertiesUtil.getString("ssl.keyStore.password"); 67 | 68 | /** 69 | * 扫描bean的包路径 70 | */ 71 | public static final String BEAN_SCAN_PACKAGE = propertiesUtil.getString("bean.scan.package"); 72 | 73 | /** 74 | * 扫描interceptor的包路径 75 | */ 76 | public static final String INTERCEPTOR_SCAN_PACKAGE = propertiesUtil.getString("interceptor.scan.package"); 77 | 78 | /** 79 | * 服务端出错时的错误描述 80 | */ 81 | public static final String SERVER_INTERNAL_ERROR_DESC = propertiesUtil.getString("server.internal.error.desc"); 82 | 83 | public static final String FAVICON_ICO = "/favicon.ico"; 84 | 85 | public static final String CONNECTION_KEEP_ALIVE = "keep-alive"; 86 | 87 | public static final String CONNECTION_CLOSE = "close"; 88 | 89 | /** 90 | * 是否异步处理业务逻辑 91 | */ 92 | public static final boolean ASYNC_EXECUTE_EVENT = propertiesUtil.getBoolean("async.execute.event"); 93 | 94 | /** 95 | * 业务线程池核心线程数 96 | */ 97 | public static final int EVENT_EXECUTOR_POOL_CORE_SIZE = propertiesUtil.getInt("async.executor.pool.core.size",10); 98 | 99 | /** 100 | * 业务线程池最大线程数 101 | */ 102 | public static final int EVENT_EXECUTOR_POOL_MAX_SIZE = propertiesUtil.getInt("async.executor.pool.max.size",20); 103 | 104 | /** 105 | * 业务线程池临时线程存活时间,单位:s 106 | */ 107 | public static final int EVENT_EXECUTOR_POOL_KEEP_ALIVE_SECONDS = propertiesUtil.getInt("async.executor.pool.keep.alive.seconds",10); 108 | 109 | /** 110 | * 业务线程池阻塞队列大学 111 | */ 112 | public static final int EVENT_EXECUTOR_POOL_BLOCKING_QUEUE_SIZE = propertiesUtil.getInt("async.executor.pool.blocking.queue.size",10); 113 | 114 | } 115 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/enums/ContentType.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.enums; 2 | 3 | public enum ContentType { 4 | 5 | APPLICATION_ATOM_XML("application/atom+xml"), 6 | APPLICATION_FORM_URLENCODED("application/x-www-form-urlencoded"), 7 | APPLICATION_JSON("application/json"), 8 | APPLICATION_OCTET_STREAM("application/octet-stream"), 9 | APPLICATION_SVG_XML("application/svg+xml"), 10 | APPLICATION_XHTML_XML("application/xhtml+xml"), 11 | APPLICATION_XML("application/xml") 12 | ; 13 | 14 | private String content; 15 | 16 | ContentType(String content){ 17 | this.content = content; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return content; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/enums/RequestMethod.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.enums; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | 5 | /** 6 | * @author houyi.wh 7 | * @date 2017/12/1 8 | **/ 9 | public enum RequestMethod { 10 | /** 11 | * GET 12 | */ 13 | GET(HttpMethod.GET), 14 | /** 15 | * HEAD 16 | */ 17 | HEAD(HttpMethod.HEAD), 18 | /** 19 | * POST 20 | */ 21 | POST(HttpMethod.POST), 22 | /** 23 | * PUT 24 | */ 25 | PUT(HttpMethod.PUT), 26 | /** 27 | * PATCH 28 | */ 29 | PATCH(HttpMethod.PATCH), 30 | /** 31 | * DELETE 32 | */ 33 | DELETE(HttpMethod.DELETE), 34 | /** 35 | * OPTIONS 36 | */ 37 | OPTIONS(HttpMethod.OPTIONS), 38 | /** 39 | * TRACE 40 | */ 41 | TRACE(HttpMethod.TRACE); 42 | 43 | HttpMethod httpMethod; 44 | 45 | RequestMethod(HttpMethod httpMethod) { 46 | this.httpMethod = httpMethod; 47 | } 48 | 49 | public static HttpMethod getHttpMethod(RequestMethod requestMethod){ 50 | for(RequestMethod method : values()){ 51 | if(requestMethod==method){ 52 | return method.httpMethod; 53 | } 54 | } 55 | return null; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/exception/InvalidSessionException.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.exception; 2 | 3 | /** 4 | * 非法session 5 | * @author houyi.wh 6 | * @date 2017-10-20 7 | */ 8 | public class InvalidSessionException extends RuntimeException{ 9 | private static final long serialVersionUID = 1L; 10 | 11 | public InvalidSessionException(String s) { 12 | super(s); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/exception/InvocationException.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.exception; 2 | 3 | public class InvocationException extends Exception{ 4 | private static final long serialVersionUID = 1L; 5 | 6 | public InvocationException(String message, Throwable cause) { 7 | super(message, cause); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/exception/ValidationException.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.exception; 2 | 3 | /** 4 | * ValidationException 5 | * @author houyi.wh 6 | * @date 2017-10-20 7 | */ 8 | public class ValidationException extends RuntimeException{ 9 | private static final long serialVersionUID = 1L; 10 | 11 | public ValidationException(String s) { 12 | super(s); 13 | } 14 | 15 | public ValidationException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/html/DefaultHtmlMaker.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.html; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import com.redant.core.common.view.HtmlKeyHolder; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * 默认的HtmlMaker,只处理字符串 10 | * @author houyi.wh 11 | * @date 2017/12/1 12 | **/ 13 | public class DefaultHtmlMaker implements HtmlMaker { 14 | 15 | @Override 16 | public String make(String htmlTemplate, Map contentMap) { 17 | String html = htmlTemplate; 18 | if(CollectionUtil.isNotEmpty(contentMap)){ 19 | for(Map.Entry entry : contentMap.entrySet()){ 20 | String key = entry.getKey(); 21 | Object val = entry.getValue(); 22 | if(val instanceof String){ 23 | html = html.replaceAll(HtmlKeyHolder.START_ESCAPE+key+HtmlKeyHolder.END,val.toString()); 24 | } 25 | } 26 | } 27 | return html; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/html/HtmlMaker.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.html; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * html生成器 7 | * @author houyi.wh 8 | * @date 2017/12/1 9 | **/ 10 | public interface HtmlMaker { 11 | 12 | /** 13 | * 根据html模板生成html内容 14 | * @param htmlTemplate html模板 15 | * @param contentMap 参数 16 | * @return html内容 17 | */ 18 | String make(String htmlTemplate,Map contentMap); 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/html/HtmlMakerEnum.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.html; 2 | 3 | /** 4 | * @author houyi.wh 5 | * @date 2017/12/1 6 | **/ 7 | public enum HtmlMakerEnum { 8 | /** 9 | * 字符串 10 | */ 11 | STRING 12 | } 13 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/html/HtmlMakerFactory.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.html; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.locks.Lock; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | 12 | /** 13 | * @author houyi.wh 14 | * @date 2017/12/1 15 | **/ 16 | public class HtmlMakerFactory { 17 | 18 | private volatile static HtmlMakerFactory factory; 19 | 20 | private Map htmlMakerMap; 21 | 22 | private final Lock lock; 23 | 24 | private HtmlMakerFactory(){ 25 | htmlMakerMap = new ConcurrentHashMap<>(); 26 | lock = new ReentrantLock(); 27 | } 28 | 29 | /** 30 | * 获取工厂实例 31 | */ 32 | public static HtmlMakerFactory instance(){ 33 | if(factory==null){ 34 | synchronized (HtmlMakerFactory.class) { 35 | if (factory==null) { 36 | factory = new HtmlMakerFactory(); 37 | } 38 | } 39 | } 40 | return factory; 41 | } 42 | 43 | /** 44 | * 创建HtmlMaker实例 45 | */ 46 | public HtmlMaker build(HtmlMakerEnum type,Class clazz){ 47 | if(type==null){ 48 | return null; 49 | }else{ 50 | HtmlMaker htmlMaker = htmlMakerMap.get(type); 51 | if(htmlMaker==null){ 52 | lock.lock(); 53 | try { 54 | if(!htmlMakerMap.containsKey(type)) { 55 | htmlMaker = ReflectUtil.newInstance(clazz); 56 | htmlMakerMap.putIfAbsent(type,htmlMaker); 57 | }else{ 58 | htmlMaker = htmlMakerMap.get(type); 59 | } 60 | }finally { 61 | lock.unlock(); 62 | } 63 | } 64 | return htmlMaker; 65 | } 66 | } 67 | 68 | public static void main(String[] args) { 69 | int loopTimes = 200; 70 | 71 | class Runner implements Runnable{ 72 | 73 | private Logger logger = LoggerFactory.getLogger(Runner.class); 74 | 75 | @Override 76 | public void run() { 77 | HtmlMakerFactory factory = HtmlMakerFactory.instance(); 78 | logger.info("factory={},currentThread={}",(factory!=null?factory.getClass().getName():"null"),Thread.currentThread().getName()); 79 | } 80 | } 81 | 82 | for(int i=0;i maps, List names){} 24 | * @param method 方法 25 | * @param index 第几个输入参数 26 | * @return 输入参数的泛型参数的实际类型集合, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回空集合 27 | */ 28 | @SuppressWarnings("rawtypes") 29 | public static List getMethodGenericParameterTypes(Method method, int index) { 30 | List results = new ArrayList(); 31 | Type[] genericParameterTypes = method.getGenericParameterTypes(); 32 | if (index >= genericParameterTypes.length || index < 0) { 33 | throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数")); 34 | } 35 | Type genericParameterType = genericParameterTypes[index]; 36 | if (genericParameterType instanceof ParameterizedType) { 37 | ParameterizedType aType = (ParameterizedType) genericParameterType; 38 | Type[] parameterArgTypes = aType.getActualTypeArguments(); 39 | for (Type parameterArgType : parameterArgTypes) { 40 | Class parameterArgClass = (Class) parameterArgType; 41 | results.add(parameterArgClass); 42 | } 43 | return results; 44 | } 45 | return results; 46 | } 47 | 48 | 49 | /** 50 | * 断言非空 51 | * @param dataName 参数 52 | * @param values 值 53 | */ 54 | public static void checkNull(String dataName, Object... values){ 55 | if(values == null){ 56 | throw new ValidationException("["+ dataName + "] cannot be null"); 57 | } 58 | for (Object value : values) { 59 | if (value == null) { 60 | throw new ValidationException("[" + dataName + "] cannot be null"); 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * 断言非空 67 | * @param dataName 参数 68 | * @param values 值 69 | */ 70 | public static void checkBlank(String dataName, Object... values){ 71 | if(values == null){ 72 | throw new ValidationException("["+ dataName + "] cannot be null"); 73 | } 74 | for (Object value : values) { 75 | if (value == null || StrUtil.isBlank(value.toString())) { 76 | throw new ValidationException("[" + dataName + "] cannot be blank"); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * 获取ipV4 83 | * @return ipV4 84 | */ 85 | public static String getLocalIpV4(){ 86 | LinkedHashSet ipV4Set = NetUtil.localIpv4s(); 87 | return ipV4Set.isEmpty()?"":ipV4Set.toArray()[0].toString(); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/util/HtmlContentUtil.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.util; 2 | 3 | import com.redant.core.common.html.HtmlMaker; 4 | import com.redant.core.common.constants.CommonConstants; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * @author houyi.wh 12 | * @date 2017/12/1 13 | **/ 14 | public class HtmlContentUtil { 15 | 16 | private final static Logger logger = LoggerFactory.getLogger(HtmlContentUtil.class); 17 | 18 | private HtmlContentUtil(){ 19 | 20 | } 21 | 22 | /** 23 | * 获取页面内容 24 | * @param htmlMaker htmlMaker 25 | * @param htmlTemplate html模板 26 | * @param contentMap 参数 27 | * @return 页面内容 28 | */ 29 | public static String getPageContent(HtmlMaker htmlMaker, String htmlTemplate, Map contentMap){ 30 | try { 31 | return htmlMaker.make(htmlTemplate,contentMap); 32 | } catch (Exception e) { 33 | logger.error("getPageContent Error,cause:",e); 34 | } 35 | return CommonConstants.SERVER_INTERNAL_ERROR_DESC; 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/util/HttpRenderUtil.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.redant.core.common.html.DefaultHtmlMaker; 5 | import com.redant.core.common.html.HtmlMaker; 6 | import com.redant.core.common.html.HtmlMakerEnum; 7 | import com.redant.core.common.html.HtmlMakerFactory; 8 | import com.redant.core.common.view.Page404; 9 | import com.redant.core.render.RenderType; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.buffer.Unpooled; 12 | import io.netty.handler.codec.http.*; 13 | import io.netty.util.CharsetUtil; 14 | 15 | /** 16 | * HttpRenderUtil 17 | * 18 | * @author houyi.wh 19 | * @date 2017-10-20 20 | */ 21 | public class HttpRenderUtil { 22 | 23 | public static final String EMPTY_CONTENT = ""; 24 | 25 | private HttpRenderUtil() { 26 | 27 | } 28 | 29 | /** 30 | * response输出 31 | * 32 | * @param content 内容 33 | * @param renderType 返回类型 34 | * @return 响应对象 35 | */ 36 | public static FullHttpResponse render(Object content, RenderType renderType) { 37 | byte[] bytes = HttpRenderUtil.getBytes(content); 38 | ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes); 39 | FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); 40 | RenderType type = renderType != null ? renderType : RenderType.JSON; 41 | response.headers().add(HttpHeaderNames.CONTENT_TYPE, type.getContentType()); 42 | response.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(byteBuf.readableBytes())); 43 | return response; 44 | } 45 | 46 | /** 47 | * 404NotFoundResponse 48 | * 49 | * @return 响应对象 50 | */ 51 | public static FullHttpResponse getNotFoundResponse() { 52 | HtmlMaker htmlMaker = HtmlMakerFactory.instance().build(HtmlMakerEnum.STRING, DefaultHtmlMaker.class); 53 | String htmlTpl = Page404.HTML; 54 | String content = HtmlContentUtil.getPageContent(htmlMaker, htmlTpl, null); 55 | return render(content, RenderType.HTML); 56 | } 57 | 58 | /** 59 | * ServerErrorResponse 60 | * 61 | * @return 响应对象 62 | */ 63 | public static FullHttpResponse getServerErrorResponse() { 64 | JSONObject object = new JSONObject(); 65 | object.put("code", 500); 66 | object.put("message", "Server Internal Error!"); 67 | return render(object, RenderType.JSON); 68 | } 69 | 70 | /** 71 | * ErrorResponse 72 | * 73 | * @param errorMessage 错误信息 74 | * @return 响应对象 75 | */ 76 | public static FullHttpResponse getErrorResponse(String errorMessage) { 77 | JSONObject object = new JSONObject(); 78 | object.put("code", 300); 79 | object.put("message", errorMessage); 80 | return render(object, RenderType.JSON); 81 | } 82 | 83 | /** 84 | * BlockedResponse 85 | * 86 | * @return 响应对象 87 | */ 88 | public static FullHttpResponse getBlockedResponse() { 89 | JSONObject object = new JSONObject(); 90 | object.put("code", 1000); 91 | object.put("message", "Blocked by user defined interceptor"); 92 | return render(object, RenderType.JSON); 93 | } 94 | 95 | /** 96 | * 转换byte 97 | * 98 | * @param content 内容 99 | * @return 响应对象 100 | */ 101 | private static byte[] getBytes(Object content) { 102 | if (content == null) { 103 | return EMPTY_CONTENT.getBytes(CharsetUtil.UTF_8); 104 | } 105 | String data = content.toString(); 106 | data = (data == null || data.trim().length() == 0) ? EMPTY_CONTENT : data; 107 | return data.getBytes(CharsetUtil.UTF_8); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/util/HttpRequestUtil.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.redant.core.common.enums.ContentType; 7 | import com.redant.core.converter.PrimitiveTypeUtil; 8 | import io.netty.handler.codec.http.*; 9 | import io.netty.util.CharsetUtil; 10 | 11 | import java.lang.reflect.Array; 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * @author houyi.wh 19 | * @date 2017/11/16 20 | **/ 21 | public class HttpRequestUtil { 22 | 23 | /** 24 | * 获取请求参数的Map 25 | * @param request http请求 26 | * @return 参数map 27 | */ 28 | public static Map> getParameterMap(HttpRequest request){ 29 | Map> paramMap = new HashMap<>(); 30 | 31 | HttpMethod method = request.method(); 32 | if(HttpMethod.GET.equals(method)){ 33 | String uri = request.uri(); 34 | QueryStringDecoder queryDecoder = new QueryStringDecoder(uri, CharsetUtil.UTF_8); 35 | paramMap = queryDecoder.parameters(); 36 | 37 | }else if(HttpMethod.POST.equals(method)){ 38 | FullHttpRequest fullRequest = (FullHttpRequest) request; 39 | paramMap = getPostParamMap(fullRequest); 40 | } 41 | 42 | return paramMap; 43 | } 44 | 45 | 46 | /** 47 | * 获取post请求的参数map 48 | * 目前支持最常用的 application/json 、application/x-www-form-urlencoded 几种 POST Content-type,可自行扩展!!! 49 | */ 50 | @SuppressWarnings("unchecked") 51 | private static Map> getPostParamMap(FullHttpRequest fullRequest) { 52 | Map> paramMap = new HashMap<>(); 53 | HttpHeaders headers = fullRequest.headers(); 54 | String contentType = getContentType(headers); 55 | if(ContentType.APPLICATION_JSON.toString().equals(contentType)){ 56 | String jsonStr = fullRequest.content().toString(CharsetUtil.UTF_8); 57 | JSONObject obj = JSON.parseObject(jsonStr); 58 | for(Map.Entry item : obj.entrySet()){ 59 | String key = item.getKey(); 60 | Object value = item.getValue(); 61 | Class valueType = value.getClass(); 62 | 63 | List valueList; 64 | if(paramMap.containsKey(key)){ 65 | valueList = paramMap.get(key); 66 | }else{ 67 | valueList = new ArrayList(); 68 | } 69 | 70 | if(PrimitiveTypeUtil.isPriType(valueType)){ 71 | valueList.add(value.toString()); 72 | paramMap.put(key, valueList); 73 | 74 | }else if(valueType.isArray()){ 75 | int length = Array.getLength(value); 76 | for(int i=0; i) value; 90 | } 91 | paramMap.put(key, valueList); 92 | 93 | }else if(Map.class.isAssignableFrom(valueType)){ 94 | Map tempMap = (Map) value; 95 | for(Map.Entry entry : tempMap.entrySet()){ 96 | List tempList = new ArrayList(); 97 | tempList.add(entry.getValue()); 98 | paramMap.put(entry.getKey(), tempList); 99 | } 100 | } 101 | } 102 | 103 | }else if(ContentType.APPLICATION_FORM_URLENCODED.toString().equals(contentType)){ 104 | String jsonStr = fullRequest.content().toString(CharsetUtil.UTF_8); 105 | QueryStringDecoder queryDecoder = new QueryStringDecoder(jsonStr, false); 106 | paramMap = queryDecoder.parameters(); 107 | } 108 | 109 | return paramMap; 110 | } 111 | 112 | /** 113 | * 获取contentType 114 | * @param headers http请求头 115 | * @return 内容类型 116 | */ 117 | private static String getContentType(HttpHeaders headers){ 118 | String contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); 119 | String[] list = contentType.split(";"); 120 | return list[0]; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/util/PropertiesUtil.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Properties; 11 | import java.util.concurrent.CountDownLatch; 12 | 13 | /** 14 | * 使用单例模式,获取配置文件中的信息 15 | * @author hwang 16 | * @date 2015-04-15 17 | */ 18 | public class PropertiesUtil { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesUtil.class); 21 | 22 | private static Map propertiesUtilsHolder = null; 23 | 24 | private static Map propertiesMap = null; 25 | 26 | private volatile boolean propertiesLoaded; 27 | 28 | private PropertiesUtil(){ 29 | 30 | } 31 | 32 | static{ 33 | propertiesUtilsHolder = new HashMap<>(); 34 | propertiesMap = new HashMap<>(); 35 | } 36 | 37 | /** 38 | * 是否加载完毕 39 | */ 40 | private boolean propertiesLoaded(){ 41 | int retryTime = 0; 42 | int retryTimeout = 1000; 43 | int sleep = 500; 44 | while(!propertiesLoaded && retryTime tags = Collections.synchronizedMap(new HashMap<>()); 21 | } 22 | 23 | /** 24 | * 新增标签点 25 | * @param tag 标签名称 26 | */ 27 | public static void addTag(String tag){ 28 | if(tag==null || tag.trim().length()==0){ 29 | throw new RuntimeException("标签名称不可以为空"); 30 | } 31 | GHandle.tags.put(tag, System.currentTimeMillis()); 32 | } 33 | 34 | /** 35 | * 计算开始标签和结束标签之间的耗时 36 | * @param startTag 开始标签名称 37 | * @param endTag 结束标签名称,如果为空,以当前调用代码所在行设置默认标签名称并计算耗时 38 | */ 39 | public static void showCost(String startTag,String endTag){ 40 | if(startTag==null || startTag.trim().length()==0){ 41 | throw new RuntimeException("开始标签名称不可以为空"); 42 | } 43 | if(endTag==null || endTag.trim().length()==0){ 44 | String tempTag= "cur_"+System.currentTimeMillis(); 45 | addTag(tempTag); 46 | endTag=tempTag; 47 | }else if(!GHandle.tags.containsKey(endTag)){ 48 | addTag(endTag); 49 | } 50 | Long start= GHandle.tags.get(startTag); 51 | Long end= GHandle.tags.get(endTag); 52 | if(start==null){ 53 | throw new RuntimeException("获取标签["+startTag+"]信息失败!"); 54 | } 55 | if(end==null){ 56 | throw new RuntimeException("获取标签["+endTag+"]信息失败!"); 57 | } 58 | long cost = end-start; 59 | LOGGER.info("from ["+startTag+"] to ["+endTag+"] cost ["+cost+"ms]"); 60 | } 61 | 62 | 63 | 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/util/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.util; 2 | 3 | /** 4 | * 线程工具类 5 | * @author houyi.wh 6 | * @date 2017-10-20 7 | */ 8 | public class ThreadUtil { 9 | 10 | /** 11 | * 获取当前线程名称 12 | * @return 线程名称 13 | */ 14 | public static String currentThreadName(){ 15 | return Thread.currentThread().getName(); 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/view/HtmlKeyHolder.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.view; 2 | 3 | /** 4 | * @author houyi.wh 5 | * @date 2017/12/1 6 | **/ 7 | public interface HtmlKeyHolder { 8 | 9 | /** 10 | * 未转义 11 | */ 12 | String START_NO_ESCAPE = "#["; 13 | 14 | /** 15 | * 对[转义 16 | */ 17 | String START_ESCAPE = "#\\["; 18 | 19 | String END = "]"; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/view/Page404.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.view; 2 | 3 | 4 | import cn.hutool.core.util.StrUtil; 5 | 6 | /** 7 | * @author houyi.wh 8 | * @date 2017/12/1 9 | **/ 10 | public final class Page404 { 11 | 12 | private Page404(){ 13 | 14 | } 15 | 16 | public static final String HTML; 17 | 18 | static{ 19 | StringBuffer sb = new StringBuffer(); 20 | sb.append("").append(StrUtil.CRLF) 21 | .append("").append(StrUtil.CRLF) 22 | .append("").append(StrUtil.CRLF) 23 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 24 | .append(StrUtil.TAB).append("404-Resource Not Found").append(StrUtil.CRLF) 25 | .append("").append(StrUtil.CRLF) 26 | .append("").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 28 | .append(StrUtil.TAB).append(StrUtil.TAB).append("Resource Not Found!").append(StrUtil.CRLF) 29 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 30 | .append("").append(StrUtil.CRLF) 31 | .append(""); 32 | HTML = sb.toString(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/view/Page500.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.view; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | 5 | /** 6 | * @author houyi.wh 7 | * @date 2017/12/1 8 | **/ 9 | public final class Page500 { 10 | 11 | private Page500(){ 12 | 13 | } 14 | 15 | public static final String HTML; 16 | 17 | static{ 18 | StringBuffer sb = new StringBuffer(); 19 | sb.append("").append(StrUtil.CRLF) 20 | .append("").append(StrUtil.CRLF) 21 | .append("").append(StrUtil.CRLF) 22 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 23 | .append(StrUtil.TAB).append("500-Server Internal Error").append(StrUtil.CRLF) 24 | .append("").append(StrUtil.CRLF) 25 | .append("").append(StrUtil.CRLF) 26 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append(StrUtil.TAB).append("Server Internal Error!").append(StrUtil.CRLF) 28 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 29 | .append("").append(StrUtil.CRLF) 30 | .append(""); 31 | HTML = sb.toString(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/view/PageError.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.view; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | 5 | /** 6 | * @author houyi.wh 7 | * @date 2017/12/1 8 | **/ 9 | public final class PageError { 10 | 11 | private PageError(){ 12 | 13 | } 14 | 15 | public static final String HTML; 16 | 17 | static{ 18 | StringBuffer sb = new StringBuffer(); 19 | sb.append("").append(StrUtil.CRLF) 20 | .append("").append(StrUtil.CRLF) 21 | .append("").append(StrUtil.CRLF) 22 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 23 | .append(StrUtil.TAB).append("Error Occur").append(StrUtil.CRLF) 24 | .append("").append(StrUtil.CRLF) 25 | .append("").append(StrUtil.CRLF) 26 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append(StrUtil.TAB).append("

").append("Error Occur,Cause:").append("

").append(StrUtil.CRLF) 28 | .append(StrUtil.TAB).append(StrUtil.TAB).append("

").append(HtmlKeyHolder.START_NO_ESCAPE+"errorMessage"+HtmlKeyHolder.END).append("

").append(StrUtil.CRLF) 29 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 30 | .append("").append(StrUtil.CRLF) 31 | .append(""); 32 | HTML = sb.toString(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/common/view/PageIndex.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.common.view; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | 5 | /** 6 | * @author houyi.wh 7 | * @date 2017/12/1 8 | **/ 9 | public final class PageIndex { 10 | 11 | private PageIndex(){ 12 | 13 | } 14 | 15 | public static final String HTML; 16 | 17 | static{ 18 | StringBuffer sb = new StringBuffer(); 19 | sb.append("").append(StrUtil.CRLF) 20 | .append("").append(StrUtil.CRLF) 21 | .append("").append(StrUtil.CRLF) 22 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 23 | .append(StrUtil.TAB).append("redant").append(StrUtil.CRLF) 24 | .append("").append(StrUtil.CRLF) 25 | .append("").append(StrUtil.CRLF) 26 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append(StrUtil.TAB).append("Welcome to redant!").append(StrUtil.CRLF) 28 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 29 | .append("").append(StrUtil.CRLF) 30 | .append(""); 31 | HTML = sb.toString(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/context/RedantContext.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.context; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http.HttpRequest; 6 | import io.netty.handler.codec.http.HttpResponse; 7 | import io.netty.handler.codec.http.cookie.Cookie; 8 | import io.netty.util.concurrent.FastThreadLocal; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | /** 16 | * @author houyi 17 | **/ 18 | public class RedantContext { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(RedantContext.class); 21 | 22 | /** 23 | * 使用FastThreadLocal替代JDK自带的ThreadLocal以提升并发性能 24 | */ 25 | private static final FastThreadLocal CONTEXT_HOLDER = new FastThreadLocal<>(); 26 | 27 | private HttpRequest request; 28 | 29 | private ChannelHandlerContext context; 30 | 31 | private HttpResponse response; 32 | 33 | private Set cookies; 34 | 35 | private RedantContext(){ 36 | 37 | } 38 | 39 | public RedantContext setRequest(HttpRequest request){ 40 | this.request = request; 41 | return this; 42 | } 43 | 44 | public RedantContext setContext(ChannelHandlerContext context){ 45 | this.context = context; 46 | return this; 47 | } 48 | 49 | public RedantContext setResponse(HttpResponse response){ 50 | this.response = response; 51 | return this; 52 | } 53 | 54 | public RedantContext addCookie(Cookie cookie){ 55 | if(cookie!=null){ 56 | if(CollectionUtil.isEmpty(cookies)){ 57 | cookies = new HashSet<>(); 58 | } 59 | cookies.add(cookie); 60 | } 61 | return this; 62 | } 63 | 64 | public RedantContext addCookies(Set cookieSet){ 65 | if(CollectionUtil.isNotEmpty(cookieSet)){ 66 | if(CollectionUtil.isEmpty(cookies)){ 67 | cookies = new HashSet<>(); 68 | } 69 | cookies.addAll(cookieSet); 70 | } 71 | return this; 72 | } 73 | 74 | public HttpRequest getRequest() { 75 | return request; 76 | } 77 | 78 | public ChannelHandlerContext getContext() { 79 | return context; 80 | } 81 | 82 | public HttpResponse getResponse() { 83 | return response; 84 | } 85 | 86 | public Set getCookies() { 87 | return cookies; 88 | } 89 | 90 | public static RedantContext currentContext(){ 91 | RedantContext context = CONTEXT_HOLDER.get(); 92 | if(context==null){ 93 | context = new RedantContext(); 94 | CONTEXT_HOLDER.set(context); 95 | } 96 | return context; 97 | } 98 | 99 | public static void clear(){ 100 | CONTEXT_HOLDER.remove(); 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/controller/ControllerProxy.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.controller; 2 | 3 | import com.redant.core.common.enums.RequestMethod; 4 | import com.redant.core.render.RenderType; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * 路由请求代理,用以根据路由调用具体的controller类 10 | * @author houyi.wh 11 | * @date 2017-10-20 12 | */ 13 | public class ControllerProxy { 14 | 15 | private RenderType renderType; 16 | 17 | private RequestMethod requestMethod; 18 | 19 | private Object controller; 20 | 21 | private Method method; 22 | 23 | private String methodName; 24 | 25 | public RenderType getRenderType() { 26 | return renderType; 27 | } 28 | 29 | public void setRenderType(RenderType renderType) { 30 | this.renderType = renderType; 31 | } 32 | 33 | public RequestMethod getRequestMethod() { 34 | return requestMethod; 35 | } 36 | 37 | public void setRequestMethod(RequestMethod requestMethod) { 38 | this.requestMethod = requestMethod; 39 | } 40 | 41 | public Object getController() { 42 | return controller; 43 | } 44 | 45 | public void setController(Object controller) { 46 | this.controller = controller; 47 | } 48 | 49 | public Method getMethod() { 50 | return method; 51 | } 52 | 53 | public void setMethod(Method method) { 54 | this.method = method; 55 | } 56 | 57 | public String getMethodName() { 58 | return methodName; 59 | } 60 | 61 | public void setMethodName(String methodName) { 62 | this.methodName = methodName; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "{requestMethod:"+requestMethod+",controller:"+controller.getClass().getName()+",methodName:"+methodName+"}"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/controller/annotation/Controller.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.controller.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Controller { 11 | 12 | /** 13 | * 请求uri 14 | * @return url 15 | */ 16 | String path() default ""; 17 | 18 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/controller/annotation/Mapping.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.controller.annotation; 2 | 3 | import com.redant.core.common.enums.RequestMethod; 4 | import com.redant.core.render.RenderType; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Target(ElementType.METHOD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Mapping { 14 | 15 | /** 16 | * 请求方法类型 17 | * @return 方法类型 18 | */ 19 | RequestMethod requestMethod() default RequestMethod.GET; 20 | 21 | /** 22 | * 请求的uri 23 | * @return url 24 | */ 25 | String path() default ""; 26 | 27 | /** 28 | * 返回类型 29 | * @return 返回类型 30 | */ 31 | RenderType renderType() default RenderType.JSON; 32 | 33 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/controller/annotation/Param.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.controller.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target({ ElementType.FIELD, ElementType.PARAMETER }) 6 | @Retention(value = RetentionPolicy.RUNTIME) 7 | public @interface Param { 8 | 9 | /** 10 | * 将使用什么样的键值读取对象, 11 | * 对于field,就是他名字 12 | * 对于method的parameter,需要指明 13 | * @return 参数的key 14 | */ 15 | String key() default ""; 16 | 17 | /** 18 | * 提供设置缺省值 19 | * @return 提供设置缺省值 20 | */ 21 | String defaultValue() default ""; 22 | 23 | /** 24 | * 是否校验参数为空 25 | * @return true:校验参数 false:不校验参数 26 | */ 27 | boolean notNull() default false; 28 | 29 | /** 30 | * 是否校验参数为空 31 | * @return true:校验参数 false:不校验参数 32 | */ 33 | boolean notBlank() default false; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/controller/context/ControllerContext.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.controller.context; 2 | 3 | import com.redant.core.controller.ControllerProxy; 4 | import io.netty.handler.codec.http.HttpMethod; 5 | 6 | /** 7 | * @author houyi.wh 8 | * @date 2019-01-15 9 | */ 10 | public interface ControllerContext { 11 | 12 | /** 13 | * 添加Controller代理 14 | * @param path 请求路径 15 | * @param proxy 代理 16 | */ 17 | void addProxy(String path,ControllerProxy proxy); 18 | 19 | /** 20 | * 获取Controller代理 21 | * @param method 请求方法类型 22 | * @param uri 请求url 23 | * @return 代理 24 | */ 25 | ControllerProxy getProxy(HttpMethod method, String uri); 26 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/controller/context/DefaultControllerContext.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.controller.context; 2 | 3 | import com.redant.core.controller.ControllerProxy; 4 | import com.redant.core.render.RenderType; 5 | import com.redant.core.router.RouteResult; 6 | import com.redant.core.router.context.DefaultRouterContext; 7 | import com.redant.core.router.context.RouterContext; 8 | import io.netty.handler.codec.http.HttpMethod; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.Map; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | /** 16 | * @author houyi.wh 17 | * @date 2019-01-15 18 | */ 19 | public class DefaultControllerContext implements ControllerContext { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultControllerContext.class); 22 | 23 | /** 24 | * 保存所有的RouterController的代理类 25 | */ 26 | private static Map proxyMap; 27 | 28 | /** 29 | * 路由上下文 30 | */ 31 | private static RouterContext routerContext; 32 | 33 | private static final class DefaultControllerContextHolder { 34 | private static DefaultControllerContext context = new DefaultControllerContext(); 35 | } 36 | 37 | private DefaultControllerContext() { 38 | routerContext = DefaultRouterContext.getInstance(); 39 | proxyMap = new ConcurrentHashMap<>(); 40 | } 41 | 42 | public static ControllerContext getInstance() { 43 | return DefaultControllerContextHolder.context; 44 | } 45 | 46 | 47 | @Override 48 | public void addProxy(String path, ControllerProxy proxy) { 49 | proxyMap.putIfAbsent(path, proxy); 50 | } 51 | 52 | @Override 53 | public ControllerProxy getProxy(HttpMethod method, String uri) { 54 | RouteResult routeResult = routerContext.getRouteResult(method, uri); 55 | if (routeResult == null) { 56 | return null; 57 | } 58 | // 获取代理 59 | ControllerProxy controllerProxy = proxyMap.get(routeResult.decodedPath()); 60 | LOGGER.debug("\n========================= getControllerProxy =========================" + 61 | "\nmethod={}, uri={}" + 62 | "\ncontrollerProxy={}" + 63 | "\n========================= getControllerProxy =========================", 64 | method, uri, controllerProxy); 65 | return controllerProxy; 66 | } 67 | 68 | 69 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/converter/AbstractConverter.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.converter; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * 转换器的抽象实现类 10 | * @author houyi.wh 11 | * @date 2017-10-20 12 | */ 13 | public abstract class AbstractConverter implements Converter { 14 | 15 | protected final Logger logger = LoggerFactory.getLogger(this.getClass()); 16 | 17 | /** 18 | * 实现了TypeConverter中的相同方法 19 | */ 20 | @Override 21 | public Object convert(Object source, Class toType, Object... parmas) { 22 | 23 | /** 24 | * 如果对象本身已经是所指定的类型则不进行转换直接返回 25 | * 如果对象能够被复制,则返回复制后的对象 26 | */ 27 | if (source != null && toType.isInstance(source)) { 28 | if (source instanceof Cloneable) { 29 | if(source.getClass().isArray() && source.getClass().getComponentType() == String.class){ 30 | // 字符串数组虽然是Cloneable的子类,但并没有clone方法 31 | return source; 32 | } 33 | try { 34 | Method m = source.getClass().getDeclaredMethod("clone", new Class[0]); 35 | m.setAccessible(true); 36 | return m.invoke(source, new Object[0]); 37 | } catch (Exception e) { 38 | logger.debug("Can not clone object " + source, e); 39 | } 40 | } 41 | 42 | return source; 43 | } 44 | 45 | /** 46 | * 如果需要转换,且value为String类型并且长度为0,则按照null值进行处理 47 | */ 48 | if (source != null && source instanceof String && ((String)source).length() == 0) { 49 | source = null; 50 | } 51 | 52 | /** 53 | * 不对Annotation, Interface, 54 | * Enummeration类型进行转换。 55 | */ 56 | if (toType == null || (source == null && !toType.isPrimitive()) 57 | || toType.isInterface() || toType.isAnnotation() 58 | || toType.isEnum()) { 59 | return null; 60 | } 61 | 62 | return doConvertValue(source, toType); 63 | } 64 | 65 | /** 66 | * 需要被子类所实现的转换方法 67 | * @param source 需要进行类型转换的对象 68 | * @param toType 需要被转换成的类型 69 | * @param params 转值时需要提供的可选参数 70 | * @return 转换后所生成的对象,如果不能够进行转换则返回null 71 | */ 72 | protected abstract Object doConvertValue(Object source, Class toType, Object... params); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/converter/Converter.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.converter; 2 | 3 | /** 4 | * 类型转换器所需要实现的总接口。TypeConverter中有唯一的一个方法,实现类请特别注意方法所需要返回的值。 5 | * @author houyi.wh 6 | * @date 2017-10-20 7 | */ 8 | public interface Converter { 9 | 10 | /** 11 | * 类型转换 12 | * @param source 需要被转换的值 13 | * @param toType 需要被转换成的类型 14 | * @param params 转值时需要提供的可选参数 15 | * @return 经转换过的类型,如果实现类没有能力进行所指定的类型转换,应返回null 16 | */ 17 | Object convert(Object source, Class toType, Object... params); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/converter/PrimitiveTypeUtil.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.converter; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * 定义了基本类型的工具类, 10 | * 可以方便的判断一个Class对象是否属于基本类型或基本类型的数组。 11 | * 本工具类所包含的基本类型判断包括如下一些内容: 12 | * 13 | * String 14 | * boolean 15 | * byte 16 | * short 17 | * int 18 | * long 19 | * float 20 | * double 21 | * char 22 | * Boolean 23 | * Byte 24 | * Short 25 | * Integer 26 | * Long 27 | * Float 28 | * Double 29 | * Character 30 | * BigInteger 31 | * BigDecimal 32 | * 33 | * @author yunfeng.cheng 34 | * @date 2016-08-12 35 | */ 36 | public class PrimitiveTypeUtil { 37 | 38 | /** 39 | * 私有的构造函数防止用户进行实例化。 40 | */ 41 | private PrimitiveTypeUtil() {} 42 | 43 | /** 基本类型 **/ 44 | private static final Class[] PRI_TYPE = { 45 | String.class, 46 | boolean.class, 47 | byte.class, 48 | short.class, 49 | int.class, 50 | long.class, 51 | float.class, 52 | double.class, 53 | char.class, 54 | Boolean.class, 55 | Byte.class, 56 | Short.class, 57 | Integer.class, 58 | Long.class, 59 | Float.class, 60 | Double.class, 61 | Character.class, 62 | BigInteger.class, 63 | BigDecimal.class 64 | }; 65 | 66 | /** 基本数组类型 **/ 67 | private static final Class[] PRI_ARRAY_TYPE = { 68 | String[].class, 69 | boolean[].class, 70 | byte[].class, 71 | short[].class, 72 | int[].class, 73 | long[].class, 74 | float[].class, 75 | double[].class, 76 | char[].class, 77 | Boolean[].class, 78 | Byte[].class, 79 | Short[].class, 80 | Integer[].class, 81 | Long[].class, 82 | Float[].class, 83 | Double[].class, 84 | Character[].class, 85 | BigInteger[].class, 86 | BigDecimal[].class 87 | }; 88 | 89 | /** 90 | * 基本类型默认值 91 | */ 92 | private static final Map, Object> primitiveDefaults = new HashMap, Object>(9); 93 | static { 94 | primitiveDefaults.put(boolean.class, false); 95 | primitiveDefaults.put(byte.class, (byte)0); 96 | primitiveDefaults.put(short.class, (short)0); 97 | primitiveDefaults.put(char.class, (char)0); 98 | primitiveDefaults.put(int.class, 0); 99 | primitiveDefaults.put(long.class, 0L); 100 | primitiveDefaults.put(float.class, 0.0f); 101 | primitiveDefaults.put(double.class, 0.0); 102 | } 103 | 104 | /** 105 | * 判断是否为基本类型 106 | * @param cls 需要进行判断的Class对象 107 | * @return 是否为基本类型 108 | */ 109 | public static boolean isPriType(Class cls) { 110 | for (Class priType : PRI_TYPE) { 111 | if (cls == priType){ 112 | return true; 113 | } 114 | } 115 | return false; 116 | } 117 | 118 | /** 119 | * 判断是否为基本类型数组 120 | * @param cls 需要进行判断的Class对象 121 | * @return 是否为基本类型数组 122 | */ 123 | public static boolean isPriArrayType(Class cls) { 124 | for (Class priType : PRI_ARRAY_TYPE) { 125 | if (cls == priType){ 126 | return true; 127 | } 128 | } 129 | return false; 130 | } 131 | 132 | /** 133 | * 获得基本类型的默认值 134 | * @param type 基本类型的Class 135 | * @return 基本类型的默认值 136 | */ 137 | public static Object getPriDefaultValue(Class type) { 138 | return primitiveDefaults.get(type); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/cookie/CookieManager.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.cookie; 2 | 3 | import io.netty.handler.codec.http.cookie.Cookie; 4 | 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | /** 9 | * cookie管理 10 | * @author houyi.wh 11 | * @date 2019-01-16 12 | */ 13 | public interface CookieManager { 14 | 15 | /** 16 | * 获取所有的cookie 17 | * @return cookie集合 18 | */ 19 | Set getCookies(); 20 | 21 | /** 22 | * 获取所有的cookie,并返回一个Map 23 | * @return cookie的map 24 | */ 25 | Map getCookieMap(); 26 | 27 | /** 28 | * 根据名称获取cookie 29 | * @param name 名称 30 | * @return cookie 31 | */ 32 | Cookie getCookie(String name); 33 | 34 | /** 35 | * 根据名称获取cookie的值 36 | * @param name 名称 37 | * @return cookie的值 38 | */ 39 | String getCookieValue(String name); 40 | 41 | /** 42 | * 设置cookie到响应结果中 43 | * @param cookie cookie 44 | */ 45 | void setCookie(Cookie cookie); 46 | 47 | /** 48 | * 获取所有的cookie后,全部设置到响应结果中 49 | */ 50 | void setCookies(); 51 | 52 | /** 53 | * 添加一个cookie 54 | * @param name cookie的名称 55 | * @param value cookie的值 56 | */ 57 | void addCookie(String name,String value); 58 | 59 | /** 60 | * 添加一个cookie 61 | * @param name cookie的名称 62 | * @param value cookie的值 63 | * @param domain cookie的作用域 64 | */ 65 | void addCookie(String name,String value,String domain); 66 | 67 | /** 68 | * 添加一个cookie 69 | * @param name cookie的名称 70 | * @param value cookie的值 71 | * @param maxAge cookie的有效期 72 | */ 73 | void addCookie(String name,String value,long maxAge); 74 | 75 | /** 76 | * 添加一个cookie 77 | * @param name cookie的名称 78 | * @param value cookie的值 79 | * @param domain cookie的作用域 80 | * @param maxAge cookie的有效期 81 | */ 82 | void addCookie(String name,String value,String domain,long maxAge); 83 | 84 | /** 85 | * 删除一个cookie 86 | * @param name cookie的名称 87 | * @return 操作结果 88 | */ 89 | boolean deleteCookie(String name); 90 | 91 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/cookie/DefaultCookieManager.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.cookie; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.redant.core.context.RedantContext; 5 | import io.netty.handler.codec.http.HttpHeaderNames; 6 | import io.netty.handler.codec.http.HttpRequest; 7 | import io.netty.handler.codec.http.cookie.Cookie; 8 | import io.netty.handler.codec.http.cookie.DefaultCookie; 9 | import io.netty.handler.codec.http.cookie.ServerCookieDecoder; 10 | 11 | import java.util.HashMap; 12 | import java.util.HashSet; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | /** 17 | * Cookie管理器 18 | * 19 | * @author houyi.wh 20 | * @date 2019-01-16 21 | */ 22 | 23 | public class DefaultCookieManager implements CookieManager { 24 | 25 | private static final class DefaultCookieManagerHolder { 26 | private static DefaultCookieManager cookieManager = new DefaultCookieManager(); 27 | } 28 | 29 | private DefaultCookieManager() { 30 | 31 | } 32 | 33 | public static CookieManager getInstance() { 34 | return DefaultCookieManagerHolder.cookieManager; 35 | } 36 | 37 | 38 | @Override 39 | public Set getCookies() { 40 | HttpRequest request = RedantContext.currentContext().getRequest(); 41 | Set cookies = new HashSet<>(); 42 | if (request != null) { 43 | String value = request.headers().get(HttpHeaderNames.COOKIE); 44 | if (value != null) { 45 | cookies = ServerCookieDecoder.STRICT.decode(value); 46 | } 47 | } 48 | return cookies; 49 | } 50 | 51 | @Override 52 | public Map getCookieMap() { 53 | Map cookieMap = new HashMap<>(); 54 | Set cookies = getCookies(); 55 | if (null != cookies && !cookies.isEmpty()) { 56 | for (Cookie cookie : cookies) { 57 | cookieMap.put(cookie.name(), cookie); 58 | } 59 | } 60 | return cookieMap; 61 | } 62 | 63 | @Override 64 | public Cookie getCookie(String name) { 65 | Map cookieMap = getCookieMap(); 66 | return cookieMap.getOrDefault(name, null); 67 | } 68 | 69 | @Override 70 | public String getCookieValue(String name) { 71 | Cookie cookie = getCookie(name); 72 | return cookie == null ? null : cookie.value(); 73 | } 74 | 75 | @Override 76 | public void setCookie(Cookie cookie) { 77 | RedantContext.currentContext().addCookie(cookie); 78 | } 79 | 80 | @Override 81 | public void setCookies() { 82 | Set cookies = getCookies(); 83 | if (!cookies.isEmpty()) { 84 | for (Cookie cookie : cookies) { 85 | setCookie(cookie); 86 | } 87 | } 88 | } 89 | 90 | @Override 91 | public void addCookie(String name, String value) { 92 | addCookie(name, value, null); 93 | } 94 | 95 | @Override 96 | public void addCookie(String name, String value, String domain) { 97 | addCookie(name, value, domain, 0); 98 | } 99 | 100 | @Override 101 | public void addCookie(String name, String value, long maxAge) { 102 | addCookie(name, value, null, maxAge); 103 | } 104 | 105 | @Override 106 | public void addCookie(String name, String value, String domain, long maxAge) { 107 | if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(value)) { 108 | Cookie cookie = new DefaultCookie(name, value); 109 | cookie.setPath("/"); 110 | if (domain != null && domain.trim().length() > 0) { 111 | cookie.setDomain(domain); 112 | } 113 | if (maxAge > 0) { 114 | cookie.setMaxAge(maxAge); 115 | } 116 | setCookie(cookie); 117 | } 118 | } 119 | 120 | @Override 121 | public boolean deleteCookie(String name) { 122 | Cookie cookie = getCookie(name); 123 | if (cookie != null) { 124 | cookie.setMaxAge(0); 125 | cookie.setPath("/"); 126 | setCookie(cookie); 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/executor/AbstractExecutor.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.executor; 2 | 3 | import com.redant.core.common.constants.CommonConstants; 4 | import io.netty.util.concurrent.Future; 5 | import io.netty.util.concurrent.Promise; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.concurrent.ArrayBlockingQueue; 10 | import java.util.concurrent.ThreadPoolExecutor; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | * @author houyi 15 | */ 16 | public abstract class AbstractExecutor implements Executor { 17 | 18 | private final static Logger LOGGER = LoggerFactory.getLogger(AbstractExecutor.class); 19 | 20 | private java.util.concurrent.Executor eventExecutor; 21 | 22 | public AbstractExecutor() { 23 | this(null); 24 | } 25 | 26 | public AbstractExecutor(java.util.concurrent.Executor eventExecutor) { 27 | this.eventExecutor = eventExecutor == null ? EventExecutorHolder.eventExecutor : eventExecutor; 28 | } 29 | 30 | @Override 31 | public T execute(Object... request) { 32 | return doExecute(request); 33 | } 34 | 35 | @Override 36 | public Future asyncExecute(Promise promise, Object... request) { 37 | if (promise == null) { 38 | throw new IllegalArgumentException("promise should not be null"); 39 | } 40 | // 异步执行 41 | eventExecutor.execute(new Runnable() { 42 | @Override 43 | public void run() { 44 | try { 45 | T response = doExecute(request); 46 | promise.setSuccess(response); 47 | } catch (Exception e) { 48 | promise.setFailure(e); 49 | } 50 | } 51 | }); 52 | // 返回promise 53 | return promise; 54 | } 55 | 56 | /** 57 | * 执行具体的方法 58 | * 59 | * @param request 请求对象 60 | * @return 返回结果 61 | */ 62 | public abstract T doExecute(Object... request); 63 | 64 | private static final class EventExecutorHolder { 65 | private static java.util.concurrent.Executor eventExecutor = new ThreadPoolExecutor( 66 | CommonConstants.EVENT_EXECUTOR_POOL_CORE_SIZE, 67 | CommonConstants.EVENT_EXECUTOR_POOL_MAX_SIZE, 68 | CommonConstants.EVENT_EXECUTOR_POOL_KEEP_ALIVE_SECONDS, 69 | TimeUnit.SECONDS, 70 | new ArrayBlockingQueue<>(CommonConstants.EVENT_EXECUTOR_POOL_BLOCKING_QUEUE_SIZE)); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/executor/Executor.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.executor; 2 | 3 | import io.netty.util.concurrent.Future; 4 | import io.netty.util.concurrent.Promise; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | public interface Executor { 10 | 11 | /** 12 | * 同步执行任务获得结果 13 | * @param request 请求对象 14 | * @return 结果 15 | */ 16 | T execute(Object... request); 17 | 18 | /** 19 | * 异步执行任务获得 Future 结果 20 | * @param promise 异步结果包装类 21 | * @param request 请求对象 22 | * @return 异步结果 23 | */ 24 | Future asyncExecute(Promise promise, Object... request); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/executor/HttpResponseExecutor.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.executor; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import com.redant.core.common.exception.InvocationException; 5 | import com.redant.core.common.util.HttpRenderUtil; 6 | import com.redant.core.common.util.HttpRequestUtil; 7 | import com.redant.core.context.RedantContext; 8 | import com.redant.core.controller.ControllerProxy; 9 | import com.redant.core.controller.ProxyInvocation; 10 | import com.redant.core.controller.context.ControllerContext; 11 | import com.redant.core.controller.context.DefaultControllerContext; 12 | import com.redant.core.interceptor.InterceptorHandler; 13 | import io.netty.handler.codec.http.FullHttpResponse; 14 | import io.netty.handler.codec.http.HttpHeaderNames; 15 | import io.netty.handler.codec.http.HttpRequest; 16 | import io.netty.handler.codec.http.HttpResponse; 17 | import io.netty.handler.codec.http.cookie.Cookie; 18 | import io.netty.handler.codec.http.cookie.ServerCookieEncoder; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Set; 25 | 26 | /** 27 | * @author houyi 28 | */ 29 | public class HttpResponseExecutor extends AbstractExecutor { 30 | 31 | private final static Logger LOGGER = LoggerFactory.getLogger(HttpResponseExecutor.class); 32 | 33 | private static ControllerContext controllerContext = DefaultControllerContext.getInstance(); 34 | 35 | public static HttpResponseExecutor getInstance() { 36 | return HttpResponseExecutorHolder.executor; 37 | } 38 | 39 | private HttpResponseExecutor() { 40 | } 41 | 42 | @Override 43 | public HttpResponse doExecute(Object... request) { 44 | HttpRequest httpRequest = (HttpRequest) request[0]; 45 | // 暂存请求对象 46 | // 将request存储到ThreadLocal中去,便于后期在其他地方获取并使用 47 | RedantContext.currentContext().setRequest(httpRequest); 48 | HttpResponse response = null; 49 | try { 50 | // 获取参数列表 51 | Map> paramMap = HttpRequestUtil.getParameterMap(httpRequest); 52 | // 处理拦截器的前置方法 53 | if (!InterceptorHandler.preHandle(paramMap)) { 54 | // 先从RedantContext中获取response,检查用户是否设置了response 55 | response = RedantContext.currentContext().getResponse(); 56 | // 若用户没有设置就返回一个默认的 57 | if (response == null) { 58 | response = HttpRenderUtil.getBlockedResponse(); 59 | } 60 | } else { 61 | // 处理业务逻辑 62 | response = invoke(httpRequest); 63 | // 处理拦截器的后置方法 64 | InterceptorHandler.postHandle(paramMap); 65 | } 66 | } catch (Exception e) { 67 | LOGGER.error("Server Internal Error,cause:", e); 68 | response = getErrorResponse(e); 69 | } finally { 70 | // 构造响应头 71 | buildHeaders(response, RedantContext.currentContext()); 72 | // 释放ThreadLocal对象 73 | RedantContext.clear(); 74 | } 75 | return response; 76 | } 77 | 78 | private HttpResponse invoke(HttpRequest request) throws Exception { 79 | // 根据路由获得具体的ControllerProxy 80 | ControllerProxy controllerProxy = controllerContext.getProxy(request.method(), request.uri()); 81 | if (controllerProxy == null) { 82 | return HttpRenderUtil.getNotFoundResponse(); 83 | } 84 | // 调用用户自定义的Controller,获得结果 85 | Object result = ProxyInvocation.invoke(controllerProxy); 86 | return HttpRenderUtil.render(result, controllerProxy.getRenderType()); 87 | } 88 | 89 | private HttpResponse getErrorResponse(Exception e) { 90 | HttpResponse response; 91 | if (e instanceof IllegalArgumentException || e instanceof InvocationException) { 92 | response = HttpRenderUtil.getErrorResponse(e.getMessage()); 93 | } else { 94 | response = HttpRenderUtil.getServerErrorResponse(); 95 | } 96 | return response; 97 | } 98 | 99 | private void buildHeaders(HttpResponse response, RedantContext redantContext) { 100 | if (response == null) { 101 | return; 102 | } 103 | FullHttpResponse fullHttpResponse = (FullHttpResponse) response; 104 | fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(fullHttpResponse.content().readableBytes())); 105 | // 写cookie 106 | Set cookies = redantContext.getCookies(); 107 | if (CollectionUtil.isNotEmpty(cookies)) { 108 | for (Cookie cookie : cookies) { 109 | fullHttpResponse.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); 110 | } 111 | } 112 | } 113 | 114 | private static final class HttpResponseExecutorHolder { 115 | private static HttpResponseExecutor executor = new HttpResponseExecutor(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/handler/ControllerDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.handler; 2 | 3 | import com.redant.core.common.constants.CommonConstants; 4 | import com.redant.core.executor.Executor; 5 | import com.redant.core.executor.HttpResponseExecutor; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.handler.codec.http.HttpRequest; 9 | import io.netty.handler.codec.http.HttpResponse; 10 | import io.netty.util.concurrent.*; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * 请求分发控制器 16 | * 17 | * @author houyi.wh 18 | * @date 2017-10-20 19 | */ 20 | public class ControllerDispatcher extends SimpleChannelInboundHandler { 21 | 22 | private final static Logger LOGGER = LoggerFactory.getLogger(ControllerDispatcher.class); 23 | 24 | private static Executor executor = HttpResponseExecutor.getInstance(); 25 | 26 | @Override 27 | public void channelRead0(ChannelHandlerContext ctx, HttpRequest request) { 28 | if (CommonConstants.ASYNC_EXECUTE_EVENT) { 29 | // 当前通道所持有的线程池 30 | EventExecutor channelExecutor = ctx.executor(); 31 | // 创建一个异步结果,并指定该promise 32 | Promise promise = new DefaultPromise<>(channelExecutor); 33 | // 在自定义线程池中执行业务逻辑,并返回一个异步结果 34 | Future future = executor.asyncExecute(promise, request); 35 | future.addListener(new GenericFutureListener>() { 36 | @Override 37 | public void operationComplete(Future f) throws Exception { 38 | // 异步结果执行成功后,取出结果 39 | HttpResponse response = f.get(); 40 | // 通过IO线程写响应结果 41 | ctx.channel().writeAndFlush(response); 42 | } 43 | }); 44 | } else { 45 | // 同步执行 46 | HttpResponse response = executor.execute(request); 47 | ctx.channel().writeAndFlush(response); 48 | } 49 | } 50 | 51 | @Override 52 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 53 | ctx.close(); 54 | LOGGER.error("ctx close,cause:", cause); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/handler/ssl/SslContextHelper.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.handler.ssl; 2 | 3 | import cn.hutool.crypto.SecureUtil; 4 | import io.netty.handler.ssl.SslContext; 5 | import io.netty.handler.ssl.SslContextBuilder; 6 | 7 | import javax.net.ssl.KeyManagerFactory; 8 | import javax.net.ssl.SSLException; 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.security.KeyStore; 14 | import java.security.KeyStoreException; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.security.UnrecoverableKeyException; 17 | import java.security.cert.CertificateException; 18 | import java.util.Map; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | /** 22 | * @author houyi.wh 23 | * @date 2017/11/19 24 | **/ 25 | public class SslContextHelper { 26 | 27 | private static final String KEY_STORE_JKS = "JKS"; 28 | 29 | private static final String ALGORITHM = "SunX509"; 30 | 31 | private static Map contents = new ConcurrentHashMap(); 32 | 33 | private static String getKey(String keyPath,String keyPassword){ 34 | if(keyPath==null || keyPath.trim().length()==0 || keyPassword==null || keyPassword.trim().length()==0){ 35 | return null; 36 | } 37 | String keyStr = keyPath+"&"+keyPassword; 38 | return SecureUtil.md5(keyStr); 39 | } 40 | 41 | /** 42 | * 获取SslContext 43 | * @param keyPath 44 | * @param keyPassword 45 | * @return 46 | */ 47 | public static SslContext getSslContext(String keyPath,String keyPassword){ 48 | if(keyPath==null || keyPath.trim().length()==0 || keyPassword==null || keyPassword.trim().length()==0){ 49 | return null; 50 | } 51 | SslContext sslContext = null; 52 | InputStream is = null; 53 | try { 54 | String key = getKey(keyPath,keyPassword); 55 | sslContext = contents.get(key); 56 | if(sslContext!=null){ 57 | return sslContext; 58 | } 59 | 60 | KeyStore keyStore = KeyStore.getInstance(KEY_STORE_JKS); 61 | is = new FileInputStream(keyPath); 62 | keyStore.load(is, keyPassword.toCharArray()); 63 | 64 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(ALGORITHM); 65 | keyManagerFactory.init(keyStore,keyPassword.toCharArray()); 66 | 67 | sslContext = SslContextBuilder.forServer(keyManagerFactory).build(); 68 | if(sslContext!=null){ 69 | contents.put(key,sslContext); 70 | } 71 | } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | IOException e) { 72 | e.printStackTrace(); 73 | } finally { 74 | if(is!=null){ 75 | try { 76 | is.close(); 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | } 82 | return sslContext; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/init/InitExecutor.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.init; 2 | 3 | 4 | import cn.hutool.core.collection.CollectionUtil; 5 | import cn.hutool.core.lang.ClassScaner; 6 | import com.redant.core.common.constants.CommonConstants; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.lang.reflect.Constructor; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.concurrent.atomic.AtomicBoolean; 15 | 16 | /** 17 | * @author houyi.wh 18 | * @date 2019-01-14 19 | */ 20 | public final class InitExecutor { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(InitExecutor.class); 23 | 24 | private static AtomicBoolean initialized = new AtomicBoolean(false); 25 | 26 | public static void init() { 27 | if (!initialized.compareAndSet(false, true)) { 28 | return; 29 | } 30 | try { 31 | Set> classSet = ClassScaner.scanPackageBySuper(CommonConstants.BEAN_SCAN_PACKAGE,InitFunc.class); 32 | if (CollectionUtil.isNotEmpty(classSet)) { 33 | List initList = new ArrayList<>(); 34 | for (Class cls : classSet) { 35 | // 如果cls是InitFunc的实现类 36 | if(!cls.isInterface() && InitFunc.class.isAssignableFrom(cls)){ 37 | Constructor constructor = cls.getDeclaredConstructor(); 38 | constructor.setAccessible(true); 39 | InitFunc initFunc = (InitFunc)constructor.newInstance(); 40 | LOGGER.info("[InitExecutor] found InitFunc: " + initFunc.getClass().getCanonicalName()); 41 | insertSorted(initList, initFunc); 42 | } 43 | } 44 | for (OrderWrapper w : initList) { 45 | w.func.init(); 46 | LOGGER.info("[InitExecutor] initialized: {} with order={}", w.func.getClass().getCanonicalName(), w.order); 47 | } 48 | } 49 | } catch (Exception ex) { 50 | LOGGER.warn("[InitExecutor] init failed", ex); 51 | ex.printStackTrace(); 52 | } catch (Error error) { 53 | LOGGER.warn("[InitExecutor] init failed with fatal error", error); 54 | error.printStackTrace(); 55 | throw error; 56 | } 57 | } 58 | 59 | private static void insertSorted(List list, InitFunc func) { 60 | int order = resolveOrder(func); 61 | int idx = 0; 62 | for (; idx < list.size(); idx++) { 63 | // 将func插入到order值比他大的第一个func前面 64 | if (list.get(idx).getOrder() > order) { 65 | break; 66 | } 67 | } 68 | list.add(idx, new OrderWrapper(order, func)); 69 | } 70 | 71 | private static int resolveOrder(InitFunc func) { 72 | if (!func.getClass().isAnnotationPresent(InitOrder.class)) { 73 | return InitOrder.LOWEST_PRECEDENCE; 74 | } else { 75 | return func.getClass().getAnnotation(InitOrder.class).value(); 76 | } 77 | } 78 | 79 | private InitExecutor() {} 80 | 81 | private static class OrderWrapper { 82 | private final int order; 83 | private final InitFunc func; 84 | 85 | OrderWrapper(int order, InitFunc func) { 86 | this.order = order; 87 | this.func = func; 88 | } 89 | 90 | int getOrder() { 91 | return order; 92 | } 93 | 94 | InitFunc getFunc() { 95 | return func; 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/init/InitFunc.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.init; 2 | 3 | /** 4 | * 初始化接口 5 | * @author houyi.wh 6 | * @date 2019-01-14 7 | */ 8 | public interface InitFunc { 9 | 10 | /** 11 | * 初始化方法 12 | */ 13 | void init(); 14 | } 15 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/init/InitOrder.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.init; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 初始化器的排序规则,升序排序 7 | * @author houyi.wh 8 | * @date 2019-01-14 9 | */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.TYPE}) 12 | @Documented 13 | public @interface InitOrder { 14 | 15 | /** 16 | * 最低优先级 17 | */ 18 | int LOWEST_PRECEDENCE = Integer.MAX_VALUE; 19 | /** 20 | * 最高优先级 21 | */ 22 | int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; 23 | 24 | /** 25 | * The order value. Lowest precedence by default. 26 | * 27 | * @return the order value 28 | */ 29 | int value() default LOWEST_PRECEDENCE; 30 | } 31 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/interceptor/Interceptor.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.interceptor; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author houyi 8 | **/ 9 | public abstract class Interceptor { 10 | 11 | /** 12 | * 拦截器的前置处理方法 13 | */ 14 | public boolean preHandle(Map> paramMap){ 15 | return true; 16 | } 17 | 18 | /** 19 | * 拦截器的后置处理方法 20 | */ 21 | public abstract void postHandle(Map> paramMap); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/interceptor/InterceptorBuilder.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.interceptor; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 用户可以通过该接口自行定义需要生效哪些拦截器 7 | * @author houyi 8 | **/ 9 | public interface InterceptorBuilder { 10 | 11 | /** 12 | * 构造拦截器列表 13 | * @return 列表 14 | */ 15 | List build(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/interceptor/InterceptorHandler.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.interceptor; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | 5 | import java.util.*; 6 | 7 | /** 8 | * @author houyi.wh 9 | * @date 2017/11/15 10 | **/ 11 | public class InterceptorHandler { 12 | 13 | public static boolean preHandle(Map> paramMap){ 14 | List interceptors = InterceptorProvider.getInterceptors(); 15 | if(CollectionUtil.isEmpty(interceptors)){ 16 | return true; 17 | } 18 | for(Interceptor interceptor : interceptors){ 19 | if(!interceptor.preHandle(paramMap)){ 20 | return false; 21 | } 22 | } 23 | return true; 24 | } 25 | 26 | public static void postHandle(Map> paramMap){ 27 | List interceptors = InterceptorProvider.getInterceptors(); 28 | if(CollectionUtil.isEmpty(interceptors)){ 29 | return; 30 | } 31 | for(Interceptor interceptor : interceptors){ 32 | interceptor.postHandle(paramMap); 33 | } 34 | } 35 | 36 | 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/interceptor/InterceptorProvider.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.interceptor; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.lang.ClassScaner; 5 | import com.redant.core.anno.Order; 6 | import com.redant.core.common.constants.CommonConstants; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Set; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * @author houyi 16 | **/ 17 | public class InterceptorProvider { 18 | 19 | private static volatile boolean loaded = false; 20 | 21 | private static volatile InterceptorBuilder builder = null; 22 | 23 | public static List getInterceptors(){ 24 | // 优先获取用户自定义的 InterceptorBuilder 构造的 Interceptor 25 | if(!loaded){ 26 | synchronized (InterceptorProvider.class) { 27 | if(!loaded) { 28 | Set> builders = ClassScaner.scanPackageBySuper(CommonConstants.INTERCEPTOR_SCAN_PACKAGE, InterceptorBuilder.class); 29 | if (CollectionUtil.isNotEmpty(builders)) { 30 | try { 31 | for (Class cls : builders) { 32 | builder = (InterceptorBuilder) cls.newInstance(); 33 | break; 34 | } 35 | } catch (IllegalAccessException | InstantiationException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | loaded = true; 40 | } 41 | } 42 | } 43 | if(builder!=null){ 44 | return builder.build(); 45 | } 46 | // 获取不到时,再扫描所有指定目录下的 Interceptor 47 | return InterceptorsHolder.interceptors; 48 | } 49 | 50 | static class InterceptorsHolder { 51 | 52 | static List interceptors; 53 | 54 | static { 55 | interceptors = scanInterceptors(); 56 | } 57 | 58 | private static List scanInterceptors() { 59 | Set> classSet = ClassScaner.scanPackageBySuper(CommonConstants.INTERCEPTOR_SCAN_PACKAGE,Interceptor.class); 60 | if(CollectionUtil.isEmpty(classSet)){ 61 | return Collections.emptyList(); 62 | } 63 | List wrappers = new ArrayList<>(classSet.size()); 64 | try { 65 | for (Class cls : classSet) { 66 | Interceptor interceptor =(Interceptor)cls.newInstance(); 67 | insertSorted(wrappers,interceptor); 68 | } 69 | }catch (IllegalAccessException | InstantiationException e) { 70 | e.printStackTrace(); 71 | } 72 | return wrappers.stream() 73 | .map(InterceptorWrapper::getInterceptor) 74 | .collect(Collectors.toList()); 75 | } 76 | 77 | private static void insertSorted(List list, Interceptor interceptor) { 78 | int order = resolveOrder(interceptor); 79 | int idx = 0; 80 | for (; idx < list.size(); idx++) { 81 | // 将当前interceptor插入到order值比他大的第一个interceptor前面 82 | if (list.get(idx).getOrder() > order) { 83 | break; 84 | } 85 | } 86 | list.add(idx, new InterceptorWrapper(order, interceptor)); 87 | } 88 | 89 | private static int resolveOrder(Interceptor interceptor) { 90 | if (!interceptor.getClass().isAnnotationPresent(Order.class)) { 91 | return Order.LOWEST_PRECEDENCE; 92 | } else { 93 | return interceptor.getClass().getAnnotation(Order.class).value(); 94 | } 95 | } 96 | 97 | private static class InterceptorWrapper { 98 | private final int order; 99 | private final Interceptor interceptor; 100 | 101 | InterceptorWrapper(int order, Interceptor interceptor) { 102 | this.order = order; 103 | this.interceptor = interceptor; 104 | } 105 | 106 | int getOrder() { 107 | return order; 108 | } 109 | 110 | Interceptor getInterceptor() { 111 | return interceptor; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/render/RenderType.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.render; 2 | 3 | /** 4 | * 返回的响应类型 5 | * @author houyi.wh 6 | * @date 2017-10-20 7 | */ 8 | public enum RenderType { 9 | 10 | /** 11 | * JSON 12 | */ 13 | JSON("application/json;charset=UTF-8"), 14 | /** 15 | * XML 16 | */ 17 | XML("text/xml;charset=UTF-8"), 18 | /** 19 | * TEXT 20 | */ 21 | TEXT("text/plain;charset=UTF-8"), 22 | /** 23 | * HTML 24 | */ 25 | HTML("text/html;charset=UTF-8"); 26 | 27 | private String contentType; 28 | 29 | RenderType(String contentType){ 30 | this.contentType = contentType; 31 | } 32 | 33 | public String getContentType() { 34 | return contentType; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/router/BadClientSilencer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.redant.core.router; 17 | 18 | import io.netty.channel.ChannelHandler.Sharable; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.SimpleChannelInboundHandler; 21 | import io.netty.handler.codec.http.LastHttpContent; 22 | import io.netty.util.internal.logging.InternalLogger; 23 | import io.netty.util.internal.logging.InternalLoggerFactory; 24 | 25 | /** 26 | * This utility handler should be put at the last position of the inbound pipeline to 27 | * catch all exceptions caused by bad client (closed connection, malformed request etc.) 28 | * and server processing, then close the connection. 29 | * 30 | * By default exceptions are logged to Netty internal LOGGER. You may need to override 31 | * {@link #onUnknownMessage(Object)}, {@link #onBadClient(Throwable)}, and 32 | * {@link #onBadServer(Throwable)} to log to more suitable places. 33 | */ 34 | @Sharable 35 | public class BadClientSilencer extends SimpleChannelInboundHandler { 36 | private static final InternalLogger log = InternalLoggerFactory.getInstance(BadClientSilencer.class); 37 | 38 | /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */ 39 | protected void onUnknownMessage(Object msg) { 40 | log.warn("Unknown msg: " + msg); 41 | } 42 | 43 | /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */ 44 | protected void onBadClient(Throwable e) { 45 | log.warn("Caught exception (maybe client is bad)", e); 46 | } 47 | 48 | /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */ 49 | protected void onBadServer(Throwable e) { 50 | log.warn("Caught exception (maybe server is bad)", e); 51 | } 52 | 53 | //---------------------------------------------------------------------------- 54 | 55 | @Override 56 | public void channelRead0(ChannelHandlerContext ctx, Object msg) { 57 | // This handler is the last inbound handler. 58 | // This means msg has not been handled by any previous handler. 59 | ctx.close(); 60 | 61 | if (msg != LastHttpContent.EMPTY_LAST_CONTENT) { 62 | onUnknownMessage(msg); 63 | } 64 | } 65 | 66 | @Override 67 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { 68 | ctx.close(); 69 | 70 | // To clarify where exceptions are from, imports are not used 71 | if (e instanceof java.io.IOException || // Connection reset by peer, Broken pipe 72 | e instanceof java.nio.channels.ClosedChannelException || 73 | e instanceof io.netty.handler.codec.DecoderException || 74 | e instanceof io.netty.handler.codec.CorruptedFrameException || // Bad WebSocket frame 75 | e instanceof IllegalArgumentException || // Use https://... to connect to HTTP server 76 | e instanceof javax.net.ssl.SSLException || // Use http://... to connect to HTTPS server 77 | e instanceof io.netty.handler.ssl.NotSslRecordException) { 78 | onBadClient(e); // Maybe client is bad 79 | } else { 80 | onBadServer(e); // Maybe server is bad 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/router/MethodlessRouter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.redant.core.router; 17 | 18 | /** 19 | * Router that contains information about route matching orders, but doesn't 20 | * contain information about HTTP request methods. 21 | * 22 | *

Routes are devided into 3 sections: "first", "last", and "other". 23 | * Routes in "first" are matched first, then in "other", then in "last". 24 | */ 25 | final class MethodlessRouter { 26 | private final OrderlessRouter first = new OrderlessRouter(); 27 | private final OrderlessRouter other = new OrderlessRouter(); 28 | private final OrderlessRouter last = new OrderlessRouter(); 29 | 30 | //-------------------------------------------------------------------------- 31 | 32 | /** 33 | * Returns the "first" router; routes in this router will be matched first. 34 | */ 35 | public OrderlessRouter first() { 36 | return first; 37 | } 38 | 39 | /** 40 | * Returns the "other" router; routes in this router will be matched after 41 | * those in the "first" router, but before those in the "last" router. 42 | */ 43 | public OrderlessRouter other() { 44 | return other; 45 | } 46 | 47 | /** 48 | * Returns the "last" router; routes in this router will be matched last. 49 | */ 50 | public OrderlessRouter last() { 51 | return last; 52 | } 53 | 54 | /** 55 | * Returns the number of routes in this router. 56 | */ 57 | public int size() { 58 | return first.routes().size() + other.routes().size() + last.routes().size(); 59 | } 60 | 61 | //-------------------------------------------------------------------------- 62 | 63 | /** 64 | * Adds route to the "first" section. 65 | * 66 | *

A path pattern can only point to one target. This method does nothing if the pattern 67 | * has already been added. 68 | */ 69 | public MethodlessRouter addRouteFirst(String pathPattern, T target) { 70 | first.addRoute(pathPattern, target); 71 | return this; 72 | } 73 | 74 | /** 75 | * Adds route to the "other" section. 76 | * 77 | *

A path pattern can only point to one target. This method does nothing if the pattern 78 | * has already been added. 79 | */ 80 | public MethodlessRouter addRoute(String pathPattern, T target) { 81 | other.addRoute(pathPattern, target); 82 | return this; 83 | } 84 | 85 | /** 86 | * Adds route to the "last" section. 87 | * 88 | *

A path pattern can only point to one target. This method does nothing if the pattern 89 | * has already been added. 90 | */ 91 | public MethodlessRouter addRouteLast(String pathPattern, T target) { 92 | last.addRoute(pathPattern, target); 93 | return this; 94 | } 95 | 96 | //-------------------------------------------------------------------------- 97 | 98 | /** 99 | * Removes the route specified by the path pattern. 100 | */ 101 | public void removePathPattern(String pathPattern) { 102 | first.removePathPattern(pathPattern); 103 | other.removePathPattern(pathPattern); 104 | last.removePathPattern(pathPattern); 105 | } 106 | 107 | /** 108 | * Removes all routes leading to the target. 109 | */ 110 | public void removeTarget(T target) { 111 | first.removeTarget(target); 112 | other.removeTarget(target); 113 | last.removeTarget(target); 114 | } 115 | 116 | //-------------------------------------------------------------------------- 117 | 118 | /** 119 | * @return {@code null} if no match 120 | */ 121 | public RouteResult route(String uri, String decodedPath, String[] pathTokens) { 122 | RouteResult ret = first.route(uri, decodedPath, pathTokens); 123 | if (ret != null) { 124 | return ret; 125 | } 126 | 127 | ret = other.route(uri, decodedPath, pathTokens); 128 | if (ret != null) { 129 | return ret; 130 | } 131 | 132 | ret = last.route(uri, decodedPath, pathTokens); 133 | if (ret != null) { 134 | return ret; 135 | } 136 | 137 | return null; 138 | } 139 | 140 | /** 141 | * Checks if there's any matching route. 142 | */ 143 | public boolean anyMatched(String[] requestPathTokens) { 144 | return first.anyMatched(requestPathTokens) || 145 | other.anyMatched(requestPathTokens) || 146 | last.anyMatched(requestPathTokens); 147 | } 148 | 149 | /** 150 | * Given a target and params, this method tries to do the reverse routing 151 | * and returns the URI. 152 | * 153 | *

Placeholders in the path pattern will be filled with the params. 154 | * The params can be a map of {@code placeholder name -> value} 155 | * or ordered values. 156 | * 157 | *

If a param doesn't have a corresponding placeholder, it will be put 158 | * to the query part of the result URI. 159 | * 160 | * @return {@code null} if there's no match 161 | */ 162 | public String uri(T target, Object... params) { 163 | String ret = first.uri(target, params); 164 | if (ret != null) { 165 | return ret; 166 | } 167 | 168 | ret = other.uri(target, params); 169 | if (ret != null) { 170 | return ret; 171 | } 172 | 173 | return last.uri(target, params); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/router/RouteResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.redant.core.router; 17 | 18 | import io.netty.handler.codec.http.HttpMethod; 19 | import io.netty.util.internal.ObjectUtil; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * Result of calling {@link Router#route(HttpMethod, String)}. 28 | */ 29 | public class RouteResult { 30 | private final String uri; 31 | private final String decodedPath; 32 | 33 | private final Map pathParams; 34 | private final Map> queryParams; 35 | 36 | private final T target; 37 | 38 | /** 39 | * The maps will be wrapped in Collections.unmodifiableMap. 40 | */ 41 | public RouteResult( 42 | String uri, String decodedPath, 43 | Map pathParams, Map> queryParams, 44 | T target 45 | ) { 46 | this.uri = ObjectUtil.checkNotNull(uri, "uri"); 47 | this.decodedPath = ObjectUtil.checkNotNull(decodedPath, "decodedPath"); 48 | this.pathParams = Collections.unmodifiableMap(ObjectUtil.checkNotNull(pathParams, "pathParams")); 49 | this.queryParams = Collections.unmodifiableMap(ObjectUtil.checkNotNull(queryParams, "queryParams")); 50 | this.target = ObjectUtil.checkNotNull(target, "target"); 51 | } 52 | 53 | /** 54 | * Returns the original request URI. 55 | */ 56 | public String uri() { 57 | return uri; 58 | } 59 | 60 | /** 61 | * Returns the decoded request path. 62 | */ 63 | public String decodedPath() { 64 | return decodedPath; 65 | } 66 | 67 | /** 68 | * Returns all params embedded in the request path. 69 | */ 70 | public Map pathParams() { 71 | return pathParams; 72 | } 73 | 74 | /** 75 | * Returns all params in the query part of the request URI. 76 | */ 77 | public Map> queryParams() { 78 | return queryParams; 79 | } 80 | 81 | public T target() { 82 | return target; 83 | } 84 | 85 | //---------------------------------------------------------------------------- 86 | // Utilities to get params. 87 | 88 | /** 89 | * Extracts the first matching param in {@code queryParams}. 90 | * 91 | * @return {@code null} if there's no match 92 | */ 93 | public String queryParam(String name) { 94 | List values = queryParams.get(name); 95 | return (values == null) ? null : values.get(0); 96 | } 97 | 98 | /** 99 | * Extracts the param in {@code pathParams} first, then falls back to the first matching 100 | * param in {@code queryParams}. 101 | * 102 | * @return {@code null} if there's no match 103 | */ 104 | public String param(String name) { 105 | String pathValue = pathParams.get(name); 106 | return (pathValue == null) ? queryParam(name) : pathValue; 107 | } 108 | 109 | /** 110 | * Extracts all params in {@code pathParams} and {@code queryParams} matching the name. 111 | * 112 | * @return Unmodifiable list; the list is empty if there's no match 113 | */ 114 | public List params(String name) { 115 | List values = queryParams.get(name); 116 | String value = pathParams.get(name); 117 | 118 | if (values == null) { 119 | return (value == null) ? Collections.emptyList() : Collections.singletonList(value); 120 | } 121 | 122 | if (value == null) { 123 | return Collections.unmodifiableList(values); 124 | } else { 125 | List aggregated = new ArrayList(values.size() + 1); 126 | aggregated.addAll(values); 127 | aggregated.add(value); 128 | return Collections.unmodifiableList(aggregated); 129 | } 130 | } 131 | 132 | @Override 133 | public String toString() { 134 | return "{target:"+target+",decodedPath:"+decodedPath+",queryParams:"+queryParams+"}"; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/router/context/RouterContext.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.router.context; 2 | 3 | import com.redant.core.render.RenderType; 4 | import com.redant.core.router.RouteResult; 5 | import io.netty.handler.codec.http.HttpMethod; 6 | 7 | /** 8 | * 路由上下文 9 | * @author houyi.wh 10 | * @date 2017-10-20 11 | */ 12 | public interface RouterContext { 13 | 14 | /** 15 | * 获取路由结果 16 | * @param method 请求类型 17 | * @param uri url 18 | * @return 路由结果 19 | */ 20 | RouteResult getRouteResult(HttpMethod method, String uri); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/server/NettyHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.server; 2 | 3 | import com.redant.core.common.constants.CommonConstants; 4 | import com.redant.core.init.InitExecutor; 5 | import io.netty.bootstrap.ServerBootstrap; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.EventLoopGroup; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.util.concurrent.DefaultThreadFactory; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | 16 | /** 17 | * NettyHttpServer 18 | * @author houyi.wh 19 | * @date 2017-10-20 20 | */ 21 | public final class NettyHttpServer implements Server { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(NettyHttpServer.class); 24 | 25 | @Override 26 | public void preStart() { 27 | InitExecutor.init(); 28 | } 29 | 30 | @Override 31 | public void start() { 32 | EventLoopGroup bossGroup = new NioEventLoopGroup(CommonConstants.BOSS_GROUP_SIZE, new DefaultThreadFactory("boss", true)); 33 | EventLoopGroup workerGroup = new NioEventLoopGroup(CommonConstants.WORKER_GROUP_SIZE, new DefaultThreadFactory("worker", true)); 34 | try { 35 | long start = System.currentTimeMillis(); 36 | ServerBootstrap b = new ServerBootstrap(); 37 | b.option(ChannelOption.SO_BACKLOG, 1024); 38 | b.group(bossGroup, workerGroup) 39 | .channel(NioServerSocketChannel.class) 40 | // .handler(new LoggingHandler(LogLevel.INFO)) 41 | .childHandler(new NettyHttpServerInitializer()); 42 | 43 | ChannelFuture future = b.bind(CommonConstants.SERVER_PORT).sync(); 44 | long cost = System.currentTimeMillis()-start; 45 | LOGGER.info("[NettyHttpServer] Startup at port:{} cost:{}[ms]",CommonConstants.SERVER_PORT,cost); 46 | 47 | // 等待服务端Socket关闭 48 | future.channel().closeFuture().sync(); 49 | } catch (InterruptedException e) { 50 | LOGGER.error("[NettyHttpServer] InterruptedException:",e); 51 | } finally { 52 | bossGroup.shutdownGracefully(); 53 | workerGroup.shutdownGracefully(); 54 | } 55 | } 56 | 57 | 58 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/server/NettyHttpServerInitializer.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.server; 2 | 3 | import com.redant.core.common.constants.CommonConstants; 4 | import com.redant.core.handler.ControllerDispatcher; 5 | import com.redant.core.handler.ssl.SslContextHelper; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.ChannelPipeline; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.handler.codec.http.HttpContentCompressor; 10 | import io.netty.handler.codec.http.HttpObjectAggregator; 11 | import io.netty.handler.codec.http.HttpServerCodec; 12 | import io.netty.handler.ssl.SslContext; 13 | import io.netty.handler.ssl.SslHandler; 14 | import io.netty.handler.stream.ChunkedWriteHandler; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import javax.net.ssl.SSLEngine; 19 | 20 | /** 21 | * @author houyi.wh 22 | * @date 2019-01-17 23 | */ 24 | public class NettyHttpServerInitializer extends ChannelInitializer { 25 | 26 | public static final Logger LOGGER = LoggerFactory.getLogger(NettyHttpServerInitializer.class); 27 | 28 | @Override 29 | public void initChannel(SocketChannel ch) { 30 | ChannelPipeline pipeline = ch.pipeline(); 31 | 32 | // HttpServerCodec is a combination of HttpRequestDecoder and HttpResponseEncoder 33 | // 使用HttpServerCodec将ByteBuf编解码为httpRequest/httpResponse 34 | pipeline.addLast(new HttpServerCodec()); 35 | addAdvanced(pipeline); 36 | pipeline.addLast(new ChunkedWriteHandler()); 37 | // 路由分发器 38 | pipeline.addLast(new ControllerDispatcher()); 39 | } 40 | 41 | private void initSsl(SocketChannel ch){ 42 | ChannelPipeline pipeline = ch.pipeline(); 43 | if(CommonConstants.USE_SSL){ 44 | SslContext context = SslContextHelper.getSslContext(CommonConstants.KEY_STORE_PATH,CommonConstants.KEY_STORE_PASSWORD); 45 | if(context!=null) { 46 | SSLEngine engine = context.newEngine(ch.alloc()); 47 | engine.setUseClientMode(false); 48 | pipeline.addLast(new SslHandler(engine)); 49 | }else{ 50 | LOGGER.warn("SslContext is null with keyPath={}",CommonConstants.KEY_STORE_PATH); 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * 可以在 HttpServerCodec 之后添加这些 ChannelHandler 进行开启高级特性 57 | */ 58 | private void addAdvanced(ChannelPipeline pipeline){ 59 | if(CommonConstants.USE_COMPRESS) { 60 | // 对 http 响应结果开启 gizp 压缩 61 | pipeline.addLast(new HttpContentCompressor()); 62 | } 63 | if(CommonConstants.USE_AGGREGATOR) { 64 | // 将多个HttpRequest组合成一个FullHttpRequest 65 | pipeline.addLast(new HttpObjectAggregator(CommonConstants.MAX_CONTENT_LENGTH)); 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/server/Server.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.server; 2 | 3 | /** 4 | * @author houyi.wh 5 | * @date 2019-01-10 6 | */ 7 | public interface Server { 8 | 9 | /** 10 | * 启动服务器之前的事件处理 11 | */ 12 | void preStart(); 13 | 14 | /** 15 | * 启动服务器 16 | */ 17 | void start(); 18 | 19 | } -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/session/HttpSession.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.session; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelId; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * HttpSession 11 | * @author houyi.wh 12 | * @date 2017/11/6 13 | */ 14 | public class HttpSession { 15 | 16 | /** 17 | * 会话id 18 | */ 19 | private ChannelId id; 20 | 21 | /** 22 | * 会话保存的ChannelHandlerContext 23 | */ 24 | private ChannelHandlerContext context; 25 | 26 | /** 27 | * 创建时间 28 | */ 29 | private Long createTime; 30 | 31 | /** 32 | * 过期时间 33 | * 每次请求时都更新过期时间 34 | */ 35 | private Long expireTime; 36 | 37 | /** 38 | * Session中存储的数据 39 | */ 40 | private Map sessionMap; 41 | 42 | 43 | private void assertSessionMapNotNull(){ 44 | if(sessionMap ==null){ 45 | sessionMap = new HashMap(); 46 | } 47 | } 48 | 49 | 50 | private HttpSession(){ 51 | 52 | } 53 | 54 | 55 | //===================================== 56 | 57 | 58 | public HttpSession(ChannelHandlerContext context){ 59 | this(context.channel().id(),context); 60 | } 61 | 62 | public HttpSession(ChannelId id,ChannelHandlerContext context){ 63 | this(id,context,System.currentTimeMillis()); 64 | } 65 | 66 | public HttpSession(ChannelId id,ChannelHandlerContext context,Long createTime){ 67 | this(id,context,createTime,createTime + SessionConfig.instance().sessionTimeOut()); 68 | } 69 | 70 | public HttpSession(ChannelId id,ChannelHandlerContext context,Long createTime,Long expireTime){ 71 | this.id = id; 72 | this.context = context; 73 | this.createTime = createTime; 74 | this.expireTime = expireTime; 75 | assertSessionMapNotNull(); 76 | } 77 | 78 | public ChannelId getId() { 79 | return id; 80 | } 81 | 82 | public void setId(ChannelId id) { 83 | this.id = id; 84 | } 85 | 86 | public ChannelHandlerContext getContext() { 87 | return context; 88 | } 89 | 90 | public void setContext(ChannelHandlerContext context) { 91 | this.context = context; 92 | } 93 | 94 | public Long getCreateTime() { 95 | return createTime; 96 | } 97 | 98 | public void setCreateTime(Long createTime) { 99 | this.createTime = createTime; 100 | } 101 | 102 | public Long getExpireTime() { 103 | return expireTime; 104 | } 105 | 106 | public void setExpireTime(Long expireTime) { 107 | this.expireTime = expireTime; 108 | } 109 | 110 | /** 111 | * 是否过期 112 | * @return 113 | */ 114 | public boolean isExpire(){ 115 | return this.expireTime>=System.currentTimeMillis(); 116 | } 117 | 118 | /** 119 | * 设置attribute 120 | * @param key 121 | * @param val 122 | */ 123 | public void setAttribute(String key,Object val){ 124 | sessionMap.put(key,val); 125 | } 126 | 127 | /** 128 | * 获取key的值 129 | * @param key 130 | */ 131 | public Object getAttribute(String key){ 132 | return sessionMap.get(key); 133 | } 134 | 135 | /** 136 | * 是否存在key 137 | * @param key 138 | */ 139 | public boolean containsAttribute(String key){ 140 | return sessionMap.containsKey(key); 141 | } 142 | 143 | 144 | } 145 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/session/SessionConfig.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.session; 2 | 3 | /** 4 | * 针对HttpSession的全局配置项 5 | * @author houyi.wh 6 | * @date 2017/11/6 7 | */ 8 | public class SessionConfig { 9 | 10 | /** 11 | * 默认超时时间 12 | */ 13 | private static final Long DEFAULT_SESSION_TIME_OUT = 60*60*1000L; 14 | 15 | private SessionConfig(){ 16 | 17 | } 18 | 19 | /** 20 | * session超时时间 21 | */ 22 | private Long sessionTimeOut = DEFAULT_SESSION_TIME_OUT; 23 | 24 | /** 25 | * 单例 26 | */ 27 | private static SessionConfig config; 28 | 29 | static{ 30 | if(config==null){ 31 | config = new SessionConfig(); 32 | } 33 | } 34 | 35 | 36 | //====================================== 37 | 38 | 39 | /** 40 | * 获取实例 41 | * @return 42 | */ 43 | public static SessionConfig instance(){ 44 | return config; 45 | } 46 | 47 | public SessionConfig sessionTimeOut(Long sessionTimeOut){ 48 | config.sessionTimeOut = sessionTimeOut; 49 | return config; 50 | } 51 | 52 | public Long sessionTimeOut(){ 53 | return config.sessionTimeOut; 54 | } 55 | 56 | 57 | @Override 58 | public String toString() { 59 | return "["+super.toString()+"]:{sessionTimeOut:"+config.sessionTimeOut+"}"; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/session/SessionHelper.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.session; 2 | 3 | import com.redant.core.common.exception.InvalidSessionException; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelId; 6 | 7 | import java.util.Iterator; 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * Session辅助器 13 | * @author houyi.wh 14 | * @date 2017/11/6 15 | */ 16 | public class SessionHelper { 17 | 18 | /** 19 | * 保存session对象的map 20 | */ 21 | private Map sessionMap; 22 | 23 | private static SessionHelper manager; 24 | 25 | private SessionHelper(){ 26 | 27 | } 28 | 29 | 30 | //====================================== 31 | 32 | 33 | /** 34 | * 获取单例 35 | * @return 36 | */ 37 | public static SessionHelper instange(){ 38 | synchronized (SessionHelper.class) { 39 | if (manager == null) { 40 | manager = new SessionHelper(); 41 | if (manager.sessionMap == null) { 42 | // 需要线程安全的Map 43 | manager.sessionMap = new ConcurrentHashMap(); 44 | } 45 | } 46 | } 47 | return manager; 48 | } 49 | 50 | /** 51 | * 判断session是否存在 52 | * @param context 53 | * @return 54 | */ 55 | public boolean containsSession(ChannelHandlerContext context){ 56 | return context!=null && context.channel()!=null && context.channel().id()!=null && manager.sessionMap.get(context.channel().id())!=null; 57 | } 58 | 59 | /** 60 | * 添加一个session 61 | * @param context 62 | * @param session 63 | */ 64 | public void addSession(ChannelHandlerContext context,HttpSession session){ 65 | if(context==null || context.channel()==null || context.channel().id()==null || session==null){ 66 | throw new InvalidSessionException("context or session is null"); 67 | } 68 | manager.sessionMap.put(context.channel().id(),session); 69 | } 70 | 71 | /** 72 | * 获取一个session 73 | * @param context 74 | * @return 75 | */ 76 | public HttpSession getSession(ChannelHandlerContext context){ 77 | if(context==null || context.channel()==null || context.channel().id()==null){ 78 | throw new InvalidSessionException("context is null"); 79 | } 80 | return manager.sessionMap.get(context.channel().id()); 81 | } 82 | 83 | /** 84 | * 获取一个session,获取不到时自动创建一个 85 | * @param context 86 | * @param createIfNull 87 | * @return 88 | */ 89 | public HttpSession getSession(ChannelHandlerContext context,boolean createIfNull){ 90 | HttpSession session = getSession(context); 91 | if(session==null && createIfNull){ 92 | session = new HttpSession(context); 93 | manager.sessionMap.put(context.channel().id(),session); 94 | } 95 | return session; 96 | } 97 | 98 | /** 99 | * 清除过期的session 100 | * 需要在定时器中执行该方法 101 | */ 102 | public void clearExpireSession(){ 103 | Iterator> iterator = manager.sessionMap.entrySet().iterator(); 104 | while(iterator.hasNext()){ 105 | Map.Entry sessionEntry = iterator.next(); 106 | if(sessionEntry.getValue()==null || sessionEntry.getValue().isExpire()){ 107 | iterator.remove(); 108 | } 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /redant-core/src/main/java/com/redant/core/session/SessionManager.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.session; 2 | 3 | /** 4 | * Session管理器 5 | * @author houyi.wh 6 | * @date 2017/11/6 7 | */ 8 | public interface SessionManager { 9 | 10 | 11 | /** 12 | * 判断session是否存在 13 | */ 14 | boolean sessionExists(); 15 | 16 | /** 17 | * 添加一个session 18 | * @param session session对象 19 | */ 20 | void addSession(HttpSession session); 21 | 22 | /** 23 | * 获取一个session 24 | * @return session对象 25 | */ 26 | HttpSession getSession(); 27 | 28 | /** 29 | * 获取一个session,获取不到时自动创建一个 30 | * @param createIfNull true:不存在时创建一个,false:不存在时也不创建 31 | * @return session对象 32 | */ 33 | HttpSession getSession(boolean createIfNull); 34 | 35 | /** 36 | * 清除过期的session 37 | * 需要在定时器中执行该方法 38 | */ 39 | void clearExpireSession(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /redant-core/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | %d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %-5level %logger:%line - %msg%n 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | WARN 31 | 32 | 33 | 34 | ${baseLogDir}/logs/%d{yyyy-MM-dd}/warn-log.log 35 | 36 | ${maxHistory} 37 | 38 | 39 | ${maxFileSize} 40 | 41 | 42 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ${baseLogDir}/logs/%d{yyyy-MM-dd}/debug-log.log 51 | 52 | ${maxHistory} 53 | 54 | 55 | ${maxFileSize} 56 | 57 | 58 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ${baseLogDir}/logs/%d{yyyy-MM-dd}/routerMsg.log 78 | 79 | ${maxHistory} 80 | 81 | 82 | ${maxFileSize} 83 | 84 | 85 | %-1relative - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{0}:%line - %msg%n 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /redant-core/src/main/resources/redant.properties: -------------------------------------------------------------------------------- 1 | # server port 2 | netty.server.port=8888 3 | 4 | # the boss thread size which set to EventLoopGroup 5 | netty.server.bossGroup.size=1 6 | 7 | # the worker thread size which set to EventLoopGroup 8 | netty.server.workerGroup.size=8 9 | 10 | # the maxContentLength which set to HttpObjectAggregator 11 | netty.maxContentLength=10485760 12 | 13 | # whether use ssl or not 14 | netty.server.use.ssl=false 15 | 16 | # KeyStore path 17 | ssl.keyStore.path= 18 | 19 | # KeyStore password 20 | ssl.keyStore.password= 21 | 22 | # the package that beans stored in 23 | bean.scan.package=com.redant 24 | 25 | # the package that netty interceptors stored in 26 | interceptor.scan.package=com.redant 27 | 28 | netty.server.use.compress=true 29 | netty.server.use.aggregator=true 30 | 31 | # the description sent to front end when server internal error occurred 32 | server.internal.error.desc=Server Internal Error 33 | 34 | # whether execute the business event in async mode 35 | async.execute.event=false 36 | 37 | # async executor thread pool 38 | async.executor.pool.core.size=10 39 | async.executor.pool.max.size=20 40 | async.executor.pool.keep.alive.seconds=10 41 | async.executor.pool.blocking.queue.size=100 -------------------------------------------------------------------------------- /redant-core/src/main/resources/zk.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "model" : "cluster", 3 | "configs" : [ 4 | { 5 | "clientPort": 2181, 6 | "tickTime": 2000, 7 | "initLimit": 10, 8 | "syncLimit": 5, 9 | "dataDir": "/zookeeper/1/data", 10 | "dataLogDir": "/zookeeper/1/log", 11 | "maxClientCnxns": 60, 12 | "myid": "/zookeeper/1/data/myid:1", 13 | "servers": [ 14 | "127.0.0.1:2887:3887", 15 | "127.0.0.1:2888:3888", 16 | "127.0.0.1:2889:3889" 17 | ] 18 | }, 19 | { 20 | "clientPort": 2182, 21 | "tickTime": 2000, 22 | "initLimit": 10, 23 | "syncLimit": 5, 24 | "dataDir": "/zookeeper/2/data", 25 | "dataLogDir": "/zookeeper/2/log", 26 | "maxClientCnxns": 60, 27 | "myid": "/zookeeper/2/data/myid:2", 28 | "servers": [ 29 | "127.0.0.1:2887:3887", 30 | "127.0.0.1:2888:3888", 31 | "127.0.0.1:2889:3889" 32 | ] 33 | }, 34 | { 35 | "clientPort": 2183, 36 | "tickTime": 2000, 37 | "initLimit": 10, 38 | "syncLimit": 5, 39 | "dataDir": "/zookeeper/3/data", 40 | "dataLogDir": "/zookeeper/3/log", 41 | "maxClientCnxns": 60, 42 | "myid": "/zookeeper/3/data/myid:3", 43 | "servers": [ 44 | "127.0.0.1:2887:3887", 45 | "127.0.0.1:2888:3888", 46 | "127.0.0.1:2889:3889" 47 | ] 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /redant-core/src/test/java/com/redant/core/context/RedantContextTest.java: -------------------------------------------------------------------------------- 1 | package com.redant.core.context; 2 | 3 | /** 4 | * @author houyi 5 | **/ 6 | public class RedantContextTest { 7 | 8 | public static void main(String[] args) { 9 | for(int i=0;i<10;i++){ 10 | new Thread(new ContextRunner()).start(); 11 | } 12 | } 13 | 14 | private static class ContextRunner implements Runnable { 15 | @Override 16 | public void run() { 17 | RedantContext context = RedantContext.currentContext(); 18 | System.out.println(context); 19 | } 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /redant-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | redant 7 | com.redant 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | redant-example 13 | jar 14 | redant-example 15 | 1.0.0-SNAPSHOT 16 | 17 | 18 | 19 | com.redant 20 | redant-core 21 | 1.0.0-SNAPSHOT 22 | 23 | 24 | com.redant 25 | redant-cluster 26 | 1.0.0-SNAPSHOT 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/bootstrap/cluster/MasterServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.bootstrap.cluster; 2 | 3 | /** 4 | * MasterServerBootstrap 5 | * @author houyi.wh 6 | * @date 2017/11/20 7 | **/ 8 | public class MasterServerBootstrap { 9 | 10 | public static void main(String[] args) { 11 | com.redant.cluster.bootstrap.MasterServerBootstrap.main(args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/bootstrap/cluster/SlaveServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.bootstrap.cluster; 2 | 3 | /** 4 | * SlaveServerBootstrap 5 | * @author houyi.wh 6 | * @date 2017/11/20 7 | **/ 8 | public class SlaveServerBootstrap { 9 | 10 | public static void main(String[] args) { 11 | com.redant.cluster.bootstrap.SlaveServerBootstrap.main(args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/bootstrap/cluster/ZkBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.bootstrap.cluster; 2 | 3 | /** 4 | * ZkBootstrap 5 | * @author houyi.wh 6 | * @date 2017/11/20 7 | **/ 8 | public class ZkBootstrap { 9 | 10 | public static void main(String[] args) { 11 | com.redant.cluster.bootstrap.ZkBootstrap.main(args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/bootstrap/standalone/ServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.bootstrap.standalone; 2 | 3 | /** 4 | * 服务端启动入口 5 | * @author houyi.wh 6 | * @date 2017-10-20 7 | */ 8 | public final class ServerBootstrap { 9 | 10 | public static void main(String[] args) { 11 | com.redant.core.ServerBootstrap.main(args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/controller/BaseController.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.controller; 2 | 3 | 4 | import com.redant.core.common.enums.RequestMethod; 5 | import com.redant.core.common.html.DefaultHtmlMaker; 6 | import com.redant.core.common.html.HtmlMaker; 7 | import com.redant.core.common.html.HtmlMakerEnum; 8 | import com.redant.core.common.html.HtmlMakerFactory; 9 | import com.redant.core.common.util.HtmlContentUtil; 10 | import com.redant.core.common.view.PageIndex; 11 | import com.redant.core.controller.annotation.Controller; 12 | import com.redant.core.render.RenderType; 13 | import com.redant.core.controller.annotation.Mapping; 14 | 15 | 16 | /** 17 | * BaseController 18 | * @author houyi.wh 19 | * @date 2017-10-20 20 | */ 21 | @Controller(path="/") 22 | public class BaseController { 23 | 24 | @Mapping(requestMethod=RequestMethod.GET,renderType=RenderType.HTML) 25 | public String index(){ 26 | HtmlMaker htmlMaker = HtmlMakerFactory.instance().build(HtmlMakerEnum.STRING,DefaultHtmlMaker.class); 27 | String htmlTpl = PageIndex.HTML; 28 | return HtmlContentUtil.getPageContent(htmlMaker, htmlTpl,null); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/controller/CookieController.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.controller; 2 | 3 | 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.redant.core.bean.annotation.Bean; 6 | import com.redant.core.common.enums.RequestMethod; 7 | import com.redant.core.controller.annotation.Controller; 8 | import com.redant.core.controller.annotation.Mapping; 9 | import com.redant.core.controller.annotation.Param; 10 | import com.redant.core.cookie.CookieManager; 11 | import com.redant.core.cookie.DefaultCookieManager; 12 | import com.redant.core.render.RenderType; 13 | 14 | /** 15 | * @author houyi.wh 16 | * @date 2017/12/1 17 | **/ 18 | @Bean 19 | @Controller(path="/cookie") 20 | public class CookieController { 21 | 22 | private CookieManager cookieManager = DefaultCookieManager.getInstance(); 23 | 24 | @Mapping(path="/add",requestMethod=RequestMethod.GET,renderType=RenderType.JSON) 25 | public JSONObject add(@Param(key="name", notBlank=true) String name, @Param(key="value", notBlank=true) String value){ 26 | JSONObject object = new JSONObject(); 27 | object.put("tip","请在响应头 Response Headers 中查看 set-cookie 的值"); 28 | object.put("cookieName",name); 29 | object.put("cookieValue",value); 30 | cookieManager.addCookie(name,value); 31 | return object; 32 | } 33 | 34 | @Mapping(path="/delete",requestMethod=RequestMethod.GET,renderType=RenderType.JSON) 35 | public JSONObject delete(@Param(key="name", notBlank=true) String name){ 36 | JSONObject object = new JSONObject(); 37 | object.put("tip","请在响应头 Response Headers 中查看 set-cookie 的值"); 38 | object.put("cookieName",name); 39 | cookieManager.deleteCookie(name); 40 | return object; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.controller; 2 | 3 | 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.redant.core.bean.annotation.Autowired; 7 | import com.redant.core.bean.annotation.Bean; 8 | import com.redant.core.common.enums.RequestMethod; 9 | import com.redant.core.controller.annotation.Controller; 10 | import com.redant.core.controller.annotation.Mapping; 11 | import com.redant.core.controller.annotation.Param; 12 | import com.redant.core.render.RenderType; 13 | import com.redant.example.service.UserBean; 14 | import com.redant.example.service.UserService; 15 | 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * @author houyi.wh 20 | * @date 2017/12/1 21 | **/ 22 | @Bean 23 | @Controller(path = "/user") 24 | public class UserController { 25 | 26 | /** 27 | * 如果需要使用Autowired,则该类自身需要使用Bean注解标注 28 | */ 29 | @Autowired(name = "userService") 30 | private UserService userService; 31 | 32 | @Mapping(path = "/info", requestMethod = RequestMethod.GET, renderType = RenderType.JSON) 33 | public UserBean info(@Param(key = "id", notNull = true) Integer id) { 34 | try { 35 | TimeUnit.MILLISECONDS.sleep(500); 36 | } catch (InterruptedException e) { 37 | e.printStackTrace(); 38 | } 39 | return userService.selectUserInfo(id); 40 | } 41 | 42 | @Mapping(path = "/list", requestMethod = RequestMethod.GET, renderType = RenderType.JSON) 43 | public JSONArray list() { 44 | JSONArray array = new JSONArray(); 45 | JSONObject object = new JSONObject(); 46 | UserBean user = new UserBean(); 47 | user.setId(23); 48 | user.setUserName("逅弈逐码"); 49 | object.put("user", user); 50 | array.add(object); 51 | return array; 52 | } 53 | 54 | @Mapping(path = "/count", requestMethod = RequestMethod.GET, renderType = RenderType.JSON) 55 | public JSONObject count() { 56 | JSONObject object = new JSONObject(); 57 | int count = userService.selectCount(); 58 | object.put("count", count); 59 | return object; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/interceptor/BlockInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.interceptor; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.redant.core.anno.Order; 6 | import com.redant.core.common.util.HttpRenderUtil; 7 | import com.redant.core.context.RedantContext; 8 | import com.redant.core.interceptor.Interceptor; 9 | import com.redant.core.render.RenderType; 10 | import io.netty.handler.codec.http.FullHttpResponse; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * 当请求的参数中有 block=true 时,就会被该拦截器拦截 19 | * @author houyi 20 | **/ 21 | @Order(value = 1) 22 | public class BlockInterceptor extends Interceptor { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(BlockInterceptor.class); 25 | 26 | @Override 27 | public boolean preHandle(Map> paramMap) { 28 | if(CollectionUtil.isNotEmpty(paramMap)) { 29 | String blockKey = "block"; 30 | String blockVal = "true"; 31 | List values = paramMap.get(blockKey); 32 | if(CollectionUtil.isNotEmpty(values)){ 33 | String val = values.get(0); 34 | if(blockVal.equals(val)){ 35 | JSONObject content = new JSONObject(); 36 | content.put("status","你被前置方法拦截了"); 37 | content.put("reason","请求参数中有 block=true"); 38 | FullHttpResponse response = HttpRenderUtil.render(content, RenderType.JSON); 39 | RedantContext.currentContext().setResponse(response); 40 | LOGGER.info("[BlockInterceptor] blocked preHandle"); 41 | return false; 42 | } 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | @Override 49 | public void postHandle(Map> paramMap) { 50 | // do nothing 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/interceptor/CustomInterceptorBuilder.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.interceptor; 2 | 3 | import com.redant.core.interceptor.Interceptor; 4 | import com.redant.core.interceptor.InterceptorBuilder; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author houyi 11 | **/ 12 | public class CustomInterceptorBuilder implements InterceptorBuilder { 13 | 14 | private volatile boolean loaded = false; 15 | 16 | private List interceptors = null; 17 | 18 | @Override 19 | public List build() { 20 | if(!loaded){ 21 | synchronized (CustomInterceptorBuilder.class) { 22 | if(!loaded){ 23 | interceptors = new ArrayList<>(); 24 | if (activeBlock()) { 25 | interceptors.add(new BlockInterceptor()); 26 | } 27 | if (activePerf()) { 28 | interceptors.add(new PerformanceInterceptor()); 29 | } 30 | loaded = true; 31 | } 32 | } 33 | } 34 | return interceptors; 35 | } 36 | 37 | private boolean activeBlock(){ 38 | return false; 39 | } 40 | 41 | private boolean activePerf(){ 42 | return true; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/interceptor/PerformanceInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.interceptor; 2 | 3 | import com.redant.core.anno.Order; 4 | import com.redant.core.context.RedantContext; 5 | import com.redant.core.interceptor.Interceptor; 6 | import io.netty.handler.codec.http.HttpRequest; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * 该拦截器可以计算出用户自定义 Controller 方法的执行时间 15 | * @author houyi 16 | **/ 17 | @Order(value = 2) 18 | public class PerformanceInterceptor extends Interceptor { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(PerformanceInterceptor.class); 21 | 22 | private ThreadLocal start = new ThreadLocal<>(); 23 | 24 | @Override 25 | public boolean preHandle(Map> paramMap) { 26 | start.set(System.currentTimeMillis()); 27 | return true; 28 | } 29 | 30 | @Override 31 | public void postHandle(Map> paramMap) { 32 | try { 33 | long end = System.currentTimeMillis(); 34 | long cost = end - start.get(); 35 | HttpRequest request = RedantContext.currentContext().getRequest(); 36 | String uri = request.uri(); 37 | LOGGER.info("uri={}, cost:{}[ms]", uri, cost); 38 | }finally { 39 | start.remove(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/service/UserBean.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.service; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * UserBean 10 | * @author houyi.wh 11 | * @date 2017-10-20 12 | */ 13 | public class UserBean implements Serializable { 14 | 15 | private Integer id; 16 | 17 | private String userName; 18 | 19 | private String password; 20 | 21 | public Integer getId() { 22 | return id; 23 | } 24 | 25 | public void setId(Integer id) { 26 | this.id = id; 27 | } 28 | 29 | public String getUserName() { 30 | return userName; 31 | } 32 | 33 | public void setUserName(String userName) { 34 | this.userName = userName; 35 | } 36 | 37 | public String getPassword() { 38 | return password; 39 | } 40 | 41 | public void setPassword(String password) { 42 | this.password = password; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return JSON.toJSONString(this); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.service; 2 | 3 | /** 4 | * @author houyi.wh 5 | * @date 2017/12/1 6 | **/ 7 | public interface UserService { 8 | 9 | /** 10 | * 获取用户信息 11 | * @param id 用户id 12 | * @return 用户信息 13 | */ 14 | UserBean selectUserInfo(Integer id); 15 | 16 | /** 17 | * 获取用户个数 18 | * @return 用户个数 19 | */ 20 | int selectCount(); 21 | } 22 | -------------------------------------------------------------------------------- /redant-example/src/main/java/com/redant/example/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.redant.example.service; 2 | 3 | import com.redant.core.bean.annotation.Bean; 4 | 5 | /** 6 | * @author houyi.wh 7 | * @date 2017/12/1 8 | **/ 9 | @Bean(name="userService") 10 | public class UserServiceImpl implements UserService { 11 | 12 | @Override 13 | public UserBean selectUserInfo(Integer id) { 14 | UserBean user = new UserBean(); 15 | user.setId(id); 16 | user.setUserName("逅弈逐码"); 17 | return user; 18 | } 19 | 20 | @Override 21 | public int selectCount() { 22 | return 10; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /redant-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | %d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %-5level %logger:%line - %msg%n 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | WARN 31 | 32 | 33 | 34 | ${baseLogDir}/logs/%d{yyyy-MM-dd}/warn-log.log 35 | 36 | ${maxHistory} 37 | 38 | 39 | ${maxFileSize} 40 | 41 | 42 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ${baseLogDir}/logs/%d{yyyy-MM-dd}/debug-log.log 51 | 52 | ${maxHistory} 53 | 54 | 55 | ${maxFileSize} 56 | 57 | 58 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ${baseLogDir}/logs/%d{yyyy-MM-dd}/routerMsg.log 78 | 79 | ${maxHistory} 80 | 81 | 82 | ${maxFileSize} 83 | 84 | 85 | %-1relative - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{0}:%line - %msg%n 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /redant-example/src/test/java/com/lememo/core/interceptor/InterceptorProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.lememo.core.interceptor; 2 | 3 | import com.redant.core.interceptor.Interceptor; 4 | import com.redant.core.interceptor.InterceptorProvider; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author houyi 10 | **/ 11 | public class InterceptorProviderTest { 12 | 13 | public static void main(String[] args) { 14 | for(int i=0;i<10;i++){ 15 | new Thread(new Run()).start(); 16 | } 17 | } 18 | 19 | static class Run implements Runnable { 20 | @Override 21 | public void run() { 22 | List interceptors = InterceptorProvider.getInterceptors(); 23 | System.out.println("Thread=[" + Thread.currentThread().getName() + "] interceptors size="+interceptors.size()); 24 | } 25 | } 26 | 27 | } 28 | --------------------------------------------------------------------------------