From c88b36b6ddadafc7a8252601df527db86e26b452 Mon Sep 17 00:00:00 2001 From: wujimin Date: Thu, 17 Aug 2017 15:14:34 +0800 Subject: [PATCH 01/18] JAV-150 inject servlet before transport init. --- .../servlet/CseXmlWebApplicationContext.java | 9 ++++++++ .../servlet/RestServletContextListener.java | 1 - .../TestCseXmlWebApplicationContext.java | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java index b0548b563de..c3a21cfe8f1 100644 --- a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java +++ b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java @@ -25,6 +25,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.web.context.support.XmlWebApplicationContext; import io.servicecomb.foundation.common.utils.BeanUtils; @@ -44,6 +45,14 @@ public void setDefaultBeanResource(String defaultBeanResource) { this.defaultBeanResource = defaultBeanResource; } + @Override + protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { + super.invokeBeanFactoryPostProcessors(beanFactory); + + // inject servlet after config installed and before transport init + RestServletInjector.defaultInject(getServletContext()); + } + @Override public String[] getConfigLocations() { String contextConfigLocation = getServletContext().getInitParameter(KEY_LOCATION); diff --git a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletContextListener.java b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletContextListener.java index 07359f90ede..c2d78d6dabd 100644 --- a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletContextListener.java +++ b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletContextListener.java @@ -33,7 +33,6 @@ public void contextInitialized(ServletContextEvent sce) { try { initLog(sce); initSpring(sce); - RestServletInjector.defaultInject(sce.getServletContext()); } catch (Exception e) { throw new Error(e); } diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java index b4cf1c8dcaf..efa8824349c 100644 --- a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestCseXmlWebApplicationContext.java @@ -17,14 +17,19 @@ package io.servicecomb.transport.rest.servlet; import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration.Dynamic; +import javax.xml.ws.Holder; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import io.servicecomb.foundation.common.utils.BeanUtils; import mockit.Expectations; +import mockit.Mock; +import mockit.MockUp; import mockit.Mocked; public class TestCseXmlWebApplicationContext { @@ -99,4 +104,20 @@ public void testGetConfigLocationsMix() { String[] result = context.getConfigLocations(); Assert.assertThat(result, Matchers.arrayContaining("a", "b", "c", BeanUtils.DEFAULT_BEAN_RESOURCE)); } + + @Test + public void testInjectServlet(@Mocked ConfigurableListableBeanFactory beanFactory) { + Holder holder = new Holder<>(); + new MockUp() { + @Mock + public Dynamic defaultInject(ServletContext servletContext) { + holder.value = true; + return null; + } + }; + + context.invokeBeanFactoryPostProcessors(beanFactory); + + Assert.assertTrue(holder.value); + } } From 46327d08055aa84a9d94483d8d7a81d9209f265b Mon Sep 17 00:00:00 2001 From: wujimin Date: Thu, 17 Aug 2017 15:51:50 +0800 Subject: [PATCH 02/18] JAV-150 urlPattern should start with / --- .../transport/rest/servlet/RestServletInjector.java | 4 ++++ .../rest/servlet/TestRestServletInjector.java | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java index 4d5b361612a..d170aa55b66 100644 --- a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java +++ b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java @@ -70,6 +70,10 @@ void checkUrlPattern(String urlPattern) { throw new ServiceCombException("not support multiple path rule."); } + if (!urlPattern.startsWith("/")) { + throw new ServiceCombException("only support rule like /* or /path/* or /path1/path2/* and so on."); + } + int idx = urlPattern.indexOf("/*"); if (idx < 0 || (idx >= 0 && idx != urlPattern.length() - 2)) { throw new ServiceCombException("only support rule like /* or /path/* or /path1/path2/* and so on."); diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java index 1a84aaa96a7..94fe134e6e0 100644 --- a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java @@ -73,6 +73,16 @@ public void testCheckUrlPatternNoWideChar() { } } + @Test + public void testCheckUrlPatternNotStartWithSlash() { + try { + injector.checkUrlPattern("abcdef/*"); + Assert.fail("must throw exception"); + } catch (ServiceCombException e) { + Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); + } + } + @Test public void testDefaultInjectEmptyUrlPattern(@Mocked ServletContext servletContext, @Mocked Dynamic dynamic) { new Expectations(ServletConfig.class) { From 369e57d1b8d2211145db22e1078a2afa4f3933da Mon Sep 17 00:00:00 2001 From: wujimin Date: Thu, 17 Aug 2017 15:52:52 +0800 Subject: [PATCH 03/18] demo-spring-boot-discovery-server use serviceComb RESTful servlet transport process request --- .../src/main/resources/microservice.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/demo-spring-boot-discovery/demo-spring-boot-discovery-server/src/main/resources/microservice.yaml b/demo/demo-spring-boot-discovery/demo-spring-boot-discovery-server/src/main/resources/microservice.yaml index c2a12259b80..b25536309fc 100644 --- a/demo/demo-spring-boot-discovery/demo-spring-boot-discovery-server/src/main/resources/microservice.yaml +++ b/demo/demo-spring-boot-discovery/demo-spring-boot-discovery-server/src/main/resources/microservice.yaml @@ -12,3 +12,4 @@ cse: chain: Provider: default: bizkeeper-provider +servicecomb.rest.servlet.urlPattern: /* \ No newline at end of file From 799f3639aaa964bcf9b339c3145f3537ee38af2d Mon Sep 17 00:00:00 2001 From: wujimin Date: Thu, 17 Aug 2017 15:53:44 +0800 Subject: [PATCH 04/18] JAV-150 save urlPrefix in system property after inject servlet --- .../main/java/io/servicecomb/core/Const.java | 2 + .../transport/RestServletInitializer.java | 2 + .../servlet/CseXmlWebApplicationContext.java | 1 + .../rest/servlet/RestServletInjector.java | 32 +--- .../transport/rest/servlet/ServletUtils.java | 87 ++++++++- .../rest/servlet/TestRestServletInjector.java | 54 +----- .../rest/servlet/TestServletUtils.java | 181 ++++++++++++++++++ 7 files changed, 283 insertions(+), 76 deletions(-) create mode 100644 transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletUtils.java diff --git a/core/src/main/java/io/servicecomb/core/Const.java b/core/src/main/java/io/servicecomb/core/Const.java index b68b91456ae..80d0c11ff31 100644 --- a/core/src/main/java/io/servicecomb/core/Const.java +++ b/core/src/main/java/io/servicecomb/core/Const.java @@ -35,4 +35,6 @@ private Const() { public static final String SRC_MICROSERVICE = "x-cse-src-microservice"; public static final String TARGET_MICROSERVICE = "x-cse-target-microservice"; + + public static final String URL_PREFIX = "urlPrefix"; } diff --git a/spring-boot-starter/spring-boot-starter-transport/src/main/java/io/servicecomb/springboot/starter/transport/RestServletInitializer.java b/spring-boot-starter/spring-boot-starter-transport/src/main/java/io/servicecomb/springboot/starter/transport/RestServletInitializer.java index f42a5d9288d..5144a26fac9 100644 --- a/spring-boot-starter/spring-boot-starter-transport/src/main/java/io/servicecomb/springboot/starter/transport/RestServletInitializer.java +++ b/spring-boot-starter/spring-boot-starter-transport/src/main/java/io/servicecomb/springboot/starter/transport/RestServletInitializer.java @@ -29,6 +29,7 @@ import org.springframework.stereotype.Component; import io.servicecomb.transport.rest.servlet.RestServletInjector; +import io.servicecomb.transport.rest.servlet.ServletUtils; @Component // extends from AbstractConfigurableEmbeddedServletContainer, only want to get embed web container's port and address @@ -50,6 +51,7 @@ public void onStartup(ServletContext servletContext) throws ServletException { // so mock to listen, and then close. try (ServerSocket ss = new ServerSocket(getPort(), 0, getAddress())) { RestServletInjector.defaultInject(servletContext); + ServletUtils.saveUrlPrefix(servletContext); } catch (IOException e) { throw new ServletException(e); } diff --git a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java index c3a21cfe8f1..24647e8d74f 100644 --- a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java +++ b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/CseXmlWebApplicationContext.java @@ -51,6 +51,7 @@ protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory b // inject servlet after config installed and before transport init RestServletInjector.defaultInject(getServletContext()); + ServletUtils.saveUrlPrefix(getServletContext()); } @Override diff --git a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java index d170aa55b66..9c39a95f6d4 100644 --- a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java +++ b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/RestServletInjector.java @@ -16,6 +16,8 @@ package io.servicecomb.transport.rest.servlet; +import java.util.Arrays; + import javax.servlet.ServletContext; import javax.servlet.ServletRegistration.Dynamic; @@ -23,8 +25,6 @@ import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; -import io.servicecomb.foundation.common.exceptions.ServiceCombException; - public class RestServletInjector { private static final Logger LOGGER = LoggerFactory.getLogger(RestServletInjector.class); @@ -38,7 +38,8 @@ public static Dynamic defaultInject(ServletContext servletContext) { } public Dynamic inject(ServletContext servletContext, String urlPattern) { - if (StringUtils.isEmpty(urlPattern)) { + String[] urlPatterns = splitUrlPattern(urlPattern); + if (urlPatterns.length == 0) { LOGGER.warn("urlPattern is empty, ignore register {}.", SERVLET_NAME); return null; } @@ -49,34 +50,21 @@ public Dynamic inject(ServletContext servletContext, String urlPattern) { return null; } - checkUrlPattern(urlPattern); - // dynamic deploy a servlet to handle serviceComb RESTful request Dynamic dynamic = servletContext.addServlet(SERVLET_NAME, RestServlet.class); dynamic.setAsyncSupported(true); - dynamic.addMapping(urlPattern); + dynamic.addMapping(urlPatterns); dynamic.setLoadOnStartup(0); - LOGGER.info("RESTful servlet url pattern: {}.", urlPattern); + LOGGER.info("RESTful servlet url pattern: {}.", Arrays.toString(urlPatterns)); return dynamic; } - // we only support path prefix rule, and only one path, it's what servicecomb RESTful request want. - // so only check if sidechar is the last char - // eg: *.xxx is a invalid urlPattern - // other invalid urlPattern will be check by web container, we do not handle that - void checkUrlPattern(String urlPattern) { - if (urlPattern.indexOf('\n') > 0) { - throw new ServiceCombException("not support multiple path rule."); - } - - if (!urlPattern.startsWith("/")) { - throw new ServiceCombException("only support rule like /* or /path/* or /path1/path2/* and so on."); + private String[] splitUrlPattern(String urlPattern) { + if (StringUtils.isEmpty(urlPattern)) { + return new String[] {}; } - int idx = urlPattern.indexOf("/*"); - if (idx < 0 || (idx >= 0 && idx != urlPattern.length() - 2)) { - throw new ServiceCombException("only support rule like /* or /path/* or /path1/path2/* and so on."); - } + return ServletUtils.filterUrlPatterns(urlPattern); } } diff --git a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletUtils.java b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletUtils.java index 50d3030dd07..090e4459602 100644 --- a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletUtils.java +++ b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletUtils.java @@ -16,15 +16,25 @@ package io.servicecomb.transport.rest.servlet; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; +import io.servicecomb.core.Const; +import io.servicecomb.foundation.common.exceptions.ServiceCombException; import io.servicecomb.foundation.common.net.IpPort; import io.servicecomb.foundation.common.net.NetUtils; public class ServletUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(ServletRestTransport.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ServletUtils.class); public static boolean canPublishEndpoint(String listenAddress) { if (StringUtils.isEmpty(listenAddress)) { @@ -45,4 +55,79 @@ public static boolean canPublishEndpoint(String listenAddress) { return true; } + + // we only support path prefix rule + // other invalid urlPattern will be check by web container, we do not handle that + static void checkUrlPattern(String urlPattern) { + if (!urlPattern.startsWith("/")) { + throw new ServiceCombException("only support rule like /* or /path/* or /path1/path2/* and so on."); + } + + int idx = urlPattern.indexOf("/*"); + if (idx < 0 || (idx >= 0 && idx != urlPattern.length() - 2)) { + throw new ServiceCombException("only support rule like /* or /path/* or /path1/path2/* and so on."); + } + } + + static String[] filterUrlPatterns(String... urlPatterns) { + return filterUrlPatterns(Arrays.asList(urlPatterns)); + } + + static String[] filterUrlPatterns(Collection urlPatterns) { + return urlPatterns.stream() + .filter(pattern -> { + return !pattern.trim().isEmpty(); + }) + .filter(pattern -> { + checkUrlPattern(pattern.trim()); + return true; + }) + .toArray(String[]::new); + } + + static String[] collectUrlPatterns(ServletContext servletContext, Class servletCls) { + List servlets = servletContext.getServletRegistrations() + .values() + .stream() + .filter(predicate -> { + return predicate.getClassName().equals(servletCls.getName()); + }) + .collect(Collectors.toList()); + if (servlets.isEmpty()) { + return new String[] {}; + } + + ServletRegistration servletRegistration = servlets.get(0); + Collection mappings = servletRegistration.getMappings(); + if (servlets.size() > 1) { + LOGGER.info("Found {} {} registered, select the first one, mappings={}.", + servlets.size(), + servletCls.getName(), + mappings); + } + return filterUrlPatterns(mappings); + } + + static String collectUrlPrefix(ServletContext servletContext, Class servletCls) { + String[] urlPatterns = collectUrlPatterns(servletContext, servletCls); + if (urlPatterns.length == 0) { + return null; + } + + // even have multiple urlPattern, we only choose a to set as urlPrefix + // only make sure sdk can invoke + String urlPattern = urlPatterns[0]; + return servletContext.getContextPath() + urlPattern.substring(0, urlPattern.length() - 2); + } + + public static void saveUrlPrefix(ServletContext servletContext) { + String urlPrefix = collectUrlPrefix(servletContext, RestServlet.class); + if (urlPrefix == null) { + LOGGER.info("RestServlet not found, will not save UrlPrefix."); + return; + } + + System.setProperty(Const.URL_PREFIX, urlPrefix); + LOGGER.info("UrlPrefix of this instance is \"{}\".", urlPrefix); + } } diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java index 94fe134e6e0..a82cd908034 100644 --- a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestRestServletInjector.java @@ -27,62 +27,10 @@ import org.junit.Assert; import org.junit.Test; -import io.servicecomb.foundation.common.exceptions.ServiceCombException; import mockit.Expectations; import mockit.Mocked; public class TestRestServletInjector { - private RestServletInjector injector = new RestServletInjector(); - - @Test - public void testCheckUrlPatternNormal() { - injector.checkUrlPattern("/*"); - injector.checkUrlPattern("/abc/*"); - injector.checkUrlPattern("/abc/def/*"); - - // normal, must not throw exception, no need to check - } - - @Test - public void testCheckUrlPatternMultiLine() { - try { - injector.checkUrlPattern("/abc/*\n\t\t/def/*"); - Assert.fail("must throw exception"); - } catch (ServiceCombException e) { - Assert.assertEquals("not support multiple path rule.", e.getMessage()); - } - } - - @Test - public void testCheckUrlPatternMiddleWideChar() { - try { - injector.checkUrlPattern("/abc/*def"); - Assert.fail("must throw exception"); - } catch (ServiceCombException e) { - Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); - } - } - - @Test - public void testCheckUrlPatternNoWideChar() { - try { - injector.checkUrlPattern("/abcdef"); - Assert.fail("must throw exception"); - } catch (ServiceCombException e) { - Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); - } - } - - @Test - public void testCheckUrlPatternNotStartWithSlash() { - try { - injector.checkUrlPattern("abcdef/*"); - Assert.fail("must throw exception"); - } catch (ServiceCombException e) { - Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); - } - } - @Test public void testDefaultInjectEmptyUrlPattern(@Mocked ServletContext servletContext, @Mocked Dynamic dynamic) { new Expectations(ServletConfig.class) { @@ -123,7 +71,7 @@ public void testDefaultInjectListen(@Mocked ServletContext servletContext, new Expectations(ServletConfig.class) { { ServletConfig.getServletUrlPattern(); - result = "/*"; + result = "/rest/*"; ServletConfig.getLocalServerAddress(); result = "127.0.0.1:" + port; } diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletUtils.java b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletUtils.java new file mode 100644 index 00000000000..0053e7c85fd --- /dev/null +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletUtils.java @@ -0,0 +1,181 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.transport.rest.servlet; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +import io.servicecomb.core.Const; +import io.servicecomb.foundation.common.exceptions.ServiceCombException; +import mockit.Expectations; +import mockit.Mocked; + +public class TestServletUtils { + @Test + public void testCheckUrlPatternNormal() { + ServletUtils.checkUrlPattern("/*"); + ServletUtils.checkUrlPattern("/abc/*"); + ServletUtils.checkUrlPattern("/abc/def/*"); + + // normal, must not throw exception, no need to check + } + + @Test + public void testCheckUrlPatternMiddleWideChar() { + try { + ServletUtils.checkUrlPattern("/abc/*def"); + Assert.fail("must throw exception"); + } catch (ServiceCombException e) { + Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); + } + } + + @Test + public void testCheckUrlPatternNoWideChar() { + try { + ServletUtils.checkUrlPattern("/abcdef"); + Assert.fail("must throw exception"); + } catch (ServiceCombException e) { + Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); + } + } + + @Test + public void testCheckUrlPatternNotStartWithSlash() { + try { + ServletUtils.checkUrlPattern("abcdef/*"); + Assert.fail("must throw exception"); + } catch (ServiceCombException e) { + Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); + } + } + + @Test + public void testFilterUrlPatternsNormal() { + String urlPattern = "/r1/*"; + + Collection urlPatterns = Arrays.asList(urlPattern); + String[] result = ServletUtils.filterUrlPatterns(urlPatterns); + Assert.assertThat(result, Matchers.arrayContaining("/r1/*")); + + result = ServletUtils.filterUrlPatterns(urlPattern); + Assert.assertThat(result, Matchers.arrayContaining("/r1/*")); + } + + @Test + public void testFilterUrlPatternsEmpty() { + Collection urlPatterns = Arrays.asList(" ", "\t"); + String[] result = ServletUtils.filterUrlPatterns(urlPatterns); + Assert.assertThat(result, Matchers.emptyArray()); + } + + @Test + public void testFilterUrlPatternsInvalid() { + Collection urlPatterns = Arrays.asList("/abc"); + try { + ServletUtils.filterUrlPatterns(urlPatterns); + Assert.fail("must throw exception"); + } catch (ServiceCombException e) { + Assert.assertEquals("only support rule like /* or /path/* or /path1/path2/* and so on.", e.getMessage()); + } + } + + @Test + public void testcollectUrlPatternsNoRestServlet(@Mocked ServletContext servletContext, + @Mocked ServletRegistration servletRegistration) { + new Expectations() { + { + servletRegistration.getClassName(); + result = "test"; + servletContext.getServletRegistrations(); + result = Collections.singletonMap("test", servletRegistration); + } + }; + + String[] result = ServletUtils.collectUrlPatterns(servletContext, RestServlet.class); + Assert.assertThat(result, Matchers.emptyArray()); + } + + @Test + public void testcollectUrlPatternsNormalMapping(@Mocked ServletContext servletContext, + @Mocked ServletRegistration r1, @Mocked ServletRegistration r2) { + Map servletRegistrationMap = new LinkedHashMap<>(); + servletRegistrationMap.put("r1", r1); + servletRegistrationMap.put("r2", r2); + + new Expectations() { + { + r1.getClassName(); + result = RestServlet.class.getName(); + r1.getMappings(); + result = Arrays.asList("/r1/*", "/r1/1/*"); + + r2.getClassName(); + result = RestServlet.class.getName(); + + servletContext.getServletRegistrations(); + result = servletRegistrationMap; + } + }; + + String[] result = ServletUtils.collectUrlPatterns(servletContext, RestServlet.class); + Assert.assertThat(result, Matchers.arrayContaining("/r1/*", "/r1/1/*")); + } + + @Test + public void testSaveUrlPrefixNull(@Mocked ServletContext servletContext) { + System.clearProperty(Const.URL_PREFIX); + + ServletUtils.saveUrlPrefix(servletContext); + + Assert.assertNull(System.getProperty(Const.URL_PREFIX)); + System.clearProperty(Const.URL_PREFIX); + } + + @Test + public void testSaveUrlPrefixNormal(@Mocked ServletContext servletContext, + @Mocked ServletRegistration servletRegistration) { + System.clearProperty(Const.URL_PREFIX); + new Expectations() { + { + servletContext.getContextPath(); + result = "/root"; + servletRegistration.getClassName(); + result = RestServlet.class.getName(); + servletRegistration.getMappings(); + result = Arrays.asList("/rest/*"); + servletContext.getServletRegistrations(); + result = Collections.singletonMap("test", servletRegistration); + } + }; + + ServletUtils.saveUrlPrefix(servletContext); + + Assert.assertThat(System.getProperty(Const.URL_PREFIX), Matchers.is("/root/rest")); + System.clearProperty(Const.URL_PREFIX); + } +} From df11fec7ae1e3c6c984ddc22bd0c8109606ef105 Mon Sep 17 00:00:00 2001 From: wujimin Date: Thu, 17 Aug 2017 16:20:51 +0800 Subject: [PATCH 05/18] JAV-150 URIEndpointObject, parse query by apache URLEncodedUtils, not custom implementation --- .../common/net/URIEndpointObject.java | 44 ++++++------------- .../common/net/TestURIEndpointObject.java | 40 ++++++++++++++--- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/foundations/foundation-common/src/main/java/io/servicecomb/foundation/common/net/URIEndpointObject.java b/foundations/foundation-common/src/main/java/io/servicecomb/foundation/common/net/URIEndpointObject.java index 87707b87b94..c035c1a5b7b 100644 --- a/foundations/foundation-common/src/main/java/io/servicecomb/foundation/common/net/URIEndpointObject.java +++ b/foundations/foundation-common/src/main/java/io/servicecomb/foundation/common/net/URIEndpointObject.java @@ -16,15 +16,16 @@ package io.servicecomb.foundation.common.net; -import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; + /** * tranport公共的Endpoint Object,当transport使用URI表示的时候,可以转化为这个对象。 */ @@ -44,37 +45,19 @@ public URIEndpointObject(String endpoint) { } setPort(uri.getPort()); querys = splitQuery(uri); - if (querys.get(SSL_ENABLED_KEY) != null && querys.get(SSL_ENABLED_KEY).size() > 0) { - sslEnabled = Boolean.parseBoolean(querys.get(SSL_ENABLED_KEY).get(0)); - } else { - sslEnabled = false; - } + sslEnabled = Boolean.parseBoolean(getFirst(SSL_ENABLED_KEY)); } public static Map> splitQuery(URI uri) { final Map> queryPairs = new LinkedHashMap>(); - try { - String query = uri.getQuery(); - if (query == null || query.isEmpty()) { - return queryPairs; - } - final String[] pairs = query.split("&"); - for (String pair : pairs) { - final int idx = pair.indexOf("="); - final String key = - idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name()) : pair; - if (!queryPairs.containsKey(key)) { - queryPairs.put(key, new LinkedList()); - } - final String value = - idx > 0 && pair.length() > idx + 1 - ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) : null; - queryPairs.get(key).add(value); - } - return queryPairs; - } catch (UnsupportedEncodingException e) { - return queryPairs; + List pairs = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8.name()); + for (NameValuePair pair : pairs) { + List list = queryPairs.computeIfAbsent(pair.getName(), name -> { + return new ArrayList<>(); + }); + list.add(pair.getValue()); } + return queryPairs; } public boolean isSslEnabled() { @@ -87,7 +70,8 @@ public List getQuery(String key) { public String getFirst(String key) { List values = querys.get(key); - if (values == null || values.isEmpty()) { + // it's impossible that values is not null and size is 0 + if (values == null) { return null; } diff --git a/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/net/TestURIEndpointObject.java b/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/net/TestURIEndpointObject.java index bb8d9be549f..8fa373bfa69 100644 --- a/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/net/TestURIEndpointObject.java +++ b/foundations/foundation-common/src/test/java/io/servicecomb/foundation/common/net/TestURIEndpointObject.java @@ -16,25 +16,53 @@ package io.servicecomb.foundation.common.net; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + import org.junit.Assert; import org.junit.Test; +import mockit.Deencapsulation; + public class TestURIEndpointObject { @Test public void testRestEndpointObject() { URIEndpointObject obj = new URIEndpointObject("http://127.0.2.0:8080"); - Assert.assertEquals(obj.getHostOrIp(), "127.0.2.0"); - Assert.assertEquals(obj.getPort(), 8080); - Assert.assertEquals(obj.isSslEnabled(), false); + Assert.assertEquals("127.0.2.0", obj.getHostOrIp()); + Assert.assertEquals(8080, obj.getPort()); + Assert.assertFalse(obj.isSslEnabled()); obj = new URIEndpointObject("http://127.0.2.0:8080?sslEnabled=true"); - Assert.assertEquals(obj.getHostOrIp(), "127.0.2.0"); - Assert.assertEquals(obj.getPort(), 8080); - Assert.assertEquals(obj.isSslEnabled(), true); + Assert.assertEquals("127.0.2.0", obj.getHostOrIp()); + Assert.assertEquals(8080, obj.getPort()); + Assert.assertTrue(obj.isSslEnabled()); + Assert.assertNull(obj.getFirst("notExist")); } @Test(expected = IllegalArgumentException.class) public void testRestEndpointObjectException() { new URIEndpointObject("http://127.0.2.0"); } + + @Test + public void testQueryChineseAndSpaceAndEmpty() throws UnsupportedEncodingException { + String strUri = + "cse://1.1.1.1:1234/abc?a=1&b=&country=" + URLEncoder.encode("中 国", StandardCharsets.UTF_8.name()); + URIEndpointObject ep = new URIEndpointObject(strUri); + + Map> querys = Deencapsulation.getField(ep, "querys"); + Assert.assertEquals(3, querys.size()); + + Assert.assertEquals(1, ep.getQuery("a").size()); + Assert.assertEquals("1", ep.getFirst("a")); + + Assert.assertEquals(1, ep.getQuery("b").size()); + Assert.assertEquals("", ep.getFirst("b")); + + Assert.assertEquals(1, ep.getQuery("country").size()); + Assert.assertEquals("中 国", ep.getFirst("country")); + } } From dfa8b1586fbdc318c7ad7c935252f308066c1269 Mon Sep 17 00:00:00 2001 From: wujimin Date: Thu, 17 Aug 2017 16:35:49 +0800 Subject: [PATCH 06/18] JAV-150 use apache URIBuilder to create uri, not custom implementation, avoid to handle query encode. --- .../serviceregistry/RegistryUtils.java | 76 +++++++++---------- .../serviceregistry/TestRegistry.java | 9 ++- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/service-registry/src/main/java/io/servicecomb/serviceregistry/RegistryUtils.java b/service-registry/src/main/java/io/servicecomb/serviceregistry/RegistryUtils.java index f1d06245927..3d3e5f46501 100644 --- a/service-registry/src/main/java/io/servicecomb/serviceregistry/RegistryUtils.java +++ b/service-registry/src/main/java/io/servicecomb/serviceregistry/RegistryUtils.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import org.apache.http.client.utils.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -147,11 +148,6 @@ public static String getPublishAddress(String schema, String address) { } try { - String publicAddressSetting = DynamicPropertyFactory.getInstance() - .getStringProperty(PUBLISH_ADDRESS, "") - .get(); - publicAddressSetting = publicAddressSetting.trim(); - URI originalURI = new URI(schema + "://" + address); IpPort ipPort = NetUtils.parseIpPort(originalURI.getAuthority()); if (ipPort == null) { @@ -159,45 +155,49 @@ public static String getPublishAddress(String schema, String address) { return null; } - InetSocketAddress socketAddress = ipPort.getSocketAddress(); - String host = socketAddress.getAddress().getHostAddress(); + IpPort publishIpPort = genPublishIpPort(schema, ipPort); + URIBuilder builder = new URIBuilder(originalURI); + return builder.setHost(publishIpPort.getHostOrIp()).setPort(publishIpPort.getPort()).build().toString(); + } catch (URISyntaxException e) { + LOGGER.warn("address {} not valid.", address); + return null; + } + } - if (publicAddressSetting.isEmpty()) { - if (socketAddress.getAddress().isAnyLocalAddress()) { - host = NetUtils.getHostAddress(); - LOGGER.warn("address {}, auto select a host address to publish {}:{}, maybe not the correct one", - address, - host, - socketAddress.getPort()); - URI newURI = new URI(originalURI.getScheme(), originalURI.getUserInfo(), host, - originalURI.getPort(), originalURI.getPath(), originalURI.getQuery(), - originalURI.getFragment()); - return newURI.toString(); - } else { - return originalURI.toString(); - } - } + private static IpPort genPublishIpPort(String schema, IpPort ipPort) { + String publicAddressSetting = DynamicPropertyFactory.getInstance() + .getStringProperty(PUBLISH_ADDRESS, "") + .get(); + publicAddressSetting = publicAddressSetting.trim(); - if (publicAddressSetting.startsWith("{") && publicAddressSetting.endsWith("}")) { - publicAddressSetting = NetUtils - .ensureGetInterfaceAddress( - publicAddressSetting.substring(1, publicAddressSetting.length() - 1)) - .getHostAddress(); + if (publicAddressSetting.isEmpty()) { + InetSocketAddress socketAddress = ipPort.getSocketAddress(); + String host = socketAddress.getAddress().getHostAddress(); + if (socketAddress.getAddress().isAnyLocalAddress()) { + host = NetUtils.getHostAddress(); + LOGGER.warn("address {}, auto select a host address to publish {}:{}, maybe not the correct one", + socketAddress, + host, + socketAddress.getPort()); + return new IpPort(host, ipPort.getPort()); } - String publishPortKey = PUBLISH_PORT.replace("{transport_name}", originalURI.getScheme()); - int publishPortSetting = DynamicPropertyFactory.getInstance() - .getIntProperty(publishPortKey, 0) - .get(); - int publishPort = publishPortSetting == 0 ? originalURI.getPort() : publishPortSetting; - URI newURI = new URI(originalURI.getScheme(), originalURI.getUserInfo(), publicAddressSetting, - publishPort, originalURI.getPath(), originalURI.getQuery(), originalURI.getFragment()); - return newURI.toString(); + return ipPort; + } - } catch (URISyntaxException e) { - LOGGER.warn("address {} not valid.", address); - return null; + if (publicAddressSetting.startsWith("{") && publicAddressSetting.endsWith("}")) { + publicAddressSetting = NetUtils + .ensureGetInterfaceAddress( + publicAddressSetting.substring(1, publicAddressSetting.length() - 1)) + .getHostAddress(); } + + String publishPortKey = PUBLISH_PORT.replace("{transport_name}", schema); + int publishPortSetting = DynamicPropertyFactory.getInstance() + .getIntProperty(publishPortKey, 0) + .get(); + int publishPort = publishPortSetting == 0 ? ipPort.getPort() : publishPortSetting; + return new IpPort(publicAddressSetting, publishPort); } public static List findServiceInstance(String appId, String serviceName, diff --git a/service-registry/src/test/java/io/servicecomb/serviceregistry/TestRegistry.java b/service-registry/src/test/java/io/servicecomb/serviceregistry/TestRegistry.java index f08434f100b..5127b20e2df 100644 --- a/service-registry/src/test/java/io/servicecomb/serviceregistry/TestRegistry.java +++ b/service-registry/src/test/java/io/servicecomb/serviceregistry/TestRegistry.java @@ -20,11 +20,15 @@ import java.net.InetAddress; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.configuration.AbstractConfiguration; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; @@ -213,6 +217,9 @@ public String get() { }; } }; - Assert.assertEquals("rest://1.1.1.1:8080", RegistryUtils.getPublishAddress("rest", "172.0.0.0:8080")); + String query = URLEncodedUtils.format(Arrays.asList(new BasicNameValuePair("country", "中 国")), + StandardCharsets.UTF_8.name()); + Assert.assertEquals("rest://1.1.1.1:8080?" + query, + RegistryUtils.getPublishAddress("rest", "172.0.0.0:8080?" + query)); } } From 0c697038595271cb6a64e02b49e0088b32a172d6 Mon Sep 17 00:00:00 2001 From: wujimin Date: Thu, 17 Aug 2017 16:58:08 +0800 Subject: [PATCH 07/18] JAV-150 save urlPrefix to publish endpoint --- .../rest/servlet/ServletRestTransport.java | 12 +++- .../servlet/TestServletRestTransport.java | 66 ++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletRestTransport.java b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletRestTransport.java index 1d24b7b1ff3..01ec73c0c5b 100644 --- a/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletRestTransport.java +++ b/transports/transport-rest/transport-rest-servlet/src/main/java/io/servicecomb/transport/rest/servlet/ServletRestTransport.java @@ -16,9 +16,13 @@ package io.servicecomb.transport.rest.servlet; +import java.util.HashMap; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import io.servicecomb.core.Const; import io.servicecomb.core.Invocation; @@ -55,8 +59,14 @@ public boolean canInit() { @Override public boolean init() { + String urlPrefix = System.getProperty(Const.URL_PREFIX); + Map queryMap = new HashMap<>(); + if (!StringUtils.isEmpty(urlPrefix)) { + queryMap.put(Const.URL_PREFIX, urlPrefix); + } + String listenAddress = ServletConfig.getLocalServerAddress(); - setListenAddressWithoutSchema(listenAddress); + setListenAddressWithoutSchema(listenAddress, queryMap); return deployClient(); } diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java index b068941fe0d..23034757f29 100644 --- a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java @@ -23,18 +23,82 @@ import org.junit.Test; import org.mockito.Mockito; +import io.servicecomb.core.Const; import io.servicecomb.core.Endpoint; import io.servicecomb.core.Invocation; import io.servicecomb.foundation.common.net.URIEndpointObject; import io.servicecomb.swagger.invocation.AsyncResponse; +import io.servicecomb.transport.rest.client.RestTransportClient; +import io.servicecomb.transport.rest.client.RestTransportClientManager; import mockit.Expectations; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; public class TestServletRestTransport { ServletRestTransport transport = new ServletRestTransport(); @Test - public void testInit() { + public void testInitNotPublish(@Mocked RestTransportClient restTransportClient) { + new MockUp() { + @Mock + public RestTransportClient getRestTransportClient(boolean sslEnabled) { + return restTransportClient; + } + }; + + new Expectations(ServletConfig.class) { + { + ServletConfig.getLocalServerAddress(); + result = null; + } + }; + Assert.assertTrue(transport.init()); + Assert.assertNull(transport.getPublishEndpoint()); + } + + @Test + public void testInitPublishNoUrlPrefix(@Mocked RestTransportClient restTransportClient) { + new MockUp() { + @Mock + public RestTransportClient getRestTransportClient(boolean sslEnabled) { + return restTransportClient; + } + }; + + new Expectations(ServletConfig.class) { + { + ServletConfig.getLocalServerAddress(); + result = "1.1.1.1:1234"; + } + }; + System.clearProperty(Const.URL_PREFIX); + + Assert.assertTrue(transport.init()); + Assert.assertEquals("rest://1.1.1.1:1234", transport.getPublishEndpoint().getEndpoint()); + } + + @Test + public void testInitPublishWithUrlPrefix(@Mocked RestTransportClient restTransportClient) { + new MockUp() { + @Mock + public RestTransportClient getRestTransportClient(boolean sslEnabled) { + return restTransportClient; + } + }; + + new Expectations(ServletConfig.class) { + { + ServletConfig.getLocalServerAddress(); + result = "1.1.1.1:1234"; + } + }; + System.setProperty(Const.URL_PREFIX, "/root"); + Assert.assertTrue(transport.init()); + Assert.assertEquals("rest://1.1.1.1:1234?urlPrefix=/root", transport.getPublishEndpoint().getEndpoint()); + + System.clearProperty(Const.URL_PREFIX); } @Test From d5963a98f748b2e4a86389c7890bfd9f18e2f7f7 Mon Sep 17 00:00:00 2001 From: wujimin Date: Fri, 18 Aug 2017 09:13:57 +0800 Subject: [PATCH 08/18] JAV-150 allowed control logic by service center's features --- .../servicecomb/serviceregistry/Features.java | 29 +++++++++++++++++++ .../serviceregistry/ServiceRegistry.java | 2 ++ .../registry/AbstractServiceRegistry.java | 8 +++++ 3 files changed, 39 insertions(+) create mode 100644 service-registry/src/main/java/io/servicecomb/serviceregistry/Features.java diff --git a/service-registry/src/main/java/io/servicecomb/serviceregistry/Features.java b/service-registry/src/main/java/io/servicecomb/serviceregistry/Features.java new file mode 100644 index 00000000000..92e4de26f9a --- /dev/null +++ b/service-registry/src/main/java/io/servicecomb/serviceregistry/Features.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.serviceregistry; + +public class Features { + private boolean canEncodeEndpoint; + + public boolean isCanEncodeEndpoint() { + return canEncodeEndpoint; + } + + public void setCanEncodeEndpoint(boolean canEncodeEndpoint) { + this.canEncodeEndpoint = canEncodeEndpoint; + } +} diff --git a/service-registry/src/main/java/io/servicecomb/serviceregistry/ServiceRegistry.java b/service-registry/src/main/java/io/servicecomb/serviceregistry/ServiceRegistry.java index 04697fb8ca9..d2a16839f86 100644 --- a/service-registry/src/main/java/io/servicecomb/serviceregistry/ServiceRegistry.java +++ b/service-registry/src/main/java/io/servicecomb/serviceregistry/ServiceRegistry.java @@ -52,4 +52,6 @@ List findServiceInstance(String appId, String microservice boolean updateInstanceProperties(Map instanceProperties); Microservice getRemoteMicroservice(String microserviceId); + + Features getFeatures(); } diff --git a/service-registry/src/main/java/io/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java b/service-registry/src/main/java/io/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java index 4a8a3da82d4..fac8e02764a 100644 --- a/service-registry/src/main/java/io/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java +++ b/service-registry/src/main/java/io/servicecomb/serviceregistry/registry/AbstractServiceRegistry.java @@ -27,6 +27,7 @@ import com.google.common.eventbus.Subscribe; import io.servicecomb.serviceregistry.ServiceRegistry; +import io.servicecomb.serviceregistry.Features; import io.servicecomb.serviceregistry.api.Const; import io.servicecomb.serviceregistry.api.registry.BasePath; import io.servicecomb.serviceregistry.api.registry.Microservice; @@ -47,6 +48,8 @@ public abstract class AbstractServiceRegistry implements ServiceRegistry { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractServiceRegistry.class); + private Features features = new Features(); + private MicroserviceFactory microserviceFactory = new MicroserviceFactory(); protected EventBus eventBus; @@ -96,6 +99,11 @@ public void init() { eventBus.register(this); } + @Override + public Features getFeatures() { + return features; + } + public EventBus getEventBus() { return eventBus; } From d7decd00ad7156f7f84f3d3bbb011fbed4910908 Mon Sep 17 00:00:00 2001 From: wujimin Date: Fri, 18 Aug 2017 09:19:32 +0800 Subject: [PATCH 09/18] JAV-150 encode publishEndpoint by service center's feature --- .../core/transport/AbstractTransport.java | 89 +++++++++++++++---- .../core/transport/TestAbstractTransport.java | 87 ++++++++++++++++-- .../servlet/TestServletRestTransport.java | 16 +++- 3 files changed, 164 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/io/servicecomb/core/transport/AbstractTransport.java b/core/src/main/java/io/servicecomb/core/transport/AbstractTransport.java index 37e2b763b14..e89eff8677a 100644 --- a/core/src/main/java/io/servicecomb/core/transport/AbstractTransport.java +++ b/core/src/main/java/io/servicecomb/core/transport/AbstractTransport.java @@ -16,20 +16,34 @@ package io.servicecomb.core.transport; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.Map; -import java.util.Map.Entry; +import java.util.stream.Collectors; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.config.DynamicPropertyFactory; + +import io.servicecomb.core.Const; import io.servicecomb.core.Endpoint; import io.servicecomb.core.Transport; -import io.servicecomb.serviceregistry.RegistryUtils; +import io.servicecomb.foundation.common.exceptions.ServiceCombException; import io.servicecomb.foundation.common.net.NetUtils; import io.servicecomb.foundation.common.net.URIEndpointObject; import io.servicecomb.foundation.vertx.VertxUtils; -import com.netflix.config.DynamicPropertyFactory; - +import io.servicecomb.serviceregistry.RegistryUtils; import io.vertx.core.Vertx; public abstract class AbstractTransport implements Transport { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTransport.class); + /** * 用于参数传递:比如向RestServerVerticle传递endpoint地址。 */ @@ -82,21 +96,8 @@ protected void setListenAddressWithoutSchema(String addressWithoutSchema) { */ protected void setListenAddressWithoutSchema(String addressWithoutSchema, Map pairs) { - if (addressWithoutSchema != null && pairs != null && !pairs.isEmpty()) { - int idx = addressWithoutSchema.indexOf('?'); - if (idx == -1) { - addressWithoutSchema += "?"; - } else { - addressWithoutSchema += "&"; - } - - StringBuilder sb = new StringBuilder(); - for (Entry entry : pairs.entrySet()) { - sb.append(entry.getKey()).append('=').append(entry.getValue()).append('&'); - } - sb.setLength(sb.length() - 1); - addressWithoutSchema += sb.toString(); - } + addressWithoutSchema = genAddressWithoutSchema(addressWithoutSchema, pairs); + this.endpoint = new Endpoint(this, NetUtils.getRealListenAddress(getName(), addressWithoutSchema)); if (this.endpoint.getEndpoint() != null) { this.publishEndpoint = new Endpoint(this, RegistryUtils.getPublishAddress(getName(), @@ -107,6 +108,56 @@ protected void setListenAddressWithoutSchema(String addressWithoutSchema, } + private String genAddressWithoutSchema(String addressWithoutSchema, Map pairs) { + if (addressWithoutSchema == null || pairs == null || pairs.isEmpty()) { + return addressWithoutSchema; + } + + int idx = addressWithoutSchema.indexOf('?'); + if (idx == -1) { + addressWithoutSchema += "?"; + } else { + addressWithoutSchema += "&"; + } + + String encodedQuery = URLEncodedUtils.format(pairs.entrySet().stream().map(entry -> { + return new BasicNameValuePair(entry.getKey(), entry.getValue()); + }).collect(Collectors.toList()), StandardCharsets.UTF_8.name()); + + if (!RegistryUtils.getServiceRegistry().getFeatures().isCanEncodeEndpoint()) { + addressWithoutSchema = genAddressWithoutSchemaForOldSC(addressWithoutSchema, encodedQuery); + } else { + addressWithoutSchema += encodedQuery; + } + + return addressWithoutSchema; + } + + private String genAddressWithoutSchemaForOldSC(String addressWithoutSchema, String encodedQuery) { + // old service center do not support encodedQuery + // sdk must query service center's version, and determine if encode query + // traced by JAV-307 + try { + LOGGER.warn("Service center do not support encoded query, so we use unencoded query, " + + "this caused not support chinese/space (and maybe other char) in query value."); + String decodedQuery = URLDecoder.decode(encodedQuery, StandardCharsets.UTF_8.name()); + addressWithoutSchema += decodedQuery; + } catch (UnsupportedEncodingException e) { + // never happended + throw new ServiceCombException("Failed to decode query.", e); + } + + try { + // make sure consumer can handle this endpoint + new URI(Const.RESTFUL + "://" + addressWithoutSchema); + } catch (URISyntaxException e) { + throw new ServiceCombException( + "current service center not support encoded endpoint, please do not use chinese or space or anything need to be encoded.", + e); + } + return addressWithoutSchema; + } + @Override public Object parseAddress(String address) { if (address == null) { diff --git a/core/src/test/java/io/servicecomb/core/transport/TestAbstractTransport.java b/core/src/test/java/io/servicecomb/core/transport/TestAbstractTransport.java index b1fc3070772..91801fcc410 100644 --- a/core/src/test/java/io/servicecomb/core/transport/TestAbstractTransport.java +++ b/core/src/test/java/io/servicecomb/core/transport/TestAbstractTransport.java @@ -16,12 +16,24 @@ package io.servicecomb.core.transport; -import io.servicecomb.core.Invocation; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import io.servicecomb.core.Invocation; +import io.servicecomb.foundation.common.exceptions.ServiceCombException; import io.servicecomb.foundation.common.net.IpPort; +import io.servicecomb.serviceregistry.RegistryUtils; +import io.servicecomb.serviceregistry.registry.AbstractServiceRegistry; import io.servicecomb.swagger.invocation.AsyncResponse; +import mockit.Expectations; +import mockit.Injectable; import mockit.Mocked; public class TestAbstractTransport { @@ -41,19 +53,78 @@ public void send(Invocation invocation, AsyncResponse asyncResp) throws Exceptio } } + @Injectable + AbstractServiceRegistry serviceRegistry; + + @Before + public void setup() { + RegistryUtils.setServiceRegistry(serviceRegistry); + } + + @After + public void teardown() { + RegistryUtils.setServiceRegistry(null); + } + + @Test + public void testSetListenAddressWithoutSchemaChineseSpaceNewSC() throws UnsupportedEncodingException { + new Expectations() { + { + serviceRegistry.getFeatures().isCanEncodeEndpoint(); + result = true; + } + }; + + MyAbstractTransport transport = new MyAbstractTransport(); + transport.setListenAddressWithoutSchema("127.0.0.1:9090", Collections.singletonMap("country", "中 国")); + Assert.assertEquals("my://127.0.0.1:9090?country=" + URLEncoder.encode("中 国", StandardCharsets.UTF_8.name()), + transport.getEndpoint().getEndpoint()); + } + + @Test + public void testSetListenAddressWithoutSchemaChineseSpaceOldSC() throws UnsupportedEncodingException { + MyAbstractTransport transport = new MyAbstractTransport(); + try { + transport.setListenAddressWithoutSchema("127.0.0.1:9090", Collections.singletonMap("country", "中 国")); + Assert.fail("must throw exception"); + } catch (ServiceCombException e) { + Assert.assertEquals( + "current service center not support encoded endpoint, please do not use chinese or space or anything need to be encoded.", + e.getMessage()); + Assert.assertEquals( + "Illegal character in query at index 31: rest://127.0.0.1:9090?country=中 国", + e.getCause().getMessage()); + } + } + + @Test + public void testSetListenAddressWithoutSchemaNormalNotEncode() throws UnsupportedEncodingException { + MyAbstractTransport transport = new MyAbstractTransport(); + transport.setListenAddressWithoutSchema("127.0.0.1:9090", Collections.singletonMap("country", "chinese")); + Assert.assertEquals("my://127.0.0.1:9090?country=chinese", transport.getEndpoint().getEndpoint()); + } + + @Test + public void testSetListenAddressWithoutSchemaAlreadyHaveQuery() throws UnsupportedEncodingException { + MyAbstractTransport transport = new MyAbstractTransport(); + transport.setListenAddressWithoutSchema("127.0.0.1:9090?a=aValue", + Collections.singletonMap("country", "chinese")); + Assert.assertEquals("my://127.0.0.1:9090?a=aValue&country=chinese", transport.getEndpoint().getEndpoint()); + } + @Test public void testMyAbstractTransport() throws Exception { MyAbstractTransport transport = new MyAbstractTransport(); transport.setListenAddressWithoutSchema("127.0.0.1:9090"); - Assert.assertEquals(transport.getName(), "my"); - Assert.assertEquals(transport.getEndpoint().getEndpoint(), "my://127.0.0.1:9090"); - Assert.assertEquals(((IpPort) transport.parseAddress("my://127.0.0.1:9090")).getHostOrIp(), "127.0.0.1"); + Assert.assertEquals("my", transport.getName()); + Assert.assertEquals("my://127.0.0.1:9090", transport.getEndpoint().getEndpoint()); + Assert.assertEquals("127.0.0.1", ((IpPort) transport.parseAddress("my://127.0.0.1:9090")).getHostOrIp()); transport.setListenAddressWithoutSchema("0.0.0.0:9090"); - Assert.assertNotEquals(transport.getEndpoint().getEndpoint(), "my://127.0.0.1:9090"); + Assert.assertNotEquals("my://127.0.0.1:9090", transport.getEndpoint().getEndpoint()); transport.setListenAddressWithoutSchema(null); - Assert.assertEquals(transport.getEndpoint().getEndpoint(), null); - Assert.assertEquals(transport.parseAddress(null), null); - Assert.assertEquals(AbstractTransport.getRequestTimeout(), 30000); + Assert.assertNull(transport.getEndpoint().getEndpoint()); + Assert.assertNull(transport.parseAddress(null)); + Assert.assertEquals(30000, AbstractTransport.getRequestTimeout()); } @Test(expected = NumberFormatException.class) diff --git a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java index 23034757f29..2d1936f77dd 100644 --- a/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java +++ b/transports/transport-rest/transport-rest-servlet/src/test/java/io/servicecomb/transport/rest/servlet/TestServletRestTransport.java @@ -27,6 +27,9 @@ import io.servicecomb.core.Endpoint; import io.servicecomb.core.Invocation; import io.servicecomb.foundation.common.net.URIEndpointObject; +import io.servicecomb.serviceregistry.Features; +import io.servicecomb.serviceregistry.RegistryUtils; +import io.servicecomb.serviceregistry.ServiceRegistry; import io.servicecomb.swagger.invocation.AsyncResponse; import io.servicecomb.transport.rest.client.RestTransportClient; import io.servicecomb.transport.rest.client.RestTransportClientManager; @@ -79,7 +82,18 @@ public RestTransportClient getRestTransportClient(boolean sslEnabled) { } @Test - public void testInitPublishWithUrlPrefix(@Mocked RestTransportClient restTransportClient) { + public void testInitPublishWithUrlPrefix(@Mocked RestTransportClient restTransportClient, + @Mocked ServiceRegistry serviceRegistry) { + Features features = new Features(); + new Expectations(RegistryUtils.class) { + { + RegistryUtils.getServiceRegistry(); + result = serviceRegistry; + serviceRegistry.getFeatures(); + result = features; + } + }; + new MockUp() { @Mock public RestTransportClient getRestTransportClient(boolean sslEnabled) { From abc577cf8b2c0c70150cc40b7a814dc9386a0f7f Mon Sep 17 00:00:00 2001 From: wujimin Date: Fri, 18 Aug 2017 09:49:20 +0800 Subject: [PATCH 10/18] JAV-150 rest client create request path by urlPrefix --- .../rest/client/http/VertxHttpMethod.java | 38 +++--- .../rest/client/http/TestVertxHttpMethod.java | 119 ++++++++++++++++-- 2 files changed, 130 insertions(+), 27 deletions(-) diff --git a/transports/transport-rest/transport-rest-client/src/main/java/io/servicecomb/transport/rest/client/http/VertxHttpMethod.java b/transports/transport-rest/transport-rest-client/src/main/java/io/servicecomb/transport/rest/client/http/VertxHttpMethod.java index c5821c14b75..104af6fc68f 100644 --- a/transports/transport-rest/transport-rest-client/src/main/java/io/servicecomb/transport/rest/client/http/VertxHttpMethod.java +++ b/transports/transport-rest/transport-rest-client/src/main/java/io/servicecomb/transport/rest/client/http/VertxHttpMethod.java @@ -16,6 +16,12 @@ package io.servicecomb.transport.rest.client.http; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + import io.servicecomb.common.rest.RestConst; import io.servicecomb.common.rest.codec.RestCodec; import io.servicecomb.common.rest.codec.param.RestClientRequestImpl; @@ -26,6 +32,7 @@ import io.servicecomb.core.definition.OperationMeta; import io.servicecomb.core.transport.AbstractTransport; import io.servicecomb.foundation.common.net.IpPort; +import io.servicecomb.foundation.common.net.URIEndpointObject; import io.servicecomb.foundation.common.utils.JsonUtils; import io.servicecomb.foundation.vertx.client.http.HttpClientWithContext; import io.servicecomb.swagger.invocation.AsyncResponse; @@ -36,9 +43,6 @@ import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public abstract class VertxHttpMethod { private static final Logger LOGGER = LoggerFactory.getLogger(VertxHttpMethod.class); @@ -68,12 +72,12 @@ public void doMethod(HttpClientWithContext httpClientWithContext, Invocation inv }); LOGGER.debug( - "Running HTTP method {} on {} at {}:{}{}", - invocation.getOperationMeta().getMethod(), - invocation.getMicroserviceName(), - ipPort.getHostOrIp(), - ipPort.getPort(), - path); + "Running HTTP method {} on {} at {}:{}{}", + invocation.getOperationMeta().getMethod(), + invocation.getMicroserviceName(), + ipPort.getHostOrIp(), + ipPort.getPort(), + path); // 从业务线程转移到网络线程中去发送 httpClientWithContext.runOnContext(httpClient -> { @@ -153,12 +157,18 @@ protected void setCseContext(Invocation invocation, HttpClientRequest request) { protected String createRequestPath(Invocation invocation, RestOperationMeta swaggerRestOperation) throws Exception { - Object path = invocation.getHandlerContext().get(RestConst.REST_CLIENT_REQUEST_PATH); - if (path != null) { - return (String) path; + URIEndpointObject address = (URIEndpointObject) invocation.getEndpoint().getAddress(); + String urlPrefix = address.getFirst(Const.URL_PREFIX); + + String path = (String) invocation.getHandlerContext().get(RestConst.REST_CLIENT_REQUEST_PATH); + if (path == null) { + path = swaggerRestOperation.getPathBuilder().createRequestPath(invocation.getArgs()); } - return swaggerRestOperation.getPathBuilder().createRequestPath(invocation.getArgs()); - } + if (StringUtils.isEmpty(urlPrefix) || path.startsWith(urlPrefix)) { + return path; + } + return urlPrefix + path; + } } diff --git a/transports/transport-rest/transport-rest-client/src/test/java/io/servicecomb/transport/rest/client/http/TestVertxHttpMethod.java b/transports/transport-rest/transport-rest-client/src/test/java/io/servicecomb/transport/rest/client/http/TestVertxHttpMethod.java index 6dc150e418b..4218ec92604 100644 --- a/transports/transport-rest/transport-rest-client/src/test/java/io/servicecomb/transport/rest/client/http/TestVertxHttpMethod.java +++ b/transports/transport-rest/transport-rest-client/src/test/java/io/servicecomb/transport/rest/client/http/TestVertxHttpMethod.java @@ -18,14 +18,24 @@ import static org.mockito.Mockito.when; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + import io.servicecomb.common.rest.RestConst; import io.servicecomb.common.rest.codec.produce.ProduceProcessor; import io.servicecomb.common.rest.definition.RestOperationMeta; import io.servicecomb.common.rest.definition.path.URLPathBuilder; +import io.servicecomb.core.Const; import io.servicecomb.core.Endpoint; import io.servicecomb.core.Invocation; import io.servicecomb.core.definition.OperationMeta; import io.servicecomb.foundation.common.net.IpPort; +import io.servicecomb.foundation.common.net.URIEndpointObject; import io.servicecomb.foundation.vertx.client.http.HttpClientWithContext; import io.servicecomb.swagger.invocation.AsyncResponse; import io.vertx.core.Context; @@ -33,13 +43,11 @@ import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpClientResponse; +import mockit.Expectations; +import mockit.Injectable; import mockit.Mock; import mockit.MockUp; import mockit.Mocked; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; public class TestVertxHttpMethod extends VertxHttpMethod { @@ -51,7 +59,7 @@ public void setup() { } @Test - public void testDoMethod(@Mocked HttpClient httpClient) throws Exception { + public void testDoMethod(@Mocked HttpClient httpClient, @Injectable URIEndpointObject address) throws Exception { Context context = new MockUp() { @Mock public void runOnContext(Handler action) { @@ -72,7 +80,7 @@ public void runOnContext(Handler action) { operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION); when(operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION)).thenReturn(swaggerRestOperation); when(invocation.getEndpoint()).thenReturn(endpoint); - when(endpoint.getAddress()).thenReturn(new IpPort()); + when(endpoint.getAddress()).thenReturn(address); when(request.exceptionHandler(Mockito.any())).then(answer -> null); @@ -142,12 +150,97 @@ protected HttpClientRequest createRequest(HttpClient client, Invocation invocati } @Test - public void testCreateRequestPath() throws Exception { - Invocation invocation = Mockito.mock(Invocation.class); - RestOperationMeta restOperationMeta = Mockito.mock(RestOperationMeta.class); - URLPathBuilder urlPathBuilder = Mockito.mock(URLPathBuilder.class); - when(restOperationMeta.getPathBuilder()).thenReturn(urlPathBuilder); - String pathUrl = this.createRequestPath(invocation, restOperationMeta); - Assert.assertNull(pathUrl); + public void testCreateRequestPathNoUrlPrefixNoPath(@Injectable Invocation invocation, + @Injectable RestOperationMeta swaggerRestOperation, @Injectable Endpoint endpoint, + @Injectable URIEndpointObject address, @Injectable URLPathBuilder builder) throws Exception { + new Expectations() { + { + endpoint.getAddress(); + result = address; + builder.createRequestPath((Object[]) any); + result = "/path"; + } + }; + String path = this.createRequestPath(invocation, swaggerRestOperation); + Assert.assertEquals("/path", path); + } + + @Test + public void testCreateRequestPathNoUrlPrefixHavePath(@Injectable Invocation invocation, + @Injectable RestOperationMeta swaggerRestOperation, @Injectable Endpoint endpoint, + @Injectable URIEndpointObject address, @Injectable URLPathBuilder builder) throws Exception { + Map contextMap = new HashMap<>(); + contextMap.put(RestConst.REST_CLIENT_REQUEST_PATH, "/client/path"); + + new Expectations() { + { + endpoint.getAddress(); + result = address; + invocation.getHandlerContext(); + result = contextMap; + } + }; + String path = this.createRequestPath(invocation, swaggerRestOperation); + Assert.assertEquals("/client/path", path); + } + + @Test + public void testCreateRequestPathHaveUrlPrefixNoPath(@Injectable Invocation invocation, + @Injectable RestOperationMeta swaggerRestOperation, @Injectable Endpoint endpoint, + @Injectable URIEndpointObject address, @Injectable URLPathBuilder builder) throws Exception { + new Expectations() { + { + endpoint.getAddress(); + result = address; + address.getFirst(Const.URL_PREFIX); + result = "/root"; + builder.createRequestPath((Object[]) any); + result = "/path"; + } + }; + String path = this.createRequestPath(invocation, swaggerRestOperation); + Assert.assertEquals("/root/path", path); + } + + @Test + public void testCreateRequestPathHaveUrlPrefixHavePath(@Injectable Invocation invocation, + @Injectable RestOperationMeta swaggerRestOperation, @Injectable Endpoint endpoint, + @Injectable URIEndpointObject address, @Injectable URLPathBuilder builder) throws Exception { + Map contextMap = new HashMap<>(); + contextMap.put(RestConst.REST_CLIENT_REQUEST_PATH, "/client/path"); + + new Expectations() { + { + endpoint.getAddress(); + result = address; + address.getFirst(Const.URL_PREFIX); + result = "/root"; + invocation.getHandlerContext(); + result = contextMap; + } + }; + String path = this.createRequestPath(invocation, swaggerRestOperation); + Assert.assertEquals("/root/client/path", path); + } + + @Test + public void testCreateRequestPathHaveUrlPrefixHavePathAndStartWith(@Injectable Invocation invocation, + @Injectable RestOperationMeta swaggerRestOperation, @Injectable Endpoint endpoint, + @Injectable URIEndpointObject address, @Injectable URLPathBuilder builder) throws Exception { + Map contextMap = new HashMap<>(); + contextMap.put(RestConst.REST_CLIENT_REQUEST_PATH, "/client/path"); + + new Expectations() { + { + endpoint.getAddress(); + result = address; + address.getFirst(Const.URL_PREFIX); + result = "/client"; + invocation.getHandlerContext(); + result = contextMap; + } + }; + String path = this.createRequestPath(invocation, swaggerRestOperation); + Assert.assertEquals("/client/path", path); } } From 72b1480f0c420c091cdc69ff988e6a3719af32dd Mon Sep 17 00:00:00 2001 From: wujimin Date: Mon, 21 Aug 2017 09:41:28 +0800 Subject: [PATCH 11/18] optimize log, when failed to get microservice from service center --- .../core/definition/schema/ConsumerSchemaFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/io/servicecomb/core/definition/schema/ConsumerSchemaFactory.java b/core/src/main/java/io/servicecomb/core/definition/schema/ConsumerSchemaFactory.java index 4f2262cf623..fe799acd551 100644 --- a/core/src/main/java/io/servicecomb/core/definition/schema/ConsumerSchemaFactory.java +++ b/core/src/main/java/io/servicecomb/core/definition/schema/ConsumerSchemaFactory.java @@ -72,7 +72,8 @@ public MicroserviceMeta getOrCreateMicroserviceMeta(String microserviceName, Str findMicroservice(microserviceMeta, microserviceVersionRule); if (microservice == null) { throw new Error( - String.format("can not get microservice from service center, name=%s", + String.format("can not get microservice from service center, appId=%s, name=%s", + microserviceMeta.getAppId(), microserviceName)); } From 6325220f8d7a0d20cdb75bf53beb5ef8f2231e51 Mon Sep 17 00:00:00 2001 From: wujimin Date: Mon, 21 Aug 2017 10:15:17 +0800 Subject: [PATCH 12/18] bug fix:servlet RESTful, support encode/decode null result --- .../codec/produce/ProduceJsonProcessor.java | 5 +- .../rest/codec/produce/ProduceProcessor.java | 26 ++++-- .../produce/ProduceTextPlainProcessor.java | 4 +- .../common/rest/codec/TestProduce.java | 69 ---------------- .../produce/TestProduceJsonProcessor.java | 82 +++++++++++++++++++ .../produce/TestProduceProcessorManager.java | 27 ++++++ .../TestProduceTextPlainProcessor.java | 82 +++++++++++++++++++ 7 files changed, 217 insertions(+), 78 deletions(-) delete mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/codec/TestProduce.java create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceJsonProcessor.java create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceProcessorManager.java create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceTextPlainProcessor.java diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceJsonProcessor.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceJsonProcessor.java index 01052e81c02..8a2310aa0b1 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceJsonProcessor.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceJsonProcessor.java @@ -22,6 +22,7 @@ import javax.ws.rs.core.MediaType; import com.fasterxml.jackson.databind.JavaType; + import io.servicecomb.common.rest.codec.RestObjectMapper; public class ProduceJsonProcessor extends AbstractProduceProcessor { @@ -32,12 +33,12 @@ public String getName() { } @Override - public void encodeResponse(OutputStream output, Object result) throws Exception { + public void doEncodeResponse(OutputStream output, Object result) throws Exception { RestObjectMapper.INSTANCE.writeValue(output, result); } @Override - public Object decodeResponse(InputStream input, JavaType type) throws Exception { + public Object doDecodeResponse(InputStream input, JavaType type) throws Exception { return RestObjectMapper.INSTANCE.readValue(input, type); } } diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceProcessor.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceProcessor.java index 426b870ae63..8494dd72ded 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceProcessor.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceProcessor.java @@ -20,15 +20,23 @@ import java.io.OutputStream; import com.fasterxml.jackson.databind.JavaType; + import io.servicecomb.foundation.vertx.stream.BufferInputStream; import io.servicecomb.foundation.vertx.stream.BufferOutputStream; - import io.vertx.core.buffer.Buffer; public interface ProduceProcessor { String getName(); - void encodeResponse(OutputStream output, Object result) throws Exception; + default void encodeResponse(OutputStream output, Object result) throws Exception { + if (result == null) { + return; + } + + doEncodeResponse(output, result); + } + + void doEncodeResponse(OutputStream output, Object result) throws Exception; default Buffer encodeResponse(Object result) throws Exception { if (null == result) { @@ -36,12 +44,20 @@ default Buffer encodeResponse(Object result) throws Exception { } try (BufferOutputStream output = new BufferOutputStream()) { - encodeResponse(output, result); + doEncodeResponse(output, result); return output.getBuffer(); } } - Object decodeResponse(InputStream input, JavaType type) throws Exception; + default Object decodeResponse(InputStream input, JavaType type) throws Exception { + if (input.available() == 0) { + return null; + } + + return doDecodeResponse(input, type); + } + + Object doDecodeResponse(InputStream input, JavaType type) throws Exception; default Object decodeResponse(Buffer buffer, JavaType type) throws Exception { if (buffer.length() == 0) { @@ -49,7 +65,7 @@ default Object decodeResponse(Buffer buffer, JavaType type) throws Exception { } try (BufferInputStream input = new BufferInputStream(buffer.getByteBuf())) { - return decodeResponse(input, type); + return doDecodeResponse(input, type); } } } diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceTextPlainProcessor.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceTextPlainProcessor.java index 475983bb241..61fca6599b1 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceTextPlainProcessor.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/codec/produce/ProduceTextPlainProcessor.java @@ -33,12 +33,12 @@ public String getName() { } @Override - public void encodeResponse(OutputStream output, Object result) throws Exception { + public void doEncodeResponse(OutputStream output, Object result) throws Exception { output.write(String.valueOf(result).getBytes(StandardCharsets.UTF_8)); } @Override - public Object decodeResponse(InputStream input, JavaType type) throws Exception { + public Object doDecodeResponse(InputStream input, JavaType type) throws Exception { // plainText类型,肯定是返回string的,想不出有其他类型的场景 return IOUtils.toString(input, StandardCharsets.UTF_8); // TODO: 该方法尚需进一步修改 diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/TestProduce.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/TestProduce.java deleted file mode 100644 index dbb16381353..00000000000 --- a/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/TestProduce.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017 Huawei Technologies Co., Ltd - * - * Licensed 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 io.servicecomb.common.rest.codec; - -import org.junit.Assert; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.OutputStream; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.servicecomb.common.rest.codec.produce.ProduceProcessorManager; -import io.servicecomb.foundation.vertx.stream.BufferOutputStream; - -import io.vertx.core.buffer.Buffer; - -public class TestProduce { - - @Before - public void setUp() throws Exception { - } - - @After - public void tearDown() throws Exception { - } - - /** - * Test Produce - * - * @throws Exception - */ - @Test - public void testProduce() throws Exception { - Assert.assertEquals("produce processor mgr", ProduceProcessorManager.INSTANCE.getName()); - Buffer oBuffer = ProduceProcessorManager.DEFAULT_PROCESSOR.encodeResponse("test"); - OutputStream oOutputStream = new BufferOutputStream(); - ProduceProcessorManager.DEFAULT_PROCESSOR.encodeResponse(oOutputStream, "test2"); - JavaType targetType = TypeFactory.defaultInstance().constructType(String.class); - InputStream oInputStream = new ByteArrayInputStream(("true").getBytes()); - ProduceProcessorManager.DEFAULT_PROCESSOR.decodeResponse(oInputStream, targetType); - ProduceProcessorManager.PLAIN_PROCESSOR.encodeResponse(new BufferOutputStream(), "test2"); - Assert.assertNotEquals(null, ProduceProcessorManager.PLAIN_PROCESSOR.decodeResponse(oInputStream, targetType)); - oInputStream = new ByteArrayInputStream(("true").getBytes()); - Assert.assertNotEquals(null, - ProduceProcessorManager.DEFAULT_PROCESSOR.decodeResponse(oInputStream, targetType)); - ProduceProcessorManager.DEFAULT_PROCESSOR.decodeResponse(oBuffer, targetType); - Assert.assertEquals(null, ProduceProcessorManager.DEFAULT_PROCESSOR.encodeResponse(null)); - - } -} diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceJsonProcessor.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceJsonProcessor.java new file mode 100644 index 00000000000..65d96dfec5c --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceJsonProcessor.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.codec.produce; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; + +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.type.TypeFactory; + +import io.vertx.core.buffer.Buffer; + +public class TestProduceJsonProcessor { + ProduceProcessor pp = ProduceProcessorManager.JSON_PROCESSOR; + + JavaType stringType = TypeFactory.defaultInstance().constructType(String.class); + + @Test + public void testEncodeResponseNull() throws Exception { + Buffer buffer = pp.encodeResponse(null); + Assert.assertNull(buffer); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + pp.encodeResponse(os, null); + Assert.assertEquals(0, os.size()); + } + + @Test + public void testdecodeResponseNull() throws Exception { + JavaType resultType = TypeFactory.unknownType(); + Object result = pp.decodeResponse(Buffer.buffer(), resultType); + Assert.assertNull(result); + + ByteArrayInputStream is = new ByteArrayInputStream(new byte[] {}); + result = pp.decodeResponse(is, resultType); + Assert.assertNull(result); + } + + @Test + public void testBufferNormal() throws Exception { + String value = "abc"; + Buffer buffer = pp.encodeResponse(value); + Assert.assertEquals("\"abc\"", buffer.toString(StandardCharsets.UTF_8)); + + Object result = pp.decodeResponse(buffer, stringType); + Assert.assertEquals(value, result); + } + + @Test + public void testStreamNormal() throws Exception { + String value = "abc"; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + pp.encodeResponse(os, value); + Assert.assertEquals("\"abc\"", os.toString(StandardCharsets.UTF_8.name())); + + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + Object result = pp.decodeResponse(is, stringType); + Assert.assertEquals(value, result); + + os.close(); + is.close(); + } +} diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceProcessorManager.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceProcessorManager.java new file mode 100644 index 00000000000..8f2e47d6fe5 --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceProcessorManager.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.codec.produce; + +import org.junit.Assert; +import org.junit.Test; + +public class TestProduceProcessorManager { + @Test + public void testDefault() { + Assert.assertSame(ProduceProcessorManager.JSON_PROCESSOR, ProduceProcessorManager.DEFAULT_PROCESSOR); + } +} diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceTextPlainProcessor.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceTextPlainProcessor.java new file mode 100644 index 00000000000..ce21b335852 --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/codec/produce/TestProduceTextPlainProcessor.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.codec.produce; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; + +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.type.TypeFactory; + +import io.vertx.core.buffer.Buffer; + +public class TestProduceTextPlainProcessor { + ProduceProcessor pp = ProduceProcessorManager.PLAIN_PROCESSOR; + + JavaType stringType = TypeFactory.defaultInstance().constructType(String.class); + + @Test + public void testEncodeResponseNull() throws Exception { + Buffer buffer = pp.encodeResponse(null); + Assert.assertNull(buffer); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + pp.encodeResponse(os, null); + Assert.assertEquals(0, os.size()); + } + + @Test + public void testdecodeResponseNull() throws Exception { + JavaType resultType = TypeFactory.unknownType(); + Object result = pp.decodeResponse(Buffer.buffer(), resultType); + Assert.assertNull(result); + + ByteArrayInputStream is = new ByteArrayInputStream(new byte[] {}); + result = pp.decodeResponse(is, resultType); + Assert.assertNull(result); + } + + @Test + public void testBufferNormal() throws Exception { + String value = "abc"; + Buffer buffer = pp.encodeResponse(value); + Assert.assertEquals(value, buffer.toString(StandardCharsets.UTF_8)); + + Object result = pp.decodeResponse(buffer, stringType); + Assert.assertEquals(value, result); + } + + @Test + public void testStreamNormal() throws Exception { + String value = "abc"; + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + pp.encodeResponse(os, value); + Assert.assertEquals(value, os.toString(StandardCharsets.UTF_8.name())); + + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + Object result = pp.decodeResponse(is, stringType); + Assert.assertEquals(value, result); + + os.close(); + is.close(); + } +} From 1b9f5a2b4d779ab156e6ede53b639e393b807f27 Mon Sep 17 00:00:00 2001 From: wujimin Date: Mon, 21 Aug 2017 11:42:02 +0800 Subject: [PATCH 13/18] add test for RestOperationComparator --- .../TestRestOperationComparator.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/definition/TestRestOperationComparator.java diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/definition/TestRestOperationComparator.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/definition/TestRestOperationComparator.java new file mode 100644 index 00000000000..6684f95e3a7 --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/definition/TestRestOperationComparator.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.definition; + +import org.junit.Assert; +import org.junit.Test; + +import io.servicecomb.common.rest.locator.MicroservicePaths; + +public class TestRestOperationComparator { + @Test + public void testStaticCharCount() { + RestOperationMeta less = new RestOperationMeta(); + less.setAbsolutePath("/a/{id}"); + + RestOperationMeta more = new RestOperationMeta(); + more.setAbsolutePath("/abc/{id}"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(less); + paths.addResource(more); + paths.sortPath(); + + Assert.assertSame(more, paths.getDynamicPathOperationList().get(0)); + Assert.assertSame(less, paths.getDynamicPathOperationList().get(1)); + } + + @Test + public void testVarGroupCount() { + RestOperationMeta less = new RestOperationMeta(); + less.setAbsolutePath("/ab/{id}"); + + RestOperationMeta more = new RestOperationMeta(); + more.setAbsolutePath("/a/{test}/{id}"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(less); + paths.addResource(more); + paths.sortPath(); + + Assert.assertSame(more, paths.getDynamicPathOperationList().get(0)); + Assert.assertSame(less, paths.getDynamicPathOperationList().get(1)); + } + + @Test + public void testGroupWithRegExpCount() { + RestOperationMeta less = new RestOperationMeta(); + less.setAbsolutePath("/a/{test}/{id}"); + + RestOperationMeta more = new RestOperationMeta(); + more.setAbsolutePath("/a/{test : .+}/{id}"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(less); + paths.addResource(more); + paths.sortPath(); + + Assert.assertSame(more, paths.getDynamicPathOperationList().get(0)); + Assert.assertSame(less, paths.getDynamicPathOperationList().get(1)); + } +} From 40b67eba8c4ef218984bb7efa619a4bfec72948c Mon Sep 17 00:00:00 2001 From: wujimin Date: Mon, 21 Aug 2017 12:46:48 +0800 Subject: [PATCH 14/18] [WIP] JAV-150 extract paths from ServicePathManager --- .../rest/definition/RestOperationMeta.java | 36 +++-- .../rest/locator/MicroservicePaths.java | 110 ++++++++++++++ .../rest/definition/UnitTestRestUtils.java | 37 +++++ .../rest/locator/TestMicroservicePaths.java | 138 ++++++++++++++++++ 4 files changed, 306 insertions(+), 15 deletions(-) create mode 100644 common/common-rest/src/main/java/io/servicecomb/common/rest/locator/MicroservicePaths.java create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/definition/UnitTestRestUtils.java create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestMicroservicePaths.java diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/definition/RestOperationMeta.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/definition/RestOperationMeta.java index f2b031c2c64..2ce3d355050 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/definition/RestOperationMeta.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/definition/RestOperationMeta.java @@ -16,14 +16,6 @@ package io.servicecomb.common.rest.definition; -import io.servicecomb.common.rest.codec.produce.ProduceProcessor; -import io.servicecomb.common.rest.codec.produce.ProduceProcessorManager; -import io.servicecomb.common.rest.definition.path.PathRegExp; -import io.servicecomb.common.rest.definition.path.URLPathBuilder; -import io.servicecomb.core.definition.OperationMeta; -import io.swagger.models.Operation; -import io.swagger.models.Swagger; -import io.swagger.models.parameters.Parameter; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; @@ -31,11 +23,22 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; + import javax.ws.rs.core.MediaType; + import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.servicecomb.common.rest.codec.produce.ProduceProcessor; +import io.servicecomb.common.rest.codec.produce.ProduceProcessorManager; +import io.servicecomb.common.rest.definition.path.PathRegExp; +import io.servicecomb.common.rest.definition.path.URLPathBuilder; +import io.servicecomb.core.definition.OperationMeta; +import io.swagger.models.Operation; +import io.swagger.models.Swagger; +import io.swagger.models.parameters.Parameter; + public class RestOperationMeta { private static final Logger LOGGER = LoggerFactory.getLogger(RestOperationMeta.class); @@ -77,8 +80,6 @@ public void init(OperationMeta operationMeta) { this.produces = swagger.getProduces(); } - setAbsolutePath(concatPath(swagger.getBasePath(), operationMeta.getOperationPath())); - this.createProduceProcessors(); Method method = operationMeta.getMethod(); @@ -96,7 +97,7 @@ public void init(OperationMeta operationMeta) { addParam(param); } - this.pathBuilder = new URLPathBuilder(absolutePath, paramMap); + setAbsolutePath(concatPath(swagger.getBasePath(), operationMeta.getOperationPath())); } public void setOperationMeta(OperationMeta operationMeta) { @@ -105,21 +106,22 @@ public void setOperationMeta(OperationMeta operationMeta) { // 输出b/c/形式的url private String concatPath(String basePath, String operationPath) { - return ("/" + nonNullify(basePath) + "/" + nonNullify(operationPath) + "/") - .replaceAll("/{2,}", "/"); + return ("/" + nonNullify(basePath) + "/" + nonNullify(operationPath) + "/") + .replaceAll("/{2,}", "/"); } - private String nonNullify(String path) { + private String nonNullify(String path) { return path == null ? "" : path; } - public String getAbsolutePath() { + public String getAbsolutePath() { return this.absolutePath; } public void setAbsolutePath(String absolutePath) { this.absolutePath = absolutePath; this.absolutePathRegExp = createPathRegExp(absolutePath); + this.pathBuilder = new URLPathBuilder(absolutePath, paramMap); } public PathRegExp getAbsolutePathRegExp() { @@ -262,4 +264,8 @@ private ProduceProcessor getDefaultOrFirstProcessor() { public String getHttpMethod() { return operationMeta.getHttpMethod(); } + + public List getProduces() { + return produces; + } } diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/MicroservicePaths.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/MicroservicePaths.java new file mode 100644 index 00000000000..385e1cd13d3 --- /dev/null +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/MicroservicePaths.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.locator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.servicecomb.common.rest.definition.RestOperationComparator; +import io.servicecomb.common.rest.definition.RestOperationMeta; +import io.servicecomb.foundation.common.exceptions.ServiceCombException; + +public class MicroservicePaths { + private static final Logger LOGGER = LoggerFactory.getLogger(MicroservicePaths.class); + + // 运行阶段,静态path,一次直接查找到目标,不必遍历查找 + // 以path为key + protected Map staticPathOperations = new HashMap<>(); + + // 运行阶段,以path优先级,从高到低排列的operation列表 + protected List dynamicPathOperationsList = new ArrayList<>(); + + public void cloneTo(MicroservicePaths other) { + other.staticPathOperations.putAll(staticPathOperations); + other.dynamicPathOperationsList.addAll(dynamicPathOperationsList); + } + + public void sortPath() { + RestOperationComparator comparator = new RestOperationComparator(); + Collections.sort(this.dynamicPathOperationsList, comparator); + } + + public void addResource(RestOperationMeta swaggerRestOperation) { + if (swaggerRestOperation.isAbsoluteStaticPath()) { + // 静态path + addStaticPathResource(swaggerRestOperation); + return; + } + + dynamicPathOperationsList.add(swaggerRestOperation); + } + + protected void addStaticPathResource(RestOperationMeta operation) { + String httpMethod = operation.getHttpMethod(); + String path = operation.getAbsolutePath(); + OperationGroup group = staticPathOperations.get(path); + if (group == null) { + group = new OperationGroup(); + group.register(httpMethod, operation); + staticPathOperations.put(path, group); + return; + } + + if (group.findValue(httpMethod) == null) { + group.register(httpMethod, operation); + return; + } + + throw new ServiceCombException( + String.format("operation with url %s, method %s is duplicated.", path, httpMethod)); + } + + public Map getStaticPathOperationMap() { + return staticPathOperations; + } + + public List getDynamicPathOperationList() { + return dynamicPathOperationsList; + } + + public void printPaths() { + for (Entry entry : staticPathOperations.entrySet()) { + OperationGroup operationGroup = entry.getValue(); + printPath(operationGroup.values()); + } + + printPath(getDynamicPathOperationList()); + } + + protected void printPath(Collection operations) { + for (RestOperationMeta operation : operations) { + LOGGER.info("Swagger mapped \"{[{}], method=[{}], produces={}}\" onto {}", + operation.getAbsolutePath(), + operation.getHttpMethod(), + operation.getProduces(), + operation.getOperationMeta().getMethod()); + } + } +} diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/definition/UnitTestRestUtils.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/definition/UnitTestRestUtils.java new file mode 100644 index 00000000000..b6bf745cbee --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/definition/UnitTestRestUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.definition; + +import java.util.Arrays; + +import javax.ws.rs.core.MediaType; + +import io.servicecomb.core.definition.OperationMeta; +import mockit.Deencapsulation; + +public class UnitTestRestUtils { + public static RestOperationMeta createRestOperatonMeta(String httpMethod, String path) { + OperationMeta om = new OperationMeta(); + om.setHttpMethod(httpMethod); + + RestOperationMeta rom = new RestOperationMeta(); + rom.setOperationMeta(om); + rom.setAbsolutePath(path); + Deencapsulation.setField(rom, "produces", Arrays.asList(MediaType.APPLICATION_JSON)); + return rom; + } +} diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestMicroservicePaths.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestMicroservicePaths.java new file mode 100644 index 00000000000..7b336ab80aa --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestMicroservicePaths.java @@ -0,0 +1,138 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.locator; + +import java.io.StringWriter; +import java.io.Writer; + +import org.apache.log4j.Logger; +import org.apache.log4j.SimpleLayout; +import org.apache.log4j.WriterAppender; +import org.junit.Assert; +import org.junit.Test; + +import io.servicecomb.common.rest.definition.RestOperationMeta; +import io.servicecomb.common.rest.definition.UnitTestRestUtils; +import io.servicecomb.foundation.common.exceptions.ServiceCombException; + +public class TestMicroservicePaths { + @Test + public void testAddResourceStaticNewGroup() { + RestOperationMeta staticRes = UnitTestRestUtils.createRestOperatonMeta("POST", "/static"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(staticRes); + + Assert.assertSame(staticRes, paths.getStaticPathOperationMap().get("/static").findValue("POST")); + } + + @Test + public void testAddResourceStaticAddToGroup() { + RestOperationMeta staticResPost = UnitTestRestUtils.createRestOperatonMeta("POST", "/static"); + RestOperationMeta staticResGet = UnitTestRestUtils.createRestOperatonMeta("GET", "/static"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(staticResPost); + paths.addResource(staticResGet); + + Assert.assertSame(staticResPost, paths.getStaticPathOperationMap().get("/static").findValue("POST")); + Assert.assertSame(staticResGet, paths.getStaticPathOperationMap().get("/static").findValue("GET")); + } + + @Test + public void testAddResourceStaticDuplicatedHttpMethod() { + RestOperationMeta staticResPost = UnitTestRestUtils.createRestOperatonMeta("POST", "/static"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(staticResPost); + + try { + paths.addResource(staticResPost); + Assert.fail("must throw exception"); + } catch (ServiceCombException e) { + Assert.assertEquals("operation with url /static, method POST is duplicated.", e.getMessage()); + } + } + + @Test + public void testAddResourceDynamic() { + RestOperationMeta dynamicRes = UnitTestRestUtils.createRestOperatonMeta("POST", "/dynamic/{id}"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(dynamicRes); + + Assert.assertSame(dynamicRes, paths.getDynamicPathOperationList().get(0)); + } + + @Test + public void testCloneTo() { + RestOperationMeta staticRes = UnitTestRestUtils.createRestOperatonMeta("POST", "/static"); + RestOperationMeta dynamicRes = UnitTestRestUtils.createRestOperatonMeta("POST", "/dynamic/{id}"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(staticRes); + paths.addResource(dynamicRes); + + MicroservicePaths other = new MicroservicePaths(); + paths.cloneTo(other); + + Assert.assertEquals(paths.getStaticPathOperationMap(), other.getStaticPathOperationMap()); + Assert.assertEquals(paths.getDynamicPathOperationList(), other.getDynamicPathOperationList()); + } + + @Test + public void testSortPath() { + // only test base rule + // completely rule test by TestRestOperationComparator + RestOperationMeta dynamicResLessStatic = UnitTestRestUtils.createRestOperatonMeta("POST", "/a/{id}"); + RestOperationMeta dynamicResMoreStatic = UnitTestRestUtils.createRestOperatonMeta("POST", "/abc/{id}"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(dynamicResLessStatic); + paths.addResource(dynamicResMoreStatic); + paths.sortPath(); + + Assert.assertSame(dynamicResMoreStatic, paths.getDynamicPathOperationList().get(0)); + Assert.assertSame(dynamicResLessStatic, paths.getDynamicPathOperationList().get(1)); + } + + @Test + public void testPrintPaths() { + RestOperationMeta staticRes = UnitTestRestUtils.createRestOperatonMeta("POST", "/static"); + RestOperationMeta dynamicRes = UnitTestRestUtils.createRestOperatonMeta("POST", "/dynamic/{id}"); + + MicroservicePaths paths = new MicroservicePaths(); + paths.addResource(staticRes); + paths.addResource(dynamicRes); + + WriterAppender appender = new WriterAppender(); + Writer writer = new StringWriter(); + appender.setWriter(writer); + appender.setLayout(new SimpleLayout()); + Logger.getRootLogger().addAppender(appender); + + paths.printPaths(); + + String[] lines = writer.toString().split("\n"); + Assert.assertEquals("INFO - Swagger mapped \"{[/static], method=[POST], produces=[application/json]}\" onto null", + lines[0].trim()); + Assert.assertEquals("INFO - Swagger mapped \"{[/dynamic/{id}], method=[POST], produces=[application/json]}\" onto null", + lines[1].trim()); + + Logger.getRootLogger().removeAppender(appender); + } +} From 038143825836d1709b5b0e032aa562a79a49b24b Mon Sep 17 00:00:00 2001 From: wujimin Date: Mon, 21 Aug 2017 15:25:15 +0800 Subject: [PATCH 15/18] [WIP] JAV-150 OperationLocator switch to use MicroservicePaths --- .../common/rest/locator/OperationLocator.java | 18 +-- .../common/rest/locator/TestLocator.java | 117 ------------------ .../rest/locator/TestOperationLocator.java | 99 +++++++++++++++ 3 files changed, 110 insertions(+), 124 deletions(-) delete mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestLocator.java create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestOperationLocator.java diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/OperationLocator.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/OperationLocator.java index 14435b4e191..9a67b7cdae4 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/OperationLocator.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/OperationLocator.java @@ -16,15 +16,18 @@ package io.servicecomb.common.rest.locator; -import io.servicecomb.common.rest.definition.RestOperationMeta; -import io.servicecomb.swagger.invocation.exception.InvocationException; import java.util.Collection; import java.util.HashMap; import java.util.Map; + import javax.ws.rs.core.Response.Status; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.servicecomb.common.rest.definition.RestOperationMeta; +import io.servicecomb.swagger.invocation.exception.InvocationException; + /** * 从path和http method定位到具体的operation */ @@ -48,16 +51,16 @@ public Map getPathVarMap() { } // 先在静态路径operation list中查找;如果找不到,则在动态路径operation list中查找 - public void locate(ServicePathManager servicePathManager, String path, String httpMethod) { + public void locate(String microserviceName, String path, String httpMethod, MicroservicePaths microservicePaths) { // 在静态路径中查找 - operation = locateStaticPathOperation(path, httpMethod, servicePathManager.getStaticPathOperationMap()); + operation = locateStaticPathOperation(path, httpMethod, microservicePaths.getStaticPathOperationMap()); if (operation != null) { // 全部定位完成 return; } // 在动态路径中查找 - operation = locateDynamicPathOperation(path, servicePathManager.getDynamicPathOperationList(), httpMethod); + operation = locateDynamicPathOperation(path, microservicePaths.getDynamicPathOperationList(), httpMethod); if (operation != null) { return; } @@ -70,7 +73,7 @@ public void locate(ServicePathManager servicePathManager, String path, String ht status, httpMethod, path, - servicePathManager.getMicroserviceMeta().getName()); + microserviceName); throw new InvocationException(status, status.getReasonPhrase()); } @@ -101,9 +104,10 @@ protected RestOperationMeta locateDynamicPathOperation(String path, Collection /a/b/c/ static String getStandardPath(String path) { if (path.length() > 0 && !path.endsWith(SLASH)) { diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestLocator.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestLocator.java deleted file mode 100644 index 5b9053f82c3..00000000000 --- a/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestLocator.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2017 Huawei Technologies Co., Ltd - * - * Licensed 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 io.servicecomb.common.rest.locator; - -import java.util.Collections; - -import io.servicecomb.common.rest.definition.RestOperationMeta; -import org.junit.Assert; -import org.junit.Test; - -import io.servicecomb.core.definition.MicroserviceMeta; -import io.servicecomb.core.definition.OperationMeta; -import io.servicecomb.swagger.invocation.exception.CommonExceptionData; -import io.servicecomb.swagger.invocation.exception.InvocationException; - -public class TestLocator { - @Test - public void testServicePathManager() { - MicroserviceMeta msm = new MicroserviceMeta("app:ms"); - ServicePathManager spm = new ServicePathManager(msm); - - RestOperationMeta rom = createRestOperatonMeta("GET", "abc/{id}"); - spm.addResource(rom); - - rom = createRestOperatonMeta("GET", "abc/{id}/xxx"); - spm.addResource(rom); - - Assert.assertEquals("abc/{id}", spm.getDynamicPathOperationList().get(0).getAbsolutePath()); - spm.sortPath(); - Assert.assertEquals("abc/{id}/xxx", spm.getDynamicPathOperationList().get(0).getAbsolutePath()); - - spm.printService(); - spm.doPrintService(); - } - - @Test - public void testLocateDynamic() { - MicroserviceMeta msm = new MicroserviceMeta("app:ms"); - ServicePathManager spm = new ServicePathManager(msm); - - RestOperationMeta rom = createRestOperatonMeta("GET", "abc/{id}"); - spm.addResource(rom); - - try { - spm.locateOperation("abc/10", "PUT"); - } catch (InvocationException e) { - Assert.assertEquals("Method Not Allowed", ((CommonExceptionData) e.getErrorData()).getMessage()); - } - - OperationLocator locator = spm.locateOperation("abc/10", "GET"); - Assert.assertEquals("10", locator.getPathVarMap().get("id")); - } - - @Test - public void testLocateStatic() { - MicroserviceMeta msm = new MicroserviceMeta("app:ms"); - ServicePathManager spm = new ServicePathManager(msm); - - RestOperationMeta rom = createRestOperatonMeta("GET", "abc/"); - spm.addResource(rom); - - rom = createRestOperatonMeta("POST", "abc/"); - spm.addResource(rom); - - try { - spm.addResource(rom); - } catch (Throwable e) { - Assert.assertEquals("operation with url abc/, method POST is duplicated", e.getMessage()); - } - - Assert.assertEquals(1, spm.getStaticPathOperationMap().size()); - Assert.assertEquals(2, spm.getStaticPathOperationMap().get("abc/").values().size()); - - try { - spm.locateOperation("abcd", "GET"); - } catch (InvocationException e) { - Assert.assertEquals("Not Found", ((CommonExceptionData) e.getErrorData()).getMessage()); - } - - try { - spm.locateOperation("abc/", "PUT"); - } catch (InvocationException e) { - Assert.assertEquals("Method Not Allowed", ((CommonExceptionData) e.getErrorData()).getMessage()); - } - - OperationLocator locator = spm.locateOperation("abc/", "GET"); - Assert.assertEquals(Collections.emptyMap(), locator.getPathVarMap()); - - locator.locate(spm, "abc/", "POST"); - Assert.assertEquals(Collections.emptyMap(), locator.getPathVarMap()); - } - - protected RestOperationMeta createRestOperatonMeta(String httpMethod, String path) { - OperationMeta om = new OperationMeta(); - om.setHttpMethod(httpMethod); - - RestOperationMeta rom = new RestOperationMeta(); - rom.setOperationMeta(om); - rom.setAbsolutePath(path); - return rom; - } - -} diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestOperationLocator.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestOperationLocator.java new file mode 100644 index 00000000000..8bfff927357 --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestOperationLocator.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.locator; + +import javax.ws.rs.core.Response.Status; + +import org.junit.Assert; +import org.junit.Test; + +import io.servicecomb.common.rest.definition.RestOperationMeta; +import io.servicecomb.common.rest.definition.UnitTestRestUtils; +import io.servicecomb.swagger.invocation.exception.InvocationException; + +public class TestOperationLocator { + MicroservicePaths paths = new MicroservicePaths(); + + OperationLocator locator = new OperationLocator(); + + private RestOperationMeta addRestOperationMeta(String httpMethod, String path) { + RestOperationMeta rom = UnitTestRestUtils.createRestOperatonMeta(httpMethod, path); + paths.addResource(rom); + return rom; + } + + @Test + public void testLocateNotFound() { + try { + locator.locate("ms", "/notExist", "GET", paths); + Assert.fail("must throw exception"); + } catch (InvocationException e) { + Assert.assertEquals(Status.NOT_FOUND, e.getStatus()); + } + } + + @Test + public void testLocateNotFoundDynamicRemained() { + addRestOperationMeta("GET", "/dynamic/{id}"); + try { + locator.locate("ms", "/dynamic/1/2", "GET", paths); + Assert.fail("must throw exception"); + } catch (InvocationException e) { + Assert.assertEquals(Status.NOT_FOUND, e.getStatus()); + } + } + + @Test + public void testLocateStaticMethodNotAllowed() { + addRestOperationMeta("GET", "/static"); + + try { + locator.locate("ms", "/static", "POST", paths); + Assert.fail("must throw exception"); + } catch (InvocationException e) { + Assert.assertEquals(Status.METHOD_NOT_ALLOWED, e.getStatus()); + } + } + + @Test + public void testLocateDynamicMethodNotAllowed() { + addRestOperationMeta("GET", "/dynamic/{id}"); + try { + locator.locate("ms", "/dynamic/1/", "POST", paths); + Assert.fail("must throw exception"); + } catch (InvocationException e) { + Assert.assertEquals(Status.METHOD_NOT_ALLOWED, e.getStatus()); + } + } + + @Test + public void testLocateStaticFound() { + RestOperationMeta rom = addRestOperationMeta("GET", "/static"); + locator.locate("ms", "/static", "GET", paths); + + Assert.assertSame(rom, locator.getOperation()); + } + + @Test + public void testLocateDynamicFound() { + RestOperationMeta rom = addRestOperationMeta("GET", "/dynamic/{id}"); + locator.locate("ms", "/dynamic/1/", "GET", paths); + + Assert.assertSame(rom, locator.getOperation()); + Assert.assertEquals("1", locator.getPathVarMap().get("id")); + } +} From a5ca3ac529358609487d22bad2cf5ef8ad535869 Mon Sep 17 00:00:00 2001 From: wujimin Date: Tue, 22 Aug 2017 10:13:31 +0800 Subject: [PATCH 16/18] JAV-150 1.ServicePathManager support consumer and producer locate 2.build producer paths before service registry task start --- .../common/rest/AbstractRestServer.java | 3 +- .../common/rest/RestEngineSchemaListener.java | 52 +++---- .../rest/locator/ServicePathManager.java | 130 +++++++++--------- .../rest/TestRestEngineSchemaListener.java | 22 +-- .../rest/locator/TestServicePathManager.java | 112 +++++++++++++++ .../reference/CseClientHttpRequest.java | 2 +- 6 files changed, 218 insertions(+), 103 deletions(-) create mode 100644 common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestServicePathManager.java diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/AbstractRestServer.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/AbstractRestServer.java index 0873010ea97..8988a3ee3a6 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/AbstractRestServer.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/AbstractRestServer.java @@ -126,7 +126,8 @@ protected RestOperationMeta findRestOperation(RestServerRequestInternal restRequ throw new InvocationException(Status.NOT_FOUND, Status.NOT_FOUND.getReasonPhrase()); } - OperationLocator locator = servicePathManager.locateOperation(restRequest.getPath(), restRequest.getMethod()); + OperationLocator locator = + servicePathManager.producerLocateOperation(restRequest.getPath(), restRequest.getMethod()); restRequest.setPathParamMap(locator.getPathVarMap()); return locator.getOperation(); diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/RestEngineSchemaListener.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/RestEngineSchemaListener.java index 528cc3e7862..7ed5746b1c3 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/RestEngineSchemaListener.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/RestEngineSchemaListener.java @@ -19,20 +19,40 @@ import java.util.HashMap; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import javax.inject.Inject; + import org.springframework.stereotype.Component; -import io.servicecomb.common.rest.definition.RestOperationMeta; import io.servicecomb.common.rest.locator.ServicePathManager; +import io.servicecomb.core.BootListener; import io.servicecomb.core.definition.MicroserviceMeta; -import io.servicecomb.core.definition.OperationMeta; +import io.servicecomb.core.definition.MicroserviceMetaManager; import io.servicecomb.core.definition.SchemaMeta; import io.servicecomb.core.definition.loader.SchemaListener; +import io.servicecomb.serviceregistry.RegistryUtils; @Component -public class RestEngineSchemaListener implements SchemaListener { - private static final Logger LOGGER = LoggerFactory.getLogger(RestEngineSchemaListener.class); +public class RestEngineSchemaListener implements SchemaListener, BootListener { + private MicroserviceMetaManager microserviceMetaManager; + + @Inject + public void setMicroserviceMetaManager(MicroserviceMetaManager microserviceMetaManager) { + this.microserviceMetaManager = microserviceMetaManager; + } + + @Override + public void onBootEvent(BootEvent event) { + if (!event.getEventType().equals(EventType.BEFORE_REGISTRY)) { + return; + } + + MicroserviceMeta microserviceMeta = + microserviceMetaManager.getOrCreateMicroserviceMeta(RegistryUtils.getMicroservice()); + ServicePathManager servicePathManager = ServicePathManager.getServicePathManager(microserviceMeta); + if (servicePathManager != null) { + servicePathManager.buildProducerPaths(); + } + } @Override public void onSchemaLoaded(SchemaMeta... schemaMetas) { @@ -41,30 +61,12 @@ public void onSchemaLoaded(SchemaMeta... schemaMetas) { for (SchemaMeta schemaMeta : schemaMetas) { MicroserviceMeta microserviceMeta = schemaMeta.getMicroserviceMeta(); ServicePathManager mgr = findPathManager(mgrMap, microserviceMeta); - - if (mgr.isSchemaExists(schemaMeta.getSchemaId())) { - LOGGER.info("on schema loaded, exists schema. {}:{}", - schemaMeta.getMicroserviceName(), - schemaMeta.getSchemaId()); - continue; - } - LOGGER.info("on schema loaded, new schema. {}:{}", - schemaMeta.getMicroserviceName(), - schemaMeta.getSchemaId()); - mgr.addSchema(schemaMeta.getSchemaId()); - - for (OperationMeta operationMeta : schemaMeta.getOperations()) { - RestOperationMeta restOperationMeta = new RestOperationMeta(); - restOperationMeta.init(operationMeta); - operationMeta.putExtData(RestConst.SWAGGER_REST_OPERATION, restOperationMeta); - mgr.addResource(restOperationMeta); - } + mgr.addSchema(schemaMeta); } for (ServicePathManager mgr : mgrMap.values()) { // 对具有动态path operation进行排序 mgr.sortPath(); - mgr.printService(); mgr.saveToMicroserviceMeta(); } diff --git a/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/ServicePathManager.java b/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/ServicePathManager.java index 50ff206fb76..c8ce4726d10 100644 --- a/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/ServicePathManager.java +++ b/common/common-rest/src/main/java/io/servicecomb/common/rest/locator/ServicePathManager.java @@ -16,19 +16,20 @@ package io.servicecomb.common.rest.locator; -import io.servicecomb.common.rest.definition.RestOperationComparator; -import io.servicecomb.common.rest.definition.RestOperationMeta; -import io.servicecomb.core.definition.MicroserviceMeta; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; +import java.util.Collection; import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.Set; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import io.servicecomb.common.rest.RestConst; +import io.servicecomb.common.rest.definition.RestOperationMeta; +import io.servicecomb.core.Const; +import io.servicecomb.core.definition.MicroserviceMeta; +import io.servicecomb.core.definition.OperationMeta; +import io.servicecomb.core.definition.SchemaMeta; /** * 对静态路径和动态路径的operation进行预先处理,加速operation的查询定位 @@ -40,12 +41,13 @@ public class ServicePathManager { protected MicroserviceMeta microserviceMeta; - // 运行阶段,静态path,一次直接查找到目标,不必遍历查找 - // 以path为key - protected Map staticPathOperations = new HashMap<>(); + // equal to swagger + protected MicroservicePaths swaggerPaths = new MicroservicePaths(); - // 运行阶段,以path优先级,从高到低排列的operation列表 - protected List dynamicPathOperationsList = new ArrayList<>(); + // we support swagger basePath is not include contextPath and urlPattern + // so for producer, we must concat contextPath and urlPattern + // only valid for microservice of this process + protected MicroservicePaths producerPaths; // 已经有哪些schemaId的path信息加进来了 // 在producer场景中,业务before producer provider事件中将契约注册进来,此时会触发事件,携带注册范围的信息 @@ -73,87 +75,81 @@ public boolean isSchemaExists(String schemaId) { return schemaIdSet.contains(schemaId); } - public void addSchema(String schemaId) { - schemaIdSet.add(schemaId); + public void addSchema(SchemaMeta schemaMeta) { + if (isSchemaExists(schemaMeta.getSchemaId())) { + return; + } + + schemaIdSet.add(schemaMeta.getSchemaId()); + for (OperationMeta operationMeta : schemaMeta.getOperations()) { + RestOperationMeta restOperationMeta = new RestOperationMeta(); + restOperationMeta.init(operationMeta); + operationMeta.putExtData(RestConst.SWAGGER_REST_OPERATION, restOperationMeta); + addResource(restOperationMeta); + } + + LOGGER.info("add schema to service paths. {}:{}", + schemaMeta.getMicroserviceName(), + schemaMeta.getSchemaId()); } public ServicePathManager cloneServicePathManager() { ServicePathManager mgr = new ServicePathManager(microserviceMeta); - mgr.staticPathOperations.putAll(staticPathOperations); - mgr.dynamicPathOperationsList.addAll(dynamicPathOperationsList); + swaggerPaths.cloneTo(mgr.swaggerPaths); mgr.schemaIdSet.addAll(schemaIdSet); return mgr; } - public OperationLocator locateOperation(String path, String httpMethod) { + public OperationLocator consumerLocateOperation(String path, String httpMethod) { String standPath = OperationLocator.getStandardPath(path); OperationLocator locator = new OperationLocator(); - locator.locate(this, standPath, httpMethod); + locator.locate(microserviceMeta.getName(), standPath, httpMethod, swaggerPaths); return locator; } - public void sortPath() { - RestOperationComparator comparator = new RestOperationComparator(); - Collections.sort(this.dynamicPathOperationsList, comparator); + public OperationLocator producerLocateOperation(String path, String httpMethod) { + String standPath = OperationLocator.getStandardPath(path); + OperationLocator locator = new OperationLocator(); + locator.locate(microserviceMeta.getName(), standPath, httpMethod, producerPaths); + + return locator; } public void addResource(RestOperationMeta swaggerRestOperation) { - if (swaggerRestOperation.isAbsoluteStaticPath()) { - // 静态path - addStaticPathResource(swaggerRestOperation); - return; - } - - dynamicPathOperationsList.add(swaggerRestOperation); + swaggerPaths.addResource(swaggerRestOperation); } - protected void addStaticPathResource(RestOperationMeta operation) { - String httpMethod = operation.getHttpMethod(); - String path = operation.getAbsolutePath(); - OperationGroup group = staticPathOperations.get(path); - if (group == null) { - group = new OperationGroup(); - group.register(httpMethod, operation); - staticPathOperations.put(path, group); - return; - } + public void sortPath() { + swaggerPaths.sortPath(); + } - if (group.findValue(httpMethod) == null) { - group.register(httpMethod, operation); + public void buildProducerPaths() { + String urlPrefix = System.getProperty(Const.URL_PREFIX); + if (StringUtils.isEmpty(urlPrefix)) { + producerPaths = swaggerPaths; + producerPaths.printPaths(); return; } - throw new RuntimeException( - String.format("operation with url %s, method %s is duplicated", path, httpMethod)); - } - - public Map getStaticPathOperationMap() { - return staticPathOperations; - } - - public List getDynamicPathOperationList() { - return dynamicPathOperationsList; - } - - public void printService() { - if (!LOGGER.isDebugEnabled()) { - return; + producerPaths = new MicroservicePaths(); + for (OperationGroup operationGroup : swaggerPaths.getStaticPathOperationMap().values()) { + addProducerPaths(urlPrefix, operationGroup.values()); } - doPrintService(); + addProducerPaths(urlPrefix, swaggerPaths.getDynamicPathOperationList()); + producerPaths.printPaths(); } - protected void doPrintService() { - for (Entry entry : staticPathOperations.entrySet()) { - OperationGroup operationGroup = entry.getValue(); - for (RestOperationMeta operation : operationGroup.values()) { - LOGGER.debug(entry.getKey() + " " + operation.getHttpMethod()); + private void addProducerPaths(String urlPrefix, Collection restOperationMetas) { + for (RestOperationMeta swaggerRestOperation : restOperationMetas) { + RestOperationMeta producerRestOperation = swaggerRestOperation; + if (!swaggerRestOperation.getAbsolutePath().startsWith(urlPrefix)) { + producerRestOperation = new RestOperationMeta(); + producerRestOperation.init(swaggerRestOperation.getOperationMeta()); + producerRestOperation.setAbsolutePath(urlPrefix + swaggerRestOperation.getAbsolutePath()); } - } - - for (RestOperationMeta operation : getDynamicPathOperationList()) { - LOGGER.debug(operation.getAbsolutePath() + " " + operation.getHttpMethod()); + producerPaths.addResource(producerRestOperation); } } } diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/TestRestEngineSchemaListener.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/TestRestEngineSchemaListener.java index 577e7610185..04d8b1b0394 100644 --- a/common/common-rest/src/test/java/io/servicecomb/common/rest/TestRestEngineSchemaListener.java +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/TestRestEngineSchemaListener.java @@ -16,6 +16,14 @@ package io.servicecomb.common.rest; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; + import io.servicecomb.common.rest.locator.ServicePathManager; import io.servicecomb.core.definition.MicroserviceMeta; import io.servicecomb.core.definition.SchemaMeta; @@ -24,17 +32,11 @@ import io.servicecomb.swagger.generator.core.SwaggerGeneratorContext; import io.servicecomb.swagger.generator.pojo.PojoSwaggerGeneratorContext; import io.swagger.models.Swagger; -import java.util.ArrayList; -import java.util.List; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.context.ApplicationContext; public class TestRestEngineSchemaListener { private final SwaggerGeneratorContext context = new PojoSwaggerGeneratorContext(); - private static class Impl { + private static class TestRestEngineSchemaListenerSchemaImpl { @SuppressWarnings("unused") public int add(int x, int y) { return 0; @@ -48,8 +50,9 @@ public void test() { MicroserviceMeta mm = new MicroserviceMeta("app:ms"); List smList = new ArrayList<>(); - SwaggerGenerator generator = new SwaggerGenerator(context, Impl.class); + SwaggerGenerator generator = new SwaggerGenerator(context, TestRestEngineSchemaListenerSchemaImpl.class); Swagger swagger = generator.generate(); + swagger.setBasePath(""); SchemaMeta sm1 = new SchemaMeta(swagger, mm, "sid1"); smList.add(sm1); @@ -62,6 +65,7 @@ public void test() { ServicePathManager spm = ServicePathManager.getServicePathManager(mm); Assert.assertEquals(mm, spm.getMicroserviceMeta()); - Assert.assertNotNull(spm.getStaticPathOperationMap().get("/Impl/add/")); + Assert.assertSame(sm1, + spm.consumerLocateOperation("/add/", "POST").getOperation().getOperationMeta().getSchemaMeta()); } } diff --git a/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestServicePathManager.java b/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestServicePathManager.java new file mode 100644 index 00000000000..f4c2325f788 --- /dev/null +++ b/common/common-rest/src/test/java/io/servicecomb/common/rest/locator/TestServicePathManager.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Huawei Technologies Co., Ltd + * + * Licensed 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 io.servicecomb.common.rest.locator; + +import java.util.Map; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; + +import io.servicecomb.core.Const; +import io.servicecomb.core.definition.MicroserviceMeta; +import io.servicecomb.core.definition.SchemaMeta; +import io.servicecomb.foundation.common.utils.BeanUtils; +import io.servicecomb.swagger.generator.core.unittest.UnitTestSwaggerUtils; +import io.swagger.models.Path; +import io.swagger.models.Swagger; +import mockit.Mocked; + +public class TestServicePathManager { + private static class TestServicePathManagerSchemaImpl { + @SuppressWarnings("unused") + public void static1() { + } + + @SuppressWarnings("unused") + public void static2() { + } + + @SuppressWarnings("unused") + public void dynamic1() { + } + + @SuppressWarnings("unused") + public void dynamic2() { + } + } + + private ServicePathManager spm; + + @Mocked + ApplicationContext applicationContext; + + @Before + public void setup() { + BeanUtils.setContext(applicationContext); + + MicroserviceMeta mm = new MicroserviceMeta("app:ms"); + Swagger swagger = UnitTestSwaggerUtils.generateSwagger(TestServicePathManagerSchemaImpl.class).getSwagger(); + Map paths = swagger.getPaths(); + + swagger.setBasePath(""); + Path path = paths.remove("/static1"); + paths.put("/root/rest/static1", path); + + path = paths.remove("/dynamic1"); + paths.put("/dynamic1/{id}", path); + + path = paths.remove("/dynamic2"); + paths.put("/dynamic2/{id}", path); + + SchemaMeta schemaMeta = new SchemaMeta(swagger, mm, "sid"); + + spm = new ServicePathManager(mm); + spm.addSchema(schemaMeta); + spm.sortPath(); + } + + @After + public void teardown() { + BeanUtils.setContext(null); + } + + @Test + public void testBuildProducerPathsNoPrefix() { + System.clearProperty(Const.URL_PREFIX); + + spm.buildProducerPaths(); + Assert.assertSame(spm.producerPaths, spm.swaggerPaths); + } + + @Test + public void testBuildProducerPathsHasPrefix() { + System.setProperty(Const.URL_PREFIX, "/root/rest"); + + spm.buildProducerPaths(); + + // all locate should be success + spm.producerLocateOperation("/root/rest/static1/", "POST"); + spm.producerLocateOperation("/root/rest/static2/", "POST"); + spm.producerLocateOperation("/root/rest/dynamic1/1/", "POST"); + spm.producerLocateOperation("/root/rest/dynamic2/1/", "POST"); + + System.clearProperty(Const.URL_PREFIX); + } +} diff --git a/providers/provider-springmvc/src/main/java/io/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java b/providers/provider-springmvc/src/main/java/io/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java index bf98d6a187a..0d220caf872 100644 --- a/providers/provider-springmvc/src/main/java/io/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java +++ b/providers/provider-springmvc/src/main/java/io/servicecomb/provider/springmvc/reference/CseClientHttpRequest.java @@ -138,7 +138,7 @@ private RequestMeta createRequestMeta(String httpMetod, URI uri) { microserviceMeta.getName())); } - OperationLocator locator = servicePathManager.locateOperation(uri.getPath(), httpMetod); + OperationLocator locator = servicePathManager.consumerLocateOperation(uri.getPath(), httpMetod); RestOperationMeta swaggerRestOperation = locator.getOperation(); Map pathParams = locator.getPathVarMap(); From 2a8e267f756efb1e429eb5b7e7b87ed48a68b8b8 Mon Sep 17 00:00:00 2001 From: wujimin Date: Tue, 22 Aug 2017 10:43:17 +0800 Subject: [PATCH 17/18] bug fix: support to load empty or all commented yaml file --- .../sources/AbstractConfigLoader.java | 6 ++++ .../sources/TestMicroserviceConfigLoader.java | 36 +++++++++++++++---- .../src/test/resources/empty.yaml | 0 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 foundations/foundation-config/src/test/resources/empty.yaml diff --git a/foundations/foundation-config/src/main/java/io/servicecomb/config/archaius/sources/AbstractConfigLoader.java b/foundations/foundation-config/src/main/java/io/servicecomb/config/archaius/sources/AbstractConfigLoader.java index 2afa04d93ba..036aed07eab 100644 --- a/foundations/foundation-config/src/main/java/io/servicecomb/config/archaius/sources/AbstractConfigLoader.java +++ b/foundations/foundation-config/src/main/java/io/servicecomb/config/archaius/sources/AbstractConfigLoader.java @@ -20,6 +20,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -52,6 +53,11 @@ protected void loadFromClassPath(String resourceName) throws IOException { public ConfigModel load(URL url) throws IOException { Map config = loadData(url); + // load a empty or all commented yaml, will get a null map + // this is not a error + if (config == null) { + config = new LinkedHashMap<>(); + } ConfigModel configModel = new ConfigModel(); configModel.setUrl(url); diff --git a/foundations/foundation-config/src/test/java/io/servicecomb/config/archaius/sources/TestMicroserviceConfigLoader.java b/foundations/foundation-config/src/test/java/io/servicecomb/config/archaius/sources/TestMicroserviceConfigLoader.java index 0e7633366f5..b19fa9610b7 100644 --- a/foundations/foundation-config/src/test/java/io/servicecomb/config/archaius/sources/TestMicroserviceConfigLoader.java +++ b/foundations/foundation-config/src/test/java/io/servicecomb/config/archaius/sources/TestMicroserviceConfigLoader.java @@ -18,10 +18,15 @@ import static org.junit.Assert.assertEquals; +import java.io.FileNotFoundException; +import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.util.List; import java.util.stream.Collectors; + +import org.junit.Assert; import org.junit.Test; public class TestMicroserviceConfigLoader { @@ -70,13 +75,30 @@ public void jarsAlwaysHaveHigherPriorityThanFiles() throws MalformedURLException private String urlsOf(List configModels) { return String.join(",", - configModels - .stream() - .map(configModel -> configModel.getUrl().toString()) - .collect(Collectors.toList())); + configModels + .stream() + .map(configModel -> configModel.getUrl().toString()) + .collect(Collectors.toList())); + } + + private String urls(String... urls) { + return String.join(",", (CharSequence[]) urls); + } + + @Test + public void testLoadEmptyYaml() throws IOException { + loader.load("empty.yaml"); + Assert.assertTrue(loader.getConfigModels().get(0).getConfig().isEmpty()); } - private String urls(String... urls) { - return String.join(",", (CharSequence[]) urls); - } + @Test + public void testLoadNotExistYaml() throws IOException { + URL url = URI.create("file:/notExist.yaml").toURL(); + try { + loader.load(url); + Assert.fail("must throw exception"); + } catch (FileNotFoundException e) { + Assert.assertTrue(true); + } + } } diff --git a/foundations/foundation-config/src/test/resources/empty.yaml b/foundations/foundation-config/src/test/resources/empty.yaml new file mode 100644 index 00000000000..e69de29bb2d From 01e2d092baecc37fd829ae26f04f576f1684045c Mon Sep 17 00:00:00 2001 From: wujimin Date: Wed, 23 Aug 2017 16:50:05 +0800 Subject: [PATCH 18/18] JAV-150 save urlPrefix + relative basePath to microservice's basePath, this make HRS work normally --- .../core/definition/loader/SchemaLoader.java | 7 +++ .../loader/TestDynamicSchemaLoader.java | 49 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/servicecomb/core/definition/loader/SchemaLoader.java b/core/src/main/java/io/servicecomb/core/definition/loader/SchemaLoader.java index 3efd6bc5a42..f77ea9087fc 100644 --- a/core/src/main/java/io/servicecomb/core/definition/loader/SchemaLoader.java +++ b/core/src/main/java/io/servicecomb/core/definition/loader/SchemaLoader.java @@ -26,7 +26,9 @@ import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import io.servicecomb.core.Const; import io.servicecomb.core.Handler; import io.servicecomb.core.definition.MicroserviceMeta; import io.servicecomb.core.definition.MicroserviceMetaManager; @@ -112,6 +114,11 @@ public void putSelfBasePathIfAbsent(String microserviceName, String basePath) { return; } + String urlPrefix = System.getProperty(Const.URL_PREFIX); + if (!StringUtils.isEmpty(urlPrefix) && !basePath.startsWith(urlPrefix)) { + basePath = urlPrefix + basePath; + } + List paths = microservice.getPaths(); for (BasePath path : paths) { if (path.getPath().equals(basePath)) { diff --git a/core/src/test/java/io/servicecomb/core/definition/loader/TestDynamicSchemaLoader.java b/core/src/test/java/io/servicecomb/core/definition/loader/TestDynamicSchemaLoader.java index b65c7006471..ef47b452248 100644 --- a/core/src/test/java/io/servicecomb/core/definition/loader/TestDynamicSchemaLoader.java +++ b/core/src/test/java/io/servicecomb/core/definition/loader/TestDynamicSchemaLoader.java @@ -15,29 +15,36 @@ */ package io.servicecomb.core.definition.loader; +import java.util.ArrayList; import java.util.Collections; +import org.hamcrest.Matchers; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import io.servicecomb.core.Const; import io.servicecomb.core.CseContext; import io.servicecomb.core.definition.MicroserviceMetaManager; import io.servicecomb.core.definition.SchemaMeta; import io.servicecomb.core.unittest.UnitTestMeta; import io.servicecomb.serviceregistry.RegistryUtils; import io.servicecomb.serviceregistry.ServiceRegistry; +import io.servicecomb.serviceregistry.api.registry.Microservice; import io.servicecomb.serviceregistry.registry.ServiceRegistryFactory; public class TestDynamicSchemaLoader { private static MicroserviceMetaManager microserviceMetaManager = new MicroserviceMetaManager(); + private static SchemaLoader loader = new SchemaLoader(); + + private static Microservice microservice; + @BeforeClass public static void init() { UnitTestMeta.init(); - SchemaLoader loader = new SchemaLoader(); loader.setMicroserviceMetaManager(microserviceMetaManager); SchemaListenerManager schemaListenerManager = new SchemaListenerManager(); @@ -50,6 +57,7 @@ public static void init() { ServiceRegistry serviceRegistry = ServiceRegistryFactory.createLocal(); serviceRegistry.init(); + microservice = serviceRegistry.getMicroservice(); RegistryUtils.setServiceRegistry(serviceRegistry); } @@ -64,11 +72,48 @@ public void testRegisterSchemas() { SchemaMeta schemaMeta = microserviceMetaManager.ensureFindSchemaMeta("perfClient", "schema"); Assert.assertEquals("cse.gen.pojotest.perfClient.schema", schemaMeta.getPackageName()); } - + @Test public void testRegisterShemasAcrossApp() { DynamicSchemaLoader.INSTANCE.registerSchemas("CSE:as", "classpath*:test/test/schema.yaml"); SchemaMeta schemaMeta = microserviceMetaManager.ensureFindSchemaMeta("CSE:as", "schema"); Assert.assertEquals("cse.gen.CSE.as.schema", schemaMeta.getPackageName()); } + + @Test + public void testPutSelfBasePathIfAbsent_noUrlPrefix() { + System.clearProperty(Const.URL_PREFIX); + microservice.setPaths(new ArrayList<>()); + + loader.putSelfBasePathIfAbsent("perfClient", "/test"); + + Assert.assertThat(microservice.getPaths().size(), Matchers.is(1)); + Assert.assertThat(microservice.getPaths().get(0).getPath(), Matchers.is("/test")); + } + + @Test + public void testPutSelfBasePathIfAbsent_WithUrlPrefix() { + System.setProperty(Const.URL_PREFIX, "/root/rest"); + microservice.setPaths(new ArrayList<>()); + + loader.putSelfBasePathIfAbsent("perfClient", "/test"); + + Assert.assertThat(microservice.getPaths().size(), Matchers.is(1)); + Assert.assertThat(microservice.getPaths().get(0).getPath(), Matchers.is("/root/rest/test")); + + System.clearProperty(Const.URL_PREFIX); + } + + @Test + public void testPutSelfBasePathIfAbsent_WithUrlPrefix_StartWithUrlPrefix() { + System.setProperty(Const.URL_PREFIX, "/root/rest"); + microservice.setPaths(new ArrayList<>()); + + loader.putSelfBasePathIfAbsent("perfClient", "/root/rest/test"); + + Assert.assertThat(microservice.getPaths().size(), Matchers.is(1)); + Assert.assertThat(microservice.getPaths().get(0).getPath(), Matchers.is("/root/rest/test")); + + System.clearProperty(Const.URL_PREFIX); + } }