diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f5c99a7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: java \ No newline at end of file diff --git a/README.md b/README.md index fa0d344..9b4d0ab 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,57 @@ # api-gateway-core -a lightweight api gateway + +[![Build Status](https://travis-ci.org/wangqifox/api-gateway-core.svg?branch=master)](https://travis-ci.org/wangqifox/api-gateway-core) +[![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) + +`api-gateway-core`是一个轻量级的api网关 + +## Background + +随着公司各个项目的扩展,不同的项目之间和第三方出现了大量调用项目API的需求。此时就面临了一系列问题,例如:如何让各个项目安全地对外开放接口、如何让调用方快速接入、如何保证接口的安全等等。最初的时候,这些工作是各个项目自己做的,这段时期的接口对接是一个极其痛苦的过程:各个项目的权限控制不一样、文档不全,接口提供方和调用方都需要经过大量重复的沟通。也是我们需要一个隔离接口提供方和调用方的中间层——API网关,它负责在抽象出各个业务需要的通用功能,例如:权限验证、限流、超时控制、熔断降级。 + +## Usage + +```java +// 网关的映射关系 +RouteMapper routeMapper = new AbstractRouteMapper() { + @Override + protected List locateRouteList(Set ids) { + List routeList = new ArrayList<>(); + try { + routeList.add(new Route(1L, HttpMethod.GET, "/", new URL("https://blog.wangqi.love/"))); + routeList.add(new Route(2L, HttpMethod.GET, "/baidu", new URL("https://www.baidu.com/"))); + routeList.add(new Route(3L, HttpMethod.GET, "/taobao", new URL("https://www.taobao.com/"))); + routeList.add(new Route(4L, HttpMethod.GET, "/github", new URL("https://github.com/"))); + routeList.add(new Route(5L, HttpMethod.GET, "/oschina", new URL("https://www.oschina.net/"))); + routeList.add(new Route(6L, HttpMethod.POST, "/users/{id}", new URL("http://127.0.0.1/pre/users/%7Bid%7D"))); + routeList.add(new Route(7L, HttpMethod.GET, "/css/main.css", new URL("https://blog.wangqi.love/css/main.css"))); + routeList.add(new Route(8L, HttpMethod.GET, "/path", new URL("http://127.0.0.1:9990/path"))); + routeList.add(new Route(9L, HttpMethod.GET, "/html", new URL("http://10.100.64.71/html/user.json"))); + routeList.add(new Route(10L, HttpMethod.GET, "/css", new URL("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/mantpl/css/news/init_7637f86c.css"))); + routeList.add(new Route(11L, HttpMethod.GET, "/google", new URL("https://www.google.com/"))); + routeList.add(new Route(12L, HttpMethod.GET, "/local", new URL("http://127.0.0.1:9999/"))); + } catch (MalformedURLException e) { + } + return routeList; + } +}; +routeMapper.refresh(null); +GatewayConfig config = GatewayConfig.getInstance(); +config.setPort(8888); +config.setHttpRequestBuilder(new DefaultHttpRequestBuilder()); +config.setRouteMapper(routeMapper); +config.setChannelWriteFinishListener(new DefaultChannelWriteFinishListener()); +config.setResponseHandler(new ResponseHandler()); +config.setExceptionHandler(new DefaultExceptionHandler()); + +GatewayServer server = new GatewayServer(); +server.start(); +``` + +## Related + +[API网关技术总结](https://blog.wangqi.love/articles/Java/API网关技术总结.html) + +## License + +[Apache License 2.0](LICENSE) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4bcdfc8..558d70f 100644 --- a/pom.xml +++ b/pom.xml @@ -22,18 +22,18 @@ junit junit - 4.11 + 4.13.1 test io.netty netty-all - 4.1.28.Final + 4.1.43.Final com.fasterxml.jackson.core jackson-databind - 2.9.9 + 2.12.3 ch.qos.logback diff --git a/src/main/java/love/wangqi/GatewayServerDemo.java b/src/main/java/love/wangqi/GatewayServerDemo.java index 34a8429..138d6a3 100644 --- a/src/main/java/love/wangqi/GatewayServerDemo.java +++ b/src/main/java/love/wangqi/GatewayServerDemo.java @@ -32,8 +32,10 @@ protected List locateRouteList(Set ids) { List routeList = new ArrayList<>(); try { routeList.add(new Route(1L, HttpMethod.GET, "/", new URL("https://blog.wangqi.love/"))); + routeList.add(new Route(1L, HttpMethod.GET, "/wangqi", new URL("http://wangqi.love/"))); + routeList.add(new Route(1L, HttpMethod.GET, "/117", new URL("http://10.0.111.117/#/login"))); routeList.add(new Route(2L, HttpMethod.GET, "/baidu", new URL("https://www.baidu.com/"))); - routeList.add(new Route(3L, HttpMethod.GET, "/taobao", new URL("https://www.taobao.com/"))); + routeList.add(new Route(3L, HttpMethod.GET, "/taobao", new URL("http://www.taobao.com/"))); routeList.add(new Route(4L, HttpMethod.GET, "/github", new URL("https://github.com/"))); routeList.add(new Route(5L, HttpMethod.GET, "/oschina", new URL("https://www.oschina.net/"))); routeList.add(new Route(6L, HttpMethod.POST, "/users/{id}", new URL("http://127.0.0.1/pre/users/%7Bid%7D"))); diff --git a/src/main/java/love/wangqi/codec/DefaultHttpRequestBuilder.java b/src/main/java/love/wangqi/codec/DefaultHttpRequestBuilder.java index 8abf908..43fe13d 100644 --- a/src/main/java/love/wangqi/codec/DefaultHttpRequestBuilder.java +++ b/src/main/java/love/wangqi/codec/DefaultHttpRequestBuilder.java @@ -56,7 +56,7 @@ public RequestHolder build(FullHttpRequest originRequest) throws Exception { if (route == null) { throw new GatewayNoRouteException(); } - URL url = route.getMapUrl(); + URL url = route.getMapUrl(); logger.info("proxy_pass {}", url.toString()); // 请求路径 @@ -80,7 +80,7 @@ public RequestHolder build(FullHttpRequest originRequest) throws Exception { if (contentType.startsWith(HttpHeaderValues.APPLICATION_JSON.toString())) { ByteBuf bbuf = Unpooled.copiedBuffer(buildContentJson(route), StandardCharsets.UTF_8); newRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, bbuf.readableBytes()); - ((FullHttpRequest)newRequest).content().writeBytes(bbuf); + ((FullHttpRequest) newRequest).content().writeBytes(bbuf); } else if (contentType.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())) { HttpPostRequestEncoder requestEncoder = new HttpPostRequestEncoder(newRequest, false); buildContentFormUrlEncoded(route).forEach((key, values) -> { @@ -107,7 +107,7 @@ public RequestHolder build(FullHttpRequest originRequest) throws Exception { } else { ByteBuf byteBuf = buildContentOther(route); newRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes()); - ((FullHttpRequest)newRequest).content().writeBytes(byteBuf); + ((FullHttpRequest) newRequest).content().writeBytes(byteBuf); } } return new RequestHolder(route, url, newRequest, newBodyRequestEncoder); @@ -115,6 +115,7 @@ public RequestHolder build(FullHttpRequest originRequest) throws Exception { /** * 返回path(不包含?后面的参数部分) + * * @return */ protected String buildPath(Route route) { @@ -123,6 +124,7 @@ protected String buildPath(Route route) { /** * 返回请求的请求参数 + * * @return */ protected Map> buildParams(Route route) { @@ -131,6 +133,7 @@ protected Map> buildParams(Route route) { /** * 返回请求的请求头 + * * @return */ protected Map> buildHeaders(Route route) { @@ -139,6 +142,7 @@ protected Map> buildHeaders(Route route) { /** * 如果content-type为application/json,获取请求体 + * * @return */ protected String buildContentJson(Route route) { @@ -147,6 +151,7 @@ protected String buildContentJson(Route route) { /** * 如果content-type为application/x-www-form-urlencoded,获取请求体 + * * @return */ protected Map> buildContentFormUrlEncoded(Route route) { @@ -155,6 +160,7 @@ protected Map> buildContentFormUrlEncoded(Route route) { /** * 如果content-type为multipart/form-data,获取请求体 + * * @return */ protected List buildContentFormData(Route route) { @@ -163,6 +169,7 @@ protected List buildContentFormData(Route route) { /** * 其他类型的content-type则直接返回相应的ByteBuf + * * @return */ private ByteBuf buildContentOther(Route route) { diff --git a/src/main/java/love/wangqi/codec/HttpRequestBuilder.java b/src/main/java/love/wangqi/codec/HttpRequestBuilder.java index eef3b9c..8a24597 100644 --- a/src/main/java/love/wangqi/codec/HttpRequestBuilder.java +++ b/src/main/java/love/wangqi/codec/HttpRequestBuilder.java @@ -13,6 +13,7 @@ public interface HttpRequestBuilder { /** * 设置路由映射器 + * * @param routeMapper * @return */ @@ -20,6 +21,7 @@ public interface HttpRequestBuilder { /** * 生成新的请求 + * * @param originRequest * @return * @throws Exception @@ -28,6 +30,7 @@ public interface HttpRequestBuilder { /** * 获取路由 + * * @param originRequest * @return */ diff --git a/src/main/java/love/wangqi/codec/HttpRequestDecomposer.java b/src/main/java/love/wangqi/codec/HttpRequestDecomposer.java index d838e78..9107d7e 100644 --- a/src/main/java/love/wangqi/codec/HttpRequestDecomposer.java +++ b/src/main/java/love/wangqi/codec/HttpRequestDecomposer.java @@ -32,6 +32,7 @@ public HttpRequestDecomposer(FullHttpRequest request) { /** * 获取请求的uri(包含?后面的参数部分) + * * @return */ public String getUri() { @@ -40,6 +41,7 @@ public String getUri() { /** * 获取请求路径(不包含?后面的参数部分) + * * @return */ public String getPath() { @@ -49,6 +51,7 @@ public String getPath() { /** * 获取请求参数 + * * @return */ public Map> getParams() { @@ -58,6 +61,7 @@ public Map> getParams() { /** * 获取content-type + * * @return */ public String getContentType() { @@ -66,6 +70,7 @@ public String getContentType() { /** * 获取请求头 + * * @return */ public Map> getHeaders() { @@ -87,6 +92,7 @@ public Map> getHeaders() { /** * 如果content-type为application/json,将内容转换成JsonNode + * * @return */ public JsonNode getContentJson() { @@ -95,6 +101,7 @@ public JsonNode getContentJson() { /** * 如果content-type为application/json,以字符串形式返回请求体 + * * @return */ public String getContentJsonAsString() { @@ -103,6 +110,7 @@ public String getContentJsonAsString() { /** * 如果content-type为application/json,将内容转换成相应的类型 + * * @return */ public T getContentJson(Class valueType) { @@ -117,6 +125,7 @@ public T getContentJson(Class valueType) { /** * 如果content-type为application/x-www-form-urlencoded,将内容转换成map + * * @return */ public Map> getContentFormUrlEncoded() { @@ -127,6 +136,7 @@ public Map> getContentFormUrlEncoded() { /** * 如果content-type为multipart/form-data,获取内容列表 + * * @return */ public List getContentFormdata() { @@ -136,6 +146,7 @@ public List getContentFormdata() { /** * 其他类型的content-type则直接返回相应的ByteBuf + * * @return */ public ByteBuf getContentOther() { @@ -144,6 +155,7 @@ public ByteBuf getContentOther() { /** * 以字符串形式返回请求体 + * * @return */ public String getContentAsString() { diff --git a/src/main/java/love/wangqi/exception/handler/ExceptionHandler.java b/src/main/java/love/wangqi/exception/handler/ExceptionHandler.java index 72e89f7..e9ba8f3 100644 --- a/src/main/java/love/wangqi/exception/handler/ExceptionHandler.java +++ b/src/main/java/love/wangqi/exception/handler/ExceptionHandler.java @@ -10,6 +10,7 @@ public interface ExceptionHandler { /** * 获取异常返回 + * * @param exception * @return */ @@ -17,6 +18,7 @@ public interface ExceptionHandler { /** * 发送异常 + * * @param channel * @param exceptionResponse */ @@ -24,6 +26,7 @@ public interface ExceptionHandler { /** * 处理异常 + * * @param channel * @param exception */ diff --git a/src/main/java/love/wangqi/filter/FilterProcessor.java b/src/main/java/love/wangqi/filter/FilterProcessor.java index c77f686..96cebf8 100644 --- a/src/main/java/love/wangqi/filter/FilterProcessor.java +++ b/src/main/java/love/wangqi/filter/FilterProcessor.java @@ -22,7 +22,8 @@ public static FilterProcessor getInstance() { return INSTANCE; } - private FilterProcessor() {} + private FilterProcessor() { + } public void preRoute(Channel channel) throws GatewayException { try { diff --git a/src/main/java/love/wangqi/filter/IGatewayFilter.java b/src/main/java/love/wangqi/filter/IGatewayFilter.java index 1499f26..3285b3e 100644 --- a/src/main/java/love/wangqi/filter/IGatewayFilter.java +++ b/src/main/java/love/wangqi/filter/IGatewayFilter.java @@ -10,6 +10,7 @@ public interface IGatewayFilter { /** * 过滤Http请求 + * * @param channel * @throws Exception */ diff --git a/src/main/java/love/wangqi/filter/SendErrorFilter.java b/src/main/java/love/wangqi/filter/SendErrorFilter.java index 2eaf7a7..4e270e0 100644 --- a/src/main/java/love/wangqi/filter/SendErrorFilter.java +++ b/src/main/java/love/wangqi/filter/SendErrorFilter.java @@ -36,6 +36,7 @@ public void filter(Channel channel) throws Exception { logger.error("Route Id: " + requestHolder.route.getId() + " Route Url: " + requestHolder.route.getMapUrl() + " " + e.getMessage(), e); } config.getExceptionHandler().handle(channel, e); + ContextUtil.clear(channel); } } } diff --git a/src/main/java/love/wangqi/handler/GatewayRunner.java b/src/main/java/love/wangqi/handler/GatewayRunner.java index aae8422..8915545 100644 --- a/src/main/java/love/wangqi/handler/GatewayRunner.java +++ b/src/main/java/love/wangqi/handler/GatewayRunner.java @@ -20,7 +20,8 @@ public class GatewayRunner { private final static GatewayRunner INSTANCE = new GatewayRunner(); - private GatewayRunner() {} + private GatewayRunner() { + } public static GatewayRunner getInstance() { return INSTANCE; @@ -53,6 +54,7 @@ public Thread newThread(Runnable r) { /** * 线程名称前缀 + * * @return */ abstract String getNamePrefix(); diff --git a/src/main/java/love/wangqi/route/AbstractRouteMapper.java b/src/main/java/love/wangqi/route/AbstractRouteMapper.java index abc1695..c6c672b 100644 --- a/src/main/java/love/wangqi/route/AbstractRouteMapper.java +++ b/src/main/java/love/wangqi/route/AbstractRouteMapper.java @@ -27,6 +27,7 @@ public abstract class AbstractRouteMapper implements RouteMapper { /** * 遍历所有的路由,返回符合请求的路由 + * * @param path * @param method * @return @@ -74,6 +75,7 @@ public Route getRoute(HttpRequest request) { /** * 获取路由列表 + * * @param ids 路由id的列表 * @return */ diff --git a/src/main/java/love/wangqi/route/RouteMapper.java b/src/main/java/love/wangqi/route/RouteMapper.java index f742f9c..a566fcc 100644 --- a/src/main/java/love/wangqi/route/RouteMapper.java +++ b/src/main/java/love/wangqi/route/RouteMapper.java @@ -14,6 +14,7 @@ public interface RouteMapper { /** * 根据路径获取Route + * * @param path * @param method * @return @@ -22,6 +23,7 @@ public interface RouteMapper { /** * 根据请求获取Route + * * @param request * @return */ @@ -29,12 +31,14 @@ public interface RouteMapper { /** * 刷新路由 + * * @param ids 路由id的列表 */ void refresh(Set ids); /** * 获取路由列表 + * * @return */ List getRouteList(); diff --git a/src/main/java/love/wangqi/util/AntPathMatcher.java b/src/main/java/love/wangqi/util/AntPathMatcher.java index ba3196f..f1be107 100644 --- a/src/main/java/love/wangqi/util/AntPathMatcher.java +++ b/src/main/java/love/wangqi/util/AntPathMatcher.java @@ -15,14 +15,16 @@ */ public class AntPathMatcher implements PathMatcher { - /** Default path separator: "/" */ + /** + * Default path separator: "/" + */ public static final String DEFAULT_PATH_SEPARATOR = "/"; private static final int CACHE_TURNOFF_THRESHOLD = 65536; private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}"); - private static final char[] WILDCARD_CHARS = { '*', '?', '{' }; + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; private String pathSeparator; @@ -50,6 +52,7 @@ public AntPathMatcher() { /** * A convenient, alternative constructor to use with a custom path separator. + * * @param pathSeparator the path separator to use, must not be {@code null}. * @since 4.1 */ @@ -71,6 +74,7 @@ public void setPathSeparator(String pathSeparator) { /** * Specify whether to perform pattern matching in a case-sensitive fashion. *

Default is {@code true}. Switch this to {@code false} for case-insensitive matching. + * * @since 4.2 */ public void setCaseSensitive(boolean caseSensitive) { @@ -149,12 +153,10 @@ protected boolean doMatch(String pattern, String path, boolean fullMatch, } } return true; - } - else if (pattIdxStart > pattIdxEnd) { + } else if (pattIdxStart > pattIdxEnd) { // String not exhausted, but pattern is. Failure. return false; - } - else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) { + } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) { // Path start definitely matches due to "**" part in pattern. return true; } @@ -368,17 +370,14 @@ public AntPathStringMatcher(String pattern, boolean caseSensitive) { String match = matcher.group(); if ("?".equals(match)) { patternBuilder.append('.'); - } - else if ("*".equals(match)) { + } else if ("*".equals(match)) { patternBuilder.append(".*"); - } - else if (match.startsWith("{") && match.endsWith("}")) { + } else if (match.startsWith("{") && match.endsWith("}")) { int colonIdx = match.indexOf(':'); if (colonIdx == -1) { patternBuilder.append(DEFAULT_VARIABLE_PATTERN); this.variableNames.add(matcher.group(1)); - } - else { + } else { String variablePattern = match.substring(colonIdx + 1, match.length() - 1); patternBuilder.append('('); patternBuilder.append(variablePattern); @@ -403,6 +402,7 @@ private String quote(String s, int start, int end) { /** * Main entry point. + * * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. */ public boolean matchStrings(String str, Map uriTemplateVariables) { @@ -423,8 +423,7 @@ public boolean matchStrings(String str, Map uriTemplateVariables } } return true; - } - else { + } else { return false; } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index eff27a7..f1a8959 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -7,7 +7,7 @@ - +