This is an automated email from the ASF dual-hosted git repository. liubao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-java-chassis.git
commit b67dfb0f1ddb2c21738a533fb58c6d446eff4350 Author: liubao <[email protected]> AuthorDate: Tue May 29 14:26:44 2018 +0800 [SCB-611]Provide URLMappedEdgeDispatcher to using edge service easier --- .../demo/edge/consumer/ConsumerMain.java | 3 + .../src/main/resources/config/log4j.properties | 1 + .../src/main/resources/microservice.yaml | 15 +- .../edge/core/DefaultEdgeDispatcher.java | 21 +-- .../edge/core/URLMappedEdgeDispatcher.java | 170 +++++++++++++++++++++ .../org/apache/servicecomb/edge/core/Utils.java | 40 +++-- ...cecomb.transport.rest.vertx.VertxHttpDispatcher | 3 +- ...patcher.java => TestDefaultEdgeDispatcher.java} | 4 +- .../edge/core/TestURLMappedEdgeDispatcher.java | 102 +++++++++++++ .../apache/servicecomb/edge/core/TestUtils.java | 25 ++- 10 files changed, 338 insertions(+), 46 deletions(-) diff --git a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java b/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java index 9a91656..6999577 100644 --- a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java +++ b/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java @@ -29,6 +29,9 @@ public class ConsumerMain { new Consumer().run("api"); System.out.println("Running rest dispater."); new Consumer().run("rest"); + System.out.println("Running url dispater."); + new Consumer().run("url"); + System.out.println("All test case finished."); } } diff --git a/demo/demo-edge/edge-service/src/main/resources/config/log4j.properties b/demo/demo-edge/edge-service/src/main/resources/config/log4j.properties index 3fc9f8b..efd6a27 100644 --- a/demo/demo-edge/edge-service/src/main/resources/config/log4j.properties +++ b/demo/demo-edge/edge-service/src/main/resources/config/log4j.properties @@ -16,3 +16,4 @@ # log4j.rootLogger=INFO,stdout +log4j.appender.stdout.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss,SSS/zzz}][%t][%p]%m %l%n \ No newline at end of file diff --git a/demo/demo-edge/edge-service/src/main/resources/microservice.yaml b/demo/demo-edge/edge-service/src/main/resources/microservice.yaml index 27b17db..e029172 100644 --- a/demo/demo-edge/edge-service/src/main/resources/microservice.yaml +++ b/demo/demo-edge/edge-service/src/main/resources/microservice.yaml @@ -44,4 +44,17 @@ servicecomb: enabled: true prefix: rest withVersion: true - pathIndex: 2 + pathIndex: 1 + url: + enabled: true + mappings: + businessV1: + pathIndex: 1 + path: "/url/business/v1/.*" + microserviceName: business + versionRule: 1.0.0-2.0.0 + businessV2: + pathIndex: 1 + path: "/url/business/v2/.*" + microserviceName: business + versionRule: 2.0.0-3.0.0 diff --git a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/DefaultEdgeDispatcher.java b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/DefaultEdgeDispatcher.java index 3070240..6ed0894 100644 --- a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/DefaultEdgeDispatcher.java +++ b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/DefaultEdgeDispatcher.java @@ -26,7 +26,7 @@ import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.CookieHandler; /** - * Provide an easy mapping dispatcher. User can configure prefix to easily using edge service. + * Provide an easy mapping dispatcher that starts with a common prefix pattern. */ public class DefaultEdgeDispatcher extends AbstractEdgeDispatcher { private static final String KEY_ENABLED = "servicecomb.http.dispatcher.edge.default.enabled"; @@ -59,7 +59,7 @@ public class DefaultEdgeDispatcher extends AbstractEdgeDispatcher { public void init(Router router) { prefix = DynamicPropertyFactory.getInstance().getStringProperty(KEY_PREFIX, "api").get(); withVersion = DynamicPropertyFactory.getInstance().getBooleanProperty(KEY_WITH_VERSION, true).get(); - pathIndex = DynamicPropertyFactory.getInstance().getIntProperty(KEY_PATH_INDEX, 2).get(); + pathIndex = DynamicPropertyFactory.getInstance().getIntProperty(KEY_PATH_INDEX, 1).get(); String regex; if (withVersion) { regex = "/" + prefix + "/([^\\\\/]+)/([^\\\\/]+)/(.*)"; @@ -74,7 +74,7 @@ public class DefaultEdgeDispatcher extends AbstractEdgeDispatcher { protected void onRequest(RoutingContext context) { Map<String, String> pathParams = context.pathParams(); String microserviceName = pathParams.get("param0"); - String path = findActualPath(context.request().path()); + String path = Utils.findActualPath(context.request().path(), pathIndex); EdgeInvocation edgeInvocation = new EdgeInvocation(); if (withVersion) { @@ -84,19 +84,4 @@ public class DefaultEdgeDispatcher extends AbstractEdgeDispatcher { edgeInvocation.init(microserviceName, context, path, httpServerFilters); edgeInvocation.edgeInvoke(); } - - protected String findActualPath(String path) { - int fromIndex = 0; - int counter = pathIndex; - char[] chars = path.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '/') { - if (--counter <= 0) { - fromIndex = i; - break; - } - } - } - return path.substring(fromIndex > 0 ? fromIndex : 0); - } } diff --git a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/URLMappedEdgeDispatcher.java b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/URLMappedEdgeDispatcher.java new file mode 100644 index 0000000..a67e883 --- /dev/null +++ b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/URLMappedEdgeDispatcher.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.servicecomb.edge.core; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.config.ConcurrentCompositeConfiguration; +import com.netflix.config.DynamicPropertyFactory; + +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.CookieHandler; + +/** + * Provide a URL mapping based dispatcher. Users configure witch URL patterns dispatch to a target service. + */ +public class URLMappedEdgeDispatcher extends AbstractEdgeDispatcher { + class ConfigurationItem { + String microserviceName; + + String versionRule; + + int pathIndex; + + Pattern pattern; + + String stringPattern; + } + + private static final Logger LOG = LoggerFactory.getLogger(URLMappedEdgeDispatcher.class); + + private static final String PATTERN_ANY = "/(.*)"; + + private static final String KEY_ENABLED = "servicecomb.http.dispatcher.edge.url.enabled"; + + private static final String KEY_MAPPING_PREIX = "servicecomb.http.dispatcher.edge.url.mappings"; + + private static final String KEY_MAPPING_PATH = ".path"; + + private static final String KEY_MAPPING_SERVICE_NAME = "servicecomb.http.dispatcher.edge.url.mappings.%s.microserviceName"; + + private static final String KEY_MAPPING_VERSION_RULE = "servicecomb.http.dispatcher.edge.url.mappings.%s.versionRule"; + + private static final String KEY_MAPPING_PATH_INDEX = "servicecomb.http.dispatcher.edge.url.mappings.%s.pathIndex"; + + private Map<String, ConfigurationItem> configurations = new HashMap<>(); + + public URLMappedEdgeDispatcher() { + if(this.enabled()) { + loadConfigurations(); + } + } + + @Override + public int getOrder() { + return 30000; + } + + @Override + public boolean enabled() { + return DynamicPropertyFactory.getInstance().getBooleanProperty(KEY_ENABLED, false).get(); + } + + @Override + public void init(Router router) { + router.routeWithRegex(PATTERN_ANY).handler(CookieHandler.create()); + router.routeWithRegex(PATTERN_ANY).handler(createBodyHandler()); + router.routeWithRegex(PATTERN_ANY).failureHandler(this::onFailure).handler(this::onRequest); + } + + private void loadConfigurations() { + ConcurrentCompositeConfiguration config = (ConcurrentCompositeConfiguration) DynamicPropertyFactory + .getBackingConfigurationSource(); + loadConfigurations(config); + config.addConfigurationListener(event -> { + if (event.getPropertyName().startsWith(KEY_MAPPING_PREIX)) { + LOG.info("Map rule have been changed. Reload configurations. Event=" + event.getType()); + loadConfigurations(config); + } + }); + } + + private void loadConfigurations(ConcurrentCompositeConfiguration config) { + Map<String, ConfigurationItem> configurations = new HashMap<>(); + Iterator<String> configsItems = config.getKeys(KEY_MAPPING_PREIX); + while (configsItems.hasNext()) { + String pathKey = configsItems.next(); + if (pathKey.endsWith(KEY_MAPPING_PATH)) { + ConfigurationItem configurationItem = new ConfigurationItem(); + String pattern = DynamicPropertyFactory.getInstance() + .getStringProperty(pathKey, null).get(); + if (StringUtils.isEmpty(pattern)) { + continue; + } + configurationItem.pattern = Pattern.compile(pattern); + configurationItem.stringPattern = pattern; + String pathKeyItem = pathKey + .substring(KEY_MAPPING_PREIX.length() + 1, pathKey.length() - KEY_MAPPING_PATH.length()); + configurationItem.microserviceName = DynamicPropertyFactory.getInstance() + .getStringProperty(String.format(KEY_MAPPING_SERVICE_NAME, pathKeyItem), null).get(); + if (StringUtils.isEmpty(configurationItem.microserviceName)) { + continue; + } + configurationItem.pathIndex = DynamicPropertyFactory.getInstance() + .getIntProperty(String.format(KEY_MAPPING_PATH_INDEX, pathKeyItem), 0).get(); + configurationItem.versionRule = DynamicPropertyFactory.getInstance() + .getStringProperty(String.format(KEY_MAPPING_VERSION_RULE, pathKeyItem), "0.0.0+").get(); + configurations.put(pathKeyItem, configurationItem); + } + } + this.configurations = configurations; + logConfigurations(); + } + + private void logConfigurations() { + for (String key : this.configurations.keySet()) { + ConfigurationItem item = this.configurations.get(key); + LOG.info("config item: key=" + key + ";pattern=" + item.stringPattern + ";service=" + item.microserviceName + + ";versionRule=" + item.versionRule); + } + } + + protected void onRequest(RoutingContext context) { + ConfigurationItem configurationItem = findConfigurationItem(context.request().path()); + if (configurationItem == null) { + context.next(); + return; + } + + String path = Utils.findActualPath(context.request().path(), configurationItem.pathIndex); + + EdgeInvocation edgeInvocation = new EdgeInvocation(); + if (configurationItem.versionRule != null) { + edgeInvocation.setVersionRule(configurationItem.versionRule); + } + edgeInvocation.init(configurationItem.microserviceName, context, path, httpServerFilters); + edgeInvocation.edgeInvoke(); + } + + private ConfigurationItem findConfigurationItem(String path) { + for (ConfigurationItem item : configurations.values()) { + if (item.pattern.matcher(path).matches()) { + return item; + } + } + return null; + } +} diff --git a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/Utils.java similarity index 52% copy from demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java copy to edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/Utils.java index 9a91656..9bbefb9 100644 --- a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java +++ b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/Utils.java @@ -15,20 +15,38 @@ * limitations under the License. */ -package org.apache.servicecomb.demo.edge.consumer; +package org.apache.servicecomb.edge.core; -import org.apache.servicecomb.foundation.common.utils.BeanUtils; -import org.apache.servicecomb.foundation.common.utils.Log4jUtils; +/** + * Commonly used methods in this package. + */ +public final class Utils { + private Utils() { -public class ConsumerMain { - public static void main(String[] args) throws Exception { - Log4jUtils.init(); - BeanUtils.init(); + } - System.out.println("Running api dispater."); - new Consumer().run("api"); - System.out.println("Running rest dispater."); - new Consumer().run("rest"); + /** + * Get the actual path without prefix + * @param path full path + * @param pathIndex the index of / that after prefix + * @return actual path + */ + public static String findActualPath(String path, int pathIndex) { + if (pathIndex <= 0) { + return path; + } + int fromIndex = 0; + int counter = pathIndex; + char[] chars = path.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '/') { + if (--counter < 0) { + fromIndex = i; + break; + } + } + } + return fromIndex > 0 ? path.substring(fromIndex) : ""; } } diff --git a/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher b/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher index cc2856f..d81bb22 100644 --- a/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher +++ b/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher @@ -15,4 +15,5 @@ # limitations under the License. # -org.apache.servicecomb.edge.core.DefaultEdgeDispatcher \ No newline at end of file +org.apache.servicecomb.edge.core.DefaultEdgeDispatcher +org.apache.servicecomb.edge.core.URLMappedEdgeDispatcher \ No newline at end of file diff --git a/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestServiceAndVersionDispatcher.java b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestDefaultEdgeDispatcher.java similarity index 95% rename from edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestServiceAndVersionDispatcher.java rename to edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestDefaultEdgeDispatcher.java index 55efc10..518740a 100644 --- a/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestServiceAndVersionDispatcher.java +++ b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestDefaultEdgeDispatcher.java @@ -35,7 +35,7 @@ import mockit.Deencapsulation; import mockit.Expectations; import mockit.Mocked; -public class TestServiceAndVersionDispatcher { +public class TestDefaultEdgeDispatcher { @Before public void setUp() { @@ -79,7 +79,7 @@ public class TestServiceAndVersionDispatcher { }; dispatcher.init(router); Assert.assertEquals(dispatcher.enabled(), false); - Assert.assertEquals(dispatcher.findActualPath("/api/test"), "/test"); + Assert.assertEquals(Utils.findActualPath("/api/test", 1), "/test"); Assert.assertEquals(dispatcher.getOrder(), 20000); dispatcher.onRequest(context); diff --git a/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestURLMappedEdgeDispatcher.java b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestURLMappedEdgeDispatcher.java new file mode 100644 index 0000000..3e0732f --- /dev/null +++ b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestURLMappedEdgeDispatcher.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.servicecomb.edge.core; + +import java.util.Map; + +import org.apache.servicecomb.edge.core.URLMappedEdgeDispatcher.ConfigurationItem; +import org.apache.servicecomb.foundation.common.utils.Log4jUtils; +import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import io.vertx.core.http.HttpServerRequest; +import io.vertx.ext.web.RoutingContext; +import mockit.Deencapsulation; +import mockit.Expectations; +import mockit.Mocked; + +public class TestURLMappedEdgeDispatcher { + @Before + public void setUp() throws Exception { + Log4jUtils.init(); + } + + @After + public void tearDown() { + ArchaiusUtils.resetConfig(); + } + + @Test + @SuppressWarnings(value = "unchecked") + public void testConfigurations(@Mocked RoutingContext context + , @Mocked HttpServerRequest requst + , @Mocked EdgeInvocation invocation) { + ArchaiusUtils.setProperty("servicecomb.http.dispatcher.edge.url.enabled", true); + + URLMappedEdgeDispatcher dispatcher = new URLMappedEdgeDispatcher(); + Map<String, ConfigurationItem> items = Deencapsulation + .getField(dispatcher, "configurations"); + Assert.assertEquals(items.size(), 0); + + new Expectations() { + { + context.next(); + } + }; + dispatcher.onRequest(context); + + ArchaiusUtils.setProperty("servicecomb.http.dispatcher.edge.url.mappings.service1.path", "/a/b/c/.*"); + ArchaiusUtils.setProperty("servicecomb.http.dispatcher.edge.url.mappings.service1.microserviceName", "serviceName"); + ArchaiusUtils.setProperty("servicecomb.http.dispatcher.edge.url.mappings.service1.pathIndex", 2); + ArchaiusUtils.setProperty("servicecomb.http.dispatcher.edge.url.mappings.service1.versionRule", "2.0.0+"); + items = Deencapsulation.getField(dispatcher, "configurations"); + Assert.assertEquals(items.size(), 1); + ConfigurationItem item = items.get("service1"); + Assert.assertEquals(item.microserviceName, "serviceName"); + Assert.assertEquals(item.pathIndex, 2); + Assert.assertEquals(item.stringPattern, "/a/b/c/.*"); + Assert.assertEquals(item.versionRule, "2.0.0+"); + + ArchaiusUtils.setProperty("servicecomb.http.dispatcher.edge.url.mappings.service2.versionRule", "2.0.0+"); + ArchaiusUtils.setProperty("servicecomb.http.dispatcher.edge.url.mappings.service3.path", "/b/c/d/.*"); + items = Deencapsulation.getField(dispatcher, "configurations"); + Assert.assertEquals(items.size(), 1); + item = items.get("service1"); + Assert.assertEquals(item.microserviceName, "serviceName"); + Assert.assertEquals(item.pathIndex, 2); + Assert.assertEquals(item.stringPattern, "/a/b/c/.*"); + Assert.assertEquals(item.versionRule, "2.0.0+"); + + new Expectations() { + { + context.request(); + result = requst; + requst.path(); + result = "/a/b/c/d/e"; + invocation.setVersionRule("2.0.0+"); + invocation.init("serviceName", context, "/c/d/e", + Deencapsulation.getField(dispatcher, "httpServerFilters")); + invocation.edgeInvoke(); + } + }; + dispatcher.onRequest(context); + } +} diff --git a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestUtils.java similarity index 59% copy from demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java copy to edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestUtils.java index 9a91656..c92373f 100644 --- a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java +++ b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestUtils.java @@ -15,20 +15,19 @@ * limitations under the License. */ -package org.apache.servicecomb.demo.edge.consumer; +package org.apache.servicecomb.edge.core; -import org.apache.servicecomb.foundation.common.utils.BeanUtils; -import org.apache.servicecomb.foundation.common.utils.Log4jUtils; - -public class ConsumerMain { - public static void main(String[] args) throws Exception { - Log4jUtils.init(); - BeanUtils.init(); - - System.out.println("Running api dispater."); - new Consumer().run("api"); - System.out.println("Running rest dispater."); - new Consumer().run("rest"); +import org.junit.Assert; +import org.junit.Test; +public class TestUtils { + @Test + public void testUtisl() { + Assert.assertEquals(Utils.findActualPath("/a/b/c", -1), "/a/b/c"); + Assert.assertEquals(Utils.findActualPath("/a/b/c", 0), "/a/b/c"); + Assert.assertEquals(Utils.findActualPath("/a/b/c", 1), "/b/c"); + Assert.assertEquals(Utils.findActualPath("/a/b/c", 2), "/c"); + Assert.assertEquals(Utils.findActualPath("/a/b/c", 3), ""); + Assert.assertEquals(Utils.findActualPath("/a/b/c", 100), ""); } } -- To stop receiving notification emails like this one, please contact [email protected].
