[
  {
    "path": ".gitignore",
    "content": "# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n\n# Logs and databases #\n######################\n*.log\n\n# OS generated files #\n######################\n.DS_Store*\nehthumbs.db\nIcon?\nThumbs.db\n\n# Editor Files #\n################\n*~\n*.swp\n\n# Gradle Files #\n################\n.gradle\n.m2\n\n# Build output directies\ntarget/\nbuild/\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n\n# IntelliJ specific files/directories\nout\n.idea\n*.ipr\n*.iws\n*.iml\natlassian-ide-plugin.xml\n\n# Eclipse specific files/directories\n.classpath\n.project\n.settings\n.metadata\n\n# NetBeans specific files/directories\n.nbattrs\nnbproject/private/\nbuild/\nnbbuild/\ndist/\nnbdist/"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"{}\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n   Copyright 2017 all4you\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# RedAnt 项目\n\n**RedAnt** 是一个基于 Netty 的轻量级 Web 容器\n\n **特性:**\n\n- [x] **IOC容器** : 通过 @Bean 注解可以管理所有对象，通过 @Autowired 注解进行对象注入\n- [x] **自定义路由**  : 通过 @Controller @Mapping @Param 注解可以自定义路由\n- [x] **自动参数转换**  : 通过 TypeConverter 接口，http 参数会被转成对象(支持基本类型,Map,List,JavaBean)\n- [x] **结果渲染**  : 支持对结果进行渲染，支持 html, xml, plain, json 格式\n- [x] **Cookie管理**  : 内置一个 Cookie 管理器\n- [x] **前置后置拦截器** ：支持前置拦截器与后置拦截器\n- [x] **单机模式**  : 支持单机模式\n- [x] **集群模式**  : 支持集群模式\n- [x] **服务注册与发现** ：实现了一个基于 Zk 的服务注册与发现，来支持多节点模式\n- [ ] **Session管理**  : 因为涉及到多节点模式，分布式 session 暂未实现\n\n\n\n\n\n## 快速启动\n\n### 1.单机模式\n\nRedant 是一个基于 Netty 的 Web 容器，类似 Tomcat 和 WebLogic 等容器\n\n只需要启动一个 Server，默认的实现类是 NettyHttpServer 就能快速启动一个 web 容器了，如下所示：\n\n``` java\npublic final class ServerBootstrap {\n    public static void main(String[] args) {\n        Server nettyServer = new NettyHttpServer();\n        // 各种初始化工作\n        nettyServer.preStart();\n        // 启动服务器\n        nettyServer.start();\n    }\n}\n```\n\n\n\n### 2.集群模式\n\n到目前为止，我描述的都是单节点模式，如果哪一天单节点的性能无法满足了，那就需要使用集群了，所以我也实现了集群模式。\n\n集群模式是由一个主节点和若干个从节点构成的。主节点接收到请求后，将请求转发给从节点来处理，从节点把处理好的结果返回给主节点，由主节点把结果响应给请求。\n\n要想实现集群模式需要有一个服务注册和发现的功能，目前是借助于 Zk 来做的服务注册与发现。\n\n\n#### 准备一个 Zk 服务端\n\n因为主节点需要把请求转发给从节点，所以主节点需要知道目前有哪些从节点，我通过 ZooKeeper 来实现服务注册与发现。\n\n如果你没有可用的 Zk 服务端的话，那你可以通过运行下面的 Main 方法来启动一个 ZooKeeper 服务端：\n\n``` java\npublic final class ZkBootstrap {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZkBootstrap.class);\n\n    public static void main(String[] args) {\n        try {\n            ZkServer zkServer = new ZkServer();\n            zkServer.startStandalone(ZkConfig.DEFAULT);\n        }catch (Exception e){\n            LOGGER.error(\"ZkBootstrap start failed,cause:\",e);\n            System.exit(1);\n        }\n    }\n}\n```\n\n这样你就可以在后面启动主从节点的时候使用这个 Zk 了。但是这并不是必须的，如果你已经有一个正在运行的 Zk 的服务端，那么你可以在启动主从节点的时候直接使用它，通过在 main 方法的参数中指定 Zk 的地址即可。\n\n\n\n#### 启动主节点\n\n只需要运行下面的代码，就可以启动一个主节点了：\n\n``` java\npublic class MasterServerBootstrap {\n    public static void main(String[] args) {\n        String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);\n\n        // 启动MasterServer\n        Server masterServer = new MasterServer(zkAddress);\n        masterServer.preStart();\n        masterServer.start();\n    }\n}\n```\n\n如果在 main 方法的参数中指定了 Zk 的地址，就通过该地址去进行服务发现，否则会使用默认的 Zk 地址\n\n\n\n#### 启动从节点\n\n只需要运行下面的代码，就可以启动一个从节点了：\n\n``` java\npublic class SlaveServerBootstrap {\n\n    public static void main(String[] args) {\n        String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);\n        Node node = Node.getNodeWithArgs(args);\n\n        // 启动SlaveServer\n        Server slaveServer = new SlaveServer(zkAddress,node);\n        slaveServer.preStart();\n        slaveServer.start();\n    }\n\n}\n```\n\n如果在 main 方法的参数中指定了 Zk 的地址，就通过该地址去进行服务注册，否则会使用默认的 Zk 地址\n\n\n\n## 例子\n\n你可以运行 redant-example 模块中提供的例子来体验一下，example 模块中内置了两个 Controller 。\n\n启动完之后，你可以在浏览器中访问 http://127.0.0.1:8888 来查看具体的效果 (默认的端口可以在 redant.properties 中修改)\n\n如果你看到了这样的消息：\"Welcome to redant!\" 这就意味着你已经启动成功了。\n\n在 redant-example 模块中，内置了以下几个默认的路由:\n\n| 方法类型           | URL                          | 响应类型                       |\n| ----------------- | ---------------------------- | ----------------------------- |\n| GET               | /                            | HTML                          |\n| \\*                | \\*                           | HTML                          |\n| GET               | /user/count                  | JSON                          |\n| GET               | /user/list                   | JSON                          |\n| GET               | /user/info                   | JSON                          |\n\n\n\n\n## Bean 管理器\n\n跟 Spring 一样，你可以通过 @Bean 注解来管理所有的对象，通过 @Autowired 来自动注入\n\n**Tips：** 更多信息请查看wiki: [Bean][1]\n\n\n\n## 自定义路由\n\n跟 Spring 一样，你可以通过 @Controller 来自定义一个 Controller.\n\n@Mapping 注解用在方法级别，@Controller + @Mapping 唯一定义一个 http 请求。\n\n@Param 注解用在方法的参数上。通过该注解可以自动将基本类型转成 POJO 对象。\n\n**Tips：** 更多信息请查看wiki: [Router][2]\n\n\n\n## Cookie 管理器\n\nCookie 管理器可以管理用户自定义的 Cookie。\n\n**Tips：** 更多信息请查看wiki: [Cookie][4]\n\n\n\n## 联系我\n\n> wh_all4you#hotmail.com\n\n![contact-me](./logo.jpg)\n\n\n\n\n[1]: https://github.com/all4you/redant/wiki/1:Bean\n[2]: https://github.com/all4you/redant/wiki/2:Router\n[3]: https://github.com/all4you/redant/wiki/3:Session\n[4]: https://github.com/all4you/redant/wiki/4:Cookie\n\n\n"
  },
  {
    "path": "md/interceptor.md",
    "content": "# 用责任链模式设计拦截器\n\n我在 Redant(https://github.com/all4you/redant) 中通过继承 ChannelHandler 实现了拦截器的功能，并且 pipeline 就是一种责任链模式的应用。但是我后面对原本的拦截器进行了重新设计，为什么这样做呢，因为原本的方式是在 ChannelHandler 的基础上操作的，而我们知道 Netty 的数据处理都是基于 ByteBuf 的，这就涉及到引用计数释放的问题，前面的 ChannelHandler 在处理时可以不关心引用计数的问题，而交给最后一个 ChannelHandler 去释放。\n\n但是拦截器的一大特性就是当某个条件不满足时需要中断后面的操作直接返回，所以这就造成了在 pipeline 中某个节点需要释放引用计数，另外一个方面就是原先的设计使用了很多自定义的 ChannelHandler，有的只做了一些简单的工作，所以完全可以对他们进行合并，使代码变得更加精简紧凑。\n\n合并多个 ChannelHandler 是比较简单的，重新设计拦截器相对就复杂一些了。\n\n## 重新设计拦截器\n\n首先我把原本的前置拦截器和后置拦截器统一成一个拦截器，然后抽象出两个方法，分别表示：前置处理，后置处理，如下图所示：\n\n![interceptor](interceptor/interceptor.png)\n\n默认前置处理的方法返回 true，用户可以根据他们的业务进行覆盖。\n\n这里是定义了一个抽象类，也可以用接口，java 8 开始接口中可以有默认方法实现。\n\n拦截器定义好之后，现在就可以在 ChannelHandler 中加入拦截器的方法调用了，如下图所示：\n\n![interceptor-handle](interceptor/interceptor-handle.png)\n\n当前置方法返回 false 时，直接返回，中断后面的业务逻辑处理，最终会到 finally 中将结果写入 response 中返回给前端。\n\n现在只要实现 InterceptorHandler 中的两个方法就可以了，其实这也很简单，只要获取到所有的 Interceptor 的实现类，然后依次调用这些实现类的前置方法和后置方法就好了，如下图所示：\n\n![interceptor-call](interceptor/interceptor-call.png)\n\n## 获取拦截器\n\n现在的重点就是怎样获取到所有的拦截器，首先可以想到的是通过扫描的方法，找到所有 Interceptor 的实现类，然后将这些实现类加入到一个 List 中即可。\n\n那怎么保证拦截器的执行顺序呢，很简单，只要在加入 List 之前对他们进行排序就可以了。再定义一个 @Order 注解来表示排序的顺序，然后用一个 Wrapper 包装类将 Interceptor 和 Order 包装起来，排序到包装类的 List 中，最后再从包装类的 List 中依次取出所有的 Interceptor 就完成了 Interceptor 的排序了。\n\n知道了大致的原理之后，实现起来就很简单了，如下图所示：\n\n![get-interceptor](interceptor/get-interceptor.png)\n\n但是我们不能每次都通过调用 scanInterceptors() 方法来获取所有的拦截器，如果这样每次都扫描一次的话性能会有影响，所以我们只需要第一次调用一下该方法，然后把结果保存在一个私有的变量中，获取的时候直接读取该变量的值即可，如下图所示：\n\n![interceptor-provider](interceptor/interceptor-provider.png)\n\n## 自定义拦截器实现类\n\n下面让我们来自定义两个拦截器实现类，来验证下具体的效果。\n\n第一个拦截器，在前置方法中对请求参数进行判断，如果请求参数中有 block=true 的参数，则进行拦截，如下图所示：\n\n![block-interceptor](interceptor/block-interceptor.png)\n\n第二个拦截器，在后置方法中打印出每次请求的耗时，如下图所示：\n\n![performance-interceptor](interceptor/performance-interceptor.png)\n\n通过 @Order 注解来指定执行的顺序，先执行 BlockInterceptor 再执行 PerformanceInterceptor。\n\n## 查看效果\n\n现在我们请求 /user/info 这个接口，查看下效果。\n\n首先我们只提交正常的参数，如下图所示：\n\n![common-request](interceptor/common-request.png)\n\n打印的结果如下图所示：\n\n![common-request-effect](interceptor/common-request-effect.png)\n\n从打印的结果中可以看到依次执行了:\n\n- BlockInterceptor 的 preHandle 方法\n- PerformanceInterceptor 的 preHandle方法\n- BlockInterceptor 的 postHandle 方法 \n- PerformanceInterceptor 的 postHandle方法\n\n这说明拦截器是按照 @Order 注解进行了排序，然后依次执行的。\n\n然后我们再提交一个 block=true 的参数，再次请求该接口，如下图所示：\n\n![block-request](interceptor/block-request.png)\n\n可以看到该请求已经被拦截器的前置方法给拦截了，再看下打印的日志，如下图所示：\n\n![block-request-effect](interceptor/block-request-effect.png)\n\n只打印了 BlockInterceptor 的 preHandler 方法中的部分日志，后面的方法都没有执行，因为被拦截了直接返回了。\n\n## 存在的问题\n\n到这里已经对拦截器完成了改造，并且也验证了效果，看上去效果还可以。但是有没有什么问题呢？\n\n还真有一个问题：所有的 Interceptor 实现类只要被扫描到了，就会被加入到 List 中去，如果不想应用某一个拦截器这时就做不到了，因为无法对 list 中的值进行动态的更改。\n\n如果我们可以动态的获取一个保存了 Interceptor 的 list ，如果该 list 中没有获取到值，再通过扫描的方式去拿到所有的 Interceptor 这样就完美了。\n\n动态获取 Interceptor 的 list 的方法，可以由用户自定义实现，根据某些规则来确定要不要将某个 Interceptor 加入到 list 中去，这样就把 Interceptor 的实现和使用进行了解耦了。用户可以实现任意多的 Interceptor，但是只根据规则去使用其中的某些 Interceptor。\n\n理清楚了原理之后，就很好实现了，首先定义一个接口，用来构造 Interceptor 的 List，如下图所示：\n\n![interceptor-builder](interceptor/interceptor-builder.png)\n\n有了 InterceptorBuilder 之后，在获取 Interceptor 的时候，就可以先根据 InterceptorBuilder 来获取了，如下图所示：\n\n![interceptor-provider-with-builder](interceptor/interceptor-provider-with-builder.png)\n\n以下是一个示例的 InterceptorBuilder，具体的可以用户自行扩展设计，如下图所示：\n\n![custom-interceptor-builder](interceptor/custom-interceptor-builder.png)\n\n这样用户只要实现一个 InterceptorBuilder 接口，即可按照自己的意图去组装所有的拦截器。\n"
  },
  {
    "path": "md/overview.md",
    "content": "**RedAnt** 是一个基于 Netty 的轻量级 Web 容器，创建这个项目的目的主要是学习使用 Netty，俗话说不要轻易的造轮子，但是通过造轮子我们可以学到很多优秀开源框架的设计思路，编写优美的代码，更好的提升自己。\n\n![features](./overview/features.png)\n\n\n\n## 快速启动\n\nRedant 是一个基于 Netty 的 Web 容器，类似 Tomcat 和 WebLogic 等容器\n\n只需要启动一个 Server，默认的实现类是 NettyHttpServer 就能快速启动一个 web 容器了，如下所示：\n\n```java\npublic final class ServerBootstrap {\n    public static void main(String[] args) {\n        Server nettyServer = new NettyHttpServer();\n        // 各种初始化工作\n        nettyServer.preStart();\n        // 启动服务器\n        nettyServer.start();\n    }\n}\n```\n\n我们可以直接启动 redant-example 模块中的 ServerBootstrap 类，因为 redant-example 中有很多示例的 Controller，我们直接运行 example 中的 ServerBootstrap，启动后你会看到如下的日志信息：\n\n![start-up](./overview/start-up.png)\n\n在 redant-example 模块中，内置了以下几个默认的路由:\n\n| 方法类型 | URL         | 响应类型 |\n| -------- | ----------- | -------- |\n| GET      | /           | HTML     |\n| \\*       | \\*          | HTML     |\n| GET      | /user/count | JSON     |\n| GET      | /user/list  | JSON     |\n| GET      | /user/info  | JSON     |\n\n启动成功后，可以访问 http://127.0.0.1:8888/ 查看效果，如下图所示：\n\n![welcome-to-redant](./overview/welcome-to-redant.png)\n\n如果你可以看到 \"Welcome to redant!\"  这样的消息，那就说明你启动成功了。\n\n\n\n## 自定义路由\n\n框架实现了自定义路由，通过 @Controller @Mapping 注解就可以唯一确定一个自定义路由。如下列的 UserController 所示：\n\n![user-controller](./overview/user-controller.png)\n\n和 Spring 的使用方式一样，访问 /user/list 来看下效果，如下图所示：\n\n![user-list](./overview/user-list.png)\n\n\n\n## 结果渲染\n\n目前支持 json、html、xml、text 等类型的结果渲染，用户只需要在 方法的 @Mapping 注解上通过 renderType 来指定具体的渲染类型即可，如果不指定的话，默认以 json 类型范围。\n\n如下图所示，首页就是通过指定 renderType 为 html 来返回一个 html 页面的：\n\n![base-controller](./overview/base-controller.png)\n\n\n\n## IOC容器\n\n从 UserController 的代码中，我们看到 userServerce 对象是通过 @Autowired 注解自动注入的，这个功能是任何一个 IOC 容器基本的能力，下面我们来看看如何实现一个简单的 IOC 容器。\n\n首先定义一个 BeanContext 接口，如下所示：\n\n``` java\npublic interface BeanContext {\n    /**\n     * 获得Bean\n     * @param name Bean的名称\n     * @return Bean\n     */\n    Object getBean(String name);\n    /**\n     * 获得Bean\n     * @param name Bean的名称\n     * @param clazz Bean的类\n     * @param <T> 泛型\n     * @return Bean\n     */\n    <T> T getBean(String name,Class<T> clazz);\n}\n```\n\n然后我们需要在系统启动的时候，扫描出所有被 @Bean 注解修饰的类，然后对这些类进行实例化，然后把实例化后的对象保存在一个 Map 中即可，如下图所示：\n\n![init-bean](./overview/init-bean.png)\n\n代码很简单，通过在指定路径下扫描出所有的类之后，把实例对象加入map中，但是对于已经加入的 bean 不能继续加入了，加入之后要获取一个 Bean 也很简单了，直接通过 name 到 map 中去获取就可以了。\n\n现在我们已经把所有 @Bean 的对象管理起来了，那对于依赖到的其他的 bean 该如何注入呢，换句话说就是将我们实例化好的对象赋值给 @Autowired 注解修饰的变量。\n\n简单点的做法就是遍历 beanMap，然后对每个 bean 进行检查，看这个 bean 里面的每个 setter 方法和属性，如果有 @Autowired 注解，那就找到具体的 bean 实例之后将值塞进去。\n\n### setter注入\n\n![setter-inject](./overview/setter-inject.png)\n\n### field注入\n\n![field-inject](./overview/field-inject.png)\n\n### 通过Aware获取BeanContext\n\nBeanContext 已经实现了，那怎么获取 BeanContext 的实例呢？想到 Spring 中有很多的 Aware 接口，每种接口负责一种实例的回调，比如我们想要获取一个 BeanFactory 那只要将我们的类实现 BeanFactoryAware 接口就可以了，接口中的 setBeanFactory(BeanFactory factory) 方法参数中的 BeanFactory 实例就是我们所需要的，我们只要实现该方法，然后将参数中的实例保存在我们的类中，后续就可以直接使用了。\n\n那现在我就来实现这样的功能，首先定义一个 Aware 接口，所有其他需要回调塞值的接口都继承自该接口，如下所示：\n\n``` java\npublic interface Aware {\n\n}\n\npublic interface BeanContextAware extends Aware{\n\n    /**\n     * 设置BeanContext\n     * @param beanContext BeanContext对象\n     */\n    void setBeanContext(BeanContext beanContext);\n}\n```\n\n接下来需要将 BeanContext 的实例注入到所有 BeanContextAware 的实现类中去。BeanContext 的实例很好得到，BeanContext 的实现类本身就是一个 BeanContext 的实例，并且可以将该实例设置为单例，这样的话所有需要获取 BeanContext 的地方都可以获取到同一个实例。\n\n拿到 BeanContext 的实例后，我们就需要扫描出所有实现了 BeanContextAware 接口的类，并实例化这些类，然后调用这些类的 setBeanContext 方法，参数就传我们拿到的 BeanContext 实例。\n\n逻辑理清楚之后，实现起来就很简单了，如下图所示：\n\n![bean-context-aware](./overview/bean-context-aware.png)\n\n\n\n## Cookie管理\n\n基本上所有的 web 容器都会有 cookie 管理的能力，那我们的 redant 也不能落后。首先定义一个 CookieManager 的接口，核心的操作 cookie 的方法如下：\n\n``` java\npublic interface CookieManager {\n\n    Set<Cookie> getCookies();\n\n    Cookie getCookie(String name);\n    \n    void addCookie(String name,String value);\n\n    void setCookie(Cookie cookie);\n\n    boolean deleteCookie(String name);\n\n}\n```\n\n其中我只列举了几个核心的方法，另外有一些不同参数的重载方法，这里就不详细介绍了。最关键的是两个方法，一个是读 Cookie 一个是写 Cookie 。\n\n### 读 Cookie\n\nNetty 中是通过 HttpRequest 的 Header 来保存请求中所携带的 Cookie的，所以要读取 Cookie 的话，最关键的是获取到 HttpRequest。而 HttpRequest 可以在 ChannelHandler 中拿到，通过 HttpServerCodec 编解码器，Netty 已经帮我们把请求的数据转换成 HttpRequest 了。但是这个 HttpRequest 只在 ChannelHandler 中才能访问到，而处理 Cookie 通常是用户自定义的操作，并且对用户来说他是不关心 HttpRequest 的，他只需要通过 CookieManager 去获取一个 Cookie 就行了。\n\n这种情况下，最适合的就是将 HttpRequest 对象保存在一个 ThreadLocal 中，在 CookieManager 中需要获取的时候，直接到 ThreadLocal 中去取出来就可以了，如下列代码所示：\n\n``` java\n@Override\npublic Set<Cookie> getCookies() {\n    HttpRequest request = TemporaryDataHolder.loadHttpRequest();\n    Set<Cookie> cookies = new HashSet<>();\n    if(request != null) {\n        String value = request.headers().get(HttpHeaderNames.COOKIE);\n        if (value != null) {\n            cookies = ServerCookieDecoder.STRICT.decode(value);\n        }\n    }\n    return cookies;\n}\n```\n\nTemporaryDataHolder 就是那个通过 ThreadLocal 保存了 HttpRequest 的类。\n\n### 写 Cookie\n\n写 Cookie 和读 Cookie 面临着一样的问题，就是写的时候需要借助于 HttpResponse，将 Cookie 写入 HttpResponse 的 Header 中去，但是用户执行写 Cookie 操作的时候，根本就不关心 HttpResponse，甚至他在写的时候，还没有 HttpResponse。\n\n这时的做法也是将需要写到 HttpResponse 中的 Cookie 保存在 ThreadLocal 中，然后在最后通过 channel 写响应之前，将 Cookie 拿出来塞到 HttpResponse 中去即可，如下列代码所示：\n\n``` java\n@Override\npublic void setCookie(Cookie cookie) {\n    TemporaryDataHolder.storeCookie(cookie);\n}\n\n/**\n * 响应消息\n */\nprivate void writeResponse(){\n    boolean close = isClose();\n    response.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(response.content().readableBytes()));\n    // 从ThreadLocal中取出待写入的cookie\n    Set<Cookie> cookies = TemporaryDataHolder.loadCookies();\n    if(!CollectionUtil.isEmpty(cookies)){\n        for(Cookie cookie : cookies){\n            // 将cookie写入response中\n            response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));\n        }\n    }\n    ChannelFuture future = channel.write(response);\n    if(close){\n        future.addListener(ChannelFutureListener.CLOSE);\n    }\n}\n```\n\n\n\n## 拦截器\n\n拦截器是一个框架很重要的功能，通过拦截器可以实现一些通用的工作，比如登录鉴权，事务处理等等。记得在 Servlet 的年代，拦截器是非常重要的一个功能，基本上每个系统都会在 web.xml 中配置很多的拦截器。\n\n拦截器的基本思想是，通过一连串的类去执行某个拦截的操作，一旦某个类中的拦截操作返回了 false，那就终止后面的所有流程，直接返回。\n\n这种场景非常适合用责任链模式去实现，而 Netty 的 pipeline 本身就是一个责任链模式的应用，所以我们就可以通过 pipeline 来实现我们的拦截器。这里我定义了两种类型的拦截器：前置拦截器和后置拦截器。\n\n前置拦截器是在处理用户的业务逻辑之前的一个拦截操作，如果该操作返回了 false 则直接 return，不会继续执行用户的业务逻辑。\n\n后置拦截器就有点不同了，后置拦截器主要就是处理一些后续的操作，因为后置拦截器再跟前置拦截器一样，当操作返回了 false 直接 return 的话，已经没有意义了，因为业务逻辑已经执行完了。\n\n理解清楚了具体的逻辑之后，实现起来就很简单了，如下列代码所示：\n\n### 前置拦截器\n\n![pre-interceptor](./overview/pre-interceptor.png)\n\n### 后置拦截器\n\n![after-interceptor](./overview/after-interceptor.png)\n\n有了实现之后，我们需要把他们加到 pipeline 中合适的位置，让他们在整个责任链中生效，如下图所示：\n\n![init-channel-handler](./overview/init-channel-handler.png)\n\n### 指定拦截器的执行顺序\n\n目前拦截器还没有实现指定顺序执行的功能，其实也很简单，可以定义一个 @InterceptorOrder 的注解应用在所有的拦截器的实现类上，扫描到拦截器的结果之后，根据该注解进行排序，然后把拍完序之后的结果添加到 pipeline 中即可。\n\n\n\n## 集群模式\n\n到目前为止，我描述的都是单节点模式，如果哪一天单节点的性能无法满足了，那就需要使用集群了，所以我也实现了集群模式。\n\n集群模式是由一个主节点和若干个从节点构成的。主节点接收到请求后，将请求转发给从节点来处理，从节点把处理好的结果返回给主节点，由主节点把结果响应给请求。\n\n要想实现集群模式需要有一个服务注册和发现的功能，目前是借助于 Zk 来做的服务注册与发现。\n\n### 准备一个 Zk 服务端\n\n因为主节点需要把请求转发给从节点，所以主节点需要知道目前有哪些从节点，我通过 ZooKeeper 来实现服务注册与发现。\n\n如果你没有可用的 Zk 服务端的话，那你可以通过运行下面的 Main 方法来启动一个 ZooKeeper 服务端：\n\n```java\npublic final class ZkBootstrap {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZkBootstrap.class);\n\n    public static void main(String[] args) {\n        try {\n            ZkServer zkServer = new ZkServer();\n            zkServer.startStandalone(ZkConfig.DEFAULT);\n        }catch (Exception e){\n            LOGGER.error(\"ZkBootstrap start failed,cause:\",e);\n            System.exit(1);\n        }\n    }\n}\n```\n\n这样你就可以在后面启动主从节点的时候使用这个 Zk 了。但是这并不是必须的，如果你已经有一个正在运行的 Zk 的服务端，那么你可以在启动主从节点的时候直接使用它，通过在 main 方法的参数中指定 Zk 的地址即可。\n\n### 启动主节点\n\n只需要运行下面的代码，就可以启动一个主节点了：\n\n```java\npublic class MasterServerBootstrap {\n    public static void main(String[] args) {\n        String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);\n\n        // 启动MasterServer\n        Server masterServer = new MasterServer(zkAddress);\n        masterServer.preStart();\n        masterServer.start();\n    }\n}\n```\n\n如果在 main 方法的参数中指定了 Zk 的地址，就通过该地址去进行服务发现，否则会使用默认的 Zk 地址。\n\n### 启动从节点\n\n只需要运行下面的代码，就可以启动一个从节点了：\n\n```java\npublic class SlaveServerBootstrap {\n\n    public static void main(String[] args) {\n        String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);\n        Node node = Node.getNodeWithArgs(args);\n\n        // 启动SlaveServer\n        Server slaveServer = new SlaveServer(zkAddress,node);\n        slaveServer.preStart();\n        slaveServer.start();\n    }\n\n}\n```\n\n如果在 main 方法的参数中指定了 Zk 的地址，就通过该地址去进行服务注册，否则会使用默认的 Zk 地址。\n\n实际上多节点模式具体的处理逻辑还是复用了单节点模式的核心功能，只是把原本一台实例扩展到多台实例而已。\n\n\n\n## 总结\n\n本文通过介绍一个基于 Netty 的 web 容器，让我们了解了一个 http 服务端的大概的构成，当然实现中可能有更加好的方法。但是主要的还是要了解内在的思想，包括 Netty 的一些基本的使用方法。\n\n我会继续优化该项目，加入更多的特性，例如服务发现与注册当前是通过 Zk 来实现的，未来可能会引入其他的组件去实现服务注册与发现。\n\n除此之外，Session 的管理还未完全实现，后续也需要对这一块进行完善。\n\n\n\n"
  },
  {
    "path": "md/processor.md",
    "content": "# 责任链\n\n我在 Redant 中实现的拦截器所使用的责任链，其实是通过了一个 List 来保存了所有的 Interceptor，那我们通常所说的责任链除了使用 List 来实现外，还可以通过真正的链表结构来实现，Netty 和 Sentinel 中都有这样的实现，下面我来实现一个简单的链式结构的责任链。\n\n责任链的应用已经有很多了，这里不再赘述，假设我们需要对前端提交的请求做以下操作：鉴权，登录，日志记录，通过责任链来做这些处理是非常合适的。\n\n首先定义一个处理接口，如下图所示：\n\n![processor](processor/processor.png)\n\n通过 List 方式的实现很简单，只需要把每个 Processor 的实现类添加到一个 List 中即可，处理的时候遍历该 List 依次处理，这里不再做具体的描述，感兴趣的可以自行实现。\n\n## 定义节点\n\n如果是通过链表的形式来实现的话，首先我们需要有一个类表示链表中的某个节点，并且该节点需要有一个同类型的私有变量表示该节点的下个节点，这样就可以实现一个链表了，如下图所示：\n\n![abstract-linked-processor](processor/abstract-linked-processor.png)\n\n## 定义容器\n\n接着我们需要定义一个容器，在容器中有头，尾两个节点，头结点作为一个空节点，真正的节点将添加到头结点的 next 节点上去，尾节点作为一个指针，用来指向当前添加的节点，下一次添加新节点时，将从尾节点处添加。有了具体的处理逻辑之后，实现起来就很简单了，这个容器的实现如下图所示：\n\n![linked-processor-chain](processor/linked-processor-chain.png)\n\n## 定义实现类\n\n下面我们可以实现具体的 Processor 来处理业务逻辑了，只要继承 AbstractLinkedProcessor 即可，如下图所示：\n\n![auth-processor](processor/auth-processor.png)\n\n其他两个实现类： LoginProcessor ，LogProcessor 类似，这里就不贴出来了。\n\n然后就可以根据规则来组装所需要的 Processor 了，假设我们的规则是需要对请求依次进行：鉴权，登录，日志记录，那组装的代码如下图所示：\n\n![linked-processor-chain-test](processor/linked-processor-chain-test.png)\n\n执行该代码，结果如下图所示：\n\n![linked-processor-chain-test-result](processor/linked-processor-chain-test-result.png)\n\n## 存在的问题\n\n看的仔细的同学可以发现了，在 AuthProcessor 的业务逻辑实现中，除了执行了具体的逻辑代码之外，还调用了一行 super.process(content) 代码，这行代码的作用是调用链表中的下一个节点的 process 方法。但是如果有一天我们在写自己的 Processor 实现类时，忘记调用这行代码的话，会是怎样的结果呢？\n\n结果就是当前节点后面的节点不会被调用，整个链表就像断掉一样，那怎样来避免这种问题的发生呢？其实我们在 AbstractProcessor 中已经实现了 process 方法，该方法就是调用下个节点的 process 方法的。那我们在这个方法触发调用下个节点之前，再抽象出一个用以具体的业务逻辑处理的方法 doProcess ，先执行 doProcess 方法，执行完之后再触发下个节点的 process ，这样就不会出现链表断掉的情况了，具体的实现如下图所示：\n\n![fixed-abstract-linked-processor](processor/fixed-abstract-linked-processor.png)\n\n相应的 LinkedProcessorChain 和具体的实现类也要做响应的调整，如下图所示：\n\n![fixed-linked-processor-chain](processor/fixed-linked-processor-chain.png)\n\n![fixed-auth-processor](processor/fixed-auth-processor.png)\n\n重新执行刚刚的测试类，发现结果和之前的一样，至此一个简单的链式责任链完成了。"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.redant</groupId>\n    <artifactId>redant</artifactId>\n    <version>1.0.0-SNAPSHOT</version>\n    <packaging>pom</packaging>\n    <name>redant</name>\n\n    <properties>\n        <java.version>1.8</java.version>\n        <java.encoding>UTF-8</java.encoding>\n        <project-name>redant</project-name>\n        <project-version>1.0.0-SNAPSHOT</project-version>\n        <common-beans.version>1.9.2</common-beans.version>\n        <slf4j.version>1.7.7</slf4j.version>\n        <logback.version>1.1.7</logback.version>\n        <fastjson.version>1.2.54</fastjson.version>\n        <netty.version>4.1.9.Final</netty.version>\n        <cglib.version>3.2.4</cglib.version>\n        <hutool-all.version>4.2.1</hutool-all.version>\n        <zookeeper.version>3.4.13</zookeeper.version>\n        <curator.version>4.0.1</curator.version>\n    </properties>\n\n    <modules>\n        <module>redant-core</module>\n        <module>redant-cluster</module>\n        <module>redant-example</module>\n    </modules>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>commons-beanutils</groupId>\n                <artifactId>commons-beanutils</artifactId>\n                <version>${common-beans.version}</version>\n            </dependency>\n            <!-- 日志文件管理包 -->\n            <!-- 格式化对象，方便输出日志 -->\n            <dependency>\n                <groupId>org.slf4j</groupId>\n                <artifactId>slf4j-api</artifactId>\n                <version>${slf4j.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.slf4j</groupId>\n                <artifactId>jcl-over-slf4j</artifactId>\n                <version>${slf4j.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>ch.qos.logback</groupId>\n                <artifactId>logback-classic</artifactId>\n                <version>${logback.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>fastjson</artifactId>\n                <version>${fastjson.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.netty</groupId>\n                <artifactId>netty-all</artifactId>\n                <version>${netty.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>cglib</groupId>\n                <artifactId>cglib</artifactId>\n                <version>${cglib.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>cn.hutool</groupId>\n                <artifactId>hutool-all</artifactId>\n                <version>${hutool-all.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.zookeeper</groupId>\n                <artifactId>zookeeper</artifactId>\n                <version>${zookeeper.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.curator</groupId>\n                <artifactId>curator-recipes</artifactId>\n                <version>${curator.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.redant</groupId>\n                <artifactId>redant-core</artifactId>\n                <version>${project-version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.redant</groupId>\n                <artifactId>redant-cluster</artifactId>\n                <version>${project-version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.redant</groupId>\n                <artifactId>redant-example</artifactId>\n                <version>${project-version}</version>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.0</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${java.encoding}</encoding>\n                </configuration>\n            </plugin>\n        </plugins>\n        <resources>\n            <resource>\n                <directory>src/main/java</directory>\n                <includes>\n                    <include>**/*.vm</include>\n                </includes>\n            </resource>\n        </resources>\n    </build>\n</project>\n"
  },
  {
    "path": "redant-cluster/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>redant</artifactId>\n        <groupId>com.redant</groupId>\n        <version>1.0.0-SNAPSHOT</version>\n    </parent>\n\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>redant-cluster</artifactId>\n    <packaging>jar</packaging>\n    <name>redant-cluster</name>\n    <version>1.0.0-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.redant</groupId>\n            <artifactId>redant-core</artifactId>\n            <version>1.0.0-SNAPSHOT</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.zookeeper</groupId>\n            <artifactId>zookeeper</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.curator</groupId>\n            <artifactId>curator-recipes</artifactId>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.apache.zookeeper</groupId>\n                    <artifactId>zookeeper</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/bootstrap/MasterServerBootstrap.java",
    "content": "package com.redant.cluster.bootstrap;\n\nimport com.redant.cluster.master.MasterServer;\nimport com.redant.cluster.zk.ZkConfig;\nimport com.redant.cluster.zk.ZkServer;\nimport com.redant.core.server.Server;\n\n/**\n * MasterServerBootstrap\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic class MasterServerBootstrap {\n\n    public static void main(String[] args) {\n        String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);\n\n        // 启动MasterServer\n        Server masterServer = new MasterServer(zkAddress);\n        masterServer.preStart();\n        masterServer.start();\n    }\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/bootstrap/SlaveServerBootstrap.java",
    "content": "package com.redant.cluster.bootstrap;\n\nimport com.redant.cluster.node.Node;\nimport com.redant.cluster.slave.SlaveServer;\nimport com.redant.cluster.zk.ZkConfig;\nimport com.redant.cluster.zk.ZkServer;\nimport com.redant.core.server.Server;\n\n/**\n * SlaveServerBootstrap\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic class SlaveServerBootstrap {\n\n    public static void main(String[] args) {\n        String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);\n        Node node = Node.getNodeWithArgs(args);\n\n        // 启动SlaveServer\n        Server slaveServer = new SlaveServer(zkAddress,node);\n        slaveServer.preStart();\n        slaveServer.start();\n    }\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/bootstrap/ZkBootstrap.java",
    "content": "package com.redant.cluster.bootstrap;\n\nimport com.redant.cluster.zk.ZkConfig;\nimport com.redant.cluster.zk.ZkServer;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n\n/**\n * ZK启动入口\n * @author houyi.wh\n * @date 2017/11/21\n **/\npublic class ZkBootstrap {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZkBootstrap.class);\n\n\n    public static void main(String[] args) {\n        try {\n            ZkServer zkServer = new ZkServer();\n            zkServer.startStandalone(ZkConfig.DEFAULT);\n        }catch (Exception e){\n            LOGGER.error(\"ZkBootstrap start failed,cause:\",e);\n            System.exit(1);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/master/MasterServer.java",
    "content": "package com.redant.cluster.master;\n\nimport com.redant.cluster.service.discover.ZkServiceDiscover;\nimport com.redant.core.common.constants.CommonConstants;\nimport com.redant.core.server.Server;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.*;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.handler.codec.http.HttpContentCompressor;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.stream.ChunkedWriteHandler;\nimport io.netty.util.concurrent.DefaultThreadFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n\n/**\n * MasterServer\n * @author houyi.wh\n * @date 2017/11/20\n */\npublic final class MasterServer implements Server {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(MasterServer.class);\n\n    private String zkAddress;\n\n    public MasterServer(String zkAddress){\n        this.zkAddress = zkAddress;\n    }\n\n    @Override\n    public void preStart() {\n        // 监听SlaveNode的变化\n        ZkServiceDiscover.getInstance(zkAddress).watch();\n    }\n\n    @Override\n    public void start() {\n        EventLoopGroup bossGroup = new NioEventLoopGroup(CommonConstants.BOSS_GROUP_SIZE, new DefaultThreadFactory(\"boss\", true));\n        EventLoopGroup workerGroup = new NioEventLoopGroup(CommonConstants.WORKER_GROUP_SIZE, new DefaultThreadFactory(\"worker\", true));\n        try {\n            long start = System.currentTimeMillis();\n            ServerBootstrap b = new ServerBootstrap();\n            b.option(ChannelOption.SO_BACKLOG, 1024);\n            b.group(bossGroup, workerGroup)\n             .channel(NioServerSocketChannel.class)\n//             .handler(new LoggingHandler(LogLevel.INFO))\n             .childHandler(new MasterServerInitializer(zkAddress));\n\n            ChannelFuture future = b.bind(CommonConstants.SERVER_PORT).sync();\n            long cost = System.currentTimeMillis()-start;\n            LOGGER.info(\"[MasterServer] Startup at port:{} cost:{}[ms]\",CommonConstants.SERVER_PORT,cost);\n\n            // 等待服务端Socket关闭\n            future.channel().closeFuture().sync();\n        } catch (InterruptedException e) {\n            LOGGER.error(\"InterruptedException:\",e);\n        } finally {\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n\n    private static class MasterServerInitializer extends ChannelInitializer<SocketChannel> {\n\n        private String zkAddress;\n\n        MasterServerInitializer(String zkAddress){\n            this.zkAddress = zkAddress;\n        }\n\n        @Override\n        public void initChannel(SocketChannel ch) {\n            ChannelPipeline pipeline = ch.pipeline();\n\n            pipeline.addLast(new HttpServerCodec());\n            addAdvanced(pipeline);\n            pipeline.addLast(new ChunkedWriteHandler());\n            pipeline.addLast(new MasterServerHandler(zkAddress));\n        }\n\n        /**\n         * 可以在 HttpServerCodec 之后添加这些 ChannelHandler 进行开启高级特性\n         */\n        private void addAdvanced(ChannelPipeline pipeline){\n            if(CommonConstants.USE_COMPRESS) {\n                // 对 http 响应结果开启 gizp 压缩\n                pipeline.addLast(new HttpContentCompressor());\n            }\n            if(CommonConstants.USE_AGGREGATOR) {\n                // 将多个HttpRequest组合成一个FullHttpRequest\n                pipeline.addLast(new HttpObjectAggregator(CommonConstants.MAX_CONTENT_LENGTH));\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/master/MasterServerBackendHandler.java",
    "content": "package com.redant.cluster.master;\n\nimport io.netty.channel.*;\n\n/**\n * @author houyi.wh\n * @date 2018/1/18\n **/\npublic class MasterServerBackendHandler extends ChannelInboundHandlerAdapter {\n\n    private final Channel inboundChannel;\n\n    public MasterServerBackendHandler(Channel inboundChannel){\n        this.inboundChannel = inboundChannel;\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) {\n        ctx.read();\n    }\n\n    @Override\n    public void channelRead(final ChannelHandlerContext ctx, Object msg) {\n        inboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() {\n            @Override\n            public void operationComplete(ChannelFuture future) {\n                if (future.isSuccess()) {\n                    ctx.channel().read();\n                } else {\n                    future.channel().close();\n                }\n            }\n        });\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) {\n        MasterServerHandler.closeOnFlush(inboundChannel);\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n        cause.printStackTrace();\n        MasterServerHandler.closeOnFlush(ctx.channel());\n    }\n\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/master/MasterServerHandler.java",
    "content": "package com.redant.cluster.master;\n\nimport com.redant.cluster.service.discover.ServiceDiscover;\nimport com.redant.cluster.service.discover.ZkServiceDiscover;\nimport com.redant.cluster.node.Node;\nimport com.redant.core.common.constants.CommonConstants;\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.*;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.codec.http.HttpClientCodec;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpRequest;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * MasterServerHandler is a http master which\n * will transfer http request to node server\n * @author houyi.wh\n * @date 2017/11/20\n */\npublic class MasterServerHandler extends ChannelInboundHandlerAdapter {\n\n    private final static Logger LOGGER = LoggerFactory.getLogger(MasterServerHandler.class);\n\n    private Node node;\n\n    /**\n     * Client--->Master Channel\n     */\n    private Channel inboundChannel;\n\n    /**\n     * Master--->Slave Channel\n     */\n    private Channel outboundChannel;\n\n    private ChannelFuture outboundConnectFuture;\n\n    private ServiceDiscover serviceDiscover;\n\n    public MasterServerHandler(String zkAddress){\n        this.serviceDiscover = ZkServiceDiscover.getInstance(zkAddress);\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        node = serviceDiscover.discover();\n        inboundChannel = ctx.channel();\n\n        // Start the connection attempt.\n        Bootstrap bootstrap = new Bootstrap();\n        bootstrap.group(inboundChannel.eventLoop())\n                .channel(ctx.channel().getClass())\n                // use master inboundChannel to write back the response get from remote server\n                .handler(new ChannelInitializer<SocketChannel>() {\n                    @Override\n                    public void initChannel(SocketChannel channel) throws Exception {\n                        ChannelPipeline pipeline = channel.pipeline();\n                        pipeline.addLast(new HttpClientCodec());\n                        pipeline.addLast(new HttpObjectAggregator(CommonConstants.MAX_CONTENT_LENGTH));\n                        pipeline.addLast(new MasterServerBackendHandler(inboundChannel));\n                    }\n                });\n        bootstrap.option(ChannelOption.AUTO_READ, false);\n        // connect to node\n        outboundConnectFuture = bootstrap.connect(node.getHost(), node.getPort());\n        // get outboundChannel to remote server\n        outboundChannel = outboundConnectFuture.channel();\n\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        outboundConnectFuture.addListener(new ChannelFutureListener() {\n            @Override\n            public void operationComplete(ChannelFuture future) {\n                if (future.isSuccess()) {\n                    // connection complete start to read first data\n                    inboundChannel.read();\n                    if(outboundChannel.isActive()) {\n                        if(msg instanceof HttpRequest){\n                            HttpRequest request = (HttpRequest)msg;\n                            if(request.uri().equals(CommonConstants.FAVICON_ICO)){\n                                return;\n                            }\n                            outboundChannel.writeAndFlush(request).addListener(new ChannelFutureListener() {\n                                @Override\n                                public void operationComplete(ChannelFuture future) {\n                                    if (future.isSuccess()) {\n                                        // was able to flush out data, start to read the next chunk\n                                        ctx.channel().read();\n                                    } else {\n                                        LOGGER.error(\"write to backend {}:{} error,cause:{}\", node.getHost(), node.getPort(),future.cause());\n                                        future.channel().close();\n                                    }\n                                }\n                            });\n                        }else{\n                            closeOnFlush(ctx.channel());\n                        }\n                    }\n                } else {\n                    LOGGER.error(\"connect to backend {}:{} error,cause:{}\", node.getHost(), node.getPort(),future.cause());\n                    // Close the connection if the connection attempt has failed.\n                    inboundChannel.close();\n                }\n            }\n        });\n\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) {\n        if (outboundChannel != null) {\n            closeOnFlush(outboundChannel);\n        }\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n        cause.printStackTrace();\n        closeOnFlush(ctx.channel());\n    }\n\n    /**\n     * Closes the specified channel after all queued write requests are flushed.\n     */\n    static void closeOnFlush(Channel channel) {\n        if (channel.isActive()) {\n            channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);\n        }\n    }\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/node/Node.java",
    "content": "package com.redant.cluster.node;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONObject;\nimport com.redant.core.common.util.GenericsUtil;\n\n/**\n * Node\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic class Node {\n\n    private static final String DEFAULT_HOST = GenericsUtil.getLocalIpV4();\n\n    private static final int DEFAULT_PORT = 8088;\n\n    public static final Node DEFAULT_PORT_NODE = new Node(DEFAULT_HOST,DEFAULT_PORT);\n\n    private String id;\n\n    private String host;\n\n    private int port;\n\n    public Node(int port){\n        this(DEFAULT_HOST,port);\n    }\n\n    public Node(String host, int port){\n        this(SecureUtil.md5(host+\"&\"+port),host,port);\n    }\n\n    public Node(String id, String host, int port){\n        this.id = id;\n        this.host = host;\n        this.port = port;\n    }\n\n    public static Node getNodeWithArgs(String[] args){\n        Node node = Node.DEFAULT_PORT_NODE;\n        if(args.length>1 && NumberUtil.isInteger(args[1])){\n            node = new Node(Integer.parseInt(args[1]));\n        }\n        return node;\n    }\n\n    /**\n     * 从JsonObject中解析出SlaveNode\n     * @param object 对象\n     * @return 节点\n     */\n    public static Node parse(JSONObject object){\n        if(object==null){\n            return null;\n        }\n        String host = object.getString(\"host\");\n        int port = object.getIntValue(\"port\");\n        String id = object.getString(\"id\");\n        return new Node(id,host,port);\n    }\n\n    public String getHost() {\n        return host;\n    }\n\n    public void setHost(String host) {\n        this.host = host;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    public void setPort(int port) {\n        this.port = port;\n    }\n\n    public String getId(){\n        return id;\n    }\n\n    @Override\n    public String toString() {\n        return JSON.toJSONString(this);\n    }\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/service/discover/ServiceDiscover.java",
    "content": "package com.redant.cluster.service.discover;\n\nimport com.redant.cluster.node.Node;\n\n/**\n * 服务发现-应用级别\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic interface ServiceDiscover {\n\n    /**\n     * 监听Slave节点\n     */\n    void watch();\n\n    /**\n     * 发现可用的Slave节点\n     * @return 可用节点\n     */\n    Node discover();\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/service/discover/ZkServiceDiscover.java",
    "content": "package com.redant.cluster.service.discover;\n\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONObject;\nimport com.redant.cluster.node.Node;\nimport com.redant.cluster.zk.ZkClient;\nimport com.redant.cluster.zk.ZkNode;\nimport io.netty.util.CharsetUtil;\nimport org.apache.curator.framework.CuratorFramework;\nimport org.apache.curator.framework.recipes.cache.ChildData;\nimport org.apache.curator.framework.recipes.cache.PathChildrenCache;\nimport org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;\nimport org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * 服务发现\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic class ZkServiceDiscover implements ServiceDiscover {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZkServiceDiscover.class);\n\n    private CuratorFramework client;\n\n    private Map<String,Node> nodeMap;\n\n    private Lock lock;\n\n    private int slaveIndex = 0;\n\n    private static ServiceDiscover discover;\n\n    private ZkServiceDiscover(){\n\n    }\n\n    private ZkServiceDiscover(String zkAddress){\n        client = ZkClient.getClient(zkAddress);\n        nodeMap = new HashMap<>();\n        lock = new ReentrantLock();\n    }\n\n    public static ServiceDiscover getInstance(String zkAddress){\n        if(discover==null) {\n            synchronized (ZkServiceDiscover.class) {\n                if(discover==null) {\n                    discover = new ZkServiceDiscover(zkAddress);\n                }\n            }\n        }\n        return discover;\n    }\n\n    @Override\n    public void watch() {\n        if(client==null){\n            throw new IllegalArgumentException(\"param illegal with client=null\");\n        }\n        initNodeOnFirst();\n        doWatch();\n    }\n\n    @Override\n    public Node discover() {\n        if(client==null){\n            throw new IllegalArgumentException(\"param illegal with client=null\");\n        }\n        lock.lock();\n        try {\n            if (nodeMap.size() == 0) {\n                LOGGER.error(\"No available Node!\");\n                return null;\n            }\n            Node[] nodes = new Node[]{};\n            nodes = nodeMap.values().toArray(nodes);\n            // 通过CAS循环获取下一个可用服务\n            if (slaveIndex>=nodes.length) {\n                slaveIndex = 0;\n            }\n            return nodes[slaveIndex++];\n        }finally {\n            lock.unlock();\n        }\n    }\n\n    private void initNodeOnFirst(){\n        try {\n            if(client.checkExists().forPath(ZkNode.ROOT_NODE_PATH)!=null){\n                List<String> children = client.getChildren().forPath(ZkNode.ROOT_NODE_PATH);\n                for(String child : children){\n                    String childPath = ZkNode.ROOT_NODE_PATH+\"/\"+child;\n                    byte[] data = client.getData().forPath(childPath);\n                    Node node = Node.parse(JSON.parseObject(data,JSONObject.class));\n                    if(node !=null){\n                        LOGGER.info(\"add slave={} to nodeMap when init\", node);\n                        nodeMap.put(node.getId(), node);\n                    }\n                }\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"initNodeOnFirst error cause:\",e);\n        }\n    }\n\n    private void doWatch(){\n        try {\n            PathChildrenCache watcher = new PathChildrenCache(\n                    client,\n                    ZkNode.ROOT_NODE_PATH,\n                    true\n            );\n            watcher.getListenable().addListener(new SlaveNodeWatcher());\n            watcher.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);\n        }catch(Exception e){\n            LOGGER.error(\"doWatch error cause:\",e);\n        }\n    }\n\n    private class SlaveNodeWatcher implements PathChildrenCacheListener {\n        @Override\n        public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {\n            ChildData data = event.getData();\n            if(data==null || data.getData()==null){\n                return;\n            }\n            Node node = Node.parse(JSON.parseObject(data.getData(),JSONObject.class));\n            if(node ==null){\n                LOGGER.error(\"get a null slave with eventType={},path={},data={}\",event.getType(),data.getPath(),data.getData());\n            }else {\n                switch (event.getType()) {\n                    case CHILD_ADDED:\n                        nodeMap.put(node.getId(), node);\n                        LOGGER.info(\"CHILD_ADDED with path={},data={},current slave size={}\", data.getPath(), new String(data.getData(),CharsetUtil.UTF_8), nodeMap.size());\n                        break;\n                    case CHILD_REMOVED:\n                        nodeMap.remove(node.getId());\n                        LOGGER.info(\"CHILD_REMOVED with path={},data={},current slave size={}\", data.getPath(), new String(data.getData(),CharsetUtil.UTF_8), nodeMap.size());\n                        break;\n                    case CHILD_UPDATED:\n                        nodeMap.replace(node.getId(), node);\n                        LOGGER.info(\"CHILD_UPDATED with path={},data={},current slave size={}\", data.getPath(), new String(data.getData(),CharsetUtil.UTF_8), nodeMap.size());\n                        break;\n                    default:\n                        break;\n                }\n            }\n        }\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/service/register/ServiceRegister.java",
    "content": "package com.redant.cluster.service.register;\n\nimport com.redant.cluster.node.Node;\n\n/**\n * 服务注册-应用级别\n * @author houyi.wh\n * @date 2017/11/21\n **/\npublic interface ServiceRegister {\n\n    /**\n     * 注册节点\n     * @param node 节点\n     */\n    void register(Node node);\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/service/register/ZkServiceRegister.java",
    "content": "package com.redant.cluster.service.register;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.redant.cluster.node.Node;\nimport com.redant.cluster.zk.ZkClient;\nimport com.redant.cluster.zk.ZkNode;\nimport org.apache.curator.framework.CuratorFramework;\nimport org.apache.zookeeper.CreateMode;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * @author houyi.wh\n * @date 2017/11/21\n **/\npublic class ZkServiceRegister implements ServiceRegister {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZkServiceRegister.class);\n\n    private CuratorFramework client;\n\n    private static ZkServiceRegister register;\n\n    private ZkServiceRegister(){\n\n    }\n\n    private ZkServiceRegister(String zkAddress){\n        client = ZkClient.getClient(zkAddress);\n    }\n\n    public static ServiceRegister getInstance(String zkAddress){\n        if(register==null) {\n            synchronized (ZkServiceRegister.class) {\n                if(register==null) {\n                    register = new ZkServiceRegister(zkAddress);\n                }\n            }\n        }\n        return register;\n    }\n\n    @Override\n    public void register(Node node) {\n        if(client==null || node ==null){\n            throw new IllegalArgumentException(String.format(\"param illegal with client={%s},slave={%s}\",client==null?null:client.toString(), node ==null?null: node.toString()));\n        }\n        try {\n            if(client.checkExists().forPath(ZkNode.SLAVE_NODE_PATH)==null) {\n                // 创建临时节点\n                client.create()\n                      .creatingParentsIfNeeded()\n                      .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)\n                      .forPath(ZkNode.SLAVE_NODE_PATH, StrUtil.utf8Bytes(node.toString()));\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"register slave error with slave={},cause:\", node,e);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/slave/SlaveServer.java",
    "content": "package com.redant.cluster.slave;\n\nimport com.redant.cluster.node.Node;\nimport com.redant.cluster.service.register.ZkServiceRegister;\nimport com.redant.core.common.constants.CommonConstants;\nimport com.redant.core.init.InitExecutor;\nimport com.redant.core.server.NettyHttpServerInitializer;\nimport com.redant.core.server.Server;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.util.concurrent.DefaultThreadFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n\n/**\n * SlaveServer\n * @author houyi.wh\n * @date 2017/11/20\n */\npublic final class SlaveServer implements Server {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SlaveServer.class);\n\n    private String zkAddress;\n    private Node node;\n\n    public SlaveServer(String zkAddress, Node node){\n        this.zkAddress = zkAddress;\n        this.node = node;\n    }\n\n    @Override\n    public void preStart() {\n        InitExecutor.init();\n        // 注册Slave到ZK\n        ZkServiceRegister.getInstance(zkAddress).register(node);\n    }\n\n    @Override\n    public void start() {\n        if(node ==null){\n            throw new IllegalArgumentException(\"slave is null\");\n        }\n        EventLoopGroup bossGroup = new NioEventLoopGroup(CommonConstants.BOSS_GROUP_SIZE, new DefaultThreadFactory(\"boss\", true));\n        EventLoopGroup workerGroup = new NioEventLoopGroup(CommonConstants.WORKER_GROUP_SIZE, new DefaultThreadFactory(\"worker\", true));\n        try {\n            long start = System.currentTimeMillis();\n            ServerBootstrap b = new ServerBootstrap();\n            b.option(ChannelOption.SO_BACKLOG, 1024);\n            b.group(bossGroup, workerGroup)\n             .channel(NioServerSocketChannel.class)\n//             .handler(new LoggingHandler(LogLevel.INFO))\n             .childHandler(new NettyHttpServerInitializer());\n\n            ChannelFuture future = b.bind(node.getPort()).sync();\n            long cost = System.currentTimeMillis()-start;\n            LOGGER.info(\"SlaveServer Startup at port:{} cost:{}[ms]\", node.getPort(),cost);\n\n            // 等待服务端Socket关闭\n            future.channel().closeFuture().sync();\n        } catch (InterruptedException e) {\n            LOGGER.error(\"InterruptedException:\",e);\n        } finally {\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n\n}"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/zk/ZkClient.java",
    "content": "package com.redant.cluster.zk;\n\nimport org.apache.curator.framework.CuratorFramework;\nimport org.apache.curator.framework.CuratorFrameworkFactory;\nimport org.apache.curator.retry.RetryNTimes;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * 操作ZK的客户端\n * @author houyi.wh\n * @date 2017/11/21\n **/\npublic class ZkClient {\n\n    /**\n     * 节点的session超时时间，当Slave服务停掉后，\n     * curator客户端需要等待该节点超时后才会触发CHILD_REMOVED事件\n     */\n    private static final int DEFAULT_SESSION_TIMEOUT_MS = 5000;\n\n    private static final int DEFAULT_CONNECTION_TIMEOUT_MS = 15000;\n\n    /**\n     * 操作ZK的客户端\n     */\n    private static Map<String,CuratorFramework> clients;\n\n    private static Lock lock;\n\n    static{\n        clients = new ConcurrentHashMap<>();\n        lock = new ReentrantLock();\n    }\n\n    /**\n     * 获取ZK客户端\n     * @param zkAddress zk服务端地址\n     * @return zk客户端\n     */\n    public static CuratorFramework getClient(String zkAddress){\n        if(zkAddress == null || zkAddress.trim().length() == 0){\n            return null;\n        }\n        CuratorFramework client = clients.get(zkAddress);\n        if(client==null){\n            lock.lock();\n            try {\n                if(!clients.containsKey(zkAddress)) {\n                    client = CuratorFrameworkFactory.newClient(\n                            zkAddress,\n                            DEFAULT_SESSION_TIMEOUT_MS,\n                            DEFAULT_CONNECTION_TIMEOUT_MS,\n                            new RetryNTimes(10, 5000)\n                    );\n                    client.start();\n                    clients.putIfAbsent(zkAddress,client);\n                }else{\n                    client = clients.get(zkAddress);\n                }\n            }finally {\n                lock.unlock();\n            }\n        }\n        return client;\n    }\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/zk/ZkConfig.java",
    "content": "package com.redant.cluster.zk;\n\n\nimport com.redant.core.common.util.GenericsUtil;\n\nimport java.util.Properties;\n\n/**\n * 启动zk所需要的配置信息\n * @author houyi.wh\n * @date 2017/11/21\n */\npublic class ZkConfig {\n\n    private interface ZkConstant {\n        int CLIENT_PORT = 2181;\n        String DATA_DIR = \"/Users/houyi/zookeeper/data\";\n        String DATA_LOG_DIR = \"/Users/houyi/zookeeper/log\";\n    }\n\n    /**\n     * 客户端连接的端口\n     */\n    private int clientPort;\n\n    private int tickTime = 2000;\n\n    private int initLimit = 10;\n\n    private int syncLimit = 5;\n\n    /**\n     * 数据存储目录，格式为：\n     * /home/zookeeper/1/data\n     */\n    private String dataDir;\n\n    /**\n     * 日志存储目录，格式为：\n     * /home/zookeeper/1/log\n     */\n    private String dataLogDir;\n\n    /**\n     * 客户端连接数上限\n     */\n    private int maxClientCnxns = 60;\n\n    public ZkConfig(int clientPort,String dataDir,String dataLogDir){\n        this.clientPort = clientPort;\n        this.dataDir = dataDir;\n        this.dataLogDir = dataLogDir;\n    }\n\n    public static ZkConfig DEFAULT = new ZkConfig(ZkConstant.CLIENT_PORT,ZkConstant.DATA_DIR,ZkConstant.DATA_LOG_DIR);\n\n\n    public String generateZkAddress(){\n        return GenericsUtil.getLocalIpV4()+\":\"+this.clientPort;\n    }\n\n    public Properties toProp(){\n        Properties properties = new Properties();\n        properties.put(\"clientPort\",this.clientPort);\n        properties.put(\"clientPortAddress\",GenericsUtil.getLocalIpV4());\n        properties.put(\"tickTime\",this.tickTime);\n        properties.put(\"initLimit\",this.initLimit);\n        properties.put(\"syncLimit\",this.syncLimit);\n        properties.put(\"dataDir\",this.dataDir);\n        properties.put(\"dataLogDir\",this.dataLogDir);\n        properties.put(\"maxClientCnxns\",this.maxClientCnxns);\n\n        return properties;\n    }\n\n\n\n\n\n    public int getClientPort() {\n        return clientPort;\n    }\n\n    public void setClientPort(int clientPort) {\n        this.clientPort = clientPort;\n    }\n\n    public int getTickTime() {\n        return tickTime;\n    }\n\n    public void setTickTime(int tickTime) {\n        this.tickTime = tickTime;\n    }\n\n    public int getInitLimit() {\n        return initLimit;\n    }\n\n    public void setInitLimit(int initLimit) {\n        this.initLimit = initLimit;\n    }\n\n    public int getSyncLimit() {\n        return syncLimit;\n    }\n\n    public void setSyncLimit(int syncLimit) {\n        this.syncLimit = syncLimit;\n    }\n\n    public String getDataDir() {\n        return dataDir;\n    }\n\n    public void setDataDir(String dataDir) {\n        this.dataDir = dataDir;\n    }\n\n    public String getDataLogDir() {\n        return dataLogDir;\n    }\n\n    public void setDataLogDir(String dataLogDir) {\n        this.dataLogDir = dataLogDir;\n    }\n\n    public int getMaxClientCnxns() {\n        return maxClientCnxns;\n    }\n\n    public void setMaxClientCnxns(int maxClientCnxns) {\n        this.maxClientCnxns = maxClientCnxns;\n    }\n\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/zk/ZkNode.java",
    "content": "package com.redant.cluster.zk;\n\n/**\n * @author houyi.wh\n * @date 2017/11/21\n **/\npublic class ZkNode {\n\n    /**\n     * 根节点\n     */\n    public static final String ROOT_NODE_PATH = \"/redant\";\n\n    /**\n     * SlaveNode注册的节点\n     */\n    public static final String SLAVE_NODE_PATH = ROOT_NODE_PATH+\"/slave\";\n\n\n\n}\n"
  },
  {
    "path": "redant-cluster/src/main/java/com/redant/cluster/zk/ZkServer.java",
    "content": "package com.redant.cluster.zk;\n\nimport cn.hutool.core.util.StrUtil;\nimport org.apache.zookeeper.server.ServerConfig;\nimport org.apache.zookeeper.server.ZooKeeperServerMain;\nimport org.apache.zookeeper.server.quorum.QuorumPeerConfig;\nimport org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;\nimport org.apache.zookeeper.server.quorum.QuorumPeerMain;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.util.Properties;\n\n/**\n * ZooKeeper服务端\n * @author houyi.wh\n * @date 2017/11/21\n *\n */\npublic class ZkServer {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(ZkServer.class);\n\n\tpublic static String getZkAddressArgs(String[] args, ZkConfig zkConfig){\n\t\tString zkAddress = ZkServer.getZkAddress(zkConfig);\n\t\tif(args.length>0 && StrUtil.isNotBlank(args[0])){\n\t\t\tLOGGER.info(\"zkAddress is read from args\");\n\t\t\tzkAddress = args[0];\n\t\t}\n\t\tif(StrUtil.isBlank(zkAddress)){\n\t\t\tSystem.exit(1);\n\t\t}\n\t\treturn zkAddress;\n\t}\n\n\tpublic static String getZkAddress(ZkConfig zkConfig){\n\t\treturn zkConfig!=null ? zkConfig.generateZkAddress() : null;\n\t}\n\n\t/**\n\t * 通过官方的ZooKeeperServerMain启动类启动单机模式\n\t * @param zkConfig 配置对象\n\t * @throws ConfigException 配置异常\n\t * @throws IOException IO异常\n\t */\n\tpublic void startStandalone(ZkConfig zkConfig) throws ConfigException, IOException {\n\t\tProperties zkProp = zkConfig.toProp();\n\n\t\tQuorumPeerConfig config = new QuorumPeerConfig();\n\t\tconfig.parseProperties(zkProp);\n\n\t\tServerConfig serverConfig = new ServerConfig();\n\t\tserverConfig.readFrom(config);\n\n\t\tZooKeeperServerMain zkServer = new ZooKeeperServerMain();\n\t\tzkServer.runFromConfig(serverConfig);\n\t}\n\n\t/**\n\t * 通过官方的QuorumPeerMain启动类启动真集群模式\n\t * 会执行quorumPeer.join();\n\t * 需要在不同的服务器上执行\n\t * @param zkConfig 配置对象\n\t * @throws ConfigException 配置异常\n\t * @throws IOException IO异常\n\t */\n\tpublic void startCluster(ZkConfig zkConfig) throws ConfigException, IOException {\n\t\tProperties zkProp = zkConfig.toProp();\n\t\tQuorumPeerConfig config = new QuorumPeerConfig();\n\t\tconfig.parseProperties(zkProp);\n\n\t\tQuorumPeerMain main = new QuorumPeerMain();\n\t\tmain.runFromConfig(config);\n\t}\n\n\n}"
  },
  {
    "path": "redant-core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>redant</artifactId>\n        <groupId>com.redant</groupId>\n        <version>1.0.0-SNAPSHOT</version>\n    </parent>\n\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>redant-core</artifactId>\n    <packaging>jar</packaging>\n    <name>redant-core</name>\n    <version>1.0.0-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>commons-beanutils</groupId>\n            <artifactId>commons-beanutils</artifactId>\n        </dependency>\n        <!-- 日志文件管理包 -->\n        <!-- 格式化对象，方便输出日志 -->\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>jcl-over-slf4j</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-classic</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>fastjson</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-all</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cglib</groupId>\n            <artifactId>cglib</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/ServerBootstrap.java",
    "content": "package com.redant.core;\n\nimport com.redant.core.server.NettyHttpServer;\nimport com.redant.core.server.Server;\n\n/**\n * 服务端启动入口\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic final class ServerBootstrap {\n\n    public static void main(String[] args) {\n        Server nettyServer = new NettyHttpServer();\n        // 各种初始化工作\n        nettyServer.preStart();\n        // 启动服务器\n        nettyServer.start();\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/anno/Order.java",
    "content": "package com.redant.core.anno;\n\nimport java.lang.annotation.*;\n\n/**\n * 排序规则，升序排序\n * @author houyi.wh\n * @date 2019-01-14\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE})\n@Documented\npublic @interface Order {\n\n    /**\n     * 最低优先级\n     */\n    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;\n    /**\n     * 最高优先级\n     */\n    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;\n\n    /**\n     * The order value. Lowest precedence by default.\n     *\n     * @return the order value\n     */\n    int value() default LOWEST_PRECEDENCE;\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/aware/Aware.java",
    "content": "package com.redant.core.aware;\n\n/**\n * @author houyi.wh\n * @date 2019-01-14\n */\npublic interface Aware {\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/aware/BeanContextAware.java",
    "content": "package com.redant.core.aware;\n\nimport com.redant.core.bean.context.BeanContext;\n\n/**\n * @author houyi.wh\n * @date 2019-01-14\n */\npublic interface BeanContextAware extends Aware{\n\n    /**\n     * 设置BeanContext\n     * @param beanContext BeanContext对象\n     */\n    void setBeanContext(BeanContext beanContext);\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/bean/BaseBean.java",
    "content": "package com.redant.core.bean;\n\n\nimport com.alibaba.fastjson.JSON;\n\nimport java.io.Serializable;\n\n\n/**\n * BaseBean 所有bean都继承该类\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class BaseBean implements Serializable {\n    private static final long serialVersionUID = -4976516540408695147L;\n\n    public BaseBean() {\n    }\n\n    @Override\n    public String toString() {\n        return JSON.toJSONString(this);\n    }\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/bean/annotation/Autowired.java",
    "content": "package com.redant.core.bean.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ElementType.FIELD, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Autowired {\n\n    String name() default \"\";\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/bean/annotation/Bean.java",
    "content": "package com.redant.core.bean.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Bean {\n\n    String name() default \"\";\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/bean/context/BeanContext.java",
    "content": "package com.redant.core.bean.context;\n\n/**\n * Bean上下文\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic interface BeanContext {\n\n    /**\n     * 获得Bean\n     * @param name Bean的名称\n     * @return Bean\n     */\n    Object getBean(String name);\n\n    /**\n     * 获得Bean\n     * @param name Bean的名称\n     * @param clazz Bean的类\n     * @param <T> 泛型\n     * @return Bean\n     */\n    <T> T getBean(String name,Class<T> clazz);\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/bean/context/DefaultBeanContext.java",
    "content": "package com.redant.core.bean.context;\n\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.ClassScaner;\nimport cn.hutool.core.util.StrUtil;\nimport com.redant.core.aware.BeanContextAware;\nimport com.redant.core.bean.annotation.Autowired;\nimport com.redant.core.bean.annotation.Bean;\nimport com.redant.core.common.constants.CommonConstants;\nimport com.redant.core.init.InitFunc;\nimport com.redant.core.init.InitOrder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.beans.Introspector;\nimport java.beans.PropertyDescriptor;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * DefaultBeanContext\n *\n * @author houyi.wh\n * @date 2017-10-20\n */\n@InitOrder(1)\npublic class DefaultBeanContext implements BeanContext, InitFunc {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultBeanContext.class);\n\n    /**\n     * 保存所有的bean，初始化过一次后不会改变\n     */\n    private static Map<String, Object> beanMap;\n\n    /**\n     * bean加载完毕的标志\n     */\n    private static volatile boolean inited;\n\n    private static final class DefaultBeanContextHolder {\n        private static DefaultBeanContext context = new DefaultBeanContext();\n    }\n\n    private DefaultBeanContext() {\n    }\n\n    public static BeanContext getInstance() {\n        return DefaultBeanContextHolder.context;\n    }\n\n    @Override\n    public void init() {\n        // 初始化\n        doInit();\n    }\n\n    /**\n     * 根据bean的name获取具体的bean对象\n     */\n    @Override\n    public Object getBean(String name) {\n        return inited() ? beanMap.get(name) : null;\n    }\n\n    /**\n     * 根据bean的name获取具体的bean对象\n     */\n    @Override\n    public <T> T getBean(String name, Class<T> clazz) {\n        Object bean = getBean(name);\n        return bean == null ? null : (T) bean;\n    }\n\n    /**\n     * bean是否加载完毕\n     */\n    private boolean inited() {\n        while (!inited) {\n            doInit();\n            try {\n                Thread.sleep(500);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n        return inited;\n    }\n\n    /**\n     * 执行初始化工作\n     */\n    private void doInit() {\n        // 初始化时需要同步\n        synchronized (DefaultBeanContext.class) {\n            if (!inited) {\n                LOGGER.info(\"[DefaultBeanContext] doInit\");\n                // 初始化bean\n                initBean();\n                // 对象注入\n                injectAnnotation();\n                // 注入BeanContext到BeanContextAware\n                processBeanContextAware();\n                inited = true;\n                LOGGER.info(\"[DefaultBeanContext] doInit success!\");\n            }\n        }\n    }\n\n    /**\n     * 初始化Bean\n     */\n    private void initBean() {\n        LOGGER.info(\"[DefaultBeanContext] start initBean\");\n        try {\n            /*\n             * 扫描指定package下指定的类，并返回set\n             */\n            Set<Class<?>> classSet = ClassScaner.scanPackageByAnnotation(CommonConstants.BEAN_SCAN_PACKAGE, Bean.class);\n            beanMap = new LinkedHashMap<>(classSet.size() + 1);\n            if (CollectionUtil.isNotEmpty(classSet)) {\n                /*\n                 * 遍历所有类，找出有beanClass注解的类，并封装到linkedHashMap里\n                 */\n                for (Class<?> cls : classSet) {\n                    Bean bean = cls.getAnnotation(Bean.class);\n                    if (bean != null) {\n                        String beanName = StrUtil.isNotBlank(bean.name()) ? bean.name() : cls.getName();\n                        if (beanMap.containsKey(beanName)) {\n                            LOGGER.warn(\"[DefaultBeanContext] duplicate bean with name={}\", beanName);\n                            continue;\n                        }\n                        beanMap.put(beanName, cls.newInstance());\n                    }\n                }\n                LOGGER.info(\"[DefaultBeanContext] initBean success!\");\n            } else {\n                LOGGER.warn(\"[DefaultBeanContext] no bean classes scanned!\");\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"[DefaultBeanContext] initBean error,cause:{}\", e.getMessage(), e);\n        }\n    }\n\n\n    /**\n     * 注解处理器\n     * 如果注解Autowired配置了name属性，则根据name所指定的名称获取要注入的实例引用，\n     * 否则根据属性所属类型来扫描配置文件获取要注入的实例引用\n     */\n    private void injectAnnotation() {\n        LOGGER.info(\"[DefaultBeanContext] start injectAnnotation\");\n        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {\n            Object bean = entry.getValue();\n            if (bean != null) {\n                propertyAnnotation(bean);\n                fieldAnnotation(bean);\n            }\n        }\n        LOGGER.info(\"[DefaultBeanContext] injectAnnotation success!\");\n    }\n\n\n    /**\n     * 处理BeanContextAware\n     * 让那些实现了BeanContextAware接口的类能注入BeanContext\n     */\n    private void processBeanContextAware() {\n        LOGGER.info(\"[DefaultBeanContext] start processBeanContextAware\");\n        try {\n            /*\n             * 扫描指定package下指定的类，并返回set\n             */\n            Set<Class<?>> classSet = ClassScaner.scanPackageBySuper(CommonConstants.BEAN_SCAN_PACKAGE, BeanContextAware.class);\n            if (CollectionUtil.isNotEmpty(classSet)) {\n                for (Class<?> cls : classSet) {\n                    // 如果cls是BeanContextAware的实现类\n                    if (!cls.isInterface() && BeanContextAware.class.isAssignableFrom(cls)) {\n                        Constructor<?> constructor = cls.getDeclaredConstructor();\n                        constructor.setAccessible(true);\n                        BeanContextAware aware = (BeanContextAware) constructor.newInstance();\n                        aware.setBeanContext(getInstance());\n                    }\n                }\n            }\n            LOGGER.info(\"[DefaultBeanContext] processBeanContextAware success!\");\n        } catch (Exception e) {\n            LOGGER.error(\"[DefaultBeanContext] processBeanContextAware error,cause:{}\", e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 处理在set方法加入的注解\n     *\n     * @param bean 处理的bean\n     */\n    private void propertyAnnotation(Object bean) {\n        LOGGER.info(\"[DefaultBeanContext] start propertyAnnotation\");\n        try {\n            // 获取其属性的描述\n            PropertyDescriptor[] descriptors = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();\n            for (PropertyDescriptor descriptor : descriptors) {\n                // 获取所有set方法\n                Method setter = descriptor.getWriteMethod();\n                // 判断set方法是否定义了注解\n                if (setter != null && setter.isAnnotationPresent(Autowired.class)) {\n                    // 获取当前注解，并判断name属性是否为空\n                    Autowired resource = setter.getAnnotation(Autowired.class);\n                    String name;\n                    Object value = null;\n                    if (StrUtil.isNotBlank(resource.name())) {\n                        // 获取注解的name属性的内容\n                        name = resource.name();\n                        value = beanMap.get(name);\n                    } else { // 如果当前注解没有指定name属性,则根据类型进行匹配\n                        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {\n                            // 判断当前属性所属的类型是否在beanHolderMap中存在\n                            if (descriptor.getPropertyType().isAssignableFrom(entry.getValue().getClass())) {\n                                // 获取类型匹配的实例对象\n                                value = entry.getValue();\n                                break;\n                            }\n                        }\n                    }\n                    // 允许访问private方法\n                    setter.setAccessible(true);\n                    // 把引用对象注入属性\n                    setter.invoke(bean, value);\n                }\n            }\n            LOGGER.info(\"[DefaultBeanContext] propertyAnnotation success!\");\n        } catch (Exception e) {\n            LOGGER.info(\"[DefaultBeanContext] propertyAnnotation error,cause:{}\", e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 处理在字段上的注解\n     *\n     * @param bean 处理的bean\n     */\n    private void fieldAnnotation(Object bean) {\n        LOGGER.info(\"[DefaultBeanContext] start fieldAnnotation\");\n        try {\n            // 获取其全部的字段描述\n            Field[] fields = bean.getClass().getDeclaredFields();\n            for (Field field : fields) {\n                if (field != null && field.isAnnotationPresent(Autowired.class)) {\n                    Autowired resource = field.getAnnotation(Autowired.class);\n                    String name;\n                    Object value = null;\n                    if (StrUtil.isNotBlank(resource.name())) {\n                        name = resource.name();\n                        value = beanMap.get(name);\n                    } else {\n                        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {\n                            // 判断当前属性所属的类型是否在配置文件中存在\n                            if (field.getType().isAssignableFrom(entry.getValue().getClass())) {\n                                // 获取类型匹配的实例对象\n                                value = entry.getValue();\n                                break;\n                            }\n                        }\n                    }\n                    // 允许访问private字段\n                    field.setAccessible(true);\n                    // 把引用对象注入属性\n                    field.set(bean, value);\n                }\n            }\n            LOGGER.info(\"[DefaultBeanContext] fieldAnnotation success!\");\n        } catch (Exception e) {\n            LOGGER.info(\"[DefaultBeanContext] fieldAnnotation error,cause:{}\", e.getMessage(), e);\n        }\n    }\n\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/constants/CommonConstants.java",
    "content": "package com.redant.core.common.constants;\n\nimport com.redant.core.common.util.PropertiesUtil;\n\n/**\n * 公共常量\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class CommonConstants {\n\n\n    private static final String REDANT_PROPERTIES_PATH = \"/redant.properties\";\n\n    private static PropertiesUtil propertiesUtil = PropertiesUtil.getInstance(REDANT_PROPERTIES_PATH);\n\n    /**\n     * 服务端口号\n     */\n    public static final int SERVER_PORT = propertiesUtil.getInt(\"netty.server.port\",8888);\n\n    /**\n     * BossGroup Size\n     * 先从启动参数中获取：-Dnetty.server.bossGroup.size=2\n     * 如果获取不到从配置文件中获取\n     * 如果再获取不到则取默认值\n     */\n    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);\n\n    /**\n     * WorkerGroup Size\n     * 先从启动参数中获取：-Dnetty.server.workerGroup.size=4\n     * 如果获取不到从配置文件中获取\n     * 如果再获取不到则取默认值\n     */\n    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);\n\n    /**\n     * 能处理的最大数据的字节数\n     */\n    public static final int MAX_CONTENT_LENGTH = propertiesUtil.getInt(\"netty.maxContentLength\",10485760);\n\n    /**\n     * 是否开启ssl\n     */\n    public static final boolean USE_SSL = propertiesUtil.getBoolean(\"netty.server.use.ssl\");\n\n    /**\n     * 是否开启压缩\n     */\n    public static final boolean USE_COMPRESS = propertiesUtil.getBoolean(\"netty.server.use.compress\");\n\n    /**\n     * 是否开启http对象聚合\n     */\n    public static final boolean USE_AGGREGATOR = propertiesUtil.getBoolean(\"netty.server.use.aggregator\");\n\n    /**\n     * KeyStore path\n     */\n    public static final String KEY_STORE_PATH = propertiesUtil.getString(\"ssl.keyStore.path\");\n\n    /**\n     * KeyStore password\n     */\n    public static final String KEY_STORE_PASSWORD = propertiesUtil.getString(\"ssl.keyStore.password\");\n\n    /**\n     * 扫描bean的包路径\n     */\n    public static final String BEAN_SCAN_PACKAGE = propertiesUtil.getString(\"bean.scan.package\");\n\n    /**\n     * 扫描interceptor的包路径\n     */\n    public static final String INTERCEPTOR_SCAN_PACKAGE = propertiesUtil.getString(\"interceptor.scan.package\");\n\n    /**\n     * 服务端出错时的错误描述\n     */\n    public static final String SERVER_INTERNAL_ERROR_DESC = propertiesUtil.getString(\"server.internal.error.desc\");\n\n    public static final String FAVICON_ICO = \"/favicon.ico\";\n\n    public static final String CONNECTION_KEEP_ALIVE = \"keep-alive\";\n\n    public static final String CONNECTION_CLOSE = \"close\";\n\n    /**\n     * 是否异步处理业务逻辑\n     */\n    public static final boolean ASYNC_EXECUTE_EVENT = propertiesUtil.getBoolean(\"async.execute.event\");\n\n    /**\n     * 业务线程池核心线程数\n     */\n    public static final int EVENT_EXECUTOR_POOL_CORE_SIZE = propertiesUtil.getInt(\"async.executor.pool.core.size\",10);\n\n    /**\n     * 业务线程池最大线程数\n     */\n    public static final int EVENT_EXECUTOR_POOL_MAX_SIZE = propertiesUtil.getInt(\"async.executor.pool.max.size\",20);\n\n    /**\n     * 业务线程池临时线程存活时间，单位：s\n     */\n    public static final int EVENT_EXECUTOR_POOL_KEEP_ALIVE_SECONDS = propertiesUtil.getInt(\"async.executor.pool.keep.alive.seconds\",10);\n\n    /**\n     * 业务线程池阻塞队列大学\n     */\n    public static final int EVENT_EXECUTOR_POOL_BLOCKING_QUEUE_SIZE = propertiesUtil.getInt(\"async.executor.pool.blocking.queue.size\",10);\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/enums/ContentType.java",
    "content": "package com.redant.core.common.enums;\n\npublic enum ContentType {\n\n    APPLICATION_ATOM_XML(\"application/atom+xml\"),\n    APPLICATION_FORM_URLENCODED(\"application/x-www-form-urlencoded\"),\n    APPLICATION_JSON(\"application/json\"),\n    APPLICATION_OCTET_STREAM(\"application/octet-stream\"),\n    APPLICATION_SVG_XML(\"application/svg+xml\"),\n    APPLICATION_XHTML_XML(\"application/xhtml+xml\"),\n    APPLICATION_XML(\"application/xml\")\n    ;\n\n    private String content;\n\n    ContentType(String content){\n        this.content = content;\n    }\n    \n    @Override\n    public String toString() {\n        return content;\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/enums/RequestMethod.java",
    "content": "package com.redant.core.common.enums;\n\nimport io.netty.handler.codec.http.HttpMethod;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic enum RequestMethod {\n    /**\n     * GET\n     */\n    GET(HttpMethod.GET),\n    /**\n     * HEAD\n     */\n    HEAD(HttpMethod.HEAD),\n    /**\n     * POST\n     */\n    POST(HttpMethod.POST),\n    /**\n     * PUT\n     */\n    PUT(HttpMethod.PUT),\n    /**\n     * PATCH\n     */\n    PATCH(HttpMethod.PATCH),\n    /**\n     * DELETE\n     */\n    DELETE(HttpMethod.DELETE),\n    /**\n     * OPTIONS\n     */\n    OPTIONS(HttpMethod.OPTIONS),\n    /**\n     * TRACE\n     */\n    TRACE(HttpMethod.TRACE);\n\n    HttpMethod httpMethod;\n\n    RequestMethod(HttpMethod httpMethod) {\n        this.httpMethod = httpMethod;\n    }\n\n    public static HttpMethod getHttpMethod(RequestMethod requestMethod){\n        for(RequestMethod method : values()){\n            if(requestMethod==method){\n                return method.httpMethod;\n            }\n        }\n        return null;\n    }\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/exception/InvalidSessionException.java",
    "content": "package com.redant.core.common.exception;\n\n/**\n * 非法session\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class InvalidSessionException extends RuntimeException{\n\tprivate static final long serialVersionUID = 1L;\n\n\tpublic InvalidSessionException(String s) {\n        super(s);\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/exception/InvocationException.java",
    "content": "package com.redant.core.common.exception;\n\npublic class InvocationException extends Exception{\n\tprivate static final long serialVersionUID = 1L;\n\n    public InvocationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/exception/ValidationException.java",
    "content": "package com.redant.core.common.exception;\n\n/**\n * ValidationException\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class ValidationException extends RuntimeException{\n\tprivate static final long serialVersionUID = 1L;\n\n\tpublic ValidationException(String s) {\n        super(s);\n    }\n\n\tpublic ValidationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/html/DefaultHtmlMaker.java",
    "content": "package com.redant.core.common.html;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.redant.core.common.view.HtmlKeyHolder;\n\nimport java.util.Map;\n\n/**\n * 默认的HtmlMaker，只处理字符串\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic class DefaultHtmlMaker implements HtmlMaker {\n\n    @Override\n    public String make(String htmlTemplate, Map<String, Object> contentMap) {\n        String html = htmlTemplate;\n        if(CollectionUtil.isNotEmpty(contentMap)){\n            for(Map.Entry<String,Object> entry : contentMap.entrySet()){\n                String key = entry.getKey();\n                Object val = entry.getValue();\n                if(val instanceof String){\n                    html = html.replaceAll(HtmlKeyHolder.START_ESCAPE+key+HtmlKeyHolder.END,val.toString());\n                }\n            }\n        }\n        return html;\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/html/HtmlMaker.java",
    "content": "package com.redant.core.common.html;\n\nimport java.util.Map;\n\n/**\n * html生成器\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic interface HtmlMaker {\n\n    /**\n     * 根据html模板生成html内容\n     * @param htmlTemplate html模板\n     * @param contentMap 参数\n     * @return html内容\n     */\n    String make(String htmlTemplate,Map<String, Object> contentMap);\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/html/HtmlMakerEnum.java",
    "content": "package com.redant.core.common.html;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic enum HtmlMakerEnum {\n    /**\n     * 字符串\n     */\n    STRING\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/html/HtmlMakerFactory.java",
    "content": "package com.redant.core.common.html;\n\nimport cn.hutool.core.util.ReflectUtil;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic class HtmlMakerFactory {\n\n    private volatile static HtmlMakerFactory factory;\n\n    private Map<HtmlMakerEnum,HtmlMaker> htmlMakerMap;\n\n    private final Lock lock;\n\n    private HtmlMakerFactory(){\n        htmlMakerMap = new ConcurrentHashMap<>();\n        lock = new ReentrantLock();\n    }\n\n    /**\n     * 获取工厂实例\n     */\n    public static HtmlMakerFactory instance(){\n        if(factory==null){\n            synchronized (HtmlMakerFactory.class) {\n                if (factory==null) {\n                    factory = new HtmlMakerFactory();\n                }\n            }\n        }\n        return factory;\n    }\n\n    /**\n     * 创建HtmlMaker实例\n     */\n    public HtmlMaker build(HtmlMakerEnum type,Class<? extends HtmlMaker> clazz){\n        if(type==null){\n            return null;\n        }else{\n            HtmlMaker htmlMaker = htmlMakerMap.get(type);\n            if(htmlMaker==null){\n                lock.lock();\n                try {\n                    if(!htmlMakerMap.containsKey(type)) {\n                        htmlMaker = ReflectUtil.newInstance(clazz);\n                        htmlMakerMap.putIfAbsent(type,htmlMaker);\n                    }else{\n                        htmlMaker = htmlMakerMap.get(type);\n                    }\n                }finally {\n                    lock.unlock();\n                }\n            }\n            return htmlMaker;\n        }\n    }\n\n    public static void main(String[] args) {\n        int loopTimes = 200;\n\n        class Runner implements Runnable{\n\n            private Logger logger = LoggerFactory.getLogger(Runner.class);\n\n            @Override\n            public void run() {\n                HtmlMakerFactory factory = HtmlMakerFactory.instance();\n                logger.info(\"factory={},currentThread={}\",(factory!=null?factory.getClass().getName():\"null\"),Thread.currentThread().getName());\n            }\n        }\n\n        for(int i=0;i<loopTimes;i++){\n            new Thread(new Runner()).start();\n        }\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/util/GenericsUtil.java",
    "content": "package com.redant.core.common.util;\n\nimport cn.hutool.core.util.NetUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.redant.core.common.exception.ValidationException;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.List;\n\n/**\n * GenericsUtil\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class GenericsUtil {\n\t\n\t/**\n\t * 通过反射获得Class声明的范型Class.\n\t * 通过反射,获得方法输入参数第index个输入参数的所有泛型参数的实际类型. 如: public void add(Map<String, Buyer> maps, List<String> names){}\n\t * @param method 方法\n\t * @param index 第几个输入参数\n\t * @return 输入参数的泛型参数的实际类型集合, 如果没有实现ParameterizedType接口，即不支持泛型，所以直接返回空集合\n\t */\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic static List<Class> getMethodGenericParameterTypes(Method method, int index) {\n\t\tList<Class> results = new ArrayList<Class>();\n\t\tType[] genericParameterTypes = method.getGenericParameterTypes();\n\t\tif (index >= genericParameterTypes.length || index < 0) {\n\t\t\tthrow new RuntimeException(\"你输入的索引\" + (index < 0 ? \"不能小于0\" : \"超出了参数的总数\"));\n\t\t}\n\t\tType genericParameterType = genericParameterTypes[index];\n\t\tif (genericParameterType instanceof ParameterizedType) {\n\t\t\tParameterizedType aType = (ParameterizedType) genericParameterType;\n\t\t\tType[] parameterArgTypes = aType.getActualTypeArguments();\n\t\t\tfor (Type parameterArgType : parameterArgTypes) {\n\t\t\t\tClass parameterArgClass = (Class) parameterArgType;\n\t\t\t\tresults.add(parameterArgClass);\n\t\t\t}\n\t\t\treturn results;\n\t\t}\n\t\treturn results;\n\t}\n\n\n\t/**\n\t * 断言非空\n\t * @param dataName 参数\n\t * @param values 值\n\t */\n\tpublic static void checkNull(String dataName, Object... values){\n\t\tif(values == null){\n\t\t\tthrow new ValidationException(\"[\"+ dataName + \"] cannot be null\");\n\t\t}\n\t\tfor (Object value : values) {\n\t\t\tif (value == null) {\n\t\t\t\tthrow new ValidationException(\"[\" + dataName + \"] cannot be null\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 断言非空\n\t * @param dataName 参数\n\t * @param values 值\n\t */\n\tpublic static void checkBlank(String dataName, Object... values){\n\t\tif(values == null){\n\t\t\tthrow new ValidationException(\"[\"+ dataName + \"] cannot be null\");\n\t\t}\n\t\tfor (Object value : values) {\n\t\t\tif (value == null || StrUtil.isBlank(value.toString())) {\n\t\t\t\tthrow new ValidationException(\"[\" + dataName + \"] cannot be blank\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 获取ipV4\n\t * @return ipV4\n\t */\n\tpublic static String getLocalIpV4(){\n\t\tLinkedHashSet<String> ipV4Set = NetUtil.localIpv4s();\n\t\treturn ipV4Set.isEmpty()?\"\":ipV4Set.toArray()[0].toString();\n\t}\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/util/HtmlContentUtil.java",
    "content": "package com.redant.core.common.util;\n\nimport com.redant.core.common.html.HtmlMaker;\nimport com.redant.core.common.constants.CommonConstants;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Map;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic class HtmlContentUtil {\n\n    private final static Logger logger = LoggerFactory.getLogger(HtmlContentUtil.class);\n\n    private HtmlContentUtil(){\n\n    }\n\n    /**\n     * 获取页面内容\n     * @param htmlMaker htmlMaker\n     * @param htmlTemplate html模板\n     * @param contentMap 参数\n     * @return 页面内容\n     */\n    public static String getPageContent(HtmlMaker htmlMaker, String htmlTemplate, Map<String, Object> contentMap){\n        try {\n            return htmlMaker.make(htmlTemplate,contentMap);\n        } catch (Exception e) {\n            logger.error(\"getPageContent Error,cause:\",e);\n        }\n        return CommonConstants.SERVER_INTERNAL_ERROR_DESC;\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/util/HttpRenderUtil.java",
    "content": "package com.redant.core.common.util;\n\nimport com.alibaba.fastjson.JSONObject;\nimport com.redant.core.common.html.DefaultHtmlMaker;\nimport com.redant.core.common.html.HtmlMaker;\nimport com.redant.core.common.html.HtmlMakerEnum;\nimport com.redant.core.common.html.HtmlMakerFactory;\nimport com.redant.core.common.view.Page404;\nimport com.redant.core.render.RenderType;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.*;\nimport io.netty.util.CharsetUtil;\n\n/**\n * HttpRenderUtil\n *\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class HttpRenderUtil {\n\n    public static final String EMPTY_CONTENT = \"\";\n\n    private HttpRenderUtil() {\n\n    }\n\n    /**\n     * response输出\n     *\n     * @param content    内容\n     * @param renderType 返回类型\n     * @return 响应对象\n     */\n    public static FullHttpResponse render(Object content, RenderType renderType) {\n        byte[] bytes = HttpRenderUtil.getBytes(content);\n        ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);\n        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);\n        RenderType type = renderType != null ? renderType : RenderType.JSON;\n        response.headers().add(HttpHeaderNames.CONTENT_TYPE, type.getContentType());\n        response.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(byteBuf.readableBytes()));\n        return response;\n    }\n\n    /**\n     * 404NotFoundResponse\n     *\n     * @return 响应对象\n     */\n    public static FullHttpResponse getNotFoundResponse() {\n        HtmlMaker htmlMaker = HtmlMakerFactory.instance().build(HtmlMakerEnum.STRING, DefaultHtmlMaker.class);\n        String htmlTpl = Page404.HTML;\n        String content = HtmlContentUtil.getPageContent(htmlMaker, htmlTpl, null);\n        return render(content, RenderType.HTML);\n    }\n\n    /**\n     * ServerErrorResponse\n     *\n     * @return 响应对象\n     */\n    public static FullHttpResponse getServerErrorResponse() {\n        JSONObject object = new JSONObject();\n        object.put(\"code\", 500);\n        object.put(\"message\", \"Server Internal Error!\");\n        return render(object, RenderType.JSON);\n    }\n\n    /**\n     * ErrorResponse\n     *\n     * @param errorMessage 错误信息\n     * @return 响应对象\n     */\n    public static FullHttpResponse getErrorResponse(String errorMessage) {\n        JSONObject object = new JSONObject();\n        object.put(\"code\", 300);\n        object.put(\"message\", errorMessage);\n        return render(object, RenderType.JSON);\n    }\n\n    /**\n     * BlockedResponse\n     *\n     * @return 响应对象\n     */\n    public static FullHttpResponse getBlockedResponse() {\n        JSONObject object = new JSONObject();\n        object.put(\"code\", 1000);\n        object.put(\"message\", \"Blocked by user defined interceptor\");\n        return render(object, RenderType.JSON);\n    }\n\n    /**\n     * 转换byte\n     *\n     * @param content 内容\n     * @return 响应对象\n     */\n    private static byte[] getBytes(Object content) {\n        if (content == null) {\n            return EMPTY_CONTENT.getBytes(CharsetUtil.UTF_8);\n        }\n        String data = content.toString();\n        data = (data == null || data.trim().length() == 0) ? EMPTY_CONTENT : data;\n        return data.getBytes(CharsetUtil.UTF_8);\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/util/HttpRequestUtil.java",
    "content": "package com.redant.core.common.util;\n\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONArray;\nimport com.alibaba.fastjson.JSONObject;\nimport com.redant.core.common.enums.ContentType;\nimport com.redant.core.converter.PrimitiveTypeUtil;\nimport io.netty.handler.codec.http.*;\nimport io.netty.util.CharsetUtil;\n\nimport java.lang.reflect.Array;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author houyi.wh\n * @date 2017/11/16\n **/\npublic class HttpRequestUtil {\n\n    /**\n     * 获取请求参数的Map\n     * @param request http请求\n     * @return 参数map\n     */\n    public static Map<String, List<String>> getParameterMap(HttpRequest request){\n        Map<String, List<String>> paramMap = new HashMap<>();\n\n        HttpMethod method = request.method();\n        if(HttpMethod.GET.equals(method)){\n            String uri = request.uri();\n            QueryStringDecoder queryDecoder = new QueryStringDecoder(uri, CharsetUtil.UTF_8);\n            paramMap = queryDecoder.parameters();\n\n        }else if(HttpMethod.POST.equals(method)){\n            FullHttpRequest fullRequest = (FullHttpRequest) request;\n            paramMap = getPostParamMap(fullRequest);\n        }\n\n        return paramMap;\n    }\n\n\n    /**\n     * 获取post请求的参数map\n     * 目前支持最常用的 application/json 、application/x-www-form-urlencoded 几种 POST Content-type，可自行扩展！！！\n     */\n    @SuppressWarnings(\"unchecked\")\n    private static Map<String, List<String>> getPostParamMap(FullHttpRequest fullRequest) {\n        Map<String, List<String>> paramMap = new HashMap<>();\n        HttpHeaders headers = fullRequest.headers();\n        String contentType = getContentType(headers);\n        if(ContentType.APPLICATION_JSON.toString().equals(contentType)){\n            String jsonStr = fullRequest.content().toString(CharsetUtil.UTF_8);\n            JSONObject obj = JSON.parseObject(jsonStr);\n            for(Map.Entry<String, Object> item : obj.entrySet()){\n                String key = item.getKey();\n                Object value = item.getValue();\n                Class<?> valueType = value.getClass();\n\n                List<String> valueList;\n                if(paramMap.containsKey(key)){\n                    valueList = paramMap.get(key);\n                }else{\n                    valueList = new ArrayList<String>();\n                }\n\n                if(PrimitiveTypeUtil.isPriType(valueType)){\n                    valueList.add(value.toString());\n                    paramMap.put(key, valueList);\n\n                }else if(valueType.isArray()){\n                    int length = Array.getLength(value);\n                    for(int i=0; i<length; i++){\n                        String arrayItem = String.valueOf(Array.get(value, i));\n                        valueList.add(arrayItem);\n                    }\n                    paramMap.put(key, valueList);\n\n                }else if(List.class.isAssignableFrom(valueType)){\n                    if(valueType.equals(JSONArray.class)){\n                        JSONArray jArray = JSONArray.parseArray(value.toString());\n                        for(int i=0; i<jArray.size(); i++){\n                            valueList.add(jArray.getString(i));\n                        }\n                    }else{\n                        valueList = (ArrayList<String>) value;\n                    }\n                    paramMap.put(key, valueList);\n\n                }else if(Map.class.isAssignableFrom(valueType)){\n                    Map<String, String> tempMap = (Map<String, String>) value;\n                    for(Map.Entry<String, String> entry : tempMap.entrySet()){\n                        List<String> tempList = new ArrayList<String>();\n                        tempList.add(entry.getValue());\n                        paramMap.put(entry.getKey(), tempList);\n                    }\n                }\n            }\n\n        }else if(ContentType.APPLICATION_FORM_URLENCODED.toString().equals(contentType)){\n            String jsonStr = fullRequest.content().toString(CharsetUtil.UTF_8);\n            QueryStringDecoder queryDecoder = new QueryStringDecoder(jsonStr, false);\n            paramMap = queryDecoder.parameters();\n        }\n\n        return paramMap;\n    }\n\n    /**\n     * 获取contentType\n     * @param headers http请求头\n     * @return 内容类型\n     */\n    private static String getContentType(HttpHeaders headers){\n        String contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);\n        String[] list = contentType.split(\";\");\n        return list[0];\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/util/PropertiesUtil.java",
    "content": "package com.redant.core.common.util;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.concurrent.CountDownLatch;\n\n/**\n * 使用单例模式，获取配置文件中的信息\n * @author hwang\n * @date 2015-04-15\n */\npublic class PropertiesUtil {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(PropertiesUtil.class);\n\n    private static Map<String,PropertiesUtil> propertiesUtilsHolder = null;\n    \n    private static Map<PropertiesUtil,Properties> propertiesMap = null;\n\n    private volatile boolean propertiesLoaded;\n\n    private PropertiesUtil(){\n    \t\n    }\n\n    static{\n\t\tpropertiesUtilsHolder = new HashMap<>();\n\t\tpropertiesMap = new HashMap<>();\n\t}\n\n\t/**\n\t * 是否加载完毕\n\t */\n\tprivate boolean propertiesLoaded(){\n\t\tint retryTime = 0;\n\t\tint retryTimeout = 1000;\n\t\tint sleep = 500;\n\t\twhile(!propertiesLoaded && retryTime<retryTimeout){\n\t\t\ttry {\n\t\t\t\tThread.sleep(sleep);\n\t\t\t\tretryTime+=sleep;\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\treturn propertiesLoaded;\n\t}\n\n\n\t/**\n\t * 根据Resource获取properties\n\t */\n\tpublic static Properties getPropertiesByResource(String propertiesPath){\n\t\tInputStream inputStream = null;\n\t\tProperties properties = null;\n\t\ttry{\n\t\t\tinputStream = PropertiesUtil.class.getResourceAsStream(propertiesPath);\n\t\t\tif(inputStream!=null){\n\t\t\t\tproperties = new Properties();\n\t\t\t\tproperties.load(inputStream);\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tLOGGER.error(\"getInstance occur error,cause:\",e);\n\t\t} finally{\n\t\t\ttry {\n\t\t\t\tif(inputStream!=null){\n\t\t\t\t\tinputStream.close();\n\t\t\t\t}\n\t\t\t} catch (IOException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\treturn properties;\n\t}\n\n    /**\n     * 获取实例\n     */\n    public static synchronized PropertiesUtil getInstance(String propertiesPath){\n    \tPropertiesUtil propertiesUtil = propertiesUtilsHolder.get(propertiesPath);\n    \tif(null==propertiesUtil){\n    \t\tLOGGER.info(\"[PropertiesUtil] instance is null with propertiesPath={},will create a new instance directly.\",propertiesPath);\n\t\t\tInputStream inputStream = null;\n\t\t\ttry{\n\t\t\t\tpropertiesUtil = new PropertiesUtil();\n\t\t\t\tProperties properties = new Properties();\n\t\t\t\tinputStream = PropertiesUtil.class.getResourceAsStream(propertiesPath);\n\t\t\t\tif(inputStream!=null){\n\t\t\t\t\tproperties.load(inputStream);\n\t\t\t\t\tpropertiesUtilsHolder.put(propertiesPath, propertiesUtil);\n\t\t\t\t\tpropertiesMap.put(propertiesUtil, properties);\n\n\t\t\t\t\tLOGGER.info(\"[PropertiesUtil] instance init success.\");\n\t\t\t\t\tpropertiesUtil.propertiesLoaded = true;\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\tLOGGER.error(\"[PropertiesUtil] getInstance error,cause:{}\",e.getMessage(),e);\n\t\t\t} finally{\n\t\t\t\ttry {\n\t\t\t\t\tif(inputStream!=null){\n\t\t\t\t\t\tinputStream.close();\n\t\t\t\t\t}\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n    \t}\n    \treturn propertiesUtil;\n    }\n    \n    /**\n     * 获得配置信息的String值\n     */\n    public String getString(String key){\n    \tif(propertiesLoaded()){\n\t\t\tProperties properties = propertiesMap.get(this);\n\t\t\treturn null != properties ? properties.getProperty(key) : null;\n\t\t}\n\t\treturn null;\n    }\n    \n    /**\n     * 获得配置信息的boolean值\n     */\n    public boolean getBoolean(String key){\n    \tString value = getString(key);\n    \treturn \"true\".equalsIgnoreCase(value);\n    }\n    \n    /**\n     * 获得配置信息的int值\n     */\n    public int getInt(String key,int defaultValue){\n    \tString value = getString(key);\n    \tint intValue;\n    \ttry{\n    \t\tintValue = Integer.parseInt(value);\n    \t}catch(Exception e){\n    \t\tintValue = defaultValue;\n    \t}\n    \treturn intValue;\n    }\n    \n    /**\n     * 获得配置信息的long值\n     */\n    public long getLong(String key,long defaultValue){\n    \tString value = getString(key);\n    \tlong longValue;\n    \ttry{\n    \t\tlongValue = Long.parseLong(value);\n    \t}catch(Exception e){\n    \t\tlongValue = defaultValue;\n    \t}\n    \treturn longValue;\n    }\n\n\n\tpublic static void main(String[] args) {\n\t\tint loopTimes = 200;\n\n\t\tfinal CountDownLatch latch = new CountDownLatch(loopTimes);\n\n\t\tclass Runner implements Runnable{\n\n\t\t\tprivate Logger logger = LoggerFactory.getLogger(Runner.class);\n\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tString property = PropertiesUtil.getInstance(\"/redant.properties\").getString(\"bean.scan.package\");\n\t\t\t\tlogger.info(\"property={},currentThread={}\",property,Thread.currentThread().getName());\n\t\t\t\tlatch.countDown();\n\t\t\t}\n\t\t}\n\n\t\tTagUtil.addTag(\"start\");\n\t\tfor(int i=0;i<loopTimes;i++){\n\t\t\tnew Thread(new Runner()).start();\n\t\t}\n\t\ttry {\n\t\t\tlatch.await();\n\t\t} catch (InterruptedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tTagUtil.addTag(\"end\");\n\t\tTagUtil.showCost(\"start\",\"end\");\n\t}\n    \n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/util/TagUtil.java",
    "content": "package com.redant.core.common.util;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 记录两个标签之间所耗的时间\n * @author houyi.wh\n *\n */\npublic class TagUtil {\n\t\n\tprivate final static Logger LOGGER = LoggerFactory.getLogger(TagUtil.class);\n\n\tprivate static class GHandle{\n\t\tpublic static Map<String,Long> tags = Collections.synchronizedMap(new HashMap<>());\n\t}\n\t\n\t/**\n\t * 新增标签点\n\t * @param tag 标签名称\n\t */\n\tpublic static void addTag(String tag){\n\t\tif(tag==null || tag.trim().length()==0){\n\t\t\tthrow new RuntimeException(\"标签名称不可以为空\");\n\t\t}\n\t\tGHandle.tags.put(tag, System.currentTimeMillis());\n\t}\n\t\n\t/**\n\t * 计算开始标签和结束标签之间的耗时\n\t * @param startTag 开始标签名称\n\t * @param endTag 结束标签名称，如果为空，以当前调用代码所在行设置默认标签名称并计算耗时\n\t */\n\tpublic static void showCost(String startTag,String endTag){\n\t\tif(startTag==null || startTag.trim().length()==0){\n\t\t\tthrow new RuntimeException(\"开始标签名称不可以为空\");\n\t\t}\n\t\tif(endTag==null || endTag.trim().length()==0){\n\t\t\tString tempTag= \"cur_\"+System.currentTimeMillis();\n\t\t\taddTag(tempTag);\n\t\t\tendTag=tempTag;\n\t\t}else if(!GHandle.tags.containsKey(endTag)){\n\t\t\taddTag(endTag);\n\t\t}\n\t\tLong start= GHandle.tags.get(startTag);\n\t\tLong end= GHandle.tags.get(endTag);\n\t\tif(start==null){\n\t\t\tthrow new RuntimeException(\"获取标签[\"+startTag+\"]信息失败!\");\n\t\t}\n\t\tif(end==null){\n\t\t\tthrow new RuntimeException(\"获取标签[\"+endTag+\"]信息失败!\");\n\t\t}\n\t\tlong cost = end-start;\n\t\tLOGGER.info(\"from [\"+startTag+\"] to [\"+endTag+\"] cost [\"+cost+\"ms]\");\n\t}\n\n\n\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/util/ThreadUtil.java",
    "content": "package com.redant.core.common.util;\n\n/**\n * 线程工具类\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class ThreadUtil {\n\n    /**\n     * 获取当前线程名称\n     * @return 线程名称\n     */\n    public static String currentThreadName(){\n        return Thread.currentThread().getName();\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/view/HtmlKeyHolder.java",
    "content": "package com.redant.core.common.view;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic interface HtmlKeyHolder {\n\n    /**\n     * 未转义\n     */\n    String START_NO_ESCAPE = \"#[\";\n\n    /**\n     * 对[转义\n     */\n    String START_ESCAPE = \"#\\\\[\";\n\n    String END = \"]\";\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/view/Page404.java",
    "content": "package com.redant.core.common.view;\n\n\nimport cn.hutool.core.util.StrUtil;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic final class Page404 {\n\n    private Page404(){\n\n    }\n\n    public static final String HTML;\n\n    static{\n        StringBuffer sb = new StringBuffer();\n        sb.append(\"<!DOCTYPE html>\").append(StrUtil.CRLF)\n                .append(\"<html lang=\\\"en\\\">\").append(StrUtil.CRLF)\n                .append(\"<head>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<meta charset=\\\"UTF-8\\\">\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<title>404-Resource Not Found</title>\").append(StrUtil.CRLF)\n                .append(\"</head>\").append(StrUtil.CRLF)\n                .append(\"<body>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<div>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(StrUtil.TAB).append(\"Resource Not Found!\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"</div>\").append(StrUtil.CRLF)\n                .append(\"</body>\").append(StrUtil.CRLF)\n                .append(\"</html>\");\n        HTML = sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/view/Page500.java",
    "content": "package com.redant.core.common.view;\n\nimport cn.hutool.core.util.StrUtil;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic final class Page500 {\n\n    private Page500(){\n\n    }\n\n    public static final String HTML;\n\n    static{\n        StringBuffer sb = new StringBuffer();\n        sb.append(\"<!DOCTYPE html>\").append(StrUtil.CRLF)\n                .append(\"<html lang=\\\"en\\\">\").append(StrUtil.CRLF)\n                .append(\"<head>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<meta charset=\\\"UTF-8\\\">\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<title>500-Server Internal Error</title>\").append(StrUtil.CRLF)\n                .append(\"</head>\").append(StrUtil.CRLF)\n                .append(\"<body>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<div>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(StrUtil.TAB).append(\"Server Internal Error!\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"</div>\").append(StrUtil.CRLF)\n                .append(\"</body>\").append(StrUtil.CRLF)\n                .append(\"</html>\");\n        HTML = sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/view/PageError.java",
    "content": "package com.redant.core.common.view;\n\nimport cn.hutool.core.util.StrUtil;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic final class PageError {\n\n    private PageError(){\n\n    }\n\n    public static final String HTML;\n\n    static{\n        StringBuffer sb = new StringBuffer();\n        sb.append(\"<!DOCTYPE html>\").append(StrUtil.CRLF)\n                .append(\"<html lang=\\\"en\\\">\").append(StrUtil.CRLF)\n                .append(\"<head>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<meta charset=\\\"UTF-8\\\">\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<title>Error Occur</title>\").append(StrUtil.CRLF)\n                .append(\"</head>\").append(StrUtil.CRLF)\n                .append(\"<body>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"<div>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(StrUtil.TAB).append(\"<p>\").append(\"Error Occur,Cause:\").append(\"</p>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(StrUtil.TAB).append(\"<p>\").append(HtmlKeyHolder.START_NO_ESCAPE+\"errorMessage\"+HtmlKeyHolder.END).append(\"</p>\").append(StrUtil.CRLF)\n                .append(StrUtil.TAB).append(\"</div>\").append(StrUtil.CRLF)\n                .append(\"</body>\").append(StrUtil.CRLF)\n                .append(\"</html>\");\n        HTML = sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/common/view/PageIndex.java",
    "content": "package com.redant.core.common.view;\n\nimport cn.hutool.core.util.StrUtil;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic final class PageIndex {\n\n    private PageIndex(){\n\n    }\n\n    public static final String HTML;\n\n    static{\n        StringBuffer sb = new StringBuffer();\n        sb.append(\"<!DOCTYPE html>\").append(StrUtil.CRLF)\n          .append(\"<html lang=\\\"en\\\">\").append(StrUtil.CRLF)\n          .append(\"<head>\").append(StrUtil.CRLF)\n          .append(StrUtil.TAB).append(\"<meta charset=\\\"UTF-8\\\">\").append(StrUtil.CRLF)\n          .append(StrUtil.TAB).append(\"<title>redant</title>\").append(StrUtil.CRLF)\n          .append(\"</head>\").append(StrUtil.CRLF)\n          .append(\"<body>\").append(StrUtil.CRLF)\n          .append(StrUtil.TAB).append(\"<div>\").append(StrUtil.CRLF)\n          .append(StrUtil.TAB).append(StrUtil.TAB).append(\"Welcome to redant!\").append(StrUtil.CRLF)\n          .append(StrUtil.TAB).append(\"</div>\").append(StrUtil.CRLF)\n          .append(\"</body>\").append(StrUtil.CRLF)\n          .append(\"</html>\");\n        HTML = sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/context/RedantContext.java",
    "content": "package com.redant.core.context;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport io.netty.util.concurrent.FastThreadLocal;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * @author houyi\n **/\npublic class RedantContext {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RedantContext.class);\n\n    /**\n     * 使用FastThreadLocal替代JDK自带的ThreadLocal以提升并发性能\n     */\n    private static final FastThreadLocal<RedantContext> CONTEXT_HOLDER = new FastThreadLocal<>();\n\n    private HttpRequest request;\n\n    private ChannelHandlerContext context;\n\n    private HttpResponse response;\n\n    private Set<Cookie> cookies;\n\n    private RedantContext(){\n\n    }\n\n    public RedantContext setRequest(HttpRequest request){\n        this.request = request;\n        return this;\n    }\n\n    public RedantContext setContext(ChannelHandlerContext context){\n        this.context = context;\n        return this;\n    }\n\n    public RedantContext setResponse(HttpResponse response){\n        this.response = response;\n        return this;\n    }\n\n    public RedantContext addCookie(Cookie cookie){\n        if(cookie!=null){\n            if(CollectionUtil.isEmpty(cookies)){\n                cookies = new HashSet<>();\n            }\n            cookies.add(cookie);\n        }\n        return this;\n    }\n\n    public RedantContext addCookies(Set<Cookie> cookieSet){\n        if(CollectionUtil.isNotEmpty(cookieSet)){\n            if(CollectionUtil.isEmpty(cookies)){\n                cookies = new HashSet<>();\n            }\n            cookies.addAll(cookieSet);\n        }\n        return this;\n    }\n\n    public HttpRequest getRequest() {\n        return request;\n    }\n\n    public ChannelHandlerContext getContext() {\n        return context;\n    }\n\n    public HttpResponse getResponse() {\n        return response;\n    }\n\n    public Set<Cookie> getCookies() {\n        return cookies;\n    }\n\n    public static RedantContext currentContext(){\n        RedantContext context = CONTEXT_HOLDER.get();\n        if(context==null){\n            context = new RedantContext();\n            CONTEXT_HOLDER.set(context);\n        }\n        return context;\n    }\n\n    public static void clear(){\n        CONTEXT_HOLDER.remove();\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/controller/ControllerProxy.java",
    "content": "package com.redant.core.controller;\n\nimport com.redant.core.common.enums.RequestMethod;\nimport com.redant.core.render.RenderType;\n\nimport java.lang.reflect.Method;\n\n/**\n * 路由请求代理，用以根据路由调用具体的controller类\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class ControllerProxy {\n\n    private RenderType renderType;\n\n    private RequestMethod requestMethod;\n\n    private Object controller;\n\n    private Method method;\n\n    private String methodName;\n\n    public RenderType getRenderType() {\n        return renderType;\n    }\n\n    public void setRenderType(RenderType renderType) {\n        this.renderType = renderType;\n    }\n\n    public RequestMethod getRequestMethod() {\n        return requestMethod;\n    }\n\n    public void setRequestMethod(RequestMethod requestMethod) {\n        this.requestMethod = requestMethod;\n    }\n\n    public Object getController() {\n        return controller;\n    }\n\n    public void setController(Object controller) {\n        this.controller = controller;\n    }\n\n    public Method getMethod() {\n        return method;\n    }\n\n    public void setMethod(Method method) {\n        this.method = method;\n    }\n\n    public String getMethodName() {\n        return methodName;\n    }\n\n    public void setMethodName(String methodName) {\n        this.methodName = methodName;\n    }\n\n    @Override\n    public String toString() {\n        return \"{requestMethod:\"+requestMethod+\",controller:\"+controller.getClass().getName()+\",methodName:\"+methodName+\"}\";\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/controller/ProxyInvocation.java",
    "content": "package com.redant.core.controller;\n\n\nimport com.redant.core.common.exception.InvocationException;\nimport com.redant.core.common.exception.ValidationException;\nimport com.redant.core.common.util.GenericsUtil;\nimport com.redant.core.common.util.HttpRequestUtil;\nimport com.redant.core.context.RedantContext;\nimport com.redant.core.converter.PrimitiveConverter;\nimport com.redant.core.converter.PrimitiveTypeUtil;\nimport com.redant.core.controller.annotation.Param;\nimport net.sf.cglib.reflect.FastClass;\nimport net.sf.cglib.reflect.FastMethod;\nimport org.apache.commons.beanutils.BeanUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.*;\n\n\n/**\n * 封装了ControllerProxy的调用过程\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class ProxyInvocation {\n\n\tprivate static Invocation invocation = new Invocation();\n\n\tpublic static Object invoke(ControllerProxy proxy) throws Exception{\n\t\tObject controller = proxy.getController();\n\t\tMethod method = proxy.getMethod();\n\t\tString methodName = proxy.getMethodName();\n\t\treturn invocation.invoke(controller,method,methodName);\n\t}\n\n\n\tprivate static class Invocation {\n\n\t\tprivate static Logger logger = LoggerFactory.getLogger(Invocation.class);\n\n\t\tprivate final int KEY_VALUE_SIZE = 2;\n\n\t\tpublic Invocation(){\n\n\t\t}\n\n\t\t/**\n\t\t * 获得方法调用的参数\n\t\t * @param method 方法\n\t\t * @param parameterTypes 方法参数类型\n\t\t * @return 参数\n\t\t * @throws Exception 参数异常\n\t\t */\n\t\tprivate Object[] getParameters(Method method,Class<?>[] parameterTypes) throws Exception {\n\t\t\t//用于存放调用参数的对象数组\n\t\t\tObject[] parameters = new Object[parameterTypes.length];\n\n\t\t\t//获得所调用方法的参数的Annotation数组\n\t\t\tAnnotation[][] annotationArray = method.getParameterAnnotations();\n\n\t\t\t//获取参数列表\n\t\t\tMap<String, List<String>> paramMap = HttpRequestUtil.getParameterMap(RedantContext.currentContext().getRequest());\n\n\t\t\t//构造调用所需要的参数数组\n\t\t\tfor (int i = 0; i < parameterTypes.length; i++) {\n\t\t\t\tObject parameter;\n\t\t\t\tClass<?> type = parameterTypes[i];\n\t\t\t\tAnnotation[] annotation = annotationArray[i];\n\t\t\t\t// 如果该参数没有 Param 注解\n\t\t\t\tif (annotation == null || annotation.length == 0) {\n\t\t\t\t\t// 如果该参数类型是基础类型，则需要加 Param 注解\n\t\t\t\t\tif(PrimitiveTypeUtil.isPriType(type)){\n\t\t\t\t\t\tlogger.warn(\"Must specify a @Param annotation for primitive type parameter in method={}\", method.getName());\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t// 封装对象类型的parameter\n\t\t\t\t\tparameter = type.newInstance();\n\t\t\t\t\tBeanUtils.populate(parameter,paramMap);\n\t\t\t\t\tparameters[i] = parameter;\n\t\t\t\t}else{\n\t\t\t\t\tParam param = (Param) annotation[0];\n\t\t\t\t\ttry{\n\t\t\t\t\t\t// 生成当前的调用参数\n\t\t\t\t\t\tparameter = parseParameter(paramMap, type, param, method, i);\n\t\t\t\t\t\tif(param.notNull()){\n\t\t\t\t\t\t\tGenericsUtil.checkNull(param.key(), parameter);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif(param.notBlank()){\n\t\t\t\t\t\t\tGenericsUtil.checkBlank(param.key(), parameter);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparameters[i] = parameter;\n\t\t\t\t\t}catch(Exception e){\n\t\t\t\t\t    logger.error(\"param [\"+param.key()+\"] is invalid，cause:\"+e.getMessage());\n\t\t\t\t\t\tthrow new IllegalArgumentException(\"参数 \"+param.key()+\" 不合法：\"+e.getMessage());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parameters;\n\t\t}\n\n\n\t\t/**\n\t\t * GET 参数解析\n\t\t */\n\t\t@SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n\t\tprivate Object parseParameter(Map<String, List<String>> paramMap, Class<?> type, Param param, Method method, int index) throws InstantiationException, IllegalAccessException{\n\t\t\tObject value = null;\n\t\t\tString key = param.key();\n\t\t\tString defaultValue= param.defaultValue();\n\t\t\tif(key.length() > 0){\n\t\t\t\t// 如果参数是map类型\n\t\t\t\tif(Map.class.isAssignableFrom(type)){\n\t\t\t\t\tif(index > 0){\n\t\t\t\t\t\tthrow new ValidationException(\"Must have only one Map type parameter\");\n\t\t\t\t\t}\n\n\t\t\t\t\tList<Class> types = GenericsUtil.getMethodGenericParameterTypes(method, index);\n\t\t\t\t\tif(types.size() == KEY_VALUE_SIZE && (types.get(0) != String.class || types.get(1) != String.class)){\n\t\t\t\t\t\tthrow new ValidationException(\"Map type parameter must both be String, Occurring Point: \" + method.toGenericString());\n\t\t\t\t\t}\n\n\t\t\t\t\tMap<String, String> valueMap = new HashMap<String, String>(paramMap.size());\n\t\t\t\t\tfor(Map.Entry<String, List<String>> entry : paramMap.entrySet()){\n\t\t\t\t\t\tList<String> valueList = entry.getValue();\n\t\t\t\t\t\tvalueMap.put(entry.getKey(), valueList.get(0));\n\t\t\t\t\t}\n\t\t\t\t\tvalue = valueMap;\n\t\t\t\t}else{\n\t\t\t\t\tList<String> params = paramMap.get(key);\n\t\t\t\t\tif(params != null){\n\t\t\t\t\t\t// 基础类型\n\t\t\t\t\t\tif(PrimitiveTypeUtil.isPriType(type)){\n\t\t\t\t\t\t\tvalue = PrimitiveConverter.getInstance().convert(params.get(0), type);\n\n\t\t\t\t\t\t// 数组\n\t\t\t\t\t\t}else if(type.isArray()){\n\t\t\t\t\t\t\tString[] strArray = params.toArray(new String[]{});\n\t\t\t\t\t\t\tvalue = PrimitiveConverter.getInstance().convert(strArray, type);\n\n\t\t\t\t\t\t// List\n\t\t\t\t\t\t}else if(List.class.isAssignableFrom(type)){\n\t\t\t\t\t\t\tList<Object> list;\n\t\t\t\t\t\t\tList<Class> types = GenericsUtil.getMethodGenericParameterTypes(method, index);\n\t\t\t\t\t\t\tClass<?> listType = types.size() == 1?types.get(0):String.class;\n\t\t\t\t\t\t\tif(List.class == type){\n\t\t\t\t\t\t\t\tlist = new ArrayList<Object>();\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\tlist = (List<Object>) type.newInstance();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor (String param1 : params) {\n\t\t\t\t\t\t\t\tif (param1.length() > 0) {\n\t\t\t\t\t\t\t\t\tlist.add(PrimitiveConverter.getInstance().convert(param1, listType));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvalue = list;\n\t\t\t\t\t\t}\n\t\t\t\t\t}else{\n\t\t\t\t\t\tif(PrimitiveTypeUtil.isPriType(type)){\n\t\t\t\t\t\t\tvalue = PrimitiveConverter.getInstance().convert(defaultValue, type);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\n\t\t/**\n\t\t * 返回调用异常\n\t\t * @param msg 消息\n\t\t * @param cause 异常\n\t\t * @return 调用异常\n\t\t */\n\t\tprivate InvocationException getInvokeException(String msg, Throwable cause){\n\t\t\treturn new InvocationException(msg,cause);\n\t\t}\n\n\n\t\t//==================================\n\n\n\t\t/**\n\t\t * 执行方法的调用\n\t\t * @param controller 控制器\n\t\t * @param method 方法\n\t\t * @param methodName 方法名\n\t\t * @return 渲染结果\n\t\t * @throws Exception 异常\n\t\t */\n        Object invoke(Object controller, Method method, String methodName) throws Exception {\n\t\t\tif (method == null) {\n\t\t\t\tthrow new NoSuchMethodException(\"Can not find specified method: \" + methodName);\n\t\t\t}\n\n\t\t\tClass<?> clazz = controller.getClass();\n\t\t\tClass<?>[] parameterTypes = method.getParameterTypes();\n\t\t\tObject[] parameters = null;\n\t\t\tObject result;\n\t\t\ttry {\n\t\t\t\tparameters = getParameters(method,parameterTypes);\n\t\t\t\t// 使用 CGLib 执行反射调用\n\t\t\t\tFastClass fastClass = FastClass.create(clazz);\n\t\t\t\tFastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);\n\t\t\t\t// 调用，并得到调用结果\n\t\t\t\tresult = fastMethod.invoke(controller, parameters);\n\n\t\t\t} catch(InvocationTargetException e){\n\t\t\t\tString msg = \"调用出错,请求类[\"+controller.getClass().getName()+\"],方法名[\" + method.getName() + \"],参数[\" + Arrays.toString(parameters)+\"]\";\n\t\t\t\tthrow getInvokeException(msg, e);\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t}\n\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/controller/annotation/Controller.java",
    "content": "package com.redant.core.controller.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Controller {\n\n    /**\n     * 请求uri\n     * @return url\n     */\n    String path() default \"\";\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/controller/annotation/Mapping.java",
    "content": "package com.redant.core.controller.annotation;\n\nimport com.redant.core.common.enums.RequestMethod;\nimport com.redant.core.render.RenderType;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Mapping {\n\n    /**\n     * 请求方法类型\n     * @return 方法类型\n     */\n    RequestMethod requestMethod() default RequestMethod.GET;\n\n    /**\n     * 请求的uri\n     * @return url\n     */\n    String path() default \"\";\n\n    /**\n     * 返回类型\n     * @return 返回类型\n     */\n    RenderType renderType() default RenderType.JSON;\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/controller/annotation/Param.java",
    "content": "package com.redant.core.controller.annotation;\n\nimport java.lang.annotation.*;\n\n@Target({ ElementType.FIELD, ElementType.PARAMETER })\n@Retention(value = RetentionPolicy.RUNTIME)\npublic @interface Param {\n\t\n\t/**\n\t * 将使用什么样的键值读取对象，\n\t * 对于field，就是他名字\n\t * 对于method的parameter，需要指明\n\t * @return 参数的key\n\t */\n\tString key() default \"\";\n\t\n\t/**\n\t * 提供设置缺省值\n\t * @return 提供设置缺省值\n\t */\n\tString defaultValue() default \"\";\n\t\n\t/**\n     * 是否校验参数为空\n     * @return true：校验参数 false：不校验参数\n     */\n    boolean notNull() default false;\n\n    /**\n     * 是否校验参数为空\n     * @return true：校验参数 false：不校验参数\n     */\n    boolean notBlank() default false;\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/controller/context/ControllerContext.java",
    "content": "package com.redant.core.controller.context;\n\nimport com.redant.core.controller.ControllerProxy;\nimport io.netty.handler.codec.http.HttpMethod;\n\n/**\n * @author houyi.wh\n * @date 2019-01-15\n */\npublic interface ControllerContext {\n\n    /**\n     * 添加Controller代理\n     * @param path 请求路径\n     * @param proxy 代理\n     */\n    void addProxy(String path,ControllerProxy proxy);\n\n    /**\n     * 获取Controller代理\n     * @param method 请求方法类型\n     * @param uri 请求url\n     * @return 代理\n     */\n    ControllerProxy getProxy(HttpMethod method, String uri);\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/controller/context/DefaultControllerContext.java",
    "content": "package com.redant.core.controller.context;\n\nimport com.redant.core.controller.ControllerProxy;\nimport com.redant.core.render.RenderType;\nimport com.redant.core.router.RouteResult;\nimport com.redant.core.router.context.DefaultRouterContext;\nimport com.redant.core.router.context.RouterContext;\nimport io.netty.handler.codec.http.HttpMethod;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * @author houyi.wh\n * @date 2019-01-15\n */\npublic class DefaultControllerContext implements ControllerContext {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultControllerContext.class);\n\n    /**\n     * 保存所有的RouterController的代理类\n     */\n    private static Map<String, ControllerProxy> proxyMap;\n\n    /**\n     * 路由上下文\n     */\n    private static RouterContext routerContext;\n\n    private static final class DefaultControllerContextHolder {\n        private static DefaultControllerContext context = new DefaultControllerContext();\n    }\n\n    private DefaultControllerContext() {\n        routerContext = DefaultRouterContext.getInstance();\n        proxyMap = new ConcurrentHashMap<>();\n    }\n\n    public static ControllerContext getInstance() {\n        return DefaultControllerContextHolder.context;\n    }\n\n\n    @Override\n    public void addProxy(String path, ControllerProxy proxy) {\n        proxyMap.putIfAbsent(path, proxy);\n    }\n\n    @Override\n    public ControllerProxy getProxy(HttpMethod method, String uri) {\n        RouteResult<RenderType> routeResult = routerContext.getRouteResult(method, uri);\n        if (routeResult == null) {\n            return null;\n        }\n        // 获取代理\n        ControllerProxy controllerProxy = proxyMap.get(routeResult.decodedPath());\n        LOGGER.debug(\"\\n=========================  getControllerProxy =========================\" +\n                        \"\\nmethod={}, uri={}\" +\n                        \"\\ncontrollerProxy={}\" +\n                        \"\\n=========================  getControllerProxy =========================\",\n                method, uri, controllerProxy);\n        return controllerProxy;\n    }\n\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/converter/AbstractConverter.java",
    "content": "package com.redant.core.converter;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.Method;\n\n/**\n * 转换器的抽象实现类\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic abstract class AbstractConverter implements Converter {\n\t\n\tprotected final Logger logger = LoggerFactory.getLogger(this.getClass());\n\t\n\t/**\n\t * 实现了TypeConverter中的相同方法\n\t */\n\t@Override\n\tpublic Object convert(Object source, Class<?> toType, Object... parmas) {\n\t\t\n\t\t/**\n\t\t * 如果对象本身已经是所指定的类型则不进行转换直接返回\n\t\t * 如果对象能够被复制，则返回复制后的对象\n\t\t */\n\t\tif (source != null && toType.isInstance(source)) {\n\t\t\tif (source instanceof Cloneable) {\n\t\t\t\tif(source.getClass().isArray() && source.getClass().getComponentType() == String.class){\n\t\t\t\t\t// 字符串数组虽然是Cloneable的子类，但并没有clone方法\n\t\t\t\t\treturn source;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tMethod m = source.getClass().getDeclaredMethod(\"clone\", new Class[0]);\n\t\t\t\t\tm.setAccessible(true);\n\t\t\t\t\treturn m.invoke(source, new Object[0]);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.debug(\"Can not clone object \" + source, e);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn source;\n\t\t}\n\n\t\t/**\n\t\t * 如果需要转换，且value为String类型并且长度为0，则按照null值进行处理\n\t\t */\n\t\tif (source != null && source instanceof String && ((String)source).length() == 0) {\n\t\t\tsource = null;\n\t\t}\n\n\t\t/**\n\t\t * 不对Annotation, Interface,\n\t\t * Enummeration类型进行转换。\n\t\t */\n\t\tif (toType == null || (source == null && !toType.isPrimitive())\n\t\t\t\t|| toType.isInterface() || toType.isAnnotation()\n\t\t\t\t|| toType.isEnum()) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn doConvertValue(source, toType);\n\t}\n\n\t/**\n\t * 需要被子类所实现的转换方法\n\t * @param source 需要进行类型转换的对象\n\t * @param toType　需要被转换成的类型\n\t * @param params　转值时需要提供的可选参数\n\t * @return　转换后所生成的对象，如果不能够进行转换则返回null\n\t */\n\tprotected abstract Object doConvertValue(Object source, Class<?> toType, Object... params);\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/converter/Converter.java",
    "content": "package com.redant.core.converter;\n\n/**\n * 类型转换器所需要实现的总接口。TypeConverter中有唯一的一个方法,实现类请特别注意方法所需要返回的值。\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic interface Converter {\n\n\t/**\n\t * 类型转换\n\t * @param source 需要被转换的值\n\t * @param toType 需要被转换成的类型\n\t * @param params 转值时需要提供的可选参数\n\t * @return 经转换过的类型，如果实现类没有能力进行所指定的类型转换，应返回null\n\t */\n\tObject convert(Object source, Class<?> toType, Object... params);\n\t\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/converter/PrimitiveConverter.java",
    "content": "package com.redant.core.converter;\n\nimport java.lang.reflect.Array;\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\n\n/**\n * 基本类型和基本类型数组转换器\n * 能够转换的基本类型仅限于com.sitechasia.webx.core.utils.populator.PrimitiveType中所定义的类型\n *\n * @see # com.sitechasia.webx.core.utils.populator.PrimitiveTypeUtil\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic final class PrimitiveConverter extends AbstractConverter {\n\t\n\tprivate static final PrimitiveConverter CONVERTER;\n\n\tstatic {\n\t\tCONVERTER = new PrimitiveConverter();\n\t}\n\n\tprivate static final char[] DIGITAL_CHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };\n\t\n\t/**\n\t * 对基本类型进行类型转换\n\t */\n\t@Override\n\t@SuppressWarnings(\"unused\")\n\tprotected Object doConvertValue(Object source, Class<?> toType, Object... params) {\n\t\t/**\n\t\t * 如果是基础类型，则直接返回\n\t\t */\n\t\tif (source != null && (!PrimitiveTypeUtil.isPriType(source.getClass()) || !PrimitiveTypeUtil.isPriType(toType))) {\n\t\t\treturn null;\n\t\t}\n\n\t\t/**\n\t\t * 如果都是数组类型，则构造数组\n\t\t */\n\t\tif (source != null && source.getClass().isArray() && toType.isArray()) {\n\t\t\tObject result;\n\t\t\tClass<?> componentType = toType.getComponentType();\n\t\t\tresult = Array.newInstance(componentType, Array.getLength(source));\n\n\t\t\tfor (int i = 0; i < Array.getLength(source); i++) {\n\t\t\t\tArray.set(result, i, convert(Array.get(source, i),componentType, params));\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t\treturn doConvert(source, toType);\n\t}\n\t\n\tprivate boolean isNumberString(String stringValue) {\n\t\tif (stringValue == null || stringValue.length() == 0){\n\t\t\treturn false;\n\t\t}\n\t\n\t\tOUTER: for (char charInString : stringValue.toCharArray()) {\n\t\t\tfor (char digit : DIGITAL_CHAR) {\n\t\t\t\tif (charInString == digit){\n\t\t\t\t\tcontinue OUTER;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\t\n\tprivate boolean booleanValue(Object source) {\n\t\tif (source == null){\n\t\t\treturn false;\n\t\t}\n\t\tClass<? extends Object> c = source.getClass();\n\t\tif (c == Boolean.class){\n\t\t\treturn (Boolean) source;\n\t\t}\n\t\tif (c == String.class) {\n\t\t\tString stringValue = (String) source;\n\t\t\treturn !(stringValue.length() == 0\n\t\t\t\t\t|| stringValue.equals(\"0\")\n\t\t\t\t\t|| stringValue.equalsIgnoreCase(\"false\")\n\t\t\t\t\t|| stringValue.equalsIgnoreCase(\"no\")\n\t\t\t\t\t|| stringValue.equalsIgnoreCase(\"f\") || stringValue\n\t\t\t\t\t.equalsIgnoreCase(\"n\"));\n\t\t}\n\t\tif (c == Character.class){\n\t\t\treturn ((Character) source).charValue() != 0;\n\t\t}\n\t\tif (source instanceof Number){\n\t\t\treturn ((Number) source).doubleValue() != 0;\n\t\t}\n\t\t\t\n\t\treturn true;\n\t}\n\t\n\tprivate long longValue(Object source) throws NumberFormatException {\n\t\tif (source == null){\n\t\t\treturn 0L;\n\t\t}\n\t\tClass<? extends Object> c = source.getClass();\n\t\tif (c.getSuperclass() == Number.class){\n\t\t\treturn ((Number) source).longValue();\n\t\t}\n\t\tif (c == Boolean.class){\n\t\t\treturn ((Boolean) source).booleanValue() ? 1 : 0;\n\t\t}\n\t\tif (c == Character.class){\n\t\t\treturn ((Character) source).charValue();\n\t\t}\n\t\t\t\n\t\tString s = stringValue(source, true);\n\t\treturn (s.length() == 0) ? 0L : Long.parseLong(s);\n\t}\n\t\n\tprivate double doubleValue(Object source) throws NumberFormatException {\n\t\tif (source == null){\n\t\t\treturn 0.0;\n\t\t}\n\t\tClass<? extends Object> c = source.getClass();\n\t\tif (c.getSuperclass() == Number.class){\n\t\t\treturn ((Number) source).doubleValue();\n\t\t}\n\t\tif (c == Boolean.class){\n\t\t\treturn ((Boolean) source).booleanValue() ? 1 : 0;\n\t\t}\n\t\tif (c == Character.class){\n\t\t\treturn ((Character) source).charValue();\n\t\t}\n\t\tString s = stringValue(source, true);\n\t\treturn (s.length() == 0) ? 0.0 : Double.parseDouble(s);\n\t}\n\t\n\tprivate BigInteger bigIntValue(Object source) throws NumberFormatException {\n\t\tif (source == null){\n\t\t\treturn BigInteger.valueOf(0L);\n\t\t}\n\t\tClass<? extends Object> c = source.getClass();\n\t\tif (c == BigInteger.class){\n\t\t\treturn (BigInteger) source;\n\t\t}\n\t\tif (c == BigDecimal.class){\n\t\t\treturn ((BigDecimal) source).toBigInteger();\n\t\t}\n\t\tif (c.getSuperclass() == Number.class){\n\t\t\treturn BigInteger.valueOf(((Number) source).longValue());\n\t\t}\n\t\tif (c == Boolean.class){\n\t\t\treturn BigInteger.valueOf(((Boolean) source).booleanValue() ? 1 : 0);\n\t\t}\n\t\tif (c == Character.class){\n\t\t\treturn BigInteger.valueOf(((Character) source).charValue());\n\t\t}\n\t\n\t\tString s = stringValue(source, true);\n\t\treturn (s.length() == 0) ? BigInteger.valueOf(0L) : new BigInteger(s);\n\t}\n\t\n\tprivate BigDecimal bigDecValue(Object source) throws NumberFormatException {\n\t\tif (source == null){\n\t\t\treturn BigDecimal.valueOf(0L);\n\t\t}\n\t\tClass<? extends Object> c = source.getClass();\n\t\tif (c == BigDecimal.class){\n\t\t\treturn (BigDecimal) source;\n\t\t}\n\t\tif (c == BigInteger.class){\n\t\t\treturn new BigDecimal((BigInteger) source);\n\t\t}\n\t\tif (c.getSuperclass() == Number.class){\n\t\t\treturn new BigDecimal(((Number) source).doubleValue());\n\t\t}\n\t\tif (c == Boolean.class){\n\t\t\treturn BigDecimal.valueOf(((Boolean) source).booleanValue() ? 1 : 0);\n\t\t}\n\t\tif (c == Character.class){\n\t\t\treturn BigDecimal.valueOf(((Character) source).charValue());\n\t\t}\n\t\n\t\tString s = stringValue(source, true);\n\t\treturn (s.length() == 0) ? BigDecimal.valueOf(0L) : new BigDecimal(s);\n\t}\n\t\n\tprivate String stringValue(Object source, boolean trim) {\n\t\tString result;\n\t\n\t\tif (source == null) {\n\t\t\tresult = null;\n\t\t} else {\n\t\t\tresult = source.toString();\n\t\t\tif (trim) {\n\t\t\t\tresult = result.trim();\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\t\n\tprivate char charValue(Object source) {\n\t\tchar result;\n\t\n\t\tif (source.getClass() == String.class && ((String) source).length() > 0 && !isNumberString((String) source)){\n\t\t\tresult = ((String) source).charAt(0);\n\t\t}else{\n\t\t\tresult = (char) longValue(source);\n\t\t}\n\t\n\t\treturn result;\n\t}\n\t\n\tprivate String stringValue(Object source) {\n\t\treturn stringValue(source, false);\n\t}\n\t\n\tprivate Object doConvert(Object source, Class<?> toType) {\n\t\tObject result = null;\n\t\n\t\tif (source != null) {\n\t\t\tif ((toType == Integer.class) || (toType == Integer.TYPE)) {\n\t\t\t\tresult = (int) longValue(source);\n\t\t\t}else if ((toType == Double.class) || (toType == Double.TYPE)){\n\t\t\t\tresult = doubleValue(source);\n\t\t\t}else if ((toType == Boolean.class) || (toType == Boolean.TYPE)){\n\t\t\t\tresult = booleanValue(source);\n\t\t\t}else if ((toType == Byte.class) || (toType == Byte.TYPE)){\n\t\t\t\tresult = (byte) longValue(source);\n\t\t\t}else if ((toType == Character.class) || (toType == Character.TYPE)){\n\t\t\t\tresult = charValue(source);\n\t\t\t}else if ((toType == Short.class) || (toType == Short.TYPE)){\n\t\t\t\tresult = (short) longValue(source);\n\t\t\t}else if ((toType == Long.class) || (toType == Long.TYPE)){\n\t\t\t\tresult = longValue(source);\n\t\t\t}else if ((toType == Float.class) || (toType == Float.TYPE)){\n\t\t\t\tresult = (float) doubleValue(source);\n\t\t\t}else if (toType == BigInteger.class){\n\t\t\t\tresult = bigIntValue(source);\n\t\t\t}else if (toType == BigDecimal.class){\n\t\t\t\tresult = bigDecValue(source);\n\t\t\t}else if (toType == String.class){\n\t\t\t\tresult = stringValue(source);\n\t\t\t}\n\t\t} else {\n\t\t\tif (toType.isPrimitive()) {\n\t\t\t\tresult = PrimitiveTypeUtil.getPriDefaultValue(toType);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\t\n\tpublic static PrimitiveConverter getInstance(){\n\t\treturn CONVERTER;\n\t}\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/converter/PrimitiveTypeUtil.java",
    "content": "package com.redant.core.converter;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 定义了基本类型的工具类，\n * 可以方便的判断一个Class对象是否属于基本类型或基本类型的数组。\n * 本工具类所包含的基本类型判断包括如下一些内容：\n * \n * String\n * boolean\n * byte\n * short\n * int\n * long\n * float\n * double\n * char\n * Boolean\n * Byte\n * Short\n * Integer\n * Long\n * Float\n * Double\n * Character\n * BigInteger\n * BigDecimal\n * \n * @author yunfeng.cheng\n * @date 2016-08-12\n */\npublic class PrimitiveTypeUtil {\n\n\t/**\n\t * 私有的构造函数防止用户进行实例化。\n\t */\n\tprivate PrimitiveTypeUtil() {}\n\n\t/** 基本类型  **/\n\tprivate static final Class<?>[] PRI_TYPE = { \n\t\t\tString.class, \n\t\t\tboolean.class,\n\t\t\tbyte.class, \n\t\t\tshort.class, \n\t\t\tint.class, \n\t\t\tlong.class, \n\t\t\tfloat.class,\n\t\t\tdouble.class, \n\t\t\tchar.class, \n\t\t\tBoolean.class, \n\t\t\tByte.class, \n\t\t\tShort.class,\n\t\t\tInteger.class, \n\t\t\tLong.class, \n\t\t\tFloat.class, \n\t\t\tDouble.class,\n\t\t\tCharacter.class, \n\t\t\tBigInteger.class, \n\t\t\tBigDecimal.class \n\t};\n\n\t/** 基本数组类型  **/\n\tprivate static final Class<?>[] PRI_ARRAY_TYPE = { \n\t\t\tString[].class,\n\t\t\tboolean[].class, \n\t\t\tbyte[].class, \n\t\t\tshort[].class, \n\t\t\tint[].class,\n\t\t\tlong[].class, \n\t\t\tfloat[].class, \n\t\t\tdouble[].class, \n\t\t\tchar[].class,\n\t\t\tBoolean[].class, \n\t\t\tByte[].class, \n\t\t\tShort[].class, \n\t\t\tInteger[].class,\n\t\t\tLong[].class, \n\t\t\tFloat[].class, \n\t\t\tDouble[].class, \n\t\t\tCharacter[].class,\n\t\t\tBigInteger[].class, \n\t\t\tBigDecimal[].class \n\t};\n\t\n\t/**\n\t * 基本类型默认值\n\t */\n\tprivate static final Map<Class<?>, Object> primitiveDefaults = new HashMap<Class<?>, Object>(9);\n\tstatic {\n        primitiveDefaults.put(boolean.class, false);\n        primitiveDefaults.put(byte.class, (byte)0);\n        primitiveDefaults.put(short.class, (short)0);\n        primitiveDefaults.put(char.class, (char)0);\n        primitiveDefaults.put(int.class, 0);\n        primitiveDefaults.put(long.class, 0L);\n        primitiveDefaults.put(float.class, 0.0f);\n        primitiveDefaults.put(double.class, 0.0);\n\t}\n\t\n\t/**\n\t * 判断是否为基本类型\n\t * @param cls 需要进行判断的Class对象\n\t * @return 是否为基本类型\n\t */\n\tpublic static boolean isPriType(Class<?> cls) {\n\t\tfor (Class<?> priType : PRI_TYPE) {\n\t\t\tif (cls == priType){\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * 判断是否为基本类型数组\n\t * @param cls 需要进行判断的Class对象\n\t * @return 是否为基本类型数组\n\t */\n\tpublic static boolean isPriArrayType(Class<?> cls) {\n\t\tfor (Class<?> priType : PRI_ARRAY_TYPE) {\n\t\t\tif (cls == priType){\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\t\n\t/**\n\t * 获得基本类型的默认值\n\t * @param type 基本类型的Class\n\t * @return 基本类型的默认值\n\t */\n\tpublic static Object getPriDefaultValue(Class<?> type) {\n\t\treturn primitiveDefaults.get(type);\n\t}\n\t\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/cookie/CookieManager.java",
    "content": "package com.redant.core.cookie;\n\nimport io.netty.handler.codec.http.cookie.Cookie;\n\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * cookie管理\n * @author houyi.wh\n * @date 2019-01-16\n */\npublic interface CookieManager {\n\n    /**\n     * 获取所有的cookie\n     * @return cookie集合\n     */\n    Set<Cookie> getCookies();\n\n    /**\n     * 获取所有的cookie，并返回一个Map\n     * @return cookie的map\n     */\n    Map<String,Cookie> getCookieMap();\n\n    /**\n     * 根据名称获取cookie\n     * @param name 名称\n     * @return cookie\n     */\n    Cookie getCookie(String name);\n\n    /**\n     * 根据名称获取cookie的值\n     * @param name 名称\n     * @return cookie的值\n     */\n    String getCookieValue(String name);\n\n    /**\n     * 设置cookie到响应结果中\n     * @param cookie cookie\n     */\n    void setCookie(Cookie cookie);\n\n    /**\n     * 获取所有的cookie后，全部设置到响应结果中\n     */\n    void setCookies();\n\n    /**\n     * 添加一个cookie\n     * @param name cookie的名称\n     * @param value cookie的值\n     */\n    void addCookie(String name,String value);\n\n    /**\n     * 添加一个cookie\n     * @param name cookie的名称\n     * @param value cookie的值\n     * @param domain cookie的作用域\n     */\n    void addCookie(String name,String value,String domain);\n\n    /**\n     * 添加一个cookie\n     * @param name cookie的名称\n     * @param value cookie的值\n     * @param maxAge cookie的有效期\n     */\n    void addCookie(String name,String value,long maxAge);\n\n    /**\n     * 添加一个cookie\n     * @param name cookie的名称\n     * @param value cookie的值\n     * @param domain cookie的作用域\n     * @param maxAge cookie的有效期\n     */\n    void addCookie(String name,String value,String domain,long maxAge);\n\n    /**\n     * 删除一个cookie\n     * @param name cookie的名称\n     * @return 操作结果\n     */\n    boolean deleteCookie(String name);\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/cookie/DefaultCookieManager.java",
    "content": "package com.redant.core.cookie;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.redant.core.context.RedantContext;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport io.netty.handler.codec.http.cookie.DefaultCookie;\nimport io.netty.handler.codec.http.cookie.ServerCookieDecoder;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Cookie管理器\n *\n * @author houyi.wh\n * @date 2019-01-16\n */\n\npublic class DefaultCookieManager implements CookieManager {\n\n    private static final class DefaultCookieManagerHolder {\n        private static DefaultCookieManager cookieManager = new DefaultCookieManager();\n    }\n\n    private DefaultCookieManager() {\n\n    }\n\n    public static CookieManager getInstance() {\n        return DefaultCookieManagerHolder.cookieManager;\n    }\n\n\n    @Override\n    public Set<Cookie> getCookies() {\n        HttpRequest request = RedantContext.currentContext().getRequest();\n        Set<Cookie> cookies = new HashSet<>();\n        if (request != null) {\n            String value = request.headers().get(HttpHeaderNames.COOKIE);\n            if (value != null) {\n                cookies = ServerCookieDecoder.STRICT.decode(value);\n            }\n        }\n        return cookies;\n    }\n\n    @Override\n    public Map<String, Cookie> getCookieMap() {\n        Map<String, Cookie> cookieMap = new HashMap<>();\n        Set<Cookie> cookies = getCookies();\n        if (null != cookies && !cookies.isEmpty()) {\n            for (Cookie cookie : cookies) {\n                cookieMap.put(cookie.name(), cookie);\n            }\n        }\n        return cookieMap;\n    }\n\n    @Override\n    public Cookie getCookie(String name) {\n        Map<String, Cookie> cookieMap = getCookieMap();\n        return cookieMap.getOrDefault(name, null);\n    }\n\n    @Override\n    public String getCookieValue(String name) {\n        Cookie cookie = getCookie(name);\n        return cookie == null ? null : cookie.value();\n    }\n\n    @Override\n    public void setCookie(Cookie cookie) {\n        RedantContext.currentContext().addCookie(cookie);\n    }\n\n    @Override\n    public void setCookies() {\n        Set<Cookie> cookies = getCookies();\n        if (!cookies.isEmpty()) {\n            for (Cookie cookie : cookies) {\n                setCookie(cookie);\n            }\n        }\n    }\n\n    @Override\n    public void addCookie(String name, String value) {\n        addCookie(name, value, null);\n    }\n\n    @Override\n    public void addCookie(String name, String value, String domain) {\n        addCookie(name, value, domain, 0);\n    }\n\n    @Override\n    public void addCookie(String name, String value, long maxAge) {\n        addCookie(name, value, null, maxAge);\n    }\n\n    @Override\n    public void addCookie(String name, String value, String domain, long maxAge) {\n        if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(value)) {\n            Cookie cookie = new DefaultCookie(name, value);\n            cookie.setPath(\"/\");\n            if (domain != null && domain.trim().length() > 0) {\n                cookie.setDomain(domain);\n            }\n            if (maxAge > 0) {\n                cookie.setMaxAge(maxAge);\n            }\n            setCookie(cookie);\n        }\n    }\n\n    @Override\n    public boolean deleteCookie(String name) {\n        Cookie cookie = getCookie(name);\n        if (cookie != null) {\n            cookie.setMaxAge(0);\n            cookie.setPath(\"/\");\n            setCookie(cookie);\n            return true;\n        }\n        return false;\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/executor/AbstractExecutor.java",
    "content": "package com.redant.core.executor;\n\nimport com.redant.core.common.constants.CommonConstants;\nimport io.netty.util.concurrent.Future;\nimport io.netty.util.concurrent.Promise;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author houyi\n */\npublic abstract class AbstractExecutor<T> implements Executor<T> {\n\n    private final static Logger LOGGER = LoggerFactory.getLogger(AbstractExecutor.class);\n\n    private java.util.concurrent.Executor eventExecutor;\n\n    public AbstractExecutor() {\n        this(null);\n    }\n\n    public AbstractExecutor(java.util.concurrent.Executor eventExecutor) {\n        this.eventExecutor = eventExecutor == null ? EventExecutorHolder.eventExecutor : eventExecutor;\n    }\n\n    @Override\n    public T execute(Object... request) {\n        return doExecute(request);\n    }\n\n    @Override\n    public Future<T> asyncExecute(Promise<T> promise, Object... request) {\n        if (promise == null) {\n            throw new IllegalArgumentException(\"promise should not be null\");\n        }\n        // 异步执行\n        eventExecutor.execute(new Runnable() {\n            @Override\n            public void run() {\n                try {\n                    T response = doExecute(request);\n                    promise.setSuccess(response);\n                } catch (Exception e) {\n                    promise.setFailure(e);\n                }\n            }\n        });\n        // 返回promise\n        return promise;\n    }\n\n    /**\n     * 执行具体的方法\n     *\n     * @param request 请求对象\n     * @return 返回结果\n     */\n    public abstract T doExecute(Object... request);\n\n    private static final class EventExecutorHolder {\n        private static java.util.concurrent.Executor eventExecutor = new ThreadPoolExecutor(\n                                                                            CommonConstants.EVENT_EXECUTOR_POOL_CORE_SIZE,\n                                                                            CommonConstants.EVENT_EXECUTOR_POOL_MAX_SIZE,\n                                                                            CommonConstants.EVENT_EXECUTOR_POOL_KEEP_ALIVE_SECONDS,\n                                                                            TimeUnit.SECONDS,\n                                                                            new ArrayBlockingQueue<>(CommonConstants.EVENT_EXECUTOR_POOL_BLOCKING_QUEUE_SIZE));\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/executor/Executor.java",
    "content": "package com.redant.core.executor;\n\nimport io.netty.util.concurrent.Future;\nimport io.netty.util.concurrent.Promise;\n\n/**\n * @author houyi\n */\npublic interface Executor<T> {\n\n    /**\n     * 同步执行任务获得结果\n     * @param request 请求对象\n     * @return  结果\n     */\n    T execute(Object... request);\n\n    /**\n     * 异步执行任务获得 Future 结果\n     * @param promise 异步结果包装类\n     * @param request 请求对象\n     * @return  异步结果\n     */\n    Future<T> asyncExecute(Promise<T> promise, Object... request);\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/executor/HttpResponseExecutor.java",
    "content": "package com.redant.core.executor;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.redant.core.common.exception.InvocationException;\nimport com.redant.core.common.util.HttpRenderUtil;\nimport com.redant.core.common.util.HttpRequestUtil;\nimport com.redant.core.context.RedantContext;\nimport com.redant.core.controller.ControllerProxy;\nimport com.redant.core.controller.ProxyInvocation;\nimport com.redant.core.controller.context.ControllerContext;\nimport com.redant.core.controller.context.DefaultControllerContext;\nimport com.redant.core.interceptor.InterceptorHandler;\nimport io.netty.handler.codec.http.FullHttpResponse;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport io.netty.handler.codec.http.cookie.ServerCookieEncoder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * @author houyi\n */\npublic class HttpResponseExecutor extends AbstractExecutor<HttpResponse> {\n\n    private final static Logger LOGGER = LoggerFactory.getLogger(HttpResponseExecutor.class);\n\n    private static ControllerContext controllerContext = DefaultControllerContext.getInstance();\n\n    public static HttpResponseExecutor getInstance() {\n        return HttpResponseExecutorHolder.executor;\n    }\n\n    private HttpResponseExecutor() {\n    }\n\n    @Override\n    public HttpResponse doExecute(Object... request) {\n        HttpRequest httpRequest = (HttpRequest) request[0];\n        // 暂存请求对象\n        // 将request存储到ThreadLocal中去，便于后期在其他地方获取并使用\n        RedantContext.currentContext().setRequest(httpRequest);\n        HttpResponse response = null;\n        try {\n            // 获取参数列表\n            Map<String, List<String>> paramMap = HttpRequestUtil.getParameterMap(httpRequest);\n            // 处理拦截器的前置方法\n            if (!InterceptorHandler.preHandle(paramMap)) {\n                // 先从RedantContext中获取response，检查用户是否设置了response\n                response = RedantContext.currentContext().getResponse();\n                // 若用户没有设置就返回一个默认的\n                if (response == null) {\n                    response = HttpRenderUtil.getBlockedResponse();\n                }\n            } else {\n                // 处理业务逻辑\n                response = invoke(httpRequest);\n                // 处理拦截器的后置方法\n                InterceptorHandler.postHandle(paramMap);\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"Server Internal Error,cause:\", e);\n            response = getErrorResponse(e);\n        } finally {\n            // 构造响应头\n            buildHeaders(response, RedantContext.currentContext());\n            // 释放ThreadLocal对象\n            RedantContext.clear();\n        }\n        return response;\n    }\n\n    private HttpResponse invoke(HttpRequest request) throws Exception {\n        // 根据路由获得具体的ControllerProxy\n        ControllerProxy controllerProxy = controllerContext.getProxy(request.method(), request.uri());\n        if (controllerProxy == null) {\n            return HttpRenderUtil.getNotFoundResponse();\n        }\n        // 调用用户自定义的Controller，获得结果\n        Object result = ProxyInvocation.invoke(controllerProxy);\n        return HttpRenderUtil.render(result, controllerProxy.getRenderType());\n    }\n\n    private HttpResponse getErrorResponse(Exception e) {\n        HttpResponse response;\n        if (e instanceof IllegalArgumentException || e instanceof InvocationException) {\n            response = HttpRenderUtil.getErrorResponse(e.getMessage());\n        } else {\n            response = HttpRenderUtil.getServerErrorResponse();\n        }\n        return response;\n    }\n\n    private void buildHeaders(HttpResponse response, RedantContext redantContext) {\n        if (response == null) {\n            return;\n        }\n        FullHttpResponse fullHttpResponse = (FullHttpResponse) response;\n        fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(fullHttpResponse.content().readableBytes()));\n        // 写cookie\n        Set<Cookie> cookies = redantContext.getCookies();\n        if (CollectionUtil.isNotEmpty(cookies)) {\n            for (Cookie cookie : cookies) {\n                fullHttpResponse.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));\n            }\n        }\n    }\n\n    private static final class HttpResponseExecutorHolder {\n        private static HttpResponseExecutor executor = new HttpResponseExecutor();\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/handler/ControllerDispatcher.java",
    "content": "package com.redant.core.handler;\n\nimport com.redant.core.common.constants.CommonConstants;\nimport com.redant.core.executor.Executor;\nimport com.redant.core.executor.HttpResponseExecutor;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.util.concurrent.*;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * 请求分发控制器\n *\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class ControllerDispatcher extends SimpleChannelInboundHandler<HttpRequest> {\n\n    private final static Logger LOGGER = LoggerFactory.getLogger(ControllerDispatcher.class);\n\n    private static Executor<HttpResponse> executor = HttpResponseExecutor.getInstance();\n\n    @Override\n    public void channelRead0(ChannelHandlerContext ctx, HttpRequest request) {\n        if (CommonConstants.ASYNC_EXECUTE_EVENT) {\n            // 当前通道所持有的线程池\n            EventExecutor channelExecutor = ctx.executor();\n            // 创建一个异步结果，并指定该promise\n            Promise<HttpResponse> promise = new DefaultPromise<>(channelExecutor);\n            // 在自定义线程池中执行业务逻辑，并返回一个异步结果\n            Future<HttpResponse> future = executor.asyncExecute(promise, request);\n            future.addListener(new GenericFutureListener<Future<HttpResponse>>() {\n                @Override\n                public void operationComplete(Future<HttpResponse> f) throws Exception {\n                    // 异步结果执行成功后，取出结果\n                    HttpResponse response = f.get();\n                    // 通过IO线程写响应结果\n                    ctx.channel().writeAndFlush(response);\n                }\n            });\n        } else {\n            // 同步执行\n            HttpResponse response = executor.execute(request);\n            ctx.channel().writeAndFlush(response);\n        }\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n        ctx.close();\n        LOGGER.error(\"ctx close,cause:\", cause);\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/handler/ssl/SslContextHelper.java",
    "content": "package com.redant.core.handler.ssl;\n\nimport cn.hutool.crypto.SecureUtil;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslContextBuilder;\n\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLException;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.UnrecoverableKeyException;\nimport java.security.cert.CertificateException;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * @author houyi.wh\n * @date 2017/11/19\n **/\npublic class SslContextHelper {\n\n    private static final String KEY_STORE_JKS = \"JKS\";\n\n    private static final String ALGORITHM = \"SunX509\";\n\n    private static Map<String,SslContext> contents = new ConcurrentHashMap<String,SslContext>();\n\n    private static String getKey(String keyPath,String keyPassword){\n        if(keyPath==null || keyPath.trim().length()==0 || keyPassword==null || keyPassword.trim().length()==0){\n            return null;\n        }\n        String keyStr = keyPath+\"&\"+keyPassword;\n        return SecureUtil.md5(keyStr);\n    }\n\n    /**\n     * 获取SslContext\n     * @param keyPath\n     * @param keyPassword\n     * @return\n     */\n    public static SslContext getSslContext(String keyPath,String keyPassword){\n        if(keyPath==null || keyPath.trim().length()==0 || keyPassword==null || keyPassword.trim().length()==0){\n            return null;\n        }\n        SslContext sslContext = null;\n        InputStream is = null;\n        try {\n            String key = getKey(keyPath,keyPassword);\n            sslContext = contents.get(key);\n            if(sslContext!=null){\n                return sslContext;\n            }\n\n            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_JKS);\n            is = new FileInputStream(keyPath);\n            keyStore.load(is, keyPassword.toCharArray());\n\n            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(ALGORITHM);\n            keyManagerFactory.init(keyStore,keyPassword.toCharArray());\n\n            sslContext = SslContextBuilder.forServer(keyManagerFactory).build();\n            if(sslContext!=null){\n                contents.put(key,sslContext);\n            }\n        } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | IOException e) {\n            e.printStackTrace();\n        } finally {\n            if(is!=null){\n                try {\n                    is.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        return sslContext;\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/init/InitExecutor.java",
    "content": "package com.redant.core.init;\n\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.ClassScaner;\nimport com.redant.core.common.constants.CommonConstants;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.Constructor;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * @author houyi.wh\n * @date 2019-01-14\n */\npublic final class InitExecutor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(InitExecutor.class);\n    \n    private static AtomicBoolean initialized = new AtomicBoolean(false);\n\n    public static void init() {\n        if (!initialized.compareAndSet(false, true)) {\n            return;\n        }\n        try {\n            Set<Class<?>> classSet = ClassScaner.scanPackageBySuper(CommonConstants.BEAN_SCAN_PACKAGE,InitFunc.class);\n            if (CollectionUtil.isNotEmpty(classSet)) {\n                List<OrderWrapper> initList = new ArrayList<>();\n                for (Class<?> cls : classSet) {\n                    // 如果cls是InitFunc的实现类\n                    if(!cls.isInterface() && InitFunc.class.isAssignableFrom(cls)){\n                        Constructor<?> constructor = cls.getDeclaredConstructor();\n                        constructor.setAccessible(true);\n                        InitFunc initFunc = (InitFunc)constructor.newInstance();\n                        LOGGER.info(\"[InitExecutor] found InitFunc: \" + initFunc.getClass().getCanonicalName());\n                        insertSorted(initList, initFunc);\n                    }\n                }\n                for (OrderWrapper w : initList) {\n                    w.func.init();\n                    LOGGER.info(\"[InitExecutor] initialized: {} with order={}\", w.func.getClass().getCanonicalName(), w.order);\n                }\n            }\n        } catch (Exception ex) {\n            LOGGER.warn(\"[InitExecutor] init failed\", ex);\n            ex.printStackTrace();\n        } catch (Error error) {\n            LOGGER.warn(\"[InitExecutor] init failed with fatal error\", error);\n            error.printStackTrace();\n            throw error;\n        }\n    }\n\n    private static void insertSorted(List<OrderWrapper> list, InitFunc func) {\n        int order = resolveOrder(func);\n        int idx = 0;\n        for (; idx < list.size(); idx++) {\n            // 将func插入到order值比他大的第一个func前面\n            if (list.get(idx).getOrder() > order) {\n                break;\n            }\n        }\n        list.add(idx, new OrderWrapper(order, func));\n    }\n\n    private static int resolveOrder(InitFunc func) {\n        if (!func.getClass().isAnnotationPresent(InitOrder.class)) {\n            return InitOrder.LOWEST_PRECEDENCE;\n        } else {\n            return func.getClass().getAnnotation(InitOrder.class).value();\n        }\n    }\n\n    private InitExecutor() {}\n\n    private static class OrderWrapper {\n        private final int order;\n        private final InitFunc func;\n\n        OrderWrapper(int order, InitFunc func) {\n            this.order = order;\n            this.func = func;\n        }\n\n        int getOrder() {\n            return order;\n        }\n\n        InitFunc getFunc() {\n            return func;\n        }\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/init/InitFunc.java",
    "content": "package com.redant.core.init;\n\n/**\n * 初始化接口\n * @author houyi.wh\n * @date 2019-01-14\n */\npublic interface InitFunc {\n\n    /**\n     * 初始化方法\n     */\n    void init();\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/init/InitOrder.java",
    "content": "package com.redant.core.init;\n\nimport java.lang.annotation.*;\n\n/**\n * 初始化器的排序规则，升序排序\n * @author houyi.wh\n * @date 2019-01-14\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE})\n@Documented\npublic @interface InitOrder {\n\n    /**\n     * 最低优先级\n     */\n    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;\n    /**\n     * 最高优先级\n     */\n    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;\n\n    /**\n     * The order value. Lowest precedence by default.\n     *\n     * @return the order value\n     */\n    int value() default LOWEST_PRECEDENCE;\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/interceptor/Interceptor.java",
    "content": "package com.redant.core.interceptor;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author houyi\n **/\npublic abstract class Interceptor {\n\n    /**\n     * 拦截器的前置处理方法\n     */\n    public boolean preHandle(Map<String, List<String>> paramMap){\n        return true;\n    }\n\n    /**\n     * 拦截器的后置处理方法\n     */\n    public abstract void postHandle(Map<String, List<String>> paramMap);\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/interceptor/InterceptorBuilder.java",
    "content": "package com.redant.core.interceptor;\n\nimport java.util.List;\n\n/**\n * 用户可以通过该接口自行定义需要生效哪些拦截器\n * @author houyi\n **/\npublic interface InterceptorBuilder {\n\n    /**\n     * 构造拦截器列表\n     * @return 列表\n     */\n    List<Interceptor> build();\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/interceptor/InterceptorHandler.java",
    "content": "package com.redant.core.interceptor;\n\nimport cn.hutool.core.collection.CollectionUtil;\n\nimport java.util.*;\n\n/**\n * @author houyi.wh\n * @date 2017/11/15\n **/\npublic class InterceptorHandler {\n\n    public static boolean preHandle(Map<String, List<String>> paramMap){\n        List<Interceptor> interceptors = InterceptorProvider.getInterceptors();\n        if(CollectionUtil.isEmpty(interceptors)){\n            return true;\n        }\n        for(Interceptor interceptor : interceptors){\n            if(!interceptor.preHandle(paramMap)){\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static void postHandle(Map<String, List<String>> paramMap){\n        List<Interceptor> interceptors = InterceptorProvider.getInterceptors();\n        if(CollectionUtil.isEmpty(interceptors)){\n            return;\n        }\n        for(Interceptor interceptor : interceptors){\n            interceptor.postHandle(paramMap);\n        }\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/interceptor/InterceptorProvider.java",
    "content": "package com.redant.core.interceptor;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.ClassScaner;\nimport com.redant.core.anno.Order;\nimport com.redant.core.common.constants.CommonConstants;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * @author houyi\n **/\npublic class InterceptorProvider {\n\n    private static volatile boolean loaded = false;\n\n    private static volatile InterceptorBuilder builder = null;\n\n    public static List<Interceptor> getInterceptors(){\n        // 优先获取用户自定义的 InterceptorBuilder 构造的 Interceptor\n        if(!loaded){\n            synchronized (InterceptorProvider.class) {\n                if(!loaded) {\n                    Set<Class<?>> builders = ClassScaner.scanPackageBySuper(CommonConstants.INTERCEPTOR_SCAN_PACKAGE, InterceptorBuilder.class);\n                    if (CollectionUtil.isNotEmpty(builders)) {\n                        try {\n                            for (Class<?> cls : builders) {\n                                builder = (InterceptorBuilder) cls.newInstance();\n                                break;\n                            }\n                        } catch (IllegalAccessException | InstantiationException e) {\n                            e.printStackTrace();\n                        }\n                    }\n                    loaded = true;\n                }\n            }\n        }\n        if(builder!=null){\n            return builder.build();\n        }\n        // 获取不到时，再扫描所有指定目录下的 Interceptor\n        return InterceptorsHolder.interceptors;\n    }\n\n    static class InterceptorsHolder {\n\n        static List<Interceptor> interceptors;\n\n        static {\n            interceptors = scanInterceptors();\n        }\n\n        private static List<Interceptor> scanInterceptors() {\n            Set<Class<?>> classSet = ClassScaner.scanPackageBySuper(CommonConstants.INTERCEPTOR_SCAN_PACKAGE,Interceptor.class);\n            if(CollectionUtil.isEmpty(classSet)){\n                return Collections.emptyList();\n            }\n            List<InterceptorWrapper> wrappers = new ArrayList<>(classSet.size());\n            try {\n                for (Class<?> cls : classSet) {\n                    Interceptor interceptor =(Interceptor)cls.newInstance();\n                    insertSorted(wrappers,interceptor);\n                }\n            }catch (IllegalAccessException | InstantiationException e) {\n                e.printStackTrace();\n            }\n            return wrappers.stream()\n                    .map(InterceptorWrapper::getInterceptor)\n                    .collect(Collectors.toList());\n        }\n\n        private static void insertSorted(List<InterceptorWrapper> list, Interceptor interceptor) {\n            int order = resolveOrder(interceptor);\n            int idx = 0;\n            for (; idx < list.size(); idx++) {\n                // 将当前interceptor插入到order值比他大的第一个interceptor前面\n                if (list.get(idx).getOrder() > order) {\n                    break;\n                }\n            }\n            list.add(idx, new InterceptorWrapper(order, interceptor));\n        }\n\n        private static int resolveOrder(Interceptor interceptor) {\n            if (!interceptor.getClass().isAnnotationPresent(Order.class)) {\n                return Order.LOWEST_PRECEDENCE;\n            } else {\n                return interceptor.getClass().getAnnotation(Order.class).value();\n            }\n        }\n\n        private static class InterceptorWrapper {\n            private final int order;\n            private final Interceptor interceptor;\n\n            InterceptorWrapper(int order, Interceptor interceptor) {\n                this.order = order;\n                this.interceptor = interceptor;\n            }\n\n            int getOrder() {\n                return order;\n            }\n\n            Interceptor getInterceptor() {\n                return interceptor;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/render/RenderType.java",
    "content": "package com.redant.core.render;\n\n/**\n * 返回的响应类型\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic enum RenderType {\n\n\t/**\n\t * JSON\n\t */\n\tJSON(\"application/json;charset=UTF-8\"),\n\t/**\n\t * XML\n\t */\n\tXML(\"text/xml;charset=UTF-8\"),\n\t/**\n\t * TEXT\n\t */\n\tTEXT(\"text/plain;charset=UTF-8\"),\n\t/**\n\t * HTML\n\t */\n\tHTML(\"text/html;charset=UTF-8\");\n\n\tprivate String contentType;\n\n\tRenderType(String contentType){\n\t\tthis.contentType = contentType;\n\t}\n\n\tpublic String getContentType() {\n\t\treturn contentType;\n\t}\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/BadClientSilencer.java",
    "content": "/*\n * Copyright 2015 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License,\n * version 2.0 (the \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at:\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\npackage com.redant.core.router;\n\nimport io.netty.channel.ChannelHandler.Sharable;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.internal.logging.InternalLogger;\nimport io.netty.util.internal.logging.InternalLoggerFactory;\n\n/**\n * This utility handler should be put at the last position of the inbound pipeline to\n * catch all exceptions caused by bad client (closed connection, malformed request etc.)\n * and server processing, then close the connection.\n *\n * By default exceptions are logged to Netty internal LOGGER. You may need to override\n * {@link #onUnknownMessage(Object)}, {@link #onBadClient(Throwable)}, and\n * {@link #onBadServer(Throwable)} to log to more suitable places.\n */\n@Sharable\npublic class BadClientSilencer extends SimpleChannelInboundHandler<Object> {\n    private static final InternalLogger log = InternalLoggerFactory.getInstance(BadClientSilencer.class);\n\n    /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */\n    protected void onUnknownMessage(Object msg) {\n        log.warn(\"Unknown msg: \" + msg);\n    }\n\n    /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */\n    protected void onBadClient(Throwable e) {\n        log.warn(\"Caught exception (maybe client is bad)\", e);\n    }\n\n    /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */\n    protected void onBadServer(Throwable e) {\n        log.warn(\"Caught exception (maybe server is bad)\", e);\n    }\n\n    //----------------------------------------------------------------------------\n\n    @Override\n    public void channelRead0(ChannelHandlerContext ctx, Object msg) {\n        // This handler is the last inbound handler.\n        // This means msg has not been handled by any previous handler.\n        ctx.close();\n\n        if (msg != LastHttpContent.EMPTY_LAST_CONTENT) {\n            onUnknownMessage(msg);\n        }\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {\n        ctx.close();\n\n        // To clarify where exceptions are from, imports are not used\n        if (e instanceof java.io.IOException                            ||  // Connection reset by peer, Broken pipe\n            e instanceof java.nio.channels.ClosedChannelException       ||\n            e instanceof io.netty.handler.codec.DecoderException        ||\n            e instanceof io.netty.handler.codec.CorruptedFrameException ||  // Bad WebSocket frame\n            e instanceof IllegalArgumentException             ||  // Use https://... to connect to HTTP server\n            e instanceof javax.net.ssl.SSLException                     ||  // Use http://... to connect to HTTPS server\n            e instanceof io.netty.handler.ssl.NotSslRecordException) {\n            onBadClient(e);  // Maybe client is bad\n        } else {\n            onBadServer(e);  // Maybe server is bad\n        }\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/MethodlessRouter.java",
    "content": "/*\n * Copyright 2015 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License,\n * version 2.0 (the \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at:\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\npackage com.redant.core.router;\n\n/**\n * Router that contains information about route matching orders, but doesn't\n * contain information about HTTP request methods.\n *\n * <p>Routes are devided into 3 sections: \"first\", \"last\", and \"other\".\n * Routes in \"first\" are matched first, then in \"other\", then in \"last\".\n */\nfinal class MethodlessRouter<T> {\n    private final OrderlessRouter<T> first = new OrderlessRouter<T>();\n    private final OrderlessRouter<T> other = new OrderlessRouter<T>();\n    private final OrderlessRouter<T> last = new OrderlessRouter<T>();\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Returns the \"first\" router; routes in this router will be matched first.\n     */\n    public OrderlessRouter<T> first() {\n        return first;\n    }\n\n    /**\n     * Returns the \"other\" router; routes in this router will be matched after\n     * those in the \"first\" router, but before those in the \"last\" router.\n     */\n    public OrderlessRouter<T> other() {\n        return other;\n    }\n\n    /**\n     * Returns the \"last\" router; routes in this router will be matched last.\n     */\n    public OrderlessRouter<T> last() {\n        return last;\n    }\n\n    /**\n     * Returns the number of routes in this router.\n     */\n    public int size() {\n        return first.routes().size() + other.routes().size() + last.routes().size();\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Adds route to the \"first\" section.\n     *\n     * <p>A path pattern can only point to one target. This method does nothing if the pattern\n     * has already been added.\n     */\n    public MethodlessRouter<T> addRouteFirst(String pathPattern, T target) {\n        first.addRoute(pathPattern, target);\n        return this;\n    }\n\n    /**\n     * Adds route to the \"other\" section.\n     *\n     * <p>A path pattern can only point to one target. This method does nothing if the pattern\n     * has already been added.\n     */\n    public MethodlessRouter<T> addRoute(String pathPattern, T target) {\n        other.addRoute(pathPattern, target);\n        return this;\n    }\n\n    /**\n     * Adds route to the \"last\" section.\n     *\n     * <p>A path pattern can only point to one target. This method does nothing if the pattern\n     * has already been added.\n     */\n    public MethodlessRouter<T> addRouteLast(String pathPattern, T target) {\n        last.addRoute(pathPattern, target);\n        return this;\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Removes the route specified by the path pattern.\n     */\n    public void removePathPattern(String pathPattern) {\n        first.removePathPattern(pathPattern);\n        other.removePathPattern(pathPattern);\n        last.removePathPattern(pathPattern);\n    }\n\n    /**\n     * Removes all routes leading to the target.\n     */\n    public void removeTarget(T target) {\n        first.removeTarget(target);\n        other.removeTarget(target);\n        last.removeTarget(target);\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * @return {@code null} if no match\n     */\n    public RouteResult<T> route(String uri, String decodedPath, String[] pathTokens) {\n        RouteResult<T> ret = first.route(uri, decodedPath, pathTokens);\n        if (ret != null) {\n            return ret;\n        }\n\n        ret = other.route(uri, decodedPath, pathTokens);\n        if (ret != null) {\n            return ret;\n        }\n\n        ret = last.route(uri, decodedPath, pathTokens);\n        if (ret != null) {\n            return ret;\n        }\n\n        return null;\n    }\n\n    /**\n     * Checks if there's any matching route.\n     */\n    public boolean anyMatched(String[] requestPathTokens) {\n        return first.anyMatched(requestPathTokens) ||\n                other.anyMatched(requestPathTokens) ||\n                last.anyMatched(requestPathTokens);\n    }\n\n    /**\n     * Given a target and params, this method tries to do the reverse routing\n     * and returns the URI.\n     *\n     * <p>Placeholders in the path pattern will be filled with the params.\n     * The params can be a map of {@code placeholder name -> value}\n     * or ordered values.\n     *\n     * <p>If a param doesn't have a corresponding placeholder, it will be put\n     * to the query part of the result URI.\n     *\n     * @return {@code null} if there's no match\n     */\n    public String uri(T target, Object... params) {\n        String ret = first.uri(target, params);\n        if (ret != null) {\n            return ret;\n        }\n\n        ret = other.uri(target, params);\n        if (ret != null) {\n            return ret;\n        }\n\n        return last.uri(target, params);\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/OrderlessRouter.java",
    "content": "/*\n * Copyright 2015 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License,\n * version 2.0 (the \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at:\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\npackage com.redant.core.router;\n\nimport io.netty.util.internal.ObjectUtil;\nimport io.netty.util.internal.logging.InternalLogger;\nimport io.netty.util.internal.logging.InternalLoggerFactory;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Router that doesn't contain information about HTTP request methods and route\n * matching orders.\n */\nfinal class OrderlessRouter<T> {\n    private static final InternalLogger log = InternalLoggerFactory.getInstance(OrderlessRouter.class);\n\n    /**\n     * A path pattern can only point to one target\n      */\n    private final Map<PathPattern, T> routes = new HashMap<PathPattern, T>();\n\n    /**\n     * Reverse index to create reverse routes fast (a target can have multiple path patterns)\n      */\n    private final Map<T, Set<PathPattern>> reverseRoutes = new HashMap<T, Set<PathPattern>>();\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Returns all routes in this router, an unmodifiable map of {@code PathPattern -> Target}.\n     */\n    public Map<PathPattern, T> routes() {\n        return Collections.unmodifiableMap(routes);\n    }\n\n    /**\n     * This method does nothing if the path pattern has already been added.\n     * A path pattern can only point to one target.\n     */\n    public OrderlessRouter<T> addRoute(String pathPattern, T target) {\n        PathPattern p = new PathPattern(pathPattern);\n        if (routes.containsKey(p)) {\n            return this;\n        }\n\n        routes.put(p, target);\n        addReverseRoute(target, p);\n        return this;\n    }\n\n    private void addReverseRoute(T target, PathPattern pathPattern) {\n        Set<PathPattern> patterns = reverseRoutes.get(target);\n        if (patterns == null) {\n            patterns = new HashSet<PathPattern>();\n            patterns.add(pathPattern);\n            reverseRoutes.put(target, patterns);\n        } else {\n            patterns.add(pathPattern);\n        }\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Removes the route specified by the path pattern.\n     */\n    public void removePathPattern(String pathPattern) {\n        PathPattern p = new PathPattern(pathPattern);\n        T target = routes.remove(p);\n        if (target == null) {\n            return;\n        }\n\n        Set<PathPattern> paths = reverseRoutes.remove(target);\n        paths.remove(p);\n    }\n\n    /**\n     * Removes all routes leading to the target.\n     */\n    public void removeTarget(T target) {\n        Set<PathPattern> patterns = reverseRoutes.remove(ObjectUtil.checkNotNull(target, \"target\"));\n        if (patterns == null) {\n            return;\n        }\n\n        // A pattern can only point to one target.\n        // A target can have multiple patterns.\n        // Remove all patterns leading to this target.\n        for (PathPattern pattern : patterns) {\n            routes.remove(pattern);\n        }\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * @return {@code null} if no match\n     */\n    public RouteResult<T> route(String uri, String decodedPath, String[] pathTokens) {\n        // Optimize: reuse requestPathTokens and pathParams in the loop\n        Map<String, String> pathParams = new HashMap<String, String>();\n        for (Map.Entry<PathPattern, T> entry : routes.entrySet()) {\n            PathPattern pattern = entry.getKey();\n            if (pattern.match(pathTokens, pathParams)) {\n                T target = entry.getValue();\n                return new RouteResult<T>(uri, decodedPath, pathParams, Collections.<String, List<String>>emptyMap(), target);\n            }\n\n            // Reset for the next try\n            pathParams.clear();\n        }\n\n        return null;\n    }\n\n    /**\n     * Checks if there's any matching route.\n     */\n    public boolean anyMatched(String[] requestPathTokens) {\n        Map<String, String> pathParams = new HashMap<String, String>();\n        for (PathPattern pattern : routes.keySet()) {\n            if (pattern.match(requestPathTokens, pathParams)) {\n                return true;\n            }\n\n            // Reset for the next loop\n            pathParams.clear();\n        }\n\n        return false;\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Given a target and params, this method tries to do the reverse routing\n     * and returns the URI.\n     *\n     * <p>Placeholders in the path pattern will be filled with the params.\n     * The params can be a map of {@code placeholder name -> value}\n     * or ordered values.\n     *\n     * <p>If a param doesn't have a corresponding placeholder, it will be put\n     * to the query part of the result URI.\n     *\n     * @return {@code null} if there's no match\n     */\n    @SuppressWarnings(\"unchecked\")\n    public String uri(T target, Object... params) {\n        if (params.length == 0) {\n            return uri(target, Collections.emptyMap());\n        }\n\n        if (params.length == 1 && params[0] instanceof Map<?, ?>) {\n            return pathMap(target, (Map<Object, Object>) params[0]);\n        }\n\n        if (params.length % 2 == 1) {\n            throw new IllegalArgumentException(\"Missing value for param: \" + params[params.length - 1]);\n        }\n\n        Map<Object, Object> map = new HashMap<Object, Object>(params.length / 2);\n        for (int i = 0; i < params.length; i += 2) {\n            String key = params[i].toString();\n            String value = params[i + 1].toString();\n            map.put(key, value);\n        }\n        return pathMap(target, map);\n    }\n\n    /**\n     * @return {@code null} if there's no match, or the params can't be UTF-8 encoded\n     */\n    private String pathMap(T target, Map<Object, Object> params) {\n        Set<PathPattern> patterns = reverseRoutes.get(target);\n        if (patterns == null) {\n            return null;\n        }\n\n        try {\n            // The best one is the one with minimum number of params in the query\n            String bestCandidate = null;\n            int minQueryParams = Integer.MAX_VALUE;\n\n            boolean matched = true;\n            Set<String> usedKeys = new HashSet<String>();\n\n            for (PathPattern pattern : patterns) {\n                matched = true;\n                usedKeys.clear();\n\n                // \"+ 16\": Just in case the part befor that is 0\n                int initialCapacity = pattern.pattern().length() + 20 * params.size() + 16;\n                StringBuilder b = new StringBuilder(initialCapacity);\n\n                for (String token : pattern.tokens()) {\n                    b.append('/');\n\n                    if (token.length() > 0 && token.charAt(0) == ':') {\n                        String key = token.substring(1);\n                        Object value = params.get(key);\n                        if (value == null) {\n                            matched = false;\n                            break;\n                        }\n\n                        usedKeys.add(key);\n                        b.append(value.toString());\n                    } else {\n                        b.append(token);\n                    }\n                }\n\n                if (matched) {\n                    int numQueryParams = params.size() - usedKeys.size();\n                    if (numQueryParams < minQueryParams) {\n                        if (numQueryParams > 0) {\n                            boolean firstQueryParam = true;\n\n                            for (Map.Entry<Object, Object> entry : params.entrySet()) {\n                                String key = entry.getKey().toString();\n                                if (!usedKeys.contains(key)) {\n                                    if (firstQueryParam) {\n                                        b.append('?');\n                                        firstQueryParam = false;\n                                    } else {\n                                        b.append('&');\n                                    }\n\n                                    String value = entry.getValue().toString();\n\n                                    // May throw UnsupportedEncodingException\n                                    b.append(URLEncoder.encode(key, \"UTF-8\"));\n\n                                    b.append('=');\n\n                                    // May throw UnsupportedEncodingException\n                                    b.append(URLEncoder.encode(value, \"UTF-8\"));\n                                }\n                            }\n                        }\n\n                        bestCandidate = b.toString();\n                        minQueryParams = numQueryParams;\n                    }\n                }\n            }\n\n            return bestCandidate;\n        } catch (UnsupportedEncodingException e) {\n            log.warn(\"Params can't be UTF-8 encoded: \" + params);\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/PathPattern.java",
    "content": "/*\n * Copyright 2015 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License,\n * version 2.0 (the \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at:\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\npackage com.redant.core.router;\n\nimport io.netty.util.internal.ObjectUtil;\n\nimport java.util.Map;\n\n/**\n * The pattern can contain constants or placeholders, example:\n * {@code constant1/:placeholder1/constant2/:*}.\n *\n * <p>{@code :*} is a special placeholder to catch the rest of the path\n * (may include slashes). If exists, it must appear at the end of the path.\n *\n * <p>The pattern must not contain query, example:\n * {@code constant1/constant2?foo=bar}.\n *\n * <p>The pattern will be broken to tokens, example:\n * {@code [\"constant1\", \":variable\", \"constant2\", \":*\"]}\n */\nfinal class PathPattern {\n    public static String removeSlashesAtBothEnds(String path) {\n        ObjectUtil.checkNotNull(path, \"path\");\n\n        if (path.isEmpty()) {\n            return path;\n        }\n\n        int beginIndex = 0;\n        while (beginIndex < path.length() && path.charAt(beginIndex) == '/') {\n            beginIndex++;\n        }\n        if (beginIndex == path.length()) {\n            return \"\";\n        }\n\n        int endIndex = path.length() - 1;\n        while (endIndex > beginIndex && path.charAt(endIndex) == '/') {\n            endIndex--;\n        }\n\n        return path.substring(beginIndex, endIndex + 1);\n    }\n\n    //--------------------------------------------------------------------------\n\n    private final String pattern;\n    private final String[] tokens;\n\n    /**\n     * The pattern must not contain query, example:\n     * {@code constant1/constant2?foo=bar}.\n     *\n     * <p>The pattern will be stored without slashes at both ends.\n     */\n    public PathPattern(String pattern) {\n        if (pattern.contains(\"?\")) {\n            throw new IllegalArgumentException(\"Path pattern must not contain query\");\n        }\n\n        this.pattern = removeSlashesAtBothEnds(ObjectUtil.checkNotNull(pattern, \"pattern\"));\n        this.tokens = this.pattern.split(\"/\");\n    }\n\n    /**\n     * Returns the pattern given at the constructor, without slashes at both ends.\n     */\n    public String pattern() {\n        return pattern;\n    }\n\n    /**\n     * Returns the pattern given at the constructor, without slashes at both ends,\n     * and split by {@code '/'}.\n     */\n    public String[] tokens() {\n        return tokens;\n    }\n\n    //--------------------------------------------------------------------------\n    // Instances of this class can be conveniently used as Map keys.\n\n    @Override\n    public int hashCode() {\n        return pattern.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n\n        if (!(o instanceof PathPattern)) {\n            return false;\n        }\n\n        PathPattern otherPathPattern = (PathPattern) o;\n        return pattern.equals(otherPathPattern.pattern);\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * {@code params} will be updated with params embedded in the request path.\n     *\n     * <p>This method signature is designed so that {@code requestPathTokens} and {@code params}\n     * can be created only once then reused, to optimize for performance when a\n     * large number of path patterns need to be matched.\n     *\n     * @return {@code false} if not matched; in this case params should be reset\n     */\n    public boolean match(String[] requestPathTokens, Map<String, String> params) {\n        if (tokens.length == requestPathTokens.length) {\n            for (int i = 0; i < tokens.length; i++) {\n                String key = tokens[i];\n                String value = requestPathTokens[i];\n\n                if (key.length() > 0 && key.charAt(0) == ':') {\n                    // This is a placeholder\n                    params.put(key.substring(1), value);\n                } else if (!key.equals(value)) {\n                    // This is a constant\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        if (tokens.length > 0 &&\n                tokens[tokens.length - 1].equals(\":*\") &&\n                tokens.length <= requestPathTokens.length) {\n            // The first part\n            for (int i = 0; i < tokens.length - 2; i++) {\n                String key = tokens[i];\n                String value = requestPathTokens[i];\n\n                if (key.length() > 0 && key.charAt(0) == ':') {\n                    // This is a placeholder\n                    params.put(key.substring(1), value);\n                } else if (!key.equals(value)) {\n                    // This is a constant\n                    return false;\n                }\n            }\n\n            // The last :* part\n            StringBuilder b = new StringBuilder(requestPathTokens[tokens.length - 1]);\n            for (int i = tokens.length; i < requestPathTokens.length; i++) {\n                b.append('/');\n                b.append(requestPathTokens[i]);\n            }\n            params.put(\"*\", b.toString());\n\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/RouteResult.java",
    "content": "/*\n * Copyright 2015 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License,\n * version 2.0 (the \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at:\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\npackage com.redant.core.router;\n\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.util.internal.ObjectUtil;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Result of calling {@link Router#route(HttpMethod, String)}.\n */\npublic class RouteResult<T> {\n    private final String uri;\n    private final String decodedPath;\n\n    private final Map<String, String> pathParams;\n    private final Map<String, List<String>> queryParams;\n\n    private final T target;\n\n    /**\n     * The maps will be wrapped in Collections.unmodifiableMap.\n     */\n    public RouteResult(\n            String uri, String decodedPath,\n            Map<String, String> pathParams, Map<String, List<String>> queryParams,\n            T target\n    ) {\n        this.uri = ObjectUtil.checkNotNull(uri, \"uri\");\n        this.decodedPath = ObjectUtil.checkNotNull(decodedPath, \"decodedPath\");\n        this.pathParams = Collections.unmodifiableMap(ObjectUtil.checkNotNull(pathParams, \"pathParams\"));\n        this.queryParams = Collections.unmodifiableMap(ObjectUtil.checkNotNull(queryParams, \"queryParams\"));\n        this.target = ObjectUtil.checkNotNull(target, \"target\");\n    }\n\n    /**\n     * Returns the original request URI.\n     */\n    public String uri() {\n        return uri;\n    }\n\n    /**\n     * Returns the decoded request path.\n     */\n    public String decodedPath() {\n        return decodedPath;\n    }\n\n    /**\n     * Returns all params embedded in the request path.\n     */\n    public Map<String, String> pathParams() {\n        return pathParams;\n    }\n\n    /**\n     * Returns all params in the query part of the request URI.\n     */\n    public Map<String, List<String>> queryParams() {\n        return queryParams;\n    }\n\n    public T target() {\n        return target;\n    }\n\n    //----------------------------------------------------------------------------\n    // Utilities to get params.\n\n    /**\n     * Extracts the first matching param in {@code queryParams}.\n     *\n     * @return {@code null} if there's no match\n     */\n    public String queryParam(String name) {\n        List<String> values = queryParams.get(name);\n        return (values == null) ? null : values.get(0);\n    }\n\n    /**\n     * Extracts the param in {@code pathParams} first, then falls back to the first matching\n     * param in {@code queryParams}.\n     *\n     * @return {@code null} if there's no match\n     */\n    public String param(String name) {\n        String pathValue = pathParams.get(name);\n        return (pathValue == null) ? queryParam(name) : pathValue;\n    }\n\n    /**\n     * Extracts all params in {@code pathParams} and {@code queryParams} matching the name.\n     *\n     * @return Unmodifiable list; the list is empty if there's no match\n     */\n    public List<String> params(String name) {\n        List<String> values = queryParams.get(name);\n        String value = pathParams.get(name);\n\n        if (values == null) {\n            return (value == null) ? Collections.<String>emptyList() : Collections.singletonList(value);\n        }\n\n        if (value == null) {\n            return Collections.unmodifiableList(values);\n        } else {\n            List<String> aggregated = new ArrayList<String>(values.size() + 1);\n            aggregated.addAll(values);\n            aggregated.add(value);\n            return Collections.unmodifiableList(aggregated);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"{target:\"+target+\",decodedPath:\"+decodedPath+\",queryParams:\"+queryParams+\"}\";\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/Router.java",
    "content": "/*\n * Copyright 2015 The Netty Project\n *\n * The Netty Project licenses this file to you under the Apache License,\n * version 2.0 (the \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at:\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations\n * under the License.\n */\npackage com.redant.core.router;\n\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.QueryStringDecoder;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\n\n/**\n * Router that contains information about both route matching orders and\n * HTTP request methods.\n *\n * <p>Routes are devided into 3 sections: \"first\", \"last\", and \"other\".\n * Routes in \"first\" are matched first, then in \"other\", then in \"last\".\n *\n * <h3>Create router</h3>\n *\n * <p>Route targets can be any type. In the below example, targets are classes:\n *\n * <pre>\n * {@code\n * Router<Class> router = new Router<Class>()\n *   .GET      (\"/articles\",     IndexHandler.class)\n *   .GET      (\"/articles/:id\", ShowHandler.class)\n *   .POST     (\"/articles\",     CreateHandler.class)\n *   .GET      (\"/download/:*\",  DownloadHandler.class)  // \":*\" must be the last token\n *   .GET_FIRST(\"/articles/new\", NewHandler.class);      // This will be matched first\n * }\n * </pre>\n *\n * <p>Slashes at both ends are ignored. These are the same:\n *\n * <pre>\n * {@code\n * router.GET(\"articles\",   IndexHandler.class);\n * router.GET(\"/articles\",  IndexHandler.class);\n * router.GET(\"/articles/\", IndexHandler.class);\n * }\n * </pre>\n *\n * <p>You can remove routes by target or by path pattern:\n *\n * <pre>\n * {@code\n * router.removeTarget(IndexHandler.class);\n * router.removePathPattern(\"/articles\");\n * }\n * </pre>\n *\n * <h3>Match with request method and URI</h3>\n *\n * <p>Use {@link #route(HttpMethod, String)}.\n *\n * <p>From the {@link RouteResult} you can extract params embedded in\n * the path and from the query part of the request URI.\n *\n * <h3>404 Not Found target</h3>\n *\n * <p>Use {@link #notFound(Object)}. It will be used as the target\n * when there's no match.\n *\n * <pre>\n * {@code\n * router.notFound(My404Handler.class);\n * }\n * </pre>\n *\n * <h3>Create reverse route</h3>\n *\n * <p>Use {@code #uri}:\n *\n * <pre>\n * {@code\n * router.uri(HttpMethod.GET, IndexHandler.class);\n * // Returns \"/articles\"\n * }\n * </pre>\n *\n * <p>You can skip HTTP method if there's no confusion:\n *\n * <pre>\n * {@code\n * router.uri(CreateHandler.class);\n * // Also returns \"/articles\"\n * }\n * </pre>\n *\n * <p>You can specify params as map:\n *\n * <pre>\n * {@code\n * // Things in params will be converted to String\n * Map<Object, Object> params = new HashMap<Object, Object>();\n * params.put(\"id\", 123);\n * router.uri(ShowHandler.class, params);\n * // Returns \"/articles/123\"\n * }\n * </pre>\n *\n * <p>Convenient way to specify params:\n *\n * <pre>\n * {@code\n * router.uri(ShowHandler.class, \"id\", 123);\n * // Returns \"/articles/123\"\n * }\n * </pre>\n *\n * <h3>Allowed methods</h3>\n *\n * <p>If you want to implement\n * <a href=\"https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods\">OPTIONS</a>\n * or\n * <a href=\"https://en.wikipedia.org/wiki/Cross-origin_resource_sharing\">CORS</a>,\n * you can use {@link #allowedMethods(String)}.\n *\n * <p>For {@code OPTIONS *}, use {@link #allAllowedMethods()}.\n */\npublic class Router<T> {\n    private final Map<HttpMethod, MethodlessRouter<T>> routers =\n            new HashMap<HttpMethod, MethodlessRouter<T>>();\n\n    private final MethodlessRouter<T> anyMethodRouter =\n            new MethodlessRouter<T>();\n\n    private T notFound;\n\n    //--------------------------------------------------------------------------\n    // Design decision:\n    // We do not allow access to routers and anyMethodRouter, because we don't\n    // want to expose MethodlessRouter, OrderlessRouter, and PathPattern.\n    // Exposing those will complicate the use of this package.\n\n    /**\n     * Returns the fallback target for use when there's no match at\n     * {@link #route(HttpMethod, String)}.\n     */\n    public T notFound() {\n        return notFound;\n    }\n\n    /**\n     * Returns the number of routes in this router.\n     */\n    public int size() {\n        int ret = anyMethodRouter.size();\n\n        for (MethodlessRouter<T> router : routers.values()) {\n            ret += router.size();\n        }\n\n        return ret;\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Add route to the \"first\" section.\n     *\n     * <p>A path pattern can only point to one target. This method does nothing if the pattern\n     * has already been added.\n     */\n    public Router<T> addRouteFirst(HttpMethod method, String pathPattern, T target) {\n        getMethodlessRouter(method).addRouteFirst(pathPattern, target);\n        return this;\n    }\n\n    /**\n     * Add route to the \"other\" section.\n     *\n     * <p>A path pattern can only point to one target. This method does nothing if the pattern\n     * has already been added.\n     */\n    public Router<T> addRoute(HttpMethod method, String pathPattern, T target) {\n        getMethodlessRouter(method).addRoute(pathPattern, target);\n        return this;\n    }\n\n    /**\n     * Add route to the \"last\" section.\n     *\n     * <p>A path pattern can only point to one target. This method does nothing if the pattern\n     * has already been added.\n     */\n    public Router<T> addRouteLast(HttpMethod method, String pathPattern, T target) {\n        getMethodlessRouter(method).addRouteLast(pathPattern, target);\n        return this;\n    }\n\n    /**\n     * Sets the fallback target for use when there's no match at\n     * {@link #route(HttpMethod, String)}.\n     */\n    public Router<T> notFound(T target) {\n        this.notFound = target;\n        return this;\n    }\n\n    private MethodlessRouter<T> getMethodlessRouter(HttpMethod method) {\n        if (method == null) {\n            return anyMethodRouter;\n        }\n\n        MethodlessRouter<T> r = routers.get(method);\n        if (r == null) {\n            r = new MethodlessRouter<T>();\n            routers.put(method, r);\n        }\n\n        return r;\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Removes the route specified by the path pattern.\n     */\n    public void removePathPattern(String pathPattern) {\n        for (MethodlessRouter<T> r : routers.values()) {\n            r.removePathPattern(pathPattern);\n        }\n        anyMethodRouter.removePathPattern(pathPattern);\n    }\n\n    /**\n     * Removes all routes leading to the target.\n     */\n    public void removeTarget(T target) {\n        for (MethodlessRouter<T> r : routers.values()) {\n            r.removeTarget(target);\n        }\n        anyMethodRouter.removeTarget(target);\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * If there's no match, returns the result with {@link #notFound(Object) notFound}\n     * as the target if it is set, otherwise returns {@code null}.\n     */\n    public RouteResult<T> route(HttpMethod method, String uri) {\n        MethodlessRouter<T> router = routers.get(method);\n        if (router == null) {\n            router = anyMethodRouter;\n        }\n\n        QueryStringDecoder decoder = new QueryStringDecoder(uri);\n        String[] tokens = decodePathTokens(uri);\n\n        RouteResult<T> ret = router.route(uri, decoder.path(), tokens);\n        if (ret != null) {\n            return new RouteResult<T>(uri, decoder.path(), ret.pathParams(), decoder.parameters(), ret.target());\n        }\n\n        if (router != anyMethodRouter) {\n            ret = anyMethodRouter.route(uri, decoder.path(), tokens);\n            if (ret != null) {\n                return new RouteResult<T>(uri, decoder.path(), ret.pathParams(), decoder.parameters(), ret.target());\n            }\n        }\n\n        if (notFound != null) {\n            return new RouteResult<T>(uri, decoder.path(), Collections.<String, String>emptyMap(), decoder.parameters(), notFound);\n        }\n\n        return null;\n    }\n\n    private String[] decodePathTokens(String uri) {\n        // Need to split the original URI (instead of QueryStringDecoder#path) then decode the tokens (components),\n        // otherwise /test1/123%2F456 will not match /test1/:p1\n\n        int qPos = uri.indexOf(\"?\");\n        String encodedPath = (qPos >= 0) ? uri.substring(0, qPos) : uri;\n\n        String[] encodedTokens = PathPattern.removeSlashesAtBothEnds(encodedPath).split(\"/\");\n\n        String[] decodedTokens = new String[encodedTokens.length];\n        for (int i = 0; i < encodedTokens.length; i++) {\n            String encodedToken = encodedTokens[i];\n            decodedTokens[i] = QueryStringDecoder.decodeComponent(encodedToken);\n        }\n\n        return decodedTokens;\n    }\n\n    //--------------------------------------------------------------------------\n    // For implementing OPTIONS and CORS.\n\n    /**\n     * Returns allowed methods for a specific URI.\n     * <p>\n     * For {@code OPTIONS *}, use {@link #allAllowedMethods()} instead of this method.\n     */\n    public Set<HttpMethod> allowedMethods(String uri) {\n        QueryStringDecoder decoder = new QueryStringDecoder(uri);\n        String[] tokens = PathPattern.removeSlashesAtBothEnds(decoder.path()).split(\"/\");\n\n        if (anyMethodRouter.anyMatched(tokens)) {\n            return allAllowedMethods();\n        }\n\n        Set<HttpMethod> ret = new HashSet<HttpMethod>(routers.size());\n        for (Entry<HttpMethod, MethodlessRouter<T>> entry : routers.entrySet()) {\n            MethodlessRouter<T> router = entry.getValue();\n            if (router.anyMatched(tokens)) {\n                HttpMethod method = entry.getKey();\n                ret.add(method);\n            }\n        }\n\n        return ret;\n    }\n\n    /**\n     * Returns all methods that this router handles. For {@code OPTIONS *}.\n     */\n    public Set<HttpMethod> allAllowedMethods() {\n        if (anyMethodRouter.size() > 0) {\n            Set<HttpMethod> ret = new HashSet<HttpMethod>(9);\n            ret.add(HttpMethod.CONNECT);\n            ret.add(HttpMethod.DELETE);\n            ret.add(HttpMethod.GET);\n            ret.add(HttpMethod.HEAD);\n            ret.add(HttpMethod.OPTIONS);\n            ret.add(HttpMethod.PATCH);\n            ret.add(HttpMethod.POST);\n            ret.add(HttpMethod.PUT);\n            ret.add(HttpMethod.TRACE);\n            return ret;\n        } else {\n            return new HashSet<HttpMethod>(routers.keySet());\n        }\n    }\n\n    //--------------------------------------------------------------------------\n    // Reverse routing.\n\n    /**\n     * Given a target and params, this method tries to do the reverse routing\n     * and returns the URI.\n     *\n     * <p>Placeholders in the path pattern will be filled with the params.\n     * The params can be a map of {@code placeholder name -> value}\n     * or ordered values.\n     *\n     * <p>If a param doesn't have a corresponding placeholder, it will be put\n     * to the query part of the result URI.\n     *\n     * @return {@code null} if there's no match\n     */\n    public String uri(HttpMethod method, T target, Object... params) {\n        MethodlessRouter<T> router = (method == null) ? anyMethodRouter : routers.get(method);\n\n        // Fallback to anyMethodRouter if no router is found for the method\n        if (router == null) {\n            router = anyMethodRouter;\n        }\n\n        String ret = router.uri(target, params);\n        if (ret != null) {\n            return ret;\n        }\n\n        // Fallback to anyMethodRouter if the router was not anyMethodRouter and no path is found\n        return (router != anyMethodRouter) ? anyMethodRouter.uri(target, params) : null;\n    }\n\n    /**\n     * Given a target and params, this method tries to do the reverse routing\n     * and returns the URI.\n     *\n     * <p>Placeholders in the path pattern will be filled with the params.\n     * The params can be a map of {@code placeholder name -> value}\n     * or ordered values.\n     *\n     * <p>If a param doesn't have a corresponding placeholder, it will be put\n     * to the query part of the result URI.\n     *\n     * @return {@code null} if there's no match\n     */\n    public String uri(T target, Object... params) {\n        Collection<MethodlessRouter<T>> rs = routers.values();\n        for (MethodlessRouter<T> r : rs) {\n            String ret = r.uri(target, params);\n            if (ret != null) {\n                return ret;\n            }\n        }\n        return anyMethodRouter.uri(target, params);\n    }\n\n    //--------------------------------------------------------------------------\n\n    /**\n     * Returns visualized routing rules.\n     */\n    @Override\n    public String toString() {\n        // Step 1/2: Dump routers and anyMethodRouter in order\n        int numRoutes = size();\n        List<String> methods = new ArrayList<String>(numRoutes);\n        List<String> patterns = new ArrayList<String>(numRoutes);\n        List<String> targets = new ArrayList<String>(numRoutes);\n\n        // For router\n        for (Entry<HttpMethod, MethodlessRouter<T>> e : routers.entrySet()) {\n            HttpMethod method = e.getKey();\n            MethodlessRouter<T> router = e.getValue();\n            aggregateRoutes(method.toString(), router.first().routes(), methods, patterns, targets);\n            aggregateRoutes(method.toString(), router.other().routes(), methods, patterns, targets);\n            aggregateRoutes(method.toString(), router.last().routes(), methods, patterns, targets);\n        }\n\n        // For anyMethodRouter\n        aggregateRoutes(\"*\", anyMethodRouter.first().routes(), methods, patterns, targets);\n        aggregateRoutes(\"*\", anyMethodRouter.other().routes(), methods, patterns, targets);\n        aggregateRoutes(\"*\", anyMethodRouter.last().routes(), methods, patterns, targets);\n\n        // For notFound\n        if (notFound != null) {\n            methods.add(\"*\");\n            patterns.add(\"*\");\n            targets.add(targetToString(notFound));\n        }\n\n        // Step 2/2: Format the List into aligned columns: <method> <patterns> <target>\n        int maxLengthMethod = maxLength(methods);\n        int maxLengthPattern = maxLength(patterns);\n        String format = \"%-\" + maxLengthMethod + \"s  %-\" + maxLengthPattern + \"s  %s\\n\";\n        int initialCapacity = (maxLengthMethod + 1 + maxLengthPattern + 1 + 20) * methods.size();\n        StringBuilder b = new StringBuilder(initialCapacity);\n        for (int i = 0; i < methods.size(); i++) {\n            String method = methods.get(i);\n            String pattern = patterns.get(i);\n            String target = targets.get(i);\n            b.append(String.format(format, method, pattern, target));\n        }\n        return b.toString();\n    }\n\n    /**\n     * Helper for toString.\n     */\n    private static <T> void aggregateRoutes(\n            String method, Map<PathPattern, T> routes,\n            List<String> accMethods, List<String> accPatterns, List<String> accTargets) {\n        for (Entry<PathPattern, T> entry : routes.entrySet()) {\n            accMethods.add(method);\n            accPatterns.add(\"/\" + entry.getKey().pattern());\n            accTargets.add(targetToString(entry.getValue()));\n        }\n    }\n\n    /**\n     * Helper for toString.\n     */\n    private static int maxLength(List<String> coll) {\n        int max = 0;\n        for (String e : coll) {\n            int length = e.length();\n            if (length > max) {\n                max = length;\n            }\n        }\n        return max;\n    }\n\n    /**\n     * Helper for toString.\n     *\n     * <p>For example, returns\n     * \"io.server.example.http.router.HttpRouterServerHandler\" instead of\n     * \"class io.server.example.http.router.HttpRouterServerHandler\"\n     */\n    private static String targetToString(Object target) {\n        if (target instanceof Class) {\n            return ((Class<?>) target).getName();\n        } else {\n            return target.toString();\n        }\n    }\n\n    //--------------------------------------------------------------------------\n\n    public Router<T> CONNECT(String path, T target) {\n        return addRoute(HttpMethod.CONNECT, path, target);\n    }\n\n    public Router<T> DELETE(String path, T target) {\n        return addRoute(HttpMethod.DELETE, path, target);\n    }\n\n    public Router<T> GET(String path, T target) {\n        return addRoute(HttpMethod.GET, path, target);\n    }\n\n    public Router<T> HEAD(String path, T target) {\n        return addRoute(HttpMethod.HEAD, path, target);\n    }\n\n    public Router<T> OPTIONS(String path, T target) {\n        return addRoute(HttpMethod.OPTIONS, path, target);\n    }\n\n    public Router<T> PATCH(String path, T target) {\n        return addRoute(HttpMethod.PATCH, path, target);\n    }\n\n    public Router<T> POST(String path, T target) {\n        return addRoute(HttpMethod.POST, path, target);\n    }\n\n    public Router<T> PUT(String path, T target) {\n        return addRoute(HttpMethod.PUT, path, target);\n    }\n\n    public Router<T> TRACE(String path, T target) {\n        return addRoute(HttpMethod.TRACE, path, target);\n    }\n\n    public Router<T> ANY(String path, T target) {\n        return addRoute(null, path, target);\n    }\n\n    //--------------------------------------------------------------------------\n\n    public Router<T> CONNECT_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.CONNECT, path, target);\n    }\n\n    public Router<T> DELETE_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.DELETE, path, target);\n    }\n\n    public Router<T> GET_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.GET, path, target);\n    }\n\n    public Router<T> HEAD_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.HEAD, path, target);\n    }\n\n    public Router<T> OPTIONS_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.OPTIONS, path, target);\n    }\n\n    public Router<T> PATCH_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.PATCH, path, target);\n    }\n\n    public Router<T> POST_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.POST, path, target);\n    }\n\n    public Router<T> PUT_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.PUT, path, target);\n    }\n\n    public Router<T> TRACE_FIRST(String path, T target) {\n        return addRouteFirst(HttpMethod.TRACE, path, target);\n    }\n\n    public Router<T> ANY_FIRST(String path, T target) {\n        return addRouteFirst(null, path, target);\n    }\n\n    //--------------------------------------------------------------------------\n\n    public Router<T> CONNECT_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.CONNECT, path, target);\n    }\n\n    public Router<T> DELETE_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.DELETE, path, target);\n    }\n\n    public Router<T> GET_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.GET, path, target);\n    }\n\n    public Router<T> HEAD_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.HEAD, path, target);\n    }\n\n    public Router<T> OPTIONS_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.OPTIONS, path, target);\n    }\n\n    public Router<T> PATCH_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.PATCH, path, target);\n    }\n\n    public Router<T> POST_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.POST, path, target);\n    }\n\n    public Router<T> PUT_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.PUT, path, target);\n    }\n\n    public Router<T> TRACE_LAST(String path, T target) {\n        return addRouteLast(HttpMethod.TRACE, path, target);\n    }\n\n    public Router<T> ANY_LAST(String path, T target) {\n        return addRouteLast(null, path, target);\n    }\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/context/DefaultRouterContext.java",
    "content": "package com.redant.core.router.context;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.lang.ClassScaner;\nimport cn.hutool.core.util.StrUtil;\nimport com.redant.core.bean.annotation.Bean;\nimport com.redant.core.bean.context.BeanContext;\nimport com.redant.core.bean.context.DefaultBeanContext;\nimport com.redant.core.common.constants.CommonConstants;\nimport com.redant.core.common.enums.RequestMethod;\nimport com.redant.core.controller.ControllerProxy;\nimport com.redant.core.controller.annotation.Controller;\nimport com.redant.core.controller.annotation.Mapping;\nimport com.redant.core.controller.context.ControllerContext;\nimport com.redant.core.controller.context.DefaultControllerContext;\nimport com.redant.core.init.InitFunc;\nimport com.redant.core.init.InitOrder;\nimport com.redant.core.render.RenderType;\nimport com.redant.core.router.RouteResult;\nimport com.redant.core.router.Router;\nimport io.netty.handler.codec.http.HttpMethod;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.lang.reflect.Method;\nimport java.util.Set;\n\n/**\n * 默认的RouterContext\n *\n * @author houyi.wh\n * @date 2017-10-20\n */\n@InitOrder(2)\npublic class DefaultRouterContext implements RouterContext, InitFunc {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRouterContext.class);\n\n    /**\n     * 所有的路由信息\n     */\n    private static Router<RenderType> router;\n\n    /**\n     * 初始化完毕的标志\n     */\n    private static volatile boolean inited;\n\n    /**\n     * Bean上下文\n     */\n    private static BeanContext beanContext;\n\n    /**\n     * Controller上下文\n     */\n    private static ControllerContext controllerContext;\n\n    private static final class DefaultRouterContextHolder {\n        private static DefaultRouterContext context = new DefaultRouterContext();\n    }\n\n    private DefaultRouterContext() {\n\n    }\n\n    public static RouterContext getInstance() {\n        return DefaultRouterContextHolder.context;\n    }\n\n    @Override\n    public void init() {\n        doInit();\n    }\n\n    /**\n     * 获取路由结果\n     */\n    @Override\n    public RouteResult<RenderType> getRouteResult(HttpMethod method, String uri) {\n        if (inited()) {\n            RouteResult<RenderType> routeResult = router.route(method, uri);\n            LOGGER.debug(\"getRouteResult with method={}, uri={}, routeResult={}\", method, uri, routeResult);\n            return routeResult;\n        }\n        return null;\n    }\n\n    /**\n     * Router是否加载完毕\n     */\n    private boolean inited() {\n        while (!inited) {\n            doInit();\n            try {\n                Thread.sleep(500);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n        return inited;\n    }\n\n    /**\n     * 执行初始化工作\n     */\n    private void doInit() {\n        // 初始化时需要同步\n        synchronized (DefaultRouterContext.class) {\n            if (!inited) {\n                LOGGER.info(\"[DefaultRouterContext] doInit\");\n                beanContext = DefaultBeanContext.getInstance();\n                controllerContext = DefaultControllerContext.getInstance();\n                initRouter();\n                // 加载完毕\n                inited = true;\n                LOGGER.info(\"[DefaultRouterContext] doInit success!\");\n            }\n        }\n    }\n\n    private void initRouter() {\n        try {\n            LOGGER.info(\"[DefaultRouterContext] initRouter\");\n            // 获取所有RouterController\n            Set<Class<?>> classSet = ClassScaner.scanPackageByAnnotation(CommonConstants.BEAN_SCAN_PACKAGE, Controller.class);\n            router = new Router<>();\n            if (CollectionUtil.isNotEmpty(classSet)) {\n                for (Class<?> cls : classSet) {\n                    Controller controller = cls.getAnnotation(Controller.class);\n                    // 获取Controller中所有的方法\n                    Method[] methods = cls.getMethods();\n                    for (Method method : methods) {\n                        Mapping mapping = method.getAnnotation(Mapping.class);\n                        if (mapping != null) {\n                            addRoute(controller, mapping);\n                            // 添加控制器\n                            addProxy(cls, method, controller, mapping);\n                        }\n                    }\n                }\n                router.notFound(RenderType.HTML);\n                LOGGER.info(\"[DefaultRouterContext] initRouter success! routers are listed blow:\" +\n                                \"\\n*************************************\" +\n                                \"\\n{}\" +\n                                \"*************************************\\n\",\n                        router);\n            } else {\n                LOGGER.warn(\"[DefaultRouterContext] No Controller Scanned!\");\n            }\n        } catch (RuntimeException e) {\n            throw e;\n        } catch (Exception e) {\n            LOGGER.error(\"[DefaultRouterContext] Init controller error,cause:\", e);\n        }\n    }\n\n    private void addRoute(Controller controller, Mapping mapping) {\n        // Controller+Mapping 唯一确定一个控制器的方法\n        String path = controller.path() + mapping.path();\n        HttpMethod method = RequestMethod.getHttpMethod(mapping.requestMethod());\n        // 添加路由\n        router.addRoute(method, path, mapping.renderType());\n    }\n\n    private void addProxy(Class<?> cls, Method method, Controller controller, Mapping mapping) {\n        try {\n            // Controller+Mapping 唯一确定一个控制器的方法\n            String path = controller.path() + mapping.path();\n            ControllerProxy proxy = new ControllerProxy();\n            proxy.setRenderType(mapping.renderType());\n            proxy.setRequestMethod(mapping.requestMethod());\n            Object object;\n            // 如果该controller也使用了Bean注解，则从BeanContext中获取该controller的实现类\n            Bean bean = cls.getAnnotation(Bean.class);\n            if (bean != null) {\n                // 如果该controller设置了bean的名字则以该名称从BeanHolder中获取bean，否则以\n                String beanName = StrUtil.isNotBlank(bean.name()) ? bean.name() : cls.getName();\n                object = beanContext.getBean(beanName);\n            } else {\n                object = cls.newInstance();\n            }\n            proxy.setController(object);\n            proxy.setMethod(method);\n            proxy.setMethodName(method.getName());\n\n            controllerContext.addProxy(path, proxy);\n            LOGGER.info(\"[DefaultRouterContext] addProxy path={} to proxy={}\", path, proxy);\n        } catch (Exception e) {\n            LOGGER.error(\"[DefaultRouterContext] addProxy error,cause:\", e.getMessage(), e);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/router/context/RouterContext.java",
    "content": "package com.redant.core.router.context;\n\nimport com.redant.core.render.RenderType;\nimport com.redant.core.router.RouteResult;\nimport io.netty.handler.codec.http.HttpMethod;\n\n/**\n * 路由上下文\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic interface RouterContext {\n\n    /**\n     * 获取路由结果\n     * @param method 请求类型\n     * @param uri url\n     * @return 路由结果\n     */\n    RouteResult<RenderType> getRouteResult(HttpMethod method, String uri);\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/server/NettyHttpServer.java",
    "content": "package com.redant.core.server;\n\nimport com.redant.core.common.constants.CommonConstants;\nimport com.redant.core.init.InitExecutor;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.util.concurrent.DefaultThreadFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n\n/**\n * NettyHttpServer\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic final class NettyHttpServer implements Server {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(NettyHttpServer.class);\n\n    @Override\n    public void preStart() {\n        InitExecutor.init();\n    }\n\n    @Override\n    public void start() {\n        EventLoopGroup bossGroup = new NioEventLoopGroup(CommonConstants.BOSS_GROUP_SIZE, new DefaultThreadFactory(\"boss\", true));\n        EventLoopGroup workerGroup = new NioEventLoopGroup(CommonConstants.WORKER_GROUP_SIZE, new DefaultThreadFactory(\"worker\", true));\n        try {\n            long start = System.currentTimeMillis();\n            ServerBootstrap b = new ServerBootstrap();\n            b.option(ChannelOption.SO_BACKLOG, 1024);\n            b.group(bossGroup, workerGroup)\n             .channel(NioServerSocketChannel.class)\n//             .handler(new LoggingHandler(LogLevel.INFO))\n             .childHandler(new NettyHttpServerInitializer());\n\n            ChannelFuture future = b.bind(CommonConstants.SERVER_PORT).sync();\n            long cost = System.currentTimeMillis()-start;\n            LOGGER.info(\"[NettyHttpServer] Startup at port:{} cost:{}[ms]\",CommonConstants.SERVER_PORT,cost);\n\n            // 等待服务端Socket关闭\n            future.channel().closeFuture().sync();\n        } catch (InterruptedException e) {\n            LOGGER.error(\"[NettyHttpServer] InterruptedException:\",e);\n        } finally {\n            bossGroup.shutdownGracefully();\n            workerGroup.shutdownGracefully();\n        }\n    }\n\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/server/NettyHttpServerInitializer.java",
    "content": "package com.redant.core.server;\n\nimport com.redant.core.common.constants.CommonConstants;\nimport com.redant.core.handler.ControllerDispatcher;\nimport com.redant.core.handler.ssl.SslContextHelper;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.socket.SocketChannel;\nimport io.netty.handler.codec.http.HttpContentCompressor;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslHandler;\nimport io.netty.handler.stream.ChunkedWriteHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.net.ssl.SSLEngine;\n\n/**\n * @author houyi.wh\n * @date 2019-01-17\n */\npublic class NettyHttpServerInitializer extends ChannelInitializer<SocketChannel> {\n\n    public static final Logger LOGGER = LoggerFactory.getLogger(NettyHttpServerInitializer.class);\n\n    @Override\n    public void initChannel(SocketChannel ch) {\n        ChannelPipeline pipeline = ch.pipeline();\n\n        // HttpServerCodec is a combination of HttpRequestDecoder and HttpResponseEncoder\n        // 使用HttpServerCodec将ByteBuf编解码为httpRequest/httpResponse\n        pipeline.addLast(new HttpServerCodec());\n        addAdvanced(pipeline);\n        pipeline.addLast(new ChunkedWriteHandler());\n        // 路由分发器\n        pipeline.addLast(new ControllerDispatcher());\n    }\n\n    private void initSsl(SocketChannel ch){\n        ChannelPipeline pipeline = ch.pipeline();\n        if(CommonConstants.USE_SSL){\n            SslContext context = SslContextHelper.getSslContext(CommonConstants.KEY_STORE_PATH,CommonConstants.KEY_STORE_PASSWORD);\n            if(context!=null) {\n                SSLEngine engine = context.newEngine(ch.alloc());\n                engine.setUseClientMode(false);\n                pipeline.addLast(new SslHandler(engine));\n            }else{\n                LOGGER.warn(\"SslContext is null with keyPath={}\",CommonConstants.KEY_STORE_PATH);\n            }\n        }\n    }\n\n    /**\n     * 可以在 HttpServerCodec 之后添加这些 ChannelHandler 进行开启高级特性\n     */\n    private void addAdvanced(ChannelPipeline pipeline){\n        if(CommonConstants.USE_COMPRESS) {\n            // 对 http 响应结果开启 gizp 压缩\n            pipeline.addLast(new HttpContentCompressor());\n        }\n        if(CommonConstants.USE_AGGREGATOR) {\n            // 将多个HttpRequest组合成一个FullHttpRequest\n            pipeline.addLast(new HttpObjectAggregator(CommonConstants.MAX_CONTENT_LENGTH));\n        }\n    }\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/server/Server.java",
    "content": "package com.redant.core.server;\n\n/**\n * @author houyi.wh\n * @date 2019-01-10\n */\npublic interface Server {\n\n    /**\n     * 启动服务器之前的事件处理\n     */\n    void preStart();\n\n    /**\n     * 启动服务器\n     */\n    void start();\n\n}"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/session/HttpSession.java",
    "content": "package com.redant.core.session;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelId;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * HttpSession\n * @author houyi.wh\n * @date 2017/11/6\n */\npublic class HttpSession {\n\n    /**\n     * 会话id\n     */\n    private ChannelId id;\n\n    /**\n     * 会话保存的ChannelHandlerContext\n     */\n    private ChannelHandlerContext context;\n\n    /**\n     * 创建时间\n     */\n    private Long createTime;\n\n    /**\n     * 过期时间\n     * 每次请求时都更新过期时间\n     */\n    private Long expireTime;\n\n    /**\n     * Session中存储的数据\n     */\n    private Map<String,Object> sessionMap;\n\n\n    private void assertSessionMapNotNull(){\n        if(sessionMap ==null){\n            sessionMap = new HashMap<String,Object>();\n        }\n    }\n\n\n    private HttpSession(){\n\n    }\n\n\n    //=====================================\n\n\n    public HttpSession(ChannelHandlerContext context){\n        this(context.channel().id(),context);\n    }\n\n    public HttpSession(ChannelId id,ChannelHandlerContext context){\n        this(id,context,System.currentTimeMillis());\n    }\n\n    public HttpSession(ChannelId id,ChannelHandlerContext context,Long createTime){\n        this(id,context,createTime,createTime + SessionConfig.instance().sessionTimeOut());\n    }\n\n    public HttpSession(ChannelId id,ChannelHandlerContext context,Long createTime,Long expireTime){\n        this.id = id;\n        this.context = context;\n        this.createTime = createTime;\n        this.expireTime = expireTime;\n        assertSessionMapNotNull();\n    }\n\n    public ChannelId getId() {\n        return id;\n    }\n\n    public void setId(ChannelId id) {\n        this.id = id;\n    }\n\n    public ChannelHandlerContext getContext() {\n        return context;\n    }\n\n    public void setContext(ChannelHandlerContext context) {\n        this.context = context;\n    }\n\n    public Long getCreateTime() {\n        return createTime;\n    }\n\n    public void setCreateTime(Long createTime) {\n        this.createTime = createTime;\n    }\n\n    public Long getExpireTime() {\n        return expireTime;\n    }\n\n    public void setExpireTime(Long expireTime) {\n        this.expireTime = expireTime;\n    }\n\n    /**\n     * 是否过期\n     * @return\n     */\n    public boolean isExpire(){\n        return this.expireTime>=System.currentTimeMillis();\n    }\n\n    /**\n     * 设置attribute\n     * @param key\n     * @param val\n     */\n    public void setAttribute(String key,Object val){\n        sessionMap.put(key,val);\n    }\n\n    /**\n     * 获取key的值\n     * @param key\n     */\n    public Object getAttribute(String key){\n        return sessionMap.get(key);\n    }\n\n    /**\n     * 是否存在key\n     * @param key\n     */\n    public boolean containsAttribute(String key){\n        return sessionMap.containsKey(key);\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/session/SessionConfig.java",
    "content": "package com.redant.core.session;\n\n/**\n * 针对HttpSession的全局配置项\n * @author houyi.wh\n * @date 2017/11/6\n */\npublic class SessionConfig {\n\n    /**\n     * 默认超时时间\n     */\n    private static final Long DEFAULT_SESSION_TIME_OUT = 60*60*1000L;\n\n    private SessionConfig(){\n\n    }\n\n    /**\n     * session超时时间\n     */\n    private Long sessionTimeOut = DEFAULT_SESSION_TIME_OUT;\n\n    /**\n     * 单例\n     */\n    private static SessionConfig config;\n\n    static{\n        if(config==null){\n            config = new SessionConfig();\n        }\n    }\n\n\n    //======================================\n\n\n    /**\n     * 获取实例\n     * @return\n     */\n    public static SessionConfig instance(){\n        return config;\n    }\n\n    public SessionConfig sessionTimeOut(Long sessionTimeOut){\n        config.sessionTimeOut = sessionTimeOut;\n        return config;\n    }\n\n    public Long sessionTimeOut(){\n        return config.sessionTimeOut;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"[\"+super.toString()+\"]:{sessionTimeOut:\"+config.sessionTimeOut+\"}\";\n    }\n\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/session/SessionHelper.java",
    "content": "package com.redant.core.session;\n\nimport com.redant.core.common.exception.InvalidSessionException;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelId;\n\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Session辅助器\n * @author houyi.wh\n * @date 2017/11/6\n */\npublic class SessionHelper {\n\n    /**\n     * 保存session对象的map\n     */\n    private Map<ChannelId,HttpSession> sessionMap;\n\n    private static SessionHelper manager;\n\n    private SessionHelper(){\n\n    }\n\n\n    //======================================\n\n\n    /**\n     * 获取单例\n     * @return\n     */\n    public static SessionHelper instange(){\n        synchronized (SessionHelper.class) {\n            if (manager == null) {\n                manager = new SessionHelper();\n                if (manager.sessionMap == null) {\n                    // 需要线程安全的Map\n                    manager.sessionMap = new ConcurrentHashMap<ChannelId,HttpSession>();\n                }\n            }\n        }\n        return manager;\n    }\n\n    /**\n     * 判断session是否存在\n     * @param context\n     * @return\n     */\n    public boolean containsSession(ChannelHandlerContext context){\n        return context!=null && context.channel()!=null && context.channel().id()!=null && manager.sessionMap.get(context.channel().id())!=null;\n    }\n\n    /**\n     * 添加一个session\n     * @param context\n     * @param session\n     */\n    public void addSession(ChannelHandlerContext context,HttpSession session){\n        if(context==null || context.channel()==null || context.channel().id()==null || session==null){\n            throw new InvalidSessionException(\"context or session is null\");\n        }\n        manager.sessionMap.put(context.channel().id(),session);\n    }\n\n    /**\n     * 获取一个session\n     * @param context\n     * @return\n     */\n    public HttpSession getSession(ChannelHandlerContext context){\n        if(context==null || context.channel()==null || context.channel().id()==null){\n            throw new InvalidSessionException(\"context is null\");\n        }\n        return manager.sessionMap.get(context.channel().id());\n    }\n\n    /**\n     * 获取一个session，获取不到时自动创建一个\n     * @param context\n     * @param createIfNull\n     * @return\n     */\n    public HttpSession getSession(ChannelHandlerContext context,boolean createIfNull){\n        HttpSession session = getSession(context);\n        if(session==null && createIfNull){\n            session = new HttpSession(context);\n            manager.sessionMap.put(context.channel().id(),session);\n        }\n        return session;\n    }\n\n    /**\n     * 清除过期的session\n     * 需要在定时器中执行该方法\n     */\n    public void clearExpireSession(){\n        Iterator<Map.Entry<ChannelId,HttpSession>> iterator = manager.sessionMap.entrySet().iterator();\n        while(iterator.hasNext()){\n            Map.Entry<ChannelId,HttpSession> sessionEntry = iterator.next();\n            if(sessionEntry.getValue()==null || sessionEntry.getValue().isExpire()){\n                iterator.remove();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "redant-core/src/main/java/com/redant/core/session/SessionManager.java",
    "content": "package com.redant.core.session;\n\n/**\n * Session管理器\n * @author houyi.wh\n * @date 2017/11/6\n */\npublic interface SessionManager {\n\n\n    /**\n     * 判断session是否存在\n     */\n    boolean sessionExists();\n\n    /**\n     * 添加一个session\n     * @param session session对象\n     */\n    void addSession(HttpSession session);\n\n    /**\n     * 获取一个session\n     * @return session对象\n     */\n    HttpSession getSession();\n\n    /**\n     * 获取一个session，获取不到时自动创建一个\n     * @param createIfNull true：不存在时创建一个，false：不存在时也不创建\n     * @return session对象\n     */\n    HttpSession getSession(boolean createIfNull);\n\n    /**\n     * 清除过期的session\n     * 需要在定时器中执行该方法\n     */\n    void clearExpireSession();\n\n}\n"
  },
  {
    "path": "redant-core/src/main/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n  \n<!-- 从高到地低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->  \n<!-- 日志输出规则  根据当前ROOT 级别，日志输出时，级别高于root默认的级别时  会输出 -->  \n<!-- 以下  每个配置的 filter 是过滤掉输出文件里面，会出现高级别文件，依然出现低级别的日志信息，通过filter 过滤只记录本级别的日志-->  \n  \n\n<!-- 属性描述 scan：性设置为true时，配置文件如果发生改变，将会被重新加载，默认值为true scanPeriod:设置监测配置文件是否有修改的时间间隔，如果没有给出时间单位，默认单位是毫秒。当scan为true时，此属性生效。默认的时间间隔为1分钟。   \n    debug:当此属性设置为true时，将打印出logback内部日志信息，实时查看logback运行状态。默认值为false。 -->  \n<configuration scan=\"true\" scanPeriod=\"60 seconds\" debug=\"false\">  \n    <!-- 日志最大的历史 7天 -->  \n    <property name=\"maxHistory\" value=\"7\"/>  \n\t<!-- 日志最大的文件大小 10MB-->\n\t<property name=\"maxFileSize\" value=\"10MB\"/>\n\n\t<property name=\"baseLogDir\" value=\"${user.dir}/logback\"/>\n\n    <!-- ConsoleAppender 控制台输出日志 -->  \n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">  \n        <!-- 对日志进行格式化 -->  \n        <encoder>  \n            <pattern>%d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %-5level %logger:%line - %msg%n</pattern>\n        </encoder>  \n    </appender>  \n  \n    <!-- WARN级别日志 appender -->  \n    <appender name=\"WARN\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">  \n        <!-- 过滤器，过滤掉 TRACE 和 DEBUG 和 INFO 级别的日志 -->  \n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">  \n            <level>WARN</level>\n        </filter>  \n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">  \n            <!-- 按天回滚 daily -->  \n            <fileNamePattern>${baseLogDir}/logs/%d{yyyy-MM-dd}/warn-log.log</fileNamePattern>  \n            <!-- 日志最大的保存天数 -->  \n            <maxHistory>${maxHistory}</maxHistory>  \n        </rollingPolicy>  \n\t\t<triggeringPolicy class=\"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy\">\n            <maxFileSize>${maxFileSize}</maxFileSize>\n        </triggeringPolicy>\n        <encoder>  \n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n</pattern>\n        </encoder>  \n    </appender>  \n\n    <!-- DEBUG级别日志 appender -->  \n    <appender name=\"DEBUG\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">   \n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">  \n            <!-- 按天回滚 daily -->  \n            <fileNamePattern>${baseLogDir}/logs/%d{yyyy-MM-dd}/debug-log.log</fileNamePattern>  \n            <!-- 日志最大的历史 60天 -->  \n            <maxHistory>${maxHistory}</maxHistory>  \n        </rollingPolicy>  \n\t\t<triggeringPolicy class=\"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy\">\n            <maxFileSize>${maxFileSize}</maxFileSize>\n        </triggeringPolicy>\n        <encoder>  \n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n</pattern>\n        </encoder>  \n    </appender>\n\n\n    <logger name=\"com.redant\" level=\"INFO\"/>\n\n    <!-- root级别   DEBUG -->  \n    <root level=\"INFO\">\n        <!-- 控制台输出 -->\n        <appender-ref ref=\"STDOUT\" />\n        <!-- 文件输出 -->\n        <appender-ref ref=\"DEBUG\" />\n    </root>\n\n\n    <!-- router的相关日志 -->\n    <appender name=\"ROUTER_MSG\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${baseLogDir}/logs/%d{yyyy-MM-dd}/routerMsg.log</fileNamePattern>\n            <!-- 日志最大的保存天数 -->\n            <maxHistory>${maxHistory}</maxHistory>\n        </rollingPolicy>\n        <triggeringPolicy class=\"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy\">\n            <maxFileSize>${maxFileSize}</maxFileSize>\n        </triggeringPolicy>\n        <encoder>\n            <pattern>%-1relative - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{0}:%line - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <!-- router的相关日志 -->\n    <logger name=\"routerMsgLog\" additivity=\"true\">\n        <level value=\"INFO\"/>\n        <appender-ref ref=\"ROUTER_MSG\" />\n    </logger>\n\n</configuration>  "
  },
  {
    "path": "redant-core/src/main/resources/redant.properties",
    "content": "# server port\nnetty.server.port=8888\n\n# the boss thread size which set to EventLoopGroup\nnetty.server.bossGroup.size=1\n\n# the worker thread size which set to EventLoopGroup\nnetty.server.workerGroup.size=8\n\n# the maxContentLength which set to HttpObjectAggregator\nnetty.maxContentLength=10485760\n\n# whether use ssl or not\nnetty.server.use.ssl=false\n\n# KeyStore path\nssl.keyStore.path=\n\n# KeyStore password\nssl.keyStore.password=\n\n# the package that beans stored in\nbean.scan.package=com.redant\n\n# the package that netty interceptors stored in\ninterceptor.scan.package=com.redant\n\nnetty.server.use.compress=true\nnetty.server.use.aggregator=true\n\n# the description sent to front end when server internal error occurred\nserver.internal.error.desc=Server Internal Error\n\n# whether execute the business event in async mode\nasync.execute.event=false\n\n# async executor thread pool\nasync.executor.pool.core.size=10\nasync.executor.pool.max.size=20\nasync.executor.pool.keep.alive.seconds=10\nasync.executor.pool.blocking.queue.size=100"
  },
  {
    "path": "redant-core/src/main/resources/zk.cfg",
    "content": "{\n\t\"model\" : \"cluster\",\n\t\"configs\" : [\n\t\t{\n            \"clientPort\": 2181,\n\t\t    \"tickTime\": 2000,\n            \"initLimit\": 10,\n            \"syncLimit\": 5,\n            \"dataDir\": \"/zookeeper/1/data\",\n            \"dataLogDir\": \"/zookeeper/1/log\",\n            \"maxClientCnxns\": 60,\n            \"myid\": \"/zookeeper/1/data/myid:1\",\n            \"servers\": [\n                \"127.0.0.1:2887:3887\",\n                \"127.0.0.1:2888:3888\",\n                \"127.0.0.1:2889:3889\"\n            ]\n\t\t},\n\t\t{\n\t\t    \"clientPort\": 2182,\n            \"tickTime\": 2000,\n            \"initLimit\": 10,\n            \"syncLimit\": 5,\n            \"dataDir\": \"/zookeeper/2/data\",\n            \"dataLogDir\": \"/zookeeper/2/log\",\n            \"maxClientCnxns\": 60,\n            \"myid\": \"/zookeeper/2/data/myid:2\",\n            \"servers\": [\n                \"127.0.0.1:2887:3887\",\n                \"127.0.0.1:2888:3888\",\n                \"127.0.0.1:2889:3889\"\n            ]\n\t\t},\n\t\t{\n\t\t    \"clientPort\": 2183,\n            \"tickTime\": 2000,\n            \"initLimit\": 10,\n            \"syncLimit\": 5,\n            \"dataDir\": \"/zookeeper/3/data\",\n            \"dataLogDir\": \"/zookeeper/3/log\",\n            \"maxClientCnxns\": 60,\n            \"myid\": \"/zookeeper/3/data/myid:3\",\n            \"servers\": [\n                \"127.0.0.1:2887:3887\",\n                \"127.0.0.1:2888:3888\",\n                \"127.0.0.1:2889:3889\"\n            ]\n\t\t}\n\t]\n}"
  },
  {
    "path": "redant-core/src/test/java/com/redant/core/context/RedantContextTest.java",
    "content": "package com.redant.core.context;\n\n/**\n * @author houyi\n **/\npublic class RedantContextTest {\n\n    public static void main(String[] args) {\n        for(int i=0;i<10;i++){\n            new Thread(new ContextRunner()).start();\n        }\n    }\n\n    private static class ContextRunner implements Runnable {\n        @Override\n        public void run() {\n            RedantContext context = RedantContext.currentContext();\n            System.out.println(context);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "redant-example/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>redant</artifactId>\n        <groupId>com.redant</groupId>\n        <version>1.0.0-SNAPSHOT</version>\n    </parent>\n\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>redant-example</artifactId>\n    <packaging>jar</packaging>\n    <name>redant-example</name>\n    <version>1.0.0-SNAPSHOT</version>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.redant</groupId>\n            <artifactId>redant-core</artifactId>\n            <version>1.0.0-SNAPSHOT</version>\n        </dependency>\n        <dependency>\n            <groupId>com.redant</groupId>\n            <artifactId>redant-cluster</artifactId>\n            <version>1.0.0-SNAPSHOT</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/bootstrap/cluster/MasterServerBootstrap.java",
    "content": "package com.redant.example.bootstrap.cluster;\n\n/**\n * MasterServerBootstrap\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic class MasterServerBootstrap {\n\n    public static void main(String[] args) {\n        com.redant.cluster.bootstrap.MasterServerBootstrap.main(args);\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/bootstrap/cluster/SlaveServerBootstrap.java",
    "content": "package com.redant.example.bootstrap.cluster;\n\n/**\n * SlaveServerBootstrap\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic class SlaveServerBootstrap {\n\n    public static void main(String[] args) {\n        com.redant.cluster.bootstrap.SlaveServerBootstrap.main(args);\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/bootstrap/cluster/ZkBootstrap.java",
    "content": "package com.redant.example.bootstrap.cluster;\n\n/**\n * ZkBootstrap\n * @author houyi.wh\n * @date 2017/11/20\n **/\npublic class ZkBootstrap {\n\n    public static void main(String[] args) {\n        com.redant.cluster.bootstrap.ZkBootstrap.main(args);\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/bootstrap/standalone/ServerBootstrap.java",
    "content": "package com.redant.example.bootstrap.standalone;\n\n/**\n * 服务端启动入口\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic final class ServerBootstrap {\n\n    public static void main(String[] args) {\n        com.redant.core.ServerBootstrap.main(args);\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/controller/BaseController.java",
    "content": "package com.redant.example.controller;\n\n\nimport com.redant.core.common.enums.RequestMethod;\nimport com.redant.core.common.html.DefaultHtmlMaker;\nimport com.redant.core.common.html.HtmlMaker;\nimport com.redant.core.common.html.HtmlMakerEnum;\nimport com.redant.core.common.html.HtmlMakerFactory;\nimport com.redant.core.common.util.HtmlContentUtil;\nimport com.redant.core.common.view.PageIndex;\nimport com.redant.core.controller.annotation.Controller;\nimport com.redant.core.render.RenderType;\nimport com.redant.core.controller.annotation.Mapping;\n\n\n/**\n * BaseController\n * @author houyi.wh\n * @date 2017-10-20\n */\n@Controller(path=\"/\")\npublic class BaseController {\n\n    @Mapping(requestMethod=RequestMethod.GET,renderType=RenderType.HTML)\n    public String index(){\n        HtmlMaker htmlMaker = HtmlMakerFactory.instance().build(HtmlMakerEnum.STRING,DefaultHtmlMaker.class);\n        String htmlTpl = PageIndex.HTML;\n        return HtmlContentUtil.getPageContent(htmlMaker, htmlTpl,null);\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/controller/CookieController.java",
    "content": "package com.redant.example.controller;\n\n\nimport com.alibaba.fastjson.JSONObject;\nimport com.redant.core.bean.annotation.Bean;\nimport com.redant.core.common.enums.RequestMethod;\nimport com.redant.core.controller.annotation.Controller;\nimport com.redant.core.controller.annotation.Mapping;\nimport com.redant.core.controller.annotation.Param;\nimport com.redant.core.cookie.CookieManager;\nimport com.redant.core.cookie.DefaultCookieManager;\nimport com.redant.core.render.RenderType;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\n@Bean\n@Controller(path=\"/cookie\")\npublic class CookieController {\n\n    private CookieManager cookieManager = DefaultCookieManager.getInstance();\n\n    @Mapping(path=\"/add\",requestMethod=RequestMethod.GET,renderType=RenderType.JSON)\n    public JSONObject add(@Param(key=\"name\", notBlank=true) String name, @Param(key=\"value\", notBlank=true) String value){\n        JSONObject object = new JSONObject();\n        object.put(\"tip\",\"请在响应头 Response Headers 中查看 set-cookie 的值\");\n        object.put(\"cookieName\",name);\n        object.put(\"cookieValue\",value);\n        cookieManager.addCookie(name,value);\n        return object;\n    }\n\n    @Mapping(path=\"/delete\",requestMethod=RequestMethod.GET,renderType=RenderType.JSON)\n    public JSONObject delete(@Param(key=\"name\", notBlank=true) String name){\n        JSONObject object = new JSONObject();\n        object.put(\"tip\",\"请在响应头 Response Headers 中查看 set-cookie 的值\");\n        object.put(\"cookieName\",name);\n        cookieManager.deleteCookie(name);\n        return object;\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/controller/UserController.java",
    "content": "package com.redant.example.controller;\n\n\nimport com.alibaba.fastjson.JSONArray;\nimport com.alibaba.fastjson.JSONObject;\nimport com.redant.core.bean.annotation.Autowired;\nimport com.redant.core.bean.annotation.Bean;\nimport com.redant.core.common.enums.RequestMethod;\nimport com.redant.core.controller.annotation.Controller;\nimport com.redant.core.controller.annotation.Mapping;\nimport com.redant.core.controller.annotation.Param;\nimport com.redant.core.render.RenderType;\nimport com.redant.example.service.UserBean;\nimport com.redant.example.service.UserService;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\n@Bean\n@Controller(path = \"/user\")\npublic class UserController {\n\n    /**\n     * 如果需要使用Autowired，则该类自身需要使用Bean注解标注\n     */\n    @Autowired(name = \"userService\")\n    private UserService userService;\n\n    @Mapping(path = \"/info\", requestMethod = RequestMethod.GET, renderType = RenderType.JSON)\n    public UserBean info(@Param(key = \"id\", notNull = true) Integer id) {\n        try {\n            TimeUnit.MILLISECONDS.sleep(500);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n        return userService.selectUserInfo(id);\n    }\n\n    @Mapping(path = \"/list\", requestMethod = RequestMethod.GET, renderType = RenderType.JSON)\n    public JSONArray list() {\n        JSONArray array = new JSONArray();\n        JSONObject object = new JSONObject();\n        UserBean user = new UserBean();\n        user.setId(23);\n        user.setUserName(\"逅弈逐码\");\n        object.put(\"user\", user);\n        array.add(object);\n        return array;\n    }\n\n    @Mapping(path = \"/count\", requestMethod = RequestMethod.GET, renderType = RenderType.JSON)\n    public JSONObject count() {\n        JSONObject object = new JSONObject();\n        int count = userService.selectCount();\n        object.put(\"count\", count);\n        return object;\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/interceptor/BlockInterceptor.java",
    "content": "package com.redant.example.interceptor;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.alibaba.fastjson.JSONObject;\nimport com.redant.core.anno.Order;\nimport com.redant.core.common.util.HttpRenderUtil;\nimport com.redant.core.context.RedantContext;\nimport com.redant.core.interceptor.Interceptor;\nimport com.redant.core.render.RenderType;\nimport io.netty.handler.codec.http.FullHttpResponse;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 当请求的参数中有 block=true 时，就会被该拦截器拦截\n * @author houyi\n **/\n@Order(value = 1)\npublic class BlockInterceptor extends Interceptor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(BlockInterceptor.class);\n\n    @Override\n    public boolean preHandle(Map<String, List<String>> paramMap) {\n        if(CollectionUtil.isNotEmpty(paramMap)) {\n            String blockKey = \"block\";\n            String blockVal = \"true\";\n            List<String> values = paramMap.get(blockKey);\n            if(CollectionUtil.isNotEmpty(values)){\n                String val = values.get(0);\n                if(blockVal.equals(val)){\n                    JSONObject content = new JSONObject();\n                    content.put(\"status\",\"你被前置方法拦截了\");\n                    content.put(\"reason\",\"请求参数中有 block=true\");\n                    FullHttpResponse response = HttpRenderUtil.render(content, RenderType.JSON);\n                    RedantContext.currentContext().setResponse(response);\n                    LOGGER.info(\"[BlockInterceptor] blocked preHandle\");\n                    return false;\n                }\n            }\n        }\n        return true;\n    }\n\n    @Override\n    public void postHandle(Map<String, List<String>> paramMap) {\n        // do nothing\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/interceptor/CustomInterceptorBuilder.java",
    "content": "package com.redant.example.interceptor;\n\nimport com.redant.core.interceptor.Interceptor;\nimport com.redant.core.interceptor.InterceptorBuilder;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @author houyi\n **/\npublic class CustomInterceptorBuilder implements InterceptorBuilder {\n\n    private volatile boolean loaded = false;\n\n    private List<Interceptor> interceptors = null;\n\n    @Override\n    public List<Interceptor> build() {\n        if(!loaded){\n            synchronized (CustomInterceptorBuilder.class) {\n                if(!loaded){\n                    interceptors = new ArrayList<>();\n                    if (activeBlock()) {\n                        interceptors.add(new BlockInterceptor());\n                    }\n                    if (activePerf()) {\n                        interceptors.add(new PerformanceInterceptor());\n                    }\n                    loaded = true;\n                }\n            }\n        }\n        return interceptors;\n    }\n\n    private boolean activeBlock(){\n        return false;\n    }\n\n    private boolean activePerf(){\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/interceptor/PerformanceInterceptor.java",
    "content": "package com.redant.example.interceptor;\n\nimport com.redant.core.anno.Order;\nimport com.redant.core.context.RedantContext;\nimport com.redant.core.interceptor.Interceptor;\nimport io.netty.handler.codec.http.HttpRequest;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 该拦截器可以计算出用户自定义 Controller 方法的执行时间\n * @author houyi\n **/\n@Order(value = 2)\npublic class PerformanceInterceptor extends Interceptor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(PerformanceInterceptor.class);\n\n    private ThreadLocal<Long> start = new ThreadLocal<>();\n\n    @Override\n    public boolean preHandle(Map<String, List<String>> paramMap) {\n        start.set(System.currentTimeMillis());\n        return true;\n    }\n\n    @Override\n    public void postHandle(Map<String, List<String>> paramMap) {\n        try {\n            long end = System.currentTimeMillis();\n            long cost = end - start.get();\n            HttpRequest request = RedantContext.currentContext().getRequest();\n            String uri = request.uri();\n            LOGGER.info(\"uri={}, cost:{}[ms]\", uri, cost);\n        }finally {\n            start.remove();\n        }\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/service/UserBean.java",
    "content": "package com.redant.example.service;\n\n\nimport com.alibaba.fastjson.JSON;\n\nimport java.io.Serializable;\n\n/**\n * UserBean\n * @author houyi.wh\n * @date 2017-10-20\n */\npublic class UserBean implements Serializable {\n\n    private Integer id;\n\n    private String userName;\n\n    private String password;\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getUserName() {\n        return userName;\n    }\n\n    public void setUserName(String userName) {\n        this.userName = userName;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    @Override\n    public String toString() {\n        return JSON.toJSONString(this);\n    }\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/service/UserService.java",
    "content": "package com.redant.example.service;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\npublic interface UserService {\n\n    /**\n     * 获取用户信息\n     * @param id 用户id\n     * @return 用户信息\n     */\n    UserBean selectUserInfo(Integer id);\n\n    /**\n     * 获取用户个数\n     * @return 用户个数\n     */\n    int selectCount();\n}\n"
  },
  {
    "path": "redant-example/src/main/java/com/redant/example/service/UserServiceImpl.java",
    "content": "package com.redant.example.service;\n\nimport com.redant.core.bean.annotation.Bean;\n\n/**\n * @author houyi.wh\n * @date 2017/12/1\n **/\n@Bean(name=\"userService\")\npublic class UserServiceImpl implements UserService {\n\n    @Override\n    public UserBean selectUserInfo(Integer id) {\n        UserBean user = new UserBean();\n        user.setId(id);\n        user.setUserName(\"逅弈逐码\");\n        return user;\n    }\n\n    @Override\n    public int selectCount() {\n        return 10;\n    }\n\n}\n"
  },
  {
    "path": "redant-example/src/main/resources/logback.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n  \n<!-- 从高到地低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->  \n<!-- 日志输出规则  根据当前ROOT 级别，日志输出时，级别高于root默认的级别时  会输出 -->  \n<!-- 以下  每个配置的 filter 是过滤掉输出文件里面，会出现高级别文件，依然出现低级别的日志信息，通过filter 过滤只记录本级别的日志-->  \n  \n\n<!-- 属性描述 scan：性设置为true时，配置文件如果发生改变，将会被重新加载，默认值为true scanPeriod:设置监测配置文件是否有修改的时间间隔，如果没有给出时间单位，默认单位是毫秒。当scan为true时，此属性生效。默认的时间间隔为1分钟。   \n    debug:当此属性设置为true时，将打印出logback内部日志信息，实时查看logback运行状态。默认值为false。 -->  \n<configuration scan=\"true\" scanPeriod=\"60 seconds\" debug=\"false\">  \n    <!-- 日志最大的历史 7天 -->  \n    <property name=\"maxHistory\" value=\"7\"/>  \n\t<!-- 日志最大的文件大小 10MB-->\n\t<property name=\"maxFileSize\" value=\"10MB\"/>\n\n\t<property name=\"baseLogDir\" value=\"${user.dir}/logback\"/>\n\n    <!-- ConsoleAppender 控制台输出日志 -->  \n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">  \n        <!-- 对日志进行格式化 -->  \n        <encoder>  \n            <pattern>%d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %-5level %logger:%line - %msg%n</pattern>\n        </encoder>  \n    </appender>  \n  \n    <!-- WARN级别日志 appender -->  \n    <appender name=\"WARN\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">  \n        <!-- 过滤器，过滤掉 TRACE 和 DEBUG 和 INFO 级别的日志 -->  \n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">  \n            <level>WARN</level>\n        </filter>  \n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">  \n            <!-- 按天回滚 daily -->  \n            <fileNamePattern>${baseLogDir}/logs/%d{yyyy-MM-dd}/warn-log.log</fileNamePattern>  \n            <!-- 日志最大的保存天数 -->  \n            <maxHistory>${maxHistory}</maxHistory>  \n        </rollingPolicy>  \n\t\t<triggeringPolicy class=\"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy\">\n            <maxFileSize>${maxFileSize}</maxFileSize>\n        </triggeringPolicy>\n        <encoder>  \n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n</pattern>\n        </encoder>  \n    </appender>  \n\n    <!-- DEBUG级别日志 appender -->  \n    <appender name=\"DEBUG\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">   \n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">  \n            <!-- 按天回滚 daily -->  \n            <fileNamePattern>${baseLogDir}/logs/%d{yyyy-MM-dd}/debug-log.log</fileNamePattern>  \n            <!-- 日志最大的历史 60天 -->  \n            <maxHistory>${maxHistory}</maxHistory>  \n        </rollingPolicy>  \n\t\t<triggeringPolicy class=\"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy\">\n            <maxFileSize>${maxFileSize}</maxFileSize>\n        </triggeringPolicy>\n        <encoder>  \n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n</pattern>\n        </encoder>  \n    </appender>\n\n\n    <logger name=\"com.redant\" level=\"INFO\"/>\n\n    <!-- root级别   DEBUG -->  \n    <root level=\"INFO\">\n        <!-- 控制台输出 -->\n        <appender-ref ref=\"STDOUT\" />\n        <!-- 文件输出 -->\n        <appender-ref ref=\"DEBUG\" />\n    </root>\n\n\n    <!-- router的相关日志 -->\n    <appender name=\"ROUTER_MSG\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${baseLogDir}/logs/%d{yyyy-MM-dd}/routerMsg.log</fileNamePattern>\n            <!-- 日志最大的保存天数 -->\n            <maxHistory>${maxHistory}</maxHistory>\n        </rollingPolicy>\n        <triggeringPolicy class=\"ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy\">\n            <maxFileSize>${maxFileSize}</maxFileSize>\n        </triggeringPolicy>\n        <encoder>\n            <pattern>%-1relative - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{0}:%line - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <!-- router的相关日志 -->\n    <logger name=\"routerMsgLog\" additivity=\"true\">\n        <level value=\"INFO\"/>\n        <appender-ref ref=\"ROUTER_MSG\" />\n    </logger>\n\n</configuration>  "
  },
  {
    "path": "redant-example/src/test/java/com/lememo/core/interceptor/InterceptorProviderTest.java",
    "content": "package com.lememo.core.interceptor;\n\nimport com.redant.core.interceptor.Interceptor;\nimport com.redant.core.interceptor.InterceptorProvider;\n\nimport java.util.List;\n\n/**\n * @author houyi\n **/\npublic class InterceptorProviderTest {\n\n    public static void main(String[] args) {\n        for(int i=0;i<10;i++){\n            new Thread(new Run()).start();\n        }\n    }\n\n    static class Run implements Runnable {\n        @Override\n        public void run() {\n            List<Interceptor> interceptors = InterceptorProvider.getInterceptors();\n            System.out.println(\"Thread=[\" + Thread.currentThread().getName() + \"] interceptors size=\"+interceptors.size());\n        }\n    }\n\n}\n"
  }
]