├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── love │ └── wangqi │ ├── GatewayServerDemo.java │ ├── codec │ ├── DefaultHttpRequestBuilder.java │ ├── HttpRequestBuilder.java │ ├── HttpRequestDecomposer.java │ └── RequestHolder.java │ ├── config │ └── GatewayConfig.java │ ├── context │ ├── Attributes.java │ ├── Constants.java │ └── ContextUtil.java │ ├── core │ ├── DefaultChannelWriteFinishListener.java │ └── ResponseHandler.java │ ├── exception │ ├── GatewayException.java │ ├── GatewayNoRouteException.java │ ├── GatewayTimeoutException.java │ └── handler │ │ ├── AbstractExceptionHandler.java │ │ ├── DefaultExceptionHandler.java │ │ ├── ExceptionHandler.java │ │ └── ExceptionResponse.java │ ├── filter │ ├── FilterProcessor.java │ ├── FilterRegistry.java │ ├── GatewayFilter.java │ ├── IGatewayFilter.java │ ├── SendErrorFilter.java │ ├── SendForwardFilter.java │ └── SendResponseFilter.java │ ├── handler │ ├── ExceptionHandler.java │ ├── GatewayRunner.java │ ├── back │ │ ├── BackClientPool.java │ │ ├── BackHandler.java │ │ └── BackPoolHandler.java │ └── front │ │ ├── FrontFilter.java │ │ └── FrontHandler.java │ ├── route │ ├── AbstractRouteMapper.java │ ├── Route.java │ └── RouteMapper.java │ ├── server │ └── GatewayServer.java │ └── util │ ├── AntPathMatcher.java │ ├── PathMatcher.java │ └── StringUtils.java └── resources └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | .idea/ 25 | *.iml 26 | /target 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # api-gateway-core 2 | 3 | [![Build Status](https://travis-ci.org/wangqifox/api-gateway-core.svg?branch=master)](https://travis-ci.org/wangqifox/api-gateway-core) 4 | [![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 5 | 6 | `api-gateway-core`是一个轻量级的api网关 7 | 8 | ## Background 9 | 10 | 随着公司各个项目的扩展,不同的项目之间和第三方出现了大量调用项目API的需求。此时就面临了一系列问题,例如:如何让各个项目安全地对外开放接口、如何让调用方快速接入、如何保证接口的安全等等。最初的时候,这些工作是各个项目自己做的,这段时期的接口对接是一个极其痛苦的过程:各个项目的权限控制不一样、文档不全,接口提供方和调用方都需要经过大量重复的沟通。也是我们需要一个隔离接口提供方和调用方的中间层——API网关,它负责在抽象出各个业务需要的通用功能,例如:权限验证、限流、超时控制、熔断降级。 11 | 12 | ## Usage 13 | 14 | ```java 15 | // 网关的映射关系 16 | RouteMapper routeMapper = new AbstractRouteMapper() { 17 | @Override 18 | protected List locateRouteList(Set ids) { 19 | List routeList = new ArrayList<>(); 20 | try { 21 | routeList.add(new Route(1L, HttpMethod.GET, "/", new URL("https://blog.wangqi.love/"))); 22 | routeList.add(new Route(2L, HttpMethod.GET, "/baidu", new URL("https://www.baidu.com/"))); 23 | routeList.add(new Route(3L, HttpMethod.GET, "/taobao", new URL("https://www.taobao.com/"))); 24 | routeList.add(new Route(4L, HttpMethod.GET, "/github", new URL("https://github.com/"))); 25 | routeList.add(new Route(5L, HttpMethod.GET, "/oschina", new URL("https://www.oschina.net/"))); 26 | routeList.add(new Route(6L, HttpMethod.POST, "/users/{id}", new URL("http://127.0.0.1/pre/users/{id}"))); 27 | routeList.add(new Route(7L, HttpMethod.GET, "/css/main.css", new URL("https://blog.wangqi.love/css/main.css"))); 28 | routeList.add(new Route(8L, HttpMethod.GET, "/path", new URL("http://127.0.0.1:9990/path"))); 29 | routeList.add(new Route(9L, HttpMethod.GET, "/html", new URL("http://10.100.64.71/html/user.json"))); 30 | routeList.add(new Route(10L, HttpMethod.GET, "/css", new URL("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/mantpl/css/news/init_7637f86c.css"))); 31 | routeList.add(new Route(11L, HttpMethod.GET, "/google", new URL("https://www.google.com"))); 32 | routeList.add(new Route(12L, HttpMethod.GET, "/local", new URL("http://127.0.0.1:9999"))); 33 | } catch (MalformedURLException e) { 34 | } 35 | return routeList; 36 | } 37 | }; 38 | routeMapper.refresh(null); 39 | GatewayConfig config = GatewayConfig.getInstance(); 40 | config.setPort(8888); 41 | config.setHttpRequestBuilder(new DefaultHttpRequestBuilder()); 42 | config.setRouteMapper(routeMapper); 43 | config.setChannelWriteFinishListener(new DefaultChannelWriteFinishListener()); 44 | config.setResponseHandler(new ResponseHandler()); 45 | config.setExceptionHandler(new DefaultExceptionHandler()); 46 | 47 | GatewayServer server = new GatewayServer(); 48 | server.start(); 49 | ``` 50 | 51 | ## Related 52 | 53 | [API网关技术总结](https://blog.wangqi.love/articles/Java/API网关技术总结.html) 54 | 55 | ## License 56 | 57 | [Apache License 2.0](LICENSE) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | love.wangqi 8 | api-gateway-core 9 | 1.0-SNAPSHOT 10 | 11 | api-gateway-core 12 | 13 | http://www.example.com 14 | 15 | 16 | UTF-8 17 | 1.8 18 | 1.8 19 | 20 | 21 | 22 | 23 | junit 24 | junit 25 | 4.13.1 26 | test 27 | 28 | 29 | io.netty 30 | netty-all 31 | 4.1.43.Final 32 | 33 | 34 | com.fasterxml.jackson.core 35 | jackson-databind 36 | 2.12.3 37 | 38 | 39 | ch.qos.logback 40 | logback-classic 41 | 1.2.3 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-surefire-plugin 50 | 51 | true 52 | 53 | 2.21.0 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-release-plugin 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-assembly-plugin 62 | 2.5.5 63 | 64 | 65 | 66 | com.xxg.Main 67 | 68 | 69 | 70 | jar-with-dependencies 71 | 72 | 73 | 74 | 75 | 76 | build-server 77 | 78 | 79 | 80 | 81 | love.wangqi.GatewayServerDemo 82 | 83 | 84 | 85 | jar-with-dependencies 86 | 87 | GatewayServer 88 | . 89 | 90 | package 91 | 92 | single 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/GatewayServerDemo.java: -------------------------------------------------------------------------------- 1 | package love.wangqi; 2 | 3 | 4 | import io.netty.handler.codec.http.HttpMethod; 5 | import love.wangqi.codec.DefaultHttpRequestBuilder; 6 | import love.wangqi.config.GatewayConfig; 7 | import love.wangqi.core.DefaultChannelWriteFinishListener; 8 | import love.wangqi.core.ResponseHandler; 9 | import love.wangqi.exception.handler.DefaultExceptionHandler; 10 | import love.wangqi.route.AbstractRouteMapper; 11 | import love.wangqi.route.Route; 12 | import love.wangqi.route.RouteMapper; 13 | import love.wangqi.server.GatewayServer; 14 | 15 | import java.net.MalformedURLException; 16 | import java.net.URL; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Set; 20 | 21 | /** 22 | * @author: wangqi 23 | * @description: 24 | * @date: Created in 2018/5/30 下午6:22 25 | */ 26 | public class GatewayServerDemo { 27 | 28 | public static void main(String[] args) { 29 | RouteMapper routeMapper = new AbstractRouteMapper() { 30 | @Override 31 | protected List locateRouteList(Set ids) { 32 | List routeList = new ArrayList<>(); 33 | try { 34 | routeList.add(new Route(1L, HttpMethod.GET, "/", new URL("https://blog.wangqi.love/"))); 35 | routeList.add(new Route(1L, HttpMethod.GET, "/wangqi", new URL("http://wangqi.love/"))); 36 | routeList.add(new Route(1L, HttpMethod.GET, "/117", new URL("http://10.0.111.117/#/login"))); 37 | routeList.add(new Route(2L, HttpMethod.GET, "/baidu", new URL("https://www.baidu.com/"))); 38 | routeList.add(new Route(3L, HttpMethod.GET, "/taobao", new URL("http://www.taobao.com/"))); 39 | routeList.add(new Route(4L, HttpMethod.GET, "/github", new URL("https://github.com/"))); 40 | routeList.add(new Route(5L, HttpMethod.GET, "/oschina", new URL("https://www.oschina.net/"))); 41 | routeList.add(new Route(6L, HttpMethod.POST, "/users/{id}", new URL("http://127.0.0.1/pre/users/{id}"))); 42 | routeList.add(new Route(7L, HttpMethod.GET, "/css/main.css", new URL("https://blog.wangqi.love/css/main.css"))); 43 | routeList.add(new Route(8L, HttpMethod.GET, "/path", new URL("http://127.0.0.1:9990/path"))); 44 | routeList.add(new Route(9L, HttpMethod.GET, "/html", new URL("http://10.100.64.71/html/user.json"))); 45 | routeList.add(new Route(10L, HttpMethod.GET, "/css", new URL("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/mantpl/css/news/init_7637f86c.css"))); 46 | routeList.add(new Route(11L, HttpMethod.GET, "/google", new URL("https://www.google.com"))); 47 | routeList.add(new Route(12L, HttpMethod.GET, "/local", new URL("http://127.0.0.1:9999"))); 48 | } catch (MalformedURLException e) { 49 | } 50 | return routeList; 51 | } 52 | }; 53 | routeMapper.refresh(null); 54 | GatewayConfig config = GatewayConfig.getInstance(); 55 | config.setPort(8888); 56 | config.setHttpRequestBuilder(new DefaultHttpRequestBuilder()); 57 | config.setRouteMapper(routeMapper); 58 | config.setChannelWriteFinishListener(new DefaultChannelWriteFinishListener()); 59 | config.setResponseHandler(new ResponseHandler()); 60 | config.setExceptionHandler(new DefaultExceptionHandler()); 61 | 62 | GatewayServer server = new GatewayServer(); 63 | server.start(); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/main/java/love/wangqi/codec/DefaultHttpRequestBuilder.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.handler.codec.http.*; 6 | import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; 7 | import io.netty.handler.codec.http.multipart.HttpDataFactory; 8 | import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; 9 | import io.netty.handler.codec.http.multipart.InterfaceHttpData; 10 | import love.wangqi.exception.GatewayNoRouteException; 11 | import love.wangqi.route.Route; 12 | import love.wangqi.route.RouteMapper; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.net.URI; 17 | import java.net.URL; 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * @author: wangqi 24 | * @description: 25 | * @date: Created in 2018/5/29 下午7:08 26 | */ 27 | public class DefaultHttpRequestBuilder implements HttpRequestBuilder { 28 | private static final Logger logger = LoggerFactory.getLogger(DefaultHttpRequestBuilder.class); 29 | 30 | private RouteMapper routeMapper; 31 | private HttpRequest newRequest; 32 | private HttpPostRequestEncoder newBodyRequestEncoder; 33 | 34 | protected HttpRequestDecomposer httpRequestDecomposer; 35 | 36 | HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); 37 | 38 | public DefaultHttpRequestBuilder() { 39 | } 40 | 41 | @Override 42 | public HttpRequestBuilder setRouteMapper(RouteMapper routeMapper) { 43 | this.routeMapper = routeMapper; 44 | return this; 45 | } 46 | 47 | @Override 48 | public Route getRoute(FullHttpRequest originRequest) { 49 | return routeMapper.getRoute(originRequest); 50 | } 51 | 52 | @Override 53 | public RequestHolder build(FullHttpRequest originRequest) throws Exception { 54 | httpRequestDecomposer = new HttpRequestDecomposer(originRequest); 55 | Route route = getRoute(originRequest); 56 | if (route == null) { 57 | throw new GatewayNoRouteException(); 58 | } 59 | URL url = route.getMapUrl(); 60 | logger.info("proxy_pass {}", url.toString()); 61 | 62 | // 请求路径 63 | QueryStringEncoder queryStringEncoder = new QueryStringEncoder(url.getPath()); 64 | // 请求参数 65 | buildParams(route).forEach((key, values) -> values.forEach(value -> { 66 | queryStringEncoder.addParam(key, value); 67 | })); 68 | newRequest = new DefaultFullHttpRequest(originRequest.protocolVersion(), originRequest.method(), new URI(queryStringEncoder.toString()).toASCIIString()); 69 | // 请求头 70 | buildHeaders(route).forEach((key, values) -> values.forEach(value -> { 71 | newRequest.headers().set(key, value); 72 | })); 73 | newRequest.headers().remove(HttpHeaderNames.COOKIE); 74 | newRequest.headers().set(HttpHeaderNames.HOST, url.getHost()); 75 | HttpUtil.setKeepAlive(newRequest, true); 76 | 77 | // 请求体 78 | String contentType = httpRequestDecomposer.getContentType(); 79 | if (contentType != null) { 80 | if (contentType.startsWith(HttpHeaderValues.APPLICATION_JSON.toString())) { 81 | ByteBuf bbuf = Unpooled.copiedBuffer(buildContentJson(route), StandardCharsets.UTF_8); 82 | newRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, bbuf.readableBytes()); 83 | ((FullHttpRequest) newRequest).content().writeBytes(bbuf); 84 | } else if (contentType.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())) { 85 | HttpPostRequestEncoder requestEncoder = new HttpPostRequestEncoder(newRequest, false); 86 | buildContentFormUrlEncoded(route).forEach((key, values) -> { 87 | values.forEach(value -> { 88 | try { 89 | requestEncoder.addBodyAttribute(key, value); 90 | } catch (HttpPostRequestEncoder.ErrorDataEncoderException e) { 91 | logger.error(e.getMessage()); 92 | } 93 | }); 94 | }); 95 | newRequest = requestEncoder.finalizeRequest(); 96 | } else if (contentType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString())) { 97 | HttpPostRequestEncoder requestEncoder = new HttpPostRequestEncoder(factory, newRequest, true); 98 | for (InterfaceHttpData data : buildContentFormData(route)) { 99 | if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) { 100 | requestEncoder.addBodyHttpData(data); 101 | } else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) { 102 | requestEncoder.addBodyHttpData(data); 103 | } 104 | } 105 | newRequest = requestEncoder.finalizeRequest(); 106 | newBodyRequestEncoder = requestEncoder; 107 | } else { 108 | ByteBuf byteBuf = buildContentOther(route); 109 | newRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes()); 110 | ((FullHttpRequest) newRequest).content().writeBytes(byteBuf); 111 | } 112 | } 113 | return new RequestHolder(route, url, newRequest, newBodyRequestEncoder); 114 | } 115 | 116 | /** 117 | * 返回path(不包含?后面的参数部分) 118 | * 119 | * @return 120 | */ 121 | protected String buildPath(Route route) { 122 | return httpRequestDecomposer.getPath(); 123 | } 124 | 125 | /** 126 | * 返回请求的请求参数 127 | * 128 | * @return 129 | */ 130 | protected Map> buildParams(Route route) { 131 | return httpRequestDecomposer.getParams(); 132 | } 133 | 134 | /** 135 | * 返回请求的请求头 136 | * 137 | * @return 138 | */ 139 | protected Map> buildHeaders(Route route) { 140 | return httpRequestDecomposer.getHeaders(); 141 | } 142 | 143 | /** 144 | * 如果content-type为application/json,获取请求体 145 | * 146 | * @return 147 | */ 148 | protected String buildContentJson(Route route) { 149 | return httpRequestDecomposer.getContentJsonAsString(); 150 | } 151 | 152 | /** 153 | * 如果content-type为application/x-www-form-urlencoded,获取请求体 154 | * 155 | * @return 156 | */ 157 | protected Map> buildContentFormUrlEncoded(Route route) { 158 | return httpRequestDecomposer.getContentFormUrlEncoded(); 159 | } 160 | 161 | /** 162 | * 如果content-type为multipart/form-data,获取请求体 163 | * 164 | * @return 165 | */ 166 | protected List buildContentFormData(Route route) { 167 | return httpRequestDecomposer.getContentFormdata(); 168 | } 169 | 170 | /** 171 | * 其他类型的content-type则直接返回相应的ByteBuf 172 | * 173 | * @return 174 | */ 175 | private ByteBuf buildContentOther(Route route) { 176 | return httpRequestDecomposer.getContentOther(); 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/codec/HttpRequestBuilder.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.codec; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import love.wangqi.route.Route; 5 | import love.wangqi.route.RouteMapper; 6 | 7 | 8 | /** 9 | * @author: wangqi 10 | * @description: 11 | * @date: Created in 2018/5/30 下午6:58 12 | */ 13 | public interface HttpRequestBuilder { 14 | /** 15 | * 设置路由映射器 16 | * 17 | * @param routeMapper 18 | * @return 19 | */ 20 | HttpRequestBuilder setRouteMapper(RouteMapper routeMapper); 21 | 22 | /** 23 | * 生成新的请求 24 | * 25 | * @param originRequest 26 | * @return 27 | * @throws Exception 28 | */ 29 | RequestHolder build(FullHttpRequest originRequest) throws Exception; 30 | 31 | /** 32 | * 获取路由 33 | * 34 | * @param originRequest 35 | * @return 36 | */ 37 | Route getRoute(FullHttpRequest originRequest); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/codec/HttpRequestDecomposer.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.codec; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.HttpHeaderNames; 8 | import io.netty.handler.codec.http.QueryStringDecoder; 9 | import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; 10 | import io.netty.handler.codec.http.multipart.InterfaceHttpData; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.IOException; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.*; 17 | 18 | /** 19 | * @author: wangqi 20 | * @description: 21 | * @date: Created in 2018/5/29 下午4:57 22 | */ 23 | public class HttpRequestDecomposer { 24 | private static final Logger logger = LoggerFactory.getLogger(HttpRequestDecomposer.class); 25 | 26 | private FullHttpRequest request; 27 | private ObjectMapper objectMapper = new ObjectMapper(); 28 | 29 | public HttpRequestDecomposer(FullHttpRequest request) { 30 | this.request = request; 31 | } 32 | 33 | /** 34 | * 获取请求的uri(包含?后面的参数部分) 35 | * 36 | * @return 37 | */ 38 | public String getUri() { 39 | return request.uri(); 40 | } 41 | 42 | /** 43 | * 获取请求路径(不包含?后面的参数部分) 44 | * 45 | * @return 46 | */ 47 | public String getPath() { 48 | QueryStringDecoder stringDecoder = new QueryStringDecoder(getUri(), StandardCharsets.UTF_8); 49 | return stringDecoder.path(); 50 | } 51 | 52 | /** 53 | * 获取请求参数 54 | * 55 | * @return 56 | */ 57 | public Map> getParams() { 58 | QueryStringDecoder stringDecoder = new QueryStringDecoder(getUri(), StandardCharsets.UTF_8); 59 | return stringDecoder.parameters(); 60 | } 61 | 62 | /** 63 | * 获取content-type 64 | * 65 | * @return 66 | */ 67 | public String getContentType() { 68 | return request.headers().get(HttpHeaderNames.CONTENT_TYPE); 69 | } 70 | 71 | /** 72 | * 获取请求头 73 | * 74 | * @return 75 | */ 76 | public Map> getHeaders() { 77 | Map> headers = new HashMap<>(); 78 | Iterator> iterator = request.headers().iteratorAsString(); 79 | while (iterator.hasNext()) { 80 | Map.Entry entry = iterator.next(); 81 | String key = entry.getKey(); 82 | String value = entry.getValue(); 83 | List values = headers.get(key); 84 | if (values == null) { 85 | values = new ArrayList<>(1); 86 | headers.put(key, values); 87 | } 88 | values.add(value); 89 | } 90 | return headers; 91 | } 92 | 93 | /** 94 | * 如果content-type为application/json,将内容转换成JsonNode 95 | * 96 | * @return 97 | */ 98 | public JsonNode getContentJson() { 99 | return getContentJson(JsonNode.class); 100 | } 101 | 102 | /** 103 | * 如果content-type为application/json,以字符串形式返回请求体 104 | * 105 | * @return 106 | */ 107 | public String getContentJsonAsString() { 108 | return getContentAsString(); 109 | } 110 | 111 | /** 112 | * 如果content-type为application/json,将内容转换成相应的类型 113 | * 114 | * @return 115 | */ 116 | public T getContentJson(Class valueType) { 117 | String content = request.content().toString(StandardCharsets.UTF_8); 118 | try { 119 | return objectMapper.readValue(content, valueType); 120 | } catch (IOException e) { 121 | logger.error(e.getMessage()); 122 | return null; 123 | } 124 | } 125 | 126 | /** 127 | * 如果content-type为application/x-www-form-urlencoded,将内容转换成map 128 | * 129 | * @return 130 | */ 131 | public Map> getContentFormUrlEncoded() { 132 | String content = request.content().toString(StandardCharsets.UTF_8); 133 | QueryStringDecoder stringDecoder = new QueryStringDecoder("?" + content, StandardCharsets.UTF_8); 134 | return stringDecoder.parameters(); 135 | } 136 | 137 | /** 138 | * 如果content-type为multipart/form-data,获取内容列表 139 | * 140 | * @return 141 | */ 142 | public List getContentFormdata() { 143 | HttpPostRequestDecoder postRequestDecoder = new HttpPostRequestDecoder(request); 144 | return postRequestDecoder.getBodyHttpDatas(); 145 | } 146 | 147 | /** 148 | * 其他类型的content-type则直接返回相应的ByteBuf 149 | * 150 | * @return 151 | */ 152 | public ByteBuf getContentOther() { 153 | return request.content(); 154 | } 155 | 156 | /** 157 | * 以字符串形式返回请求体 158 | * 159 | * @return 160 | */ 161 | public String getContentAsString() { 162 | return request.content().toString(StandardCharsets.UTF_8); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/codec/RequestHolder.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.codec; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; 6 | import love.wangqi.route.Route; 7 | 8 | import java.net.InetSocketAddress; 9 | import java.net.MalformedURLException; 10 | import java.net.URL; 11 | import java.util.Arrays; 12 | 13 | import static love.wangqi.context.Constants.HTTP; 14 | import static love.wangqi.context.Constants.HTTPS; 15 | 16 | /** 17 | * @author: wangqi 18 | * @description: 19 | * @date: Created in 2018/7/28 22:37 20 | */ 21 | public class RequestHolder { 22 | public Route route; 23 | public URL url; 24 | public HttpRequest request; 25 | public HttpPostRequestEncoder bodyRequestEncoder; 26 | 27 | public RequestHolder(Route route, URL url, HttpRequest request, HttpPostRequestEncoder bodyRequestEncoder) { 28 | this.route = route; 29 | this.url = url; 30 | this.request = request; 31 | this.bodyRequestEncoder = bodyRequestEncoder; 32 | } 33 | 34 | public String getHost() { 35 | if (url.getHost() == null) { 36 | throw new RuntimeException("no host found"); 37 | } 38 | return url.getHost(); 39 | } 40 | 41 | public int getPort() { 42 | String protocol = url.getProtocol() == null ? HTTP : url.getProtocol(); 43 | int port = url.getPort(); 44 | if (port == -1) { 45 | if (HTTP.equalsIgnoreCase(protocol)) { 46 | port = 80; 47 | } else if (HTTPS.equalsIgnoreCase(protocol)) { 48 | port = 443; 49 | } 50 | } 51 | return port; 52 | } 53 | 54 | public String getProtocol() { 55 | return url.getProtocol(); 56 | } 57 | 58 | public InetSocketAddress getSocketAddress() { 59 | return new InetSocketAddress(getHost(), getPort()); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return Arrays.hashCode(new Object[]{route, url}); 65 | } 66 | 67 | @Override 68 | public boolean equals(Object obj) { 69 | if (this == obj) { 70 | return true; 71 | } 72 | if (obj instanceof RequestHolder) { 73 | return this.route.equals(((RequestHolder) obj).route) && this.url.equals(((RequestHolder) obj).url); 74 | } 75 | return false; 76 | } 77 | 78 | public static void main(String[] args) throws MalformedURLException { 79 | RequestHolder requestHolder1 = new RequestHolder(new Route(9L, HttpMethod.GET, "/html", new URL("http://10.100.64.71/html/user.json")), new URL("http://10.100.64.71/html/user.json"), null, null); 80 | RequestHolder requestHolder2 = new RequestHolder(new Route(9L, HttpMethod.GET, "/html", new URL("http://10.100.64.71/html/user.json")), new URL("http://10.100.64.71/html/user.json"), null, null); 81 | System.out.println(requestHolder1.hashCode()); 82 | System.out.println(requestHolder2.hashCode()); 83 | System.out.println(requestHolder1.equals(requestHolder2)); 84 | 85 | InetSocketAddress socketAddress1 = new InetSocketAddress("127.0.0.1", 80); 86 | InetSocketAddress socketAddress2 = new InetSocketAddress("127.0.0.1", 80); 87 | System.out.println(socketAddress1.hashCode()); 88 | System.out.println(socketAddress2.hashCode()); 89 | System.out.println(socketAddress1.equals(socketAddress2)); 90 | } 91 | } -------------------------------------------------------------------------------- /src/main/java/love/wangqi/config/GatewayConfig.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.config; 2 | 3 | 4 | import io.netty.channel.ChannelFutureListener; 5 | import io.netty.channel.ChannelInboundHandler; 6 | import io.netty.channel.ChannelOutboundHandler; 7 | import love.wangqi.codec.HttpRequestBuilder; 8 | import love.wangqi.core.ResponseHandler; 9 | import love.wangqi.exception.handler.ExceptionHandler; 10 | import love.wangqi.filter.FilterRegistry; 11 | import love.wangqi.filter.GatewayFilter; 12 | import love.wangqi.route.RouteMapper; 13 | 14 | import java.util.*; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | 17 | /** 18 | * @author: wangqi 19 | * @description: 20 | * @date: Created in 2018/6/4 下午6:49 21 | */ 22 | public class GatewayConfig { 23 | 24 | public final static GatewayConfig INSTANCE = new GatewayConfig(); 25 | 26 | private final ConcurrentHashMap> hashFiltersByType = new ConcurrentHashMap>(); 27 | 28 | public static GatewayConfig getInstance() { 29 | return INSTANCE; 30 | } 31 | 32 | public List getFiltersByType(String filterType) { 33 | List list = hashFiltersByType.get(filterType); 34 | if (list != null) { 35 | return list; 36 | } 37 | 38 | list = new ArrayList<>(); 39 | 40 | Collection filters = FilterRegistry.instance().getAllFilters(); 41 | for (Iterator iterator = filters.iterator(); iterator.hasNext(); ) { 42 | GatewayFilter filter = iterator.next(); 43 | if (filter.filterType().equals(filterType)) { 44 | list.add(filter); 45 | } 46 | } 47 | Collections.sort(list); 48 | hashFiltersByType.putIfAbsent(filterType, list); 49 | return list; 50 | } 51 | 52 | /** 53 | * 读入流量处理器 54 | */ 55 | private List channelInboundHandlerList; 56 | /** 57 | * 写出流量处理器 58 | */ 59 | private List channelOutboundHandlerList; 60 | /** 61 | * HttpResponse的处理器 62 | */ 63 | private List httpResponseHandlerList; 64 | /** 65 | * 路由映射器 66 | */ 67 | private RouteMapper routeMapper; 68 | /** 69 | * http请求构建器 70 | */ 71 | private HttpRequestBuilder httpRequestBuilder; 72 | /** 73 | * 服务端口 74 | */ 75 | private int port; 76 | /** 77 | * channel写完成监听器 78 | */ 79 | private ChannelFutureListener channelWriteFinishListener; 80 | /** 81 | * 异常处理器 82 | */ 83 | private ExceptionHandler exceptionHandler; 84 | /** 85 | * 响应处理器 86 | */ 87 | private ResponseHandler responseHandler; 88 | 89 | 90 | private GatewayConfig() { 91 | channelInboundHandlerList = new ArrayList<>(); 92 | channelOutboundHandlerList = new ArrayList<>(); 93 | httpResponseHandlerList = new ArrayList<>(); 94 | } 95 | 96 | public void addChannelInboundHandler(ChannelInboundHandler channelInboundHandler) { 97 | channelInboundHandlerList.add(channelInboundHandler); 98 | } 99 | 100 | public List getChannelInboundHandlerList() { 101 | return channelInboundHandlerList; 102 | } 103 | 104 | public void addChannelOutboundHandler(ChannelOutboundHandler channelOutboundHandler) { 105 | channelOutboundHandlerList.add(channelOutboundHandler); 106 | } 107 | 108 | public List getChannelOutboundHandlerList() { 109 | return channelOutboundHandlerList; 110 | } 111 | 112 | public void addHttpResponseHandler(ChannelOutboundHandler channelOutboundHandler) { 113 | httpResponseHandlerList.add(channelOutboundHandler); 114 | } 115 | 116 | public List getHttpResponseHandlerList() { 117 | return httpResponseHandlerList; 118 | } 119 | 120 | public RouteMapper getRouteMapper() { 121 | return routeMapper; 122 | } 123 | 124 | public void setRouteMapper(RouteMapper routeMapper) { 125 | this.routeMapper = routeMapper; 126 | } 127 | 128 | public HttpRequestBuilder getHttpRequestBuilder() { 129 | return httpRequestBuilder; 130 | } 131 | 132 | public void setHttpRequestBuilder(HttpRequestBuilder httpRequestBuilder) { 133 | this.httpRequestBuilder = httpRequestBuilder; 134 | } 135 | 136 | public int getPort() { 137 | return port; 138 | } 139 | 140 | public void setPort(int port) { 141 | this.port = port; 142 | } 143 | 144 | public ChannelFutureListener getChannelWriteFinishListener() { 145 | return channelWriteFinishListener; 146 | } 147 | 148 | public void setChannelWriteFinishListener(ChannelFutureListener channelWriteFinishListener) { 149 | this.channelWriteFinishListener = channelWriteFinishListener; 150 | } 151 | 152 | public ExceptionHandler getExceptionHandler() { 153 | return exceptionHandler; 154 | } 155 | 156 | public void setExceptionHandler(ExceptionHandler exceptionHandler) { 157 | this.exceptionHandler = exceptionHandler; 158 | } 159 | 160 | public ResponseHandler getResponseHandler() { 161 | return responseHandler; 162 | } 163 | 164 | public void setResponseHandler(ResponseHandler responseHandler) { 165 | this.responseHandler = responseHandler; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/context/Attributes.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.context; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.pool.SimpleChannelPool; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | import io.netty.handler.codec.http.FullHttpResponse; 7 | import io.netty.util.AttributeKey; 8 | import love.wangqi.codec.RequestHolder; 9 | 10 | /** 11 | * @author: wangqi 12 | * @description: 13 | * @date: Created in 2018-11-27 11:30 14 | */ 15 | public interface Attributes { 16 | AttributeKey REQUEST = AttributeKey.newInstance("httpRequest"); 17 | AttributeKey EXCEPTION = AttributeKey.newInstance("exception"); 18 | AttributeKey RESPONSE = AttributeKey.newInstance("response"); 19 | AttributeKey KEEPALIVE = AttributeKey.newInstance("keepAlive"); 20 | AttributeKey SERVER_CHANNEL = AttributeKey.newInstance("serverChannel"); 21 | AttributeKey CLIENT_POOL = AttributeKey.newInstance("clientPool"); 22 | AttributeKey REQUEST_HOLDER = AttributeKey.newInstance("requestHolder"); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/context/Constants.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.context; 2 | 3 | /** 4 | * @author: wangqi 5 | * @description: 6 | * @date: Created in 2018-11-27 16:48 7 | */ 8 | public class Constants { 9 | public static final String HTTP = "http"; 10 | public static final String HTTPS = "https"; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/context/ContextUtil.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.context; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.FullHttpResponse; 6 | import love.wangqi.codec.RequestHolder; 7 | 8 | 9 | /** 10 | * @author: wangqi 11 | * @description: 12 | * @date: Created in 2018-11-27 15:02 13 | */ 14 | public class ContextUtil { 15 | public static void setRequest(Channel channel, FullHttpRequest request) { 16 | channel.attr(Attributes.REQUEST).set(request); 17 | } 18 | 19 | public static FullHttpRequest getRequest(Channel channel) { 20 | return channel.attr(Attributes.REQUEST).get(); 21 | } 22 | 23 | public static void setResponse(Channel channel, FullHttpResponse response) { 24 | channel.attr(Attributes.RESPONSE).set(response); 25 | } 26 | 27 | public static FullHttpResponse getResponse(Channel channel) { 28 | return channel.attr(Attributes.RESPONSE).get(); 29 | } 30 | 31 | public static void setKeepAlive(Channel channel, Boolean keepAlive) { 32 | channel.attr(Attributes.KEEPALIVE).set(keepAlive); 33 | } 34 | 35 | public static Boolean getKeepAlive(Channel channel) { 36 | return channel.attr(Attributes.KEEPALIVE).get(); 37 | } 38 | 39 | public static void setRequestHolder(Channel channel, RequestHolder requestHolder) { 40 | channel.attr(Attributes.REQUEST_HOLDER).set(requestHolder); 41 | } 42 | 43 | public static RequestHolder getRequestHolder(Channel channel) { 44 | return channel.attr(Attributes.REQUEST_HOLDER).get(); 45 | } 46 | 47 | public static void setException(Channel channel, Exception exception) { 48 | channel.attr(Attributes.EXCEPTION).set(exception); 49 | } 50 | 51 | public static Exception getException(Channel channel) { 52 | return channel.attr(Attributes.EXCEPTION).get(); 53 | } 54 | 55 | public static void clear(Channel channel) { 56 | channel.attr(Attributes.REQUEST).set(null); 57 | channel.attr(Attributes.RESPONSE).set(null); 58 | channel.attr(Attributes.KEEPALIVE).set(null); 59 | channel.attr(Attributes.EXCEPTION).set(null); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/core/DefaultChannelWriteFinishListener.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.core; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelFutureListener; 6 | import love.wangqi.context.ContextUtil; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * @author: wangqi 12 | * @description: 13 | * @date: Created in 2018-11-27 18:39 14 | */ 15 | public class DefaultChannelWriteFinishListener implements ChannelFutureListener { 16 | private Logger logger = LoggerFactory.getLogger(DefaultChannelWriteFinishListener.class); 17 | 18 | @Override 19 | public void operationComplete(ChannelFuture future) throws Exception { 20 | if (future.isSuccess()) { 21 | Channel channel = future.channel(); 22 | Boolean keepAlive = ContextUtil.getKeepAlive(channel); 23 | 24 | if (keepAlive == null || !keepAlive) { 25 | channel.close(); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/core/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.core; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.handler.codec.http.FullHttpResponse; 6 | import io.netty.handler.codec.http.HttpHeaderNames; 7 | import io.netty.handler.codec.http.HttpHeaderValues; 8 | import love.wangqi.config.GatewayConfig; 9 | import love.wangqi.context.ContextUtil; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * @author: wangqi 15 | * @description: 16 | * @date: Created in 2018/9/28 下午6:14 17 | */ 18 | public class ResponseHandler { 19 | private Logger logger = LoggerFactory.getLogger(ResponseHandler.class); 20 | 21 | private GatewayConfig config = GatewayConfig.getInstance(); 22 | 23 | public synchronized void send(Channel channel, FullHttpResponse response) { 24 | response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); 25 | Boolean keepAlive = ContextUtil.getKeepAlive(channel); 26 | if (keepAlive) { 27 | response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 28 | } 29 | 30 | ChannelFuture future = channel.writeAndFlush(response); 31 | if (config.getChannelWriteFinishListener() != null) { 32 | future.addListener(config.getChannelWriteFinishListener()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/exception/GatewayException.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.exception; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/9/3 下午6:39 9 | */ 10 | public class GatewayException extends RuntimeException { 11 | private HttpResponseStatus status; 12 | private String message; 13 | 14 | public GatewayException(HttpResponseStatus status, String message) { 15 | super(message); 16 | this.status = status; 17 | this.message = message; 18 | } 19 | 20 | public HttpResponseStatus getStatus() { 21 | return status; 22 | } 23 | 24 | @Override 25 | public String getMessage() { 26 | return message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/exception/GatewayNoRouteException.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.exception; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/6/5 下午7:26 9 | */ 10 | public class GatewayNoRouteException extends GatewayException { 11 | 12 | public GatewayNoRouteException() { 13 | super(HttpResponseStatus.NOT_FOUND, "no route found"); 14 | } 15 | 16 | @Override 17 | public synchronized Throwable fillInStackTrace() { 18 | return this; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/exception/GatewayTimeoutException.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.exception; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/7/28 09:37 9 | */ 10 | public class GatewayTimeoutException extends GatewayException { 11 | 12 | public GatewayTimeoutException() { 13 | super(HttpResponseStatus.REQUEST_TIMEOUT, "request timeout"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/exception/handler/AbstractExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.exception.handler; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/6/5 下午6:08 9 | */ 10 | public abstract class AbstractExceptionHandler implements ExceptionHandler { 11 | 12 | @Override 13 | public final void handle(Channel channel, Exception exception) { 14 | ExceptionResponse exceptionResponse = getExceptionResponse(exception); 15 | send(channel, exceptionResponse); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/exception/handler/DefaultExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.exception.handler; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 5 | import io.netty.handler.codec.http.FullHttpResponse; 6 | import io.netty.handler.codec.http.HttpResponseStatus; 7 | import io.netty.handler.codec.http.HttpVersion; 8 | import love.wangqi.config.GatewayConfig; 9 | import love.wangqi.exception.GatewayException; 10 | 11 | import java.net.ConnectException; 12 | import java.util.concurrent.RejectedExecutionException; 13 | 14 | /** 15 | * @author: wangqi 16 | * @description: 17 | * @date: Created in 2018/6/5 下午6:16 18 | */ 19 | public class DefaultExceptionHandler extends AbstractExceptionHandler { 20 | private GatewayConfig config = GatewayConfig.getInstance(); 21 | 22 | @Override 23 | public ExceptionResponse getExceptionResponse(Exception exception) { 24 | ExceptionResponse exceptionResponse = new ExceptionResponse(); 25 | if (exception instanceof GatewayException) { 26 | GatewayException gatewayException = (GatewayException) exception; 27 | exceptionResponse.setStatus(gatewayException.getStatus()); 28 | exceptionResponse.setContentType("text/plain"); 29 | exceptionResponse.setContent(gatewayException.getMessage()); 30 | } else if (exception instanceof ConnectException) { 31 | exceptionResponse.setStatus(HttpResponseStatus.NOT_FOUND); 32 | exceptionResponse.setContentType("text/plain"); 33 | exceptionResponse.setContent("connect server refused"); 34 | } else if (exception instanceof RejectedExecutionException) { 35 | exceptionResponse.setStatus(HttpResponseStatus.TOO_MANY_REQUESTS); 36 | exceptionResponse.setContentType("text/plain"); 37 | exceptionResponse.setContent("too many requests"); 38 | } else { 39 | exceptionResponse.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR); 40 | exceptionResponse.setContentType("text/plain"); 41 | exceptionResponse.setContent(exception.getMessage()); 42 | } 43 | return exceptionResponse; 44 | } 45 | 46 | @Override 47 | public void send(Channel channel, ExceptionResponse exceptionResponse) { 48 | String content = exceptionResponse.getContent(); 49 | FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, exceptionResponse.getStatus()); 50 | if (content != null) { 51 | response.headers().set("X-Ca-Error-Message", content); 52 | } 53 | config.getResponseHandler().send(channel, response); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/exception/handler/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.exception.handler; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/6/5 下午5:29 9 | */ 10 | public interface ExceptionHandler { 11 | /** 12 | * 获取异常返回 13 | * 14 | * @param exception 15 | * @return 16 | */ 17 | ExceptionResponse getExceptionResponse(Exception exception); 18 | 19 | /** 20 | * 发送异常 21 | * 22 | * @param channel 23 | * @param exceptionResponse 24 | */ 25 | void send(Channel channel, ExceptionResponse exceptionResponse); 26 | 27 | /** 28 | * 处理异常 29 | * 30 | * @param channel 31 | * @param exception 32 | */ 33 | void handle(Channel channel, Exception exception); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/exception/handler/ExceptionResponse.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.exception.handler; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/6/5 下午5:30 9 | */ 10 | public class ExceptionResponse { 11 | private HttpResponseStatus status; 12 | private String contentType; 13 | private String content; 14 | 15 | public HttpResponseStatus getStatus() { 16 | return status; 17 | } 18 | 19 | public void setStatus(HttpResponseStatus status) { 20 | this.status = status; 21 | } 22 | 23 | public String getContentType() { 24 | return contentType; 25 | } 26 | 27 | public void setContentType(String contentType) { 28 | this.contentType = contentType; 29 | } 30 | 31 | public String getContent() { 32 | return content; 33 | } 34 | 35 | public void setContent(String content) { 36 | this.content = content; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/filter/FilterProcessor.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.filter; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.HttpResponseStatus; 5 | import love.wangqi.config.GatewayConfig; 6 | import love.wangqi.exception.GatewayException; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author: wangqi 14 | * @description: 15 | * @date: Created in 2018/9/3 下午6:32 16 | */ 17 | public class FilterProcessor { 18 | private final static Logger logger = LoggerFactory.getLogger(FilterProcessor.class); 19 | private final static FilterProcessor INSTANCE = new FilterProcessor(); 20 | 21 | public static FilterProcessor getInstance() { 22 | return INSTANCE; 23 | } 24 | 25 | private FilterProcessor() { 26 | } 27 | 28 | public void preRoute(Channel channel) throws GatewayException { 29 | try { 30 | runFilters("pre", channel); 31 | } catch (GatewayException e) { 32 | throw e; 33 | } catch (Throwable e) { 34 | logger.error(e.getMessage(), e); 35 | throw new GatewayException(HttpResponseStatus.INTERNAL_SERVER_ERROR, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName()); 36 | } 37 | } 38 | 39 | public void route(Channel channel) throws GatewayException { 40 | try { 41 | runFilters("route", channel); 42 | } catch (GatewayException e) { 43 | throw e; 44 | } catch (Throwable e) { 45 | logger.error(e.getMessage(), e); 46 | throw new GatewayException(HttpResponseStatus.INTERNAL_SERVER_ERROR, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName()); 47 | } 48 | } 49 | 50 | public void postRoute(Channel channel) throws GatewayException { 51 | try { 52 | runFilters("post", channel); 53 | } catch (GatewayException e) { 54 | throw e; 55 | } catch (Throwable e) { 56 | logger.error(e.getMessage(), e); 57 | throw new GatewayException(HttpResponseStatus.INTERNAL_SERVER_ERROR, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName()); 58 | } 59 | } 60 | 61 | public void error(Channel channel) { 62 | try { 63 | runFilters("error", channel); 64 | } catch (Throwable e) { 65 | logger.error(e.getMessage(), e); 66 | } 67 | } 68 | 69 | 70 | public void runFilters(String type, Channel channel) throws Throwable { 71 | List preFilterList = GatewayConfig.getInstance().getFiltersByType(type); 72 | if (preFilterList != null) { 73 | for (GatewayFilter filter : preFilterList) { 74 | filter.filter(channel); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/filter/FilterRegistry.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.filter; 2 | 3 | import java.util.Collection; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | /** 7 | * @author: wangqi 8 | * @description: 9 | * @date: Created in 2018/9/3 下午6:02 10 | */ 11 | public class FilterRegistry { 12 | private static final FilterRegistry INSTANCE = new FilterRegistry(); 13 | 14 | public static final FilterRegistry instance() { 15 | return INSTANCE; 16 | } 17 | 18 | private final ConcurrentHashMap filters = new ConcurrentHashMap<>(); 19 | 20 | private FilterRegistry() { 21 | put("sendErrorFilter", new SendErrorFilter()); 22 | put("sendForwardFilter", new SendForwardFilter()); 23 | put("sendResponseFilter", new SendResponseFilter()); 24 | } 25 | 26 | public GatewayFilter remove(String key) { 27 | return this.filters.remove(key); 28 | } 29 | 30 | public GatewayFilter get(String key) { 31 | return this.filters.get(key); 32 | } 33 | 34 | public void put(String key, GatewayFilter filter) { 35 | this.filters.putIfAbsent(key, filter); 36 | } 37 | 38 | public int size() { 39 | return this.filters.size(); 40 | } 41 | 42 | public Collection getAllFilters() { 43 | return this.filters.values(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/filter/GatewayFilter.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.filter; 2 | 3 | /** 4 | * @author: wangqi 5 | * @description: 6 | * @date: Created in 2018/6/4 下午7:47 7 | */ 8 | public abstract class GatewayFilter implements IGatewayFilter, Comparable { 9 | 10 | abstract public String filterType(); 11 | 12 | abstract public int filterOrder(); 13 | 14 | @Override 15 | public int compareTo(GatewayFilter filter) { 16 | return Integer.compare(this.filterOrder(), filter.filterOrder()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/filter/IGatewayFilter.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.filter; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/9/3 下午6:18 9 | */ 10 | public interface IGatewayFilter { 11 | /** 12 | * 过滤Http请求 13 | * 14 | * @param channel 15 | * @throws Exception 16 | */ 17 | void filter(Channel channel) throws Exception; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/filter/SendErrorFilter.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.filter; 2 | 3 | import io.netty.channel.Channel; 4 | import love.wangqi.codec.RequestHolder; 5 | import love.wangqi.config.GatewayConfig; 6 | import love.wangqi.context.ContextUtil; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * @author: wangqi 12 | * @description: 13 | * @date: Created in 2018/9/3 下午7:56 14 | */ 15 | public class SendErrorFilter extends GatewayFilter { 16 | private final static Logger logger = LoggerFactory.getLogger(SendErrorFilter.class); 17 | 18 | private GatewayConfig config = GatewayConfig.getInstance(); 19 | 20 | @Override 21 | public String filterType() { 22 | return "error"; 23 | } 24 | 25 | @Override 26 | public int filterOrder() { 27 | return 0; 28 | } 29 | 30 | @Override 31 | public void filter(Channel channel) throws Exception { 32 | Exception e = ContextUtil.getException(channel); 33 | if (e != null) { 34 | RequestHolder requestHolder = ContextUtil.getRequestHolder(channel); 35 | if (requestHolder != null) { 36 | logger.error("Route Id: " + requestHolder.route.getId() + " Route Url: " + requestHolder.route.getMapUrl() + " " + e.getMessage(), e); 37 | } 38 | config.getExceptionHandler().handle(channel, e); 39 | ContextUtil.clear(channel); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/filter/SendForwardFilter.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.filter; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import love.wangqi.codec.HttpRequestBuilder; 6 | import love.wangqi.codec.RequestHolder; 7 | import love.wangqi.config.GatewayConfig; 8 | import love.wangqi.context.ContextUtil; 9 | import love.wangqi.handler.back.BackClientPool; 10 | 11 | /** 12 | * @author: wangqi 13 | * @description: 14 | * @date: Created in 2018/9/3 下午7:53 15 | */ 16 | public class SendForwardFilter extends GatewayFilter { 17 | private GatewayConfig config = GatewayConfig.getInstance(); 18 | 19 | @Override 20 | public String filterType() { 21 | return "route"; 22 | } 23 | 24 | @Override 25 | public int filterOrder() { 26 | return 0; 27 | } 28 | 29 | @Override 30 | public synchronized void filter(Channel channel) throws Exception { 31 | HttpRequestBuilder httpRequestBuilder = config.getHttpRequestBuilder() 32 | .setRouteMapper(config.getRouteMapper()); 33 | 34 | FullHttpRequest httpRequest = ContextUtil.getRequest(channel); 35 | RequestHolder requestHolder = httpRequestBuilder.build(httpRequest); 36 | httpRequest.release(); 37 | ContextUtil.setRequestHolder(channel, requestHolder); 38 | 39 | BackClientPool.INSTANCE.request(requestHolder, channel); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/filter/SendResponseFilter.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.filter; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import love.wangqi.config.GatewayConfig; 6 | import love.wangqi.context.ContextUtil; 7 | import love.wangqi.handler.GatewayRunner; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * @author: wangqi 13 | * @description: 14 | * @date: Created in 2018/9/5 下午2:16 15 | */ 16 | public class SendResponseFilter extends GatewayFilter { 17 | private static final Logger logger = LoggerFactory.getLogger(SendResponseFilter.class); 18 | 19 | 20 | private GatewayConfig config = GatewayConfig.getInstance(); 21 | 22 | @Override 23 | public String filterType() { 24 | return "post"; 25 | } 26 | 27 | @Override 28 | public int filterOrder() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public void filter(Channel channel) throws Exception { 34 | Exception e = ContextUtil.getException(channel); 35 | if (e == null) { 36 | try { 37 | FullHttpResponse response = ContextUtil.getResponse(channel); 38 | config.getResponseHandler().send(channel, response); 39 | } catch (Exception sendException) { 40 | logger.error(sendException.getMessage(), sendException); 41 | throw sendException; 42 | } 43 | 44 | } else { 45 | GatewayRunner.getInstance().errorAction(channel); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/handler/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.handler.codec.http.HttpResponseStatus; 6 | import love.wangqi.context.ContextUtil; 7 | import love.wangqi.exception.GatewayException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * @author: wangqi 13 | * @description: 14 | * @date: Created in 2018-11-30 09:14 15 | */ 16 | public class ExceptionHandler extends ChannelInboundHandlerAdapter { 17 | private static final Logger logger = LoggerFactory.getLogger(ExceptionHandler.class); 18 | 19 | @Override 20 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 21 | logger.error(cause.getMessage(), cause); 22 | GatewayRunner runner = GatewayRunner.getInstance(); 23 | Exception exception = new GatewayException(HttpResponseStatus.INTERNAL_SERVER_ERROR, "UNHANDLED_EXCEPTION_" + cause.getClass().getName()); 24 | ContextUtil.setException(ctx.channel(), exception); 25 | runner.errorAction(ctx.channel()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/handler/GatewayRunner.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.handler; 2 | 3 | import io.netty.channel.Channel; 4 | import love.wangqi.context.ContextUtil; 5 | import love.wangqi.exception.GatewayException; 6 | import love.wangqi.filter.FilterProcessor; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.concurrent.*; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | /** 14 | * @author: wangqi 15 | * @description: 16 | * @date: Created in 2018/9/4 上午9:01 17 | */ 18 | public class GatewayRunner { 19 | private final static Logger logger = LoggerFactory.getLogger(GatewayRunner.class); 20 | 21 | private final static GatewayRunner INSTANCE = new GatewayRunner(); 22 | 23 | private GatewayRunner() { 24 | } 25 | 26 | public static GatewayRunner getInstance() { 27 | return INSTANCE; 28 | } 29 | 30 | static abstract class AbstractDefaultThreadFactory implements ThreadFactory { 31 | static final AtomicInteger poolNumber = new AtomicInteger(1); 32 | private final ThreadGroup group; 33 | private final AtomicInteger threadNumber = new AtomicInteger(1); 34 | 35 | AbstractDefaultThreadFactory() { 36 | SecurityManager s = System.getSecurityManager(); 37 | group = (s != null) ? s.getThreadGroup() : 38 | Thread.currentThread().getThreadGroup(); 39 | } 40 | 41 | @Override 42 | public Thread newThread(Runnable r) { 43 | Thread t = new Thread(group, r, 44 | getNamePrefix() + threadNumber.getAndIncrement(), 45 | 0); 46 | if (t.isDaemon()) { 47 | t.setDaemon(false); 48 | } 49 | if (t.getPriority() != Thread.NORM_PRIORITY) { 50 | t.setPriority(Thread.NORM_PRIORITY); 51 | } 52 | return t; 53 | } 54 | 55 | /** 56 | * 线程名称前缀 57 | * 58 | * @return 59 | */ 60 | abstract String getNamePrefix(); 61 | } 62 | 63 | static class PreRouteThreadFactory extends AbstractDefaultThreadFactory { 64 | 65 | @Override 66 | String getNamePrefix() { 67 | return "PreRoute-" + poolNumber.getAndIncrement() + "-thread-"; 68 | } 69 | } 70 | 71 | static class RouteThreadFactory extends AbstractDefaultThreadFactory { 72 | 73 | @Override 74 | String getNamePrefix() { 75 | return "Route-" + poolNumber.getAndIncrement() + "-thread-"; 76 | } 77 | } 78 | 79 | static class PostRouteThreadFactory extends AbstractDefaultThreadFactory { 80 | 81 | @Override 82 | String getNamePrefix() { 83 | return "PostRoute-" + poolNumber.getAndIncrement() + "-thread-"; 84 | } 85 | } 86 | 87 | static class ErrorRouteThreadFactory extends AbstractDefaultThreadFactory { 88 | 89 | @Override 90 | String getNamePrefix() { 91 | return "ErrorRoute-" + poolNumber.getAndIncrement() + "-thread-"; 92 | } 93 | } 94 | 95 | private static ExecutorService preRoutePool = new ThreadPoolExecutor(5, 50, 96 | 30L, TimeUnit.MILLISECONDS, 97 | new LinkedBlockingQueue<>(), new PreRouteThreadFactory()); 98 | 99 | private static ExecutorService routePool = new ThreadPoolExecutor(5, 100, 100 | 30L, TimeUnit.MILLISECONDS, 101 | new LinkedBlockingQueue<>(), new RouteThreadFactory()); 102 | 103 | private static ExecutorService postRoutePool = new ThreadPoolExecutor(5, 20, 104 | 30L, TimeUnit.MILLISECONDS, 105 | new LinkedBlockingQueue<>(), new PostRouteThreadFactory()); 106 | 107 | private static ExecutorService errorRoutePool = new ThreadPoolExecutor(5, 10, 108 | 30L, TimeUnit.MILLISECONDS, 109 | new LinkedBlockingQueue<>(), new ErrorRouteThreadFactory()); 110 | 111 | private void errorAction(Channel channel, Exception e) { 112 | CompletableFuture.completedFuture(e) 113 | .thenAcceptAsync(throwable1 -> { 114 | ContextUtil.setException(channel, e); 115 | error(channel); 116 | }, errorRoutePool); 117 | } 118 | 119 | public void forwardAction(Channel channel) { 120 | CompletableFuture.completedFuture(channel) 121 | .thenApplyAsync(ch -> { 122 | preRoute(ch); 123 | return ch; 124 | }, preRoutePool) 125 | .thenApplyAsync(ch -> { 126 | route(ch); 127 | return ch; 128 | }, routePool) 129 | .exceptionally(throwable -> { 130 | errorAction(channel, (Exception) throwable.getCause()); 131 | return null; 132 | }); 133 | } 134 | 135 | public void errorAction(Channel channel) { 136 | CompletableFuture.completedFuture(channel) 137 | .thenApplyAsync(ch -> { 138 | error(ch); 139 | return ch; 140 | }, errorRoutePool) 141 | .exceptionally(throwable -> { 142 | errorAction(channel, (Exception) throwable.getCause()); 143 | return null; 144 | }); 145 | } 146 | 147 | public void postRoutAction(Channel channel) { 148 | CompletableFuture.completedFuture(channel) 149 | .thenApplyAsync(ch -> { 150 | postRoute(ch); 151 | return ch; 152 | }, postRoutePool) 153 | .exceptionally(throwable -> { 154 | errorAction(channel, (Exception) throwable.getCause()); 155 | return null; 156 | }); 157 | } 158 | 159 | private void preRoute(Channel channel) throws GatewayException { 160 | FilterProcessor.getInstance().preRoute(channel); 161 | } 162 | 163 | private void route(Channel channel) throws GatewayException { 164 | FilterProcessor.getInstance().route(channel); 165 | } 166 | 167 | private void postRoute(Channel channel) throws GatewayException { 168 | FilterProcessor.getInstance().postRoute(channel); 169 | } 170 | 171 | private void error(Channel channel) { 172 | FilterProcessor.getInstance().error(channel); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/handler/back/BackClientPool.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.handler.back; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelOption; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.pool.AbstractChannelPoolMap; 9 | import io.netty.channel.pool.ChannelPoolMap; 10 | import io.netty.channel.pool.FixedChannelPool; 11 | import io.netty.channel.pool.SimpleChannelPool; 12 | import io.netty.channel.socket.nio.NioSocketChannel; 13 | import io.netty.handler.codec.http.HttpRequest; 14 | import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; 15 | import io.netty.handler.timeout.ReadTimeoutHandler; 16 | import io.netty.handler.timeout.WriteTimeoutHandler; 17 | import io.netty.util.concurrent.Future; 18 | import io.netty.util.concurrent.FutureListener; 19 | import love.wangqi.codec.RequestHolder; 20 | import love.wangqi.context.Attributes; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.util.concurrent.TimeUnit; 25 | 26 | 27 | /** 28 | * @author: wangqi 29 | * @description: 30 | * @date: Created in 2018-11-27 16:14 31 | */ 32 | public class BackClientPool { 33 | private final static Logger logger = LoggerFactory.getLogger(BackClientPool.class); 34 | 35 | private final EventLoopGroup group = new NioEventLoopGroup(8 * 4); 36 | private final Bootstrap bootstrap = new Bootstrap(); 37 | private ChannelPoolMap poolMap; 38 | 39 | public static final BackClientPool INSTANCE = new BackClientPool(); 40 | 41 | private BackClientPool() { 42 | bootstrap 43 | .group(group) 44 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 500) 45 | .channel(NioSocketChannel.class) 46 | .option(ChannelOption.TCP_NODELAY, true) 47 | .option(ChannelOption.SO_KEEPALIVE, true); 48 | 49 | poolMap = new AbstractChannelPoolMap() { 50 | @Override 51 | protected SimpleChannelPool newPool(RequestHolder requestHolder) { 52 | return new FixedChannelPool(bootstrap.remoteAddress(requestHolder.getSocketAddress()), new BackPoolHandler(requestHolder), 50); 53 | } 54 | }; 55 | } 56 | 57 | public synchronized void request(RequestHolder requestHolder, Channel serverChannel) throws InterruptedException { 58 | final SimpleChannelPool pool = poolMap.get(requestHolder); 59 | Future f = pool.acquire().sync(); 60 | f.addListener((FutureListener) future -> { 61 | if (future.isSuccess()) { 62 | HttpRequest request = requestHolder.request; 63 | HttpPostRequestEncoder bodyRequestEncoder = requestHolder.bodyRequestEncoder; 64 | 65 | Channel clientChannel = future.getNow(); 66 | 67 | // 添加读写超时控制器 68 | clientChannel.pipeline().addFirst("ReadTimeoutHandler", 69 | new ReadTimeoutHandler(requestHolder.route.getTimeoutInMilliseconds(), TimeUnit.MILLISECONDS)); 70 | clientChannel.pipeline().addFirst("WriteTimeoutHandler", 71 | new WriteTimeoutHandler(500, TimeUnit.MILLISECONDS)); 72 | 73 | clientChannel.attr(Attributes.SERVER_CHANNEL).set(serverChannel); 74 | clientChannel.attr(Attributes.CLIENT_POOL).set(pool); 75 | 76 | clientChannel.write(request); 77 | if (bodyRequestEncoder != null && bodyRequestEncoder.isChunked()) { 78 | clientChannel.write(bodyRequestEncoder); 79 | } 80 | clientChannel.flush(); 81 | } 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/handler/back/BackHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.handler.back; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.handler.codec.http.FullHttpResponse; 7 | import io.netty.handler.timeout.ReadTimeoutException; 8 | import love.wangqi.context.Attributes; 9 | import love.wangqi.context.ContextUtil; 10 | import love.wangqi.exception.GatewayTimeoutException; 11 | import love.wangqi.handler.GatewayRunner; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * @author: wangqi 17 | * @description: 18 | * @date: Created in 2018-11-28 08:37 19 | */ 20 | public class BackHandler extends SimpleChannelInboundHandler { 21 | private Logger logger = LoggerFactory.getLogger(BackHandler.class); 22 | 23 | public BackHandler() { 24 | super(false); 25 | } 26 | 27 | @Override 28 | public void channelReadComplete(ChannelHandlerContext ctx) { 29 | ctx.flush(); 30 | } 31 | 32 | @Override 33 | public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) throws Exception { 34 | Channel clientChannel = ctx.channel(); 35 | Channel serverChannel = clientChannel.attr(Attributes.SERVER_CHANNEL).get(); 36 | 37 | ContextUtil.setResponse(serverChannel, response); 38 | GatewayRunner.getInstance().postRoutAction(serverChannel); 39 | 40 | clientChannel.attr(Attributes.CLIENT_POOL).get().release(clientChannel); 41 | 42 | // 移除读写超时控制器 43 | ctx.channel().pipeline().remove("ReadTimeoutHandler"); 44 | ctx.channel().pipeline().remove("WriteTimeoutHandler"); 45 | } 46 | 47 | @Override 48 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 49 | Channel serverChannel = ctx.channel().attr(Attributes.SERVER_CHANNEL).get(); 50 | if (cause instanceof ReadTimeoutException) { 51 | logger.error("read time out"); 52 | Exception exception = new GatewayTimeoutException(); 53 | ContextUtil.setException(serverChannel, exception); 54 | } else { 55 | logger.error(cause.getMessage(), cause); 56 | ContextUtil.setException(serverChannel, new RuntimeException(cause)); 57 | } 58 | GatewayRunner.getInstance().errorAction(serverChannel); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/handler/back/BackPoolHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.handler.back; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelPipeline; 5 | import io.netty.channel.pool.ChannelPoolHandler; 6 | import io.netty.channel.socket.nio.NioSocketChannel; 7 | import io.netty.handler.codec.http.HttpClientCodec; 8 | import io.netty.handler.codec.http.HttpContentDecompressor; 9 | import io.netty.handler.codec.http.HttpObjectAggregator; 10 | import io.netty.handler.ssl.SslContext; 11 | import io.netty.handler.ssl.SslContextBuilder; 12 | import io.netty.handler.ssl.util.InsecureTrustManagerFactory; 13 | import io.netty.handler.stream.ChunkedWriteHandler; 14 | import love.wangqi.codec.RequestHolder; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import javax.net.ssl.SSLException; 19 | 20 | import static love.wangqi.context.Constants.HTTPS; 21 | 22 | /** 23 | * @author: wangqi 24 | * @description: 25 | * @date: Created in 2018-11-27 16:24 26 | */ 27 | public class BackPoolHandler implements ChannelPoolHandler { 28 | private static final Logger logger = LoggerFactory.getLogger(BackPoolHandler.class); 29 | 30 | private SslContext sslCtx = null; 31 | 32 | public BackPoolHandler(RequestHolder requestHolder) { 33 | if (requestHolder.getProtocol().equalsIgnoreCase(HTTPS)) { 34 | try { 35 | sslCtx = SslContextBuilder.forClient() 36 | .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); 37 | } catch (SSLException e) { 38 | logger.error(e.getMessage(), e); 39 | } 40 | } 41 | } 42 | 43 | @Override 44 | public void channelReleased(Channel ch) throws Exception { 45 | logger.debug("channelReleased. Channel ID: {}", ch.id()); 46 | } 47 | 48 | @Override 49 | public void channelAcquired(Channel ch) throws Exception { 50 | logger.debug("channelAcquired. Channel ID: {}", ch.id()); 51 | } 52 | 53 | @Override 54 | public void channelCreated(Channel ch) throws Exception { 55 | logger.debug("channelCreated. Channel ID: {}", ch.id()); 56 | NioSocketChannel channel = (NioSocketChannel) ch; 57 | channel.config().setKeepAlive(true); 58 | channel.config().setTcpNoDelay(true); 59 | ChannelPipeline pipeline = channel.pipeline(); 60 | if (sslCtx != null) { 61 | pipeline.addLast(sslCtx.newHandler(ch.alloc())); 62 | } 63 | pipeline.addLast(new HttpClientCodec()); 64 | pipeline.addLast(new HttpContentDecompressor()); 65 | pipeline.addLast(new HttpObjectAggregator(1024 * 1024 * 64)); 66 | pipeline.addLast(new ChunkedWriteHandler()); 67 | // pipeline.addLast(new ReadTimeoutHandler(requestHolder.route.getTimeoutInMilliseconds(), TimeUnit.MILLISECONDS)); 68 | pipeline.addLast(new BackHandler()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/handler/front/FrontFilter.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.handler.front; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | import io.netty.channel.ChannelFutureListener; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelPipeline; 7 | import io.netty.channel.socket.SocketChannel; 8 | import io.netty.handler.codec.http.HttpObjectAggregator; 9 | import io.netty.handler.codec.http.HttpRequestDecoder; 10 | import io.netty.handler.codec.http.HttpResponseEncoder; 11 | import io.netty.handler.stream.ChunkedWriteHandler; 12 | import love.wangqi.config.GatewayConfig; 13 | import love.wangqi.handler.ExceptionHandler; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | 18 | /** 19 | * @author: wangqi 20 | * @description: 21 | * @date: Created in 2018/5/26 21:57 22 | */ 23 | public class FrontFilter extends ChannelInitializer { 24 | private Logger logger = LoggerFactory.getLogger(FrontFilter.class); 25 | 26 | private GatewayConfig config = GatewayConfig.getInstance(); 27 | 28 | @Override 29 | protected void initChannel(SocketChannel ch) throws Exception { 30 | ChannelPipeline pipeline = ch.pipeline(); 31 | 32 | config.getChannelInboundHandlerList().forEach(pipeline::addLast); 33 | config.getChannelOutboundHandlerList().forEach(pipeline::addLast); 34 | pipeline.addLast(new HttpResponseEncoder()); 35 | config.getHttpResponseHandlerList().forEach(pipeline::addLast); 36 | pipeline.addLast(new HttpRequestDecoder()); 37 | pipeline.addLast(new ChunkedWriteHandler()); 38 | pipeline.addLast(new HttpObjectAggregator(10 * 1024 * 1024)); 39 | pipeline.addLast(new FrontHandler()); 40 | pipeline.addLast(new ExceptionHandler()); 41 | 42 | 43 | ch.closeFuture().addListener(new ChannelFutureListener() { 44 | @Override 45 | public void operationComplete(ChannelFuture future) throws Exception { 46 | logger.debug("channel close"); 47 | } 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/handler/front/FrontHandler.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.handler.front; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | import io.netty.handler.codec.http.HttpUtil; 7 | import love.wangqi.context.ContextUtil; 8 | import love.wangqi.handler.GatewayRunner; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | 13 | /** 14 | * @author: wangqi 15 | * @description: 16 | * @date: Created in 2018/5/26 21:58 17 | */ 18 | public class FrontHandler extends SimpleChannelInboundHandler { 19 | private final Logger logger = LoggerFactory.getLogger(FrontHandler.class); 20 | 21 | public FrontHandler() { 22 | super(false); 23 | } 24 | 25 | @Override 26 | public void channelReadComplete(ChannelHandlerContext ctx) { 27 | ctx.flush(); 28 | } 29 | 30 | @Override 31 | public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) { 32 | Boolean keepAlive = HttpUtil.isKeepAlive(httpRequest); 33 | ContextUtil.setKeepAlive(ctx.channel(), keepAlive); 34 | ContextUtil.setRequest(ctx.channel(), httpRequest); 35 | 36 | GatewayRunner runner = GatewayRunner.getInstance(); 37 | runner.forwardAction(ctx.channel()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/route/AbstractRouteMapper.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.route; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | import love.wangqi.util.AntPathMatcher; 6 | import love.wangqi.util.PathMatcher; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.net.MalformedURLException; 11 | import java.net.URI; 12 | import java.net.URISyntaxException; 13 | import java.net.URL; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | /** 19 | * @author: wangqi 20 | * @description: 21 | * @date: Created in 2018/5/30 上午9:48 22 | */ 23 | public abstract class AbstractRouteMapper implements RouteMapper { 24 | private final Logger logger = LoggerFactory.getLogger(AbstractRouteMapper.class); 25 | private PathMatcher pathMatcher = new AntPathMatcher(); 26 | private List routeList; 27 | 28 | /** 29 | * 遍历所有的路由,返回符合请求的路由 30 | * 31 | * @param path 32 | * @param method 33 | * @return 34 | */ 35 | @Override 36 | public Route getRoute(String path, HttpMethod method) { 37 | for (Route route : getRouteList()) { 38 | if (!method.equals(route.getMethod())) { 39 | continue; 40 | } 41 | if (route.getPath().equals(path)) { 42 | route = route.clone(); 43 | return route; 44 | } 45 | if (this.pathMatcher.match(route.getPath(), path)) { 46 | route = route.clone(); 47 | Map uriTemplateVariables = this.pathMatcher.extractUriTemplateVariables(route.getPath(), path); 48 | if (!uriTemplateVariables.isEmpty()) { 49 | String mapUrl = route.getMapUrl().toString(); 50 | for (Map.Entry entry : uriTemplateVariables.entrySet()) { 51 | mapUrl = mapUrl.replaceAll(String.format("\\{%s}", entry.getKey()), entry.getValue()); 52 | } 53 | try { 54 | route.setMapUrl(new URL(mapUrl)); 55 | } catch (MalformedURLException e) { 56 | logger.error(e.getMessage()); 57 | } 58 | } 59 | 60 | return route; 61 | } 62 | } 63 | return null; 64 | } 65 | 66 | @Override 67 | public Route getRoute(HttpRequest request) { 68 | try { 69 | return getRoute(new URI(request.uri()).getPath(), request.method()); 70 | } catch (URISyntaxException e) { 71 | logger.error(e.getMessage()); 72 | } 73 | return null; 74 | } 75 | 76 | /** 77 | * 获取路由列表 78 | * 79 | * @param ids 路由id的列表 80 | * @return 81 | */ 82 | protected abstract List locateRouteList(Set ids); 83 | 84 | @Override 85 | public List getRouteList() { 86 | return routeList; 87 | } 88 | 89 | @Override 90 | public void refresh(Set ids) { 91 | routeList = locateRouteList(ids); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/route/Route.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.route; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.net.URL; 8 | 9 | /** 10 | * @author: wangqi 11 | * @description: 12 | * @date: Created in 2018/5/30 上午9:43 13 | */ 14 | public class Route implements Cloneable { 15 | private final Logger logger = LoggerFactory.getLogger(Route.class); 16 | /** 17 | * id 18 | */ 19 | private Long id; 20 | /** 21 | * 前端路径 22 | */ 23 | private String path; 24 | /** 25 | * 后端映射的URL 26 | */ 27 | private URL mapUrl; 28 | /** 29 | * 请求方法 30 | */ 31 | private HttpMethod method; 32 | /** 33 | * 请求超时时间 34 | */ 35 | private Integer timeoutInMilliseconds; 36 | 37 | private static final Integer DEFAULT_TIMEOUT = 3000; 38 | 39 | public Route() { 40 | } 41 | 42 | public Route(Long id, HttpMethod method, String path, URL mapUrl) { 43 | this(id, method, path, mapUrl, DEFAULT_TIMEOUT); 44 | } 45 | 46 | public Route(Long id, HttpMethod method, String path, URL mapUrl, Integer timeoutInMilliseconds) { 47 | this.id = id; 48 | this.path = path; 49 | this.mapUrl = mapUrl; 50 | this.method = method; 51 | this.timeoutInMilliseconds = timeoutInMilliseconds; 52 | } 53 | 54 | @Override 55 | protected Route clone() { 56 | Route route = this; 57 | try { 58 | route = (Route) super.clone(); 59 | } catch (CloneNotSupportedException e) { 60 | logger.error(e.getMessage()); 61 | } 62 | return route; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return id.hashCode(); 68 | } 69 | 70 | @Override 71 | public boolean equals(Object obj) { 72 | if (this == obj) { 73 | return true; 74 | } 75 | return this.id.equals(((Route) obj).id); 76 | } 77 | 78 | public Long getId() { 79 | return id; 80 | } 81 | 82 | public void setId(Long id) { 83 | this.id = id; 84 | } 85 | 86 | public String getPath() { 87 | return path; 88 | } 89 | 90 | public void setPath(String path) { 91 | this.path = path; 92 | } 93 | 94 | public URL getMapUrl() { 95 | return mapUrl; 96 | } 97 | 98 | public void setMapUrl(URL mapUrl) { 99 | this.mapUrl = mapUrl; 100 | } 101 | 102 | public HttpMethod getMethod() { 103 | return method; 104 | } 105 | 106 | public void setMethod(HttpMethod method) { 107 | this.method = method; 108 | } 109 | 110 | public Integer getTimeoutInMilliseconds() { 111 | return timeoutInMilliseconds; 112 | } 113 | 114 | public void setTimeoutInMilliseconds(Integer timeoutInMilliseconds) { 115 | this.timeoutInMilliseconds = timeoutInMilliseconds; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/route/RouteMapper.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.route; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | /** 10 | * @author: wangqi 11 | * @description: 12 | * @date: Created in 2018/5/30 上午9:43 13 | */ 14 | public interface RouteMapper { 15 | /** 16 | * 根据路径获取Route 17 | * 18 | * @param path 19 | * @param method 20 | * @return 21 | */ 22 | Route getRoute(String path, HttpMethod method); 23 | 24 | /** 25 | * 根据请求获取Route 26 | * 27 | * @param request 28 | * @return 29 | */ 30 | Route getRoute(HttpRequest request); 31 | 32 | /** 33 | * 刷新路由 34 | * 35 | * @param ids 路由id的列表 36 | */ 37 | void refresh(Set ids); 38 | 39 | /** 40 | * 获取路由列表 41 | * 42 | * @return 43 | */ 44 | List getRouteList(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/server/GatewayServer.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelOption; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioServerSocketChannel; 9 | import love.wangqi.config.GatewayConfig; 10 | import love.wangqi.handler.front.FrontFilter; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * @author: wangqi 16 | * @description: 17 | * @date: Created in 2018/5/26 21:47 18 | */ 19 | public class GatewayServer { 20 | private static final Logger logger = LoggerFactory.getLogger(GatewayServer.class); 21 | 22 | private GatewayConfig config = GatewayConfig.getInstance(); 23 | 24 | public void start() { 25 | EventLoopGroup bossGroup = new NioEventLoopGroup(4); 26 | EventLoopGroup workerGroup = new NioEventLoopGroup(8 * 4); 27 | ServerBootstrap bootstrap = new ServerBootstrap(); 28 | try { 29 | bootstrap.group(bossGroup, workerGroup) 30 | .channel(NioServerSocketChannel.class) 31 | .option(ChannelOption.SO_BACKLOG, 1024 * 4) 32 | .childOption(ChannelOption.SO_KEEPALIVE, true) 33 | .childOption(ChannelOption.TCP_NODELAY, true) 34 | .childOption(ChannelOption.SO_REUSEADDR, true) 35 | .childHandler(new FrontFilter()); 36 | 37 | ChannelFuture f = bootstrap.bind(config.getPort()).sync(); 38 | logger.info("服务端启动成功,端口是:{}", config.getPort()); 39 | f.channel().closeFuture().sync(); 40 | } catch (InterruptedException e) { 41 | logger.error(e.getMessage()); 42 | } finally { 43 | bossGroup.shutdownGracefully(); 44 | workerGroup.shutdownGracefully(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/util/AntPathMatcher.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.util; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | /** 12 | * @author: wangqi 13 | * @description: 14 | * @date: Created in 2018/5/30 上午9:56 15 | */ 16 | public class AntPathMatcher implements PathMatcher { 17 | 18 | /** 19 | * Default path separator: "/" 20 | */ 21 | public static final String DEFAULT_PATH_SEPARATOR = "/"; 22 | 23 | private static final int CACHE_TURNOFF_THRESHOLD = 65536; 24 | 25 | private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}"); 26 | 27 | private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; 28 | 29 | 30 | private String pathSeparator; 31 | 32 | private PathSeparatorPatternCache pathSeparatorPatternCache; 33 | 34 | private boolean caseSensitive = true; 35 | 36 | private boolean trimTokens = false; 37 | 38 | private volatile Boolean cachePatterns; 39 | 40 | private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); 41 | 42 | final Map stringMatcherCache = new ConcurrentHashMap<>(256); 43 | 44 | 45 | /** 46 | * Create a new instance with the {@link #DEFAULT_PATH_SEPARATOR}. 47 | */ 48 | public AntPathMatcher() { 49 | this.pathSeparator = DEFAULT_PATH_SEPARATOR; 50 | this.pathSeparatorPatternCache = new PathSeparatorPatternCache(DEFAULT_PATH_SEPARATOR); 51 | } 52 | 53 | /** 54 | * A convenient, alternative constructor to use with a custom path separator. 55 | * 56 | * @param pathSeparator the path separator to use, must not be {@code null}. 57 | * @since 4.1 58 | */ 59 | public AntPathMatcher(String pathSeparator) { 60 | this.pathSeparator = pathSeparator; 61 | this.pathSeparatorPatternCache = new PathSeparatorPatternCache(pathSeparator); 62 | } 63 | 64 | 65 | /** 66 | * Set the path separator to use for pattern parsing. 67 | *

Default is "/", as in Ant. 68 | */ 69 | public void setPathSeparator(String pathSeparator) { 70 | this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR); 71 | this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); 72 | } 73 | 74 | /** 75 | * Specify whether to perform pattern matching in a case-sensitive fashion. 76 | *

Default is {@code true}. Switch this to {@code false} for case-insensitive matching. 77 | * 78 | * @since 4.2 79 | */ 80 | public void setCaseSensitive(boolean caseSensitive) { 81 | this.caseSensitive = caseSensitive; 82 | } 83 | 84 | /** 85 | * Specify whether to trim tokenized paths and patterns. 86 | *

Default is {@code false}. 87 | */ 88 | public void setTrimTokens(boolean trimTokens) { 89 | this.trimTokens = trimTokens; 90 | } 91 | 92 | public void setCachePatterns(boolean cachePatterns) { 93 | this.cachePatterns = cachePatterns; 94 | } 95 | 96 | private void deactivatePatternCache() { 97 | this.cachePatterns = false; 98 | this.tokenizedPatternCache.clear(); 99 | this.stringMatcherCache.clear(); 100 | } 101 | 102 | @Override 103 | public boolean match(String pattern, String path) { 104 | return doMatch(pattern, path, true, null); 105 | } 106 | 107 | protected boolean doMatch(String pattern, String path, boolean fullMatch, 108 | Map uriTemplateVariables) { 109 | 110 | if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { 111 | return false; 112 | } 113 | 114 | String[] pattDirs = tokenizePattern(pattern); 115 | if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) { 116 | return false; 117 | } 118 | 119 | String[] pathDirs = tokenizePath(path); 120 | 121 | int pattIdxStart = 0; 122 | int pattIdxEnd = pattDirs.length - 1; 123 | int pathIdxStart = 0; 124 | int pathIdxEnd = pathDirs.length - 1; 125 | 126 | // Match all elements up to the first ** 127 | while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { 128 | String pattDir = pattDirs[pattIdxStart]; 129 | if ("**".equals(pattDir)) { 130 | break; 131 | } 132 | if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { 133 | return false; 134 | } 135 | pattIdxStart++; 136 | pathIdxStart++; 137 | } 138 | 139 | if (pathIdxStart > pathIdxEnd) { 140 | // Path is exhausted, only match if rest of pattern is * or **'s 141 | if (pattIdxStart > pattIdxEnd) { 142 | return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); 143 | } 144 | if (!fullMatch) { 145 | return true; 146 | } 147 | if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { 148 | return true; 149 | } 150 | for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 151 | if (!pattDirs[i].equals("**")) { 152 | return false; 153 | } 154 | } 155 | return true; 156 | } else if (pattIdxStart > pattIdxEnd) { 157 | // String not exhausted, but pattern is. Failure. 158 | return false; 159 | } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) { 160 | // Path start definitely matches due to "**" part in pattern. 161 | return true; 162 | } 163 | 164 | // up to last '**' 165 | while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { 166 | String pattDir = pattDirs[pattIdxEnd]; 167 | if (pattDir.equals("**")) { 168 | break; 169 | } 170 | if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { 171 | return false; 172 | } 173 | pattIdxEnd--; 174 | pathIdxEnd--; 175 | } 176 | if (pathIdxStart > pathIdxEnd) { 177 | // String is exhausted 178 | for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 179 | if (!pattDirs[i].equals("**")) { 180 | return false; 181 | } 182 | } 183 | return true; 184 | } 185 | 186 | while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { 187 | int patIdxTmp = -1; 188 | for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { 189 | if (pattDirs[i].equals("**")) { 190 | patIdxTmp = i; 191 | break; 192 | } 193 | } 194 | if (patIdxTmp == pattIdxStart + 1) { 195 | // '**/**' situation, so skip one 196 | pattIdxStart++; 197 | continue; 198 | } 199 | // Find the pattern between padIdxStart & padIdxTmp in str between 200 | // strIdxStart & strIdxEnd 201 | int patLength = (patIdxTmp - pattIdxStart - 1); 202 | int strLength = (pathIdxEnd - pathIdxStart + 1); 203 | int foundIdx = -1; 204 | 205 | strLoop: 206 | for (int i = 0; i <= strLength - patLength; i++) { 207 | for (int j = 0; j < patLength; j++) { 208 | String subPat = pattDirs[pattIdxStart + j + 1]; 209 | String subStr = pathDirs[pathIdxStart + i + j]; 210 | if (!matchStrings(subPat, subStr, uriTemplateVariables)) { 211 | continue strLoop; 212 | } 213 | } 214 | foundIdx = pathIdxStart + i; 215 | break; 216 | } 217 | 218 | if (foundIdx == -1) { 219 | return false; 220 | } 221 | 222 | pattIdxStart = patIdxTmp; 223 | pathIdxStart = foundIdx + patLength; 224 | } 225 | 226 | for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 227 | if (!pattDirs[i].equals("**")) { 228 | return false; 229 | } 230 | } 231 | 232 | return true; 233 | } 234 | 235 | @Override 236 | public Map extractUriTemplateVariables(String pattern, String path) { 237 | Map variables = new LinkedHashMap<>(); 238 | boolean result = doMatch(pattern, path, true, variables); 239 | if (!result) { 240 | throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); 241 | } 242 | return variables; 243 | } 244 | 245 | private boolean isPotentialMatch(String path, String[] pattDirs) { 246 | if (!this.trimTokens) { 247 | int pos = 0; 248 | for (String pattDir : pattDirs) { 249 | int skipped = skipSeparator(path, pos, this.pathSeparator); 250 | pos += skipped; 251 | skipped = skipSegment(path, pos, pattDir); 252 | if (skipped < pattDir.length()) { 253 | return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); 254 | } 255 | pos += skipped; 256 | } 257 | } 258 | return true; 259 | } 260 | 261 | private int skipSegment(String path, int pos, String prefix) { 262 | int skipped = 0; 263 | for (int i = 0; i < prefix.length(); i++) { 264 | char c = prefix.charAt(i); 265 | if (isWildcardChar(c)) { 266 | return skipped; 267 | } 268 | int currPos = pos + skipped; 269 | if (currPos >= path.length()) { 270 | return 0; 271 | } 272 | if (c == path.charAt(currPos)) { 273 | skipped++; 274 | } 275 | } 276 | return skipped; 277 | } 278 | 279 | private int skipSeparator(String path, int pos, String separator) { 280 | int skipped = 0; 281 | while (path.startsWith(separator, pos + skipped)) { 282 | skipped += separator.length(); 283 | } 284 | return skipped; 285 | } 286 | 287 | private boolean isWildcardChar(char c) { 288 | for (char candidate : WILDCARD_CHARS) { 289 | if (c == candidate) { 290 | return true; 291 | } 292 | } 293 | return false; 294 | } 295 | 296 | protected String[] tokenizePath(String path) { 297 | return StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true); 298 | } 299 | 300 | private boolean matchStrings(String pattern, String str, 301 | Map uriTemplateVariables) { 302 | 303 | return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); 304 | } 305 | 306 | protected AntPathStringMatcher getStringMatcher(String pattern) { 307 | AntPathStringMatcher matcher = null; 308 | Boolean cachePatterns = this.cachePatterns; 309 | if (cachePatterns == null || cachePatterns.booleanValue()) { 310 | matcher = this.stringMatcherCache.get(pattern); 311 | } 312 | if (matcher == null) { 313 | matcher = new AntPathStringMatcher(pattern, this.caseSensitive); 314 | if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { 315 | // Try to adapt to the runtime situation that we're encountering: 316 | // There are obviously too many different patterns coming in here... 317 | // So let's turn off the cache since the patterns are unlikely to be reoccurring. 318 | deactivatePatternCache(); 319 | return matcher; 320 | } 321 | if (cachePatterns == null || cachePatterns.booleanValue()) { 322 | this.stringMatcherCache.put(pattern, matcher); 323 | } 324 | } 325 | return matcher; 326 | } 327 | 328 | protected String[] tokenizePattern(String pattern) { 329 | String[] tokenized = null; 330 | Boolean cachePatterns = this.cachePatterns; 331 | if (cachePatterns == null || cachePatterns.booleanValue()) { 332 | tokenized = this.tokenizedPatternCache.get(pattern); 333 | } 334 | if (tokenized == null) { 335 | tokenized = tokenizePath(pattern); 336 | if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { 337 | // Try to adapt to the runtime situation that we're encountering: 338 | // There are obviously too many different patterns coming in here... 339 | // So let's turn off the cache since the patterns are unlikely to be reoccurring. 340 | deactivatePatternCache(); 341 | return tokenized; 342 | } 343 | if (cachePatterns == null || cachePatterns.booleanValue()) { 344 | this.tokenizedPatternCache.put(pattern, tokenized); 345 | } 346 | } 347 | return tokenized; 348 | } 349 | 350 | protected static class AntPathStringMatcher { 351 | 352 | private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}"); 353 | 354 | private static final String DEFAULT_VARIABLE_PATTERN = "(.*)"; 355 | 356 | private final Pattern pattern; 357 | 358 | private final List variableNames = new LinkedList<>(); 359 | 360 | public AntPathStringMatcher(String pattern) { 361 | this(pattern, true); 362 | } 363 | 364 | public AntPathStringMatcher(String pattern, boolean caseSensitive) { 365 | StringBuilder patternBuilder = new StringBuilder(); 366 | Matcher matcher = GLOB_PATTERN.matcher(pattern); 367 | int end = 0; 368 | while (matcher.find()) { 369 | patternBuilder.append(quote(pattern, end, matcher.start())); 370 | String match = matcher.group(); 371 | if ("?".equals(match)) { 372 | patternBuilder.append('.'); 373 | } else if ("*".equals(match)) { 374 | patternBuilder.append(".*"); 375 | } else if (match.startsWith("{") && match.endsWith("}")) { 376 | int colonIdx = match.indexOf(':'); 377 | if (colonIdx == -1) { 378 | patternBuilder.append(DEFAULT_VARIABLE_PATTERN); 379 | this.variableNames.add(matcher.group(1)); 380 | } else { 381 | String variablePattern = match.substring(colonIdx + 1, match.length() - 1); 382 | patternBuilder.append('('); 383 | patternBuilder.append(variablePattern); 384 | patternBuilder.append(')'); 385 | String variableName = match.substring(1, colonIdx); 386 | this.variableNames.add(variableName); 387 | } 388 | } 389 | end = matcher.end(); 390 | } 391 | patternBuilder.append(quote(pattern, end, pattern.length())); 392 | this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) : 393 | Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); 394 | } 395 | 396 | private String quote(String s, int start, int end) { 397 | if (start == end) { 398 | return ""; 399 | } 400 | return Pattern.quote(s.substring(start, end)); 401 | } 402 | 403 | /** 404 | * Main entry point. 405 | * 406 | * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. 407 | */ 408 | public boolean matchStrings(String str, Map uriTemplateVariables) { 409 | Matcher matcher = this.pattern.matcher(str); 410 | if (matcher.matches()) { 411 | if (uriTemplateVariables != null) { 412 | // SPR-8455 413 | if (this.variableNames.size() != matcher.groupCount()) { 414 | throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + 415 | this.pattern + " does not match the number of URI template variables it defines, " + 416 | "which can occur if capturing groups are used in a URI template regex. " + 417 | "Use non-capturing groups instead."); 418 | } 419 | for (int i = 1; i <= matcher.groupCount(); i++) { 420 | String name = this.variableNames.get(i - 1); 421 | String value = matcher.group(i); 422 | uriTemplateVariables.put(name, value); 423 | } 424 | } 425 | return true; 426 | } else { 427 | return false; 428 | } 429 | } 430 | } 431 | 432 | private static class PathSeparatorPatternCache { 433 | 434 | private final String endsOnWildCard; 435 | 436 | private final String endsOnDoubleWildCard; 437 | 438 | public PathSeparatorPatternCache(String pathSeparator) { 439 | this.endsOnWildCard = pathSeparator + "*"; 440 | this.endsOnDoubleWildCard = pathSeparator + "**"; 441 | } 442 | 443 | public String getEndsOnWildCard() { 444 | return this.endsOnWildCard; 445 | } 446 | 447 | public String getEndsOnDoubleWildCard() { 448 | return this.endsOnDoubleWildCard; 449 | } 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/util/PathMatcher.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.util; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * @author: wangqi 7 | * @description: 8 | * @date: Created in 2018/5/30 上午9:55 9 | */ 10 | public interface PathMatcher { 11 | 12 | boolean match(String pattern, String path); 13 | 14 | Map extractUriTemplateVariables(String pattern, String path); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/love/wangqi/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package love.wangqi.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.StringTokenizer; 7 | 8 | /** 9 | * @author: wangqi 10 | * @description: 11 | * @date: Created in 2018/5/30 上午10:08 12 | */ 13 | public class StringUtils { 14 | public static String[] tokenizeToStringArray( 15 | String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { 16 | 17 | if (str == null) { 18 | return new String[0]; 19 | } 20 | 21 | StringTokenizer st = new StringTokenizer(str, delimiters); 22 | List tokens = new ArrayList<>(); 23 | while (st.hasMoreTokens()) { 24 | String token = st.nextToken(); 25 | if (trimTokens) { 26 | token = token.trim(); 27 | } 28 | if (!ignoreEmptyTokens || token.length() > 0) { 29 | tokens.add(token); 30 | } 31 | } 32 | return toStringArray(tokens); 33 | } 34 | 35 | public static String[] toStringArray(Collection collection) { 36 | return collection.toArray(new String[0]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------