You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2018/06/22 10:28:24 UTC

[incubator-servicecomb-java-chassis] 01/03: [SCB-679] support mounting CorsHandler

This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-java-chassis.git

commit 47490a6d8ce8bef48b5558969d9ce74e9b63d7b6
Author: yaohaishi <ya...@huawei.com>
AuthorDate: Wed Jun 20 23:40:24 2018 +0800

    [SCB-679] support mounting CorsHandler
---
 .../transport/rest/vertx/RestServerVerticle.java   |  38 ++++++++
 .../transport/rest/vertx/TransportConfig.java      |  63 +++++++++++++
 .../rest/vertx/TestRestServerVerticle.java         |  87 ++++++++++++++++-
 .../transport/rest/vertx/TestTransportConfig.java  | 105 +++++++++++++++++----
 4 files changed, 271 insertions(+), 22 deletions(-)

diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java
index f2ac2fb..3780347 100644
--- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java
+++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestServerVerticle.java
@@ -18,6 +18,7 @@
 package org.apache.servicecomb.transport.rest.vertx;
 
 import java.util.List;
+import java.util.Set;
 
 import org.apache.servicecomb.core.Endpoint;
 import org.apache.servicecomb.core.transport.AbstractTransport;
@@ -36,9 +37,11 @@ import io.vertx.core.AbstractVerticle;
 import io.vertx.core.Context;
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpMethod;
 import io.vertx.core.http.HttpServer;
 import io.vertx.core.http.HttpServerOptions;
 import io.vertx.ext.web.Router;
+import io.vertx.ext.web.handler.CorsHandler;
 
 public class RestServerVerticle extends AbstractVerticle {
   private static final Logger LOGGER = LoggerFactory.getLogger(RestServerVerticle.class);
@@ -68,6 +71,7 @@ public class RestServerVerticle extends AbstractVerticle {
       }
       Router mainRouter = Router.router(vertx);
       mountAccessLogHandler(mainRouter);
+      mountCorsHandler(mainRouter);
       initDispatcher(mainRouter);
       HttpServer httpServer = createHttpServer();
       httpServer.requestHandler(mainRouter::accept);
@@ -90,6 +94,40 @@ public class RestServerVerticle extends AbstractVerticle {
     }
   }
 
+  /**
+   * Support CORS
+   */
+  void mountCorsHandler(Router mainRouter) {
+    if (!TransportConfig.isCorsEnabled()) {
+      return;
+    }
+
+    CorsHandler corsHandler = getCorsHandler(TransportConfig.getCorsAllowedOrigin());
+    // Access-Control-Allow-Credentials
+    corsHandler.allowCredentials(TransportConfig.isCorsAllowCredentials());
+    // Access-Control-Allow-Headers
+    corsHandler.allowedHeaders(TransportConfig.getCorsAllowedHeaders());
+    // Access-Control-Allow-Methods
+    Set<String> allowedMethods = TransportConfig.getCorsAllowedMethods();
+    for (String method : allowedMethods) {
+      corsHandler.allowedMethod(HttpMethod.valueOf(method));
+    }
+    // Access-Control-Expose-Headers
+    corsHandler.exposedHeaders(TransportConfig.getCorsExposedHeaders());
+    // Access-Control-Max-Age
+    int maxAge = TransportConfig.getCorsMaxAge();
+    if (maxAge >= 0) {
+      corsHandler.maxAgeSeconds(maxAge);
+    }
+
+    LOGGER.info("mount CorsHandler");
+    mainRouter.route().handler(corsHandler);
+  }
+
+  private CorsHandler getCorsHandler(String corsAllowedOrigin) {
+    return CorsHandler.create(corsAllowedOrigin);
+  }
+
   private void initDispatcher(Router mainRouter) {
     List<VertxHttpDispatcher> dispatchers = SPIServiceUtils.getSortedService(VertxHttpDispatcher.class);
     for (VertxHttpDispatcher dispatcher : dispatchers) {
diff --git a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java
index 01f13c8..ae08b0c 100644
--- a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java
+++ b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/TransportConfig.java
@@ -17,6 +17,12 @@
 
 package org.apache.servicecomb.transport.rest.vertx;
 
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.springframework.util.StringUtils;
+
 import com.netflix.config.DynamicIntProperty;
 import com.netflix.config.DynamicPropertyFactory;
 import com.netflix.config.DynamicStringProperty;
@@ -34,6 +40,8 @@ public final class TransportConfig {
   // 32K
   public static final int DEFAULT_SERVER_MAX_HEADER_SIZE = 32 * 1024;
 
+  public static final String SERVICECOMB_CORS_CONFIG_BASE = "servicecomb.cors";
+
   private static Class<? extends Verticle> restServerVerticle = RestServerVerticle.class;
 
   private TransportConfig() {
@@ -78,4 +86,59 @@ public final class TransportConfig {
         .getIntProperty("cse.rest.server.maxHeaderSize", DEFAULT_SERVER_MAX_HEADER_SIZE)
         .get();
   }
+
+  public static boolean isCorsEnabled() {
+    return DynamicPropertyFactory.getInstance()
+        .getBooleanProperty(SERVICECOMB_CORS_CONFIG_BASE + ".enabled", false)
+        .get();
+  }
+
+  public static String getCorsAllowedOrigin() {
+    return DynamicPropertyFactory.getInstance()
+        .getStringProperty(SERVICECOMB_CORS_CONFIG_BASE + ".origin", "*")
+        .get();
+  }
+
+  public static boolean isCorsAllowCredentials() {
+    return DynamicPropertyFactory.getInstance()
+        .getBooleanProperty(SERVICECOMB_CORS_CONFIG_BASE + ".allowCredentials", false)
+        .get();
+  }
+
+  public static Set<String> getCorsAllowedHeaders() {
+    String allowedHeaders = DynamicPropertyFactory.getInstance()
+        .getStringProperty(SERVICECOMB_CORS_CONFIG_BASE + ".allowedHeader", null)
+        .get();
+    return convertToSet(allowedHeaders);
+  }
+
+  public static Set<String> getCorsAllowedMethods() {
+    String allowedMethods = DynamicPropertyFactory.getInstance()
+        .getStringProperty(SERVICECOMB_CORS_CONFIG_BASE + ".allowedMethod", null)
+        .get();
+    return convertToSet(allowedMethods);
+  }
+
+  public static Set<String> getCorsExposedHeaders() {
+    String exposedHeaders = DynamicPropertyFactory.getInstance()
+        .getStringProperty(SERVICECOMB_CORS_CONFIG_BASE + ".exposedHeader", null)
+        .get();
+    return convertToSet(exposedHeaders);
+  }
+
+  public static int getCorsMaxAge() {
+    return DynamicPropertyFactory.getInstance()
+        .getIntProperty(SERVICECOMB_CORS_CONFIG_BASE + ".maxAge", -1)
+        .get();
+  }
+
+  private static Set<String> convertToSet(String setString) {
+    Set<String> resultSet = new HashSet<>();
+    if (!StringUtils.isEmpty(setString)) {
+      String[] arrString = setString.split(",");
+      Stream.of(arrString).map(String::trim).filter(str -> !StringUtils.isEmpty(str))
+          .forEach(resultSet::add);
+    }
+    return resultSet;
+  }
 }
diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java
index 146c561..4b65435 100644
--- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java
+++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestRestServerVerticle.java
@@ -17,22 +17,36 @@
 
 package org.apache.servicecomb.transport.rest.vertx;
 
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
 import org.apache.servicecomb.core.CseContext;
 import org.apache.servicecomb.core.Endpoint;
 import org.apache.servicecomb.core.Transport;
 import org.apache.servicecomb.core.transport.AbstractTransport;
 import org.apache.servicecomb.core.transport.TransportManager;
 import org.apache.servicecomb.foundation.common.net.URIEndpointObject;
+import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
+import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import io.vertx.core.Context;
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpMethod;
 import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.Route;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.handler.CorsHandler;
+import mockit.Deencapsulation;
 import mockit.Expectations;
+import mockit.Mock;
+import mockit.MockUp;
 import mockit.Mocked;
 
 public class TestRestServerVerticle {
@@ -42,7 +56,7 @@ public class TestRestServerVerticle {
   Future<Void> startFuture = null;
 
   @Before
-  public void setUp() throws Exception {
+  public void setUp() {
     instance = new RestServerVerticle();
     startFuture = Future.future();
 
@@ -50,7 +64,7 @@ public class TestRestServerVerticle {
   }
 
   @After
-  public void tearDown() throws Exception {
+  public void tearDown() {
     instance = null;
     startFuture = null;
   }
@@ -112,7 +126,7 @@ public class TestRestServerVerticle {
   @Test
   public void testRestServerVerticleWithHttp2(@Mocked Transport transport, @Mocked Vertx vertx,
       @Mocked Context context,
-      @Mocked JsonObject jsonObject, @Mocked Future<Void> startFuture) throws Exception {
+      @Mocked JsonObject jsonObject, @Mocked Future<Void> startFuture) {
     URIEndpointObject endpointObject = new URIEndpointObject("http://127.0.0.1:8080?protocol=http2");
     new Expectations() {
       {
@@ -164,4 +178,71 @@ public class TestRestServerVerticle {
     }
     Assert.assertFalse(status);
   }
+
+  @Test
+  public void testMountCorsHandler() {
+    ArchaiusUtils.setProperty("servicecomb.cors.enabled", true);
+    ArchaiusUtils.setProperty("servicecomb.cors.allowedMethod", "GET,PUT,POST");
+    ArchaiusUtils.setProperty("servicecomb.cors.allowedHeader", "abc,def");
+    ArchaiusUtils.setProperty("servicecomb.cors.exposedHeader", "abc2,def2");
+    ArchaiusUtils.setProperty("servicecomb.cors.maxAge", 1);
+    Set<HttpMethod> methodSet = new HashSet<>(3);
+    methodSet.add(HttpMethod.GET);
+    methodSet.add(HttpMethod.PUT);
+    methodSet.add(HttpMethod.POST);
+    AtomicInteger counter = new AtomicInteger(0);
+
+    CorsHandler corsHandler = new MockUp<CorsHandler>() {
+      @Mock
+      CorsHandler allowCredentials(boolean allow) {
+        Assert.assertFalse(allow);
+        counter.incrementAndGet();
+        return null;
+      }
+
+      @Mock
+      CorsHandler allowedHeaders(Set<String> headerNames) {
+        Assert.assertThat(headerNames, Matchers.containsInAnyOrder("abc", "def"));
+        counter.incrementAndGet();
+        return null;
+      }
+
+      @Mock
+      CorsHandler exposedHeaders(Set<String> headerNames) {
+        Assert.assertThat(headerNames, Matchers.containsInAnyOrder("abc2", "def2"));
+        counter.incrementAndGet();
+        return null;
+      }
+
+      @Mock
+      CorsHandler allowedMethod(HttpMethod method) {
+        Assert.assertTrue(methodSet.contains(method));
+        counter.incrementAndGet();
+        methodSet.remove(method);
+        return null;
+      }
+
+      @Mock
+      CorsHandler maxAgeSeconds(int maxAgeSeconds) {
+        Assert.assertEquals(1, maxAgeSeconds);
+        counter.incrementAndGet();
+        return null;
+      }
+    }.getMockInstance();
+
+    new MockUp<RestServerVerticle>() {
+      @Mock
+      CorsHandler getCorsHandler(String corsAllowedOrigin) {
+        Assert.assertEquals("*", corsAllowedOrigin);
+        return corsHandler;
+      }
+    };
+    Router router = Mockito.mock(Router.class);
+    Mockito.when(router.route()).thenReturn(Mockito.mock(Route.class));
+
+    RestServerVerticle server = new RestServerVerticle();
+
+    Deencapsulation.invoke(server, "mountCorsHandler", router);
+    Assert.assertEquals(7, counter.get());
+  }
 }
diff --git a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java
index e63dcd5..a3e84f8 100644
--- a/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java
+++ b/transports/transport-rest/transport-rest-vertx/src/test/java/org/apache/servicecomb/transport/rest/vertx/TestTransportConfig.java
@@ -17,26 +17,22 @@
 
 package org.apache.servicecomb.transport.rest.vertx;
 
-import org.apache.commons.configuration.Configuration;
 import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
-import org.junit.AfterClass;
+import org.hamcrest.Matchers;
+import org.junit.After;
 import org.junit.Assert;
-import org.junit.BeforeClass;
+import org.junit.Before;
 import org.junit.Test;
 
-import com.netflix.config.DynamicPropertyFactory;
-
 public class TestTransportConfig {
-  Configuration config = (Configuration) DynamicPropertyFactory.getBackingConfigurationSource();
 
-  @BeforeClass
-  public static void setup() {
+  @Before
+  public void before() {
     ArchaiusUtils.resetConfig();
   }
 
-
-  @AfterClass
-  public static void teardown() {
+  @After
+  public void after() {
     ArchaiusUtils.resetConfig();
   }
 
@@ -47,9 +43,8 @@ public class TestTransportConfig {
 
   @Test
   public void testGetAddressNormal() {
-    config.addProperty("cse.rest.address", "1.1.1.1");
+    ArchaiusUtils.setProperty("cse.rest.address", "1.1.1.1");
     Assert.assertEquals("1.1.1.1", TransportConfig.getAddress());
-    config.clearProperty("cse.rest.address");
   }
 
   @Test
@@ -59,18 +54,90 @@ public class TestTransportConfig {
 
   @Test
   public void testGetThreadCountNormal() {
-    config.addProperty("cse.rest.server.thread-count", 10);
+    ArchaiusUtils.setProperty("cse.rest.server.thread-count", 10);
     Assert.assertEquals(10, TransportConfig.getThreadCount());
-    config.clearProperty("cse.rest.server.thread-count");
   }
 
   @Test
   public void testGetCompressedAndHeaderSize() {
-    config.addProperty("cse.rest.server.compression", true);
+    ArchaiusUtils.setProperty("cse.rest.server.compression", true);
     Assert.assertEquals(true, TransportConfig.getCompressed());
-    config.addProperty("cse.rest.server.maxHeaderSize", 2048);
+    ArchaiusUtils.setProperty("cse.rest.server.maxHeaderSize", 2048);
     Assert.assertEquals(2048, TransportConfig.getMaxHeaderSize());
-    config.clearProperty("cse.rest.server.compression");
-    config.clearProperty("cse.rest.server.maxHeaderSize");
+  }
+
+  @Test
+  public void testIsCorsEnabled() {
+    Assert.assertFalse(TransportConfig.isCorsEnabled());
+    ArchaiusUtils.setProperty("servicecomb.cors.enabled", true);
+    Assert.assertTrue(TransportConfig.isCorsEnabled());
+    ArchaiusUtils.setProperty("servicecomb.cors.enabled", false);
+    Assert.assertFalse(TransportConfig.isCorsEnabled());
+  }
+
+  @Test
+  public void testGetCorsAllowedOrigin() {
+    Assert.assertEquals("*", TransportConfig.getCorsAllowedOrigin());
+    String origin = "http://localhost:8080";
+    ArchaiusUtils.setProperty("servicecomb.cors.origin", origin);
+    Assert.assertEquals(origin, TransportConfig.getCorsAllowedOrigin());
+  }
+
+  @Test
+  public void testIsCorsAllowCredentials() {
+    Assert.assertFalse(TransportConfig.isCorsAllowCredentials());
+    ArchaiusUtils.setProperty("servicecomb.cors.allowCredentials", true);
+    Assert.assertTrue(TransportConfig.isCorsAllowCredentials());
+    ArchaiusUtils.setProperty("servicecomb.cors.allowCredentials", false);
+    Assert.assertFalse(TransportConfig.isCorsAllowCredentials());
+  }
+
+  @Test
+  public void testGetCorsAllowedHeaders() {
+    String configKey = "servicecomb.cors.allowedHeader";
+    Assert.assertTrue(TransportConfig.getCorsAllowedHeaders().isEmpty());
+    ArchaiusUtils.setProperty(configKey, "abc");
+    Assert.assertThat(TransportConfig.getCorsAllowedHeaders(), Matchers.containsInAnyOrder("abc"));
+    ArchaiusUtils.setProperty(configKey, "abc, def");
+    Assert.assertThat(TransportConfig.getCorsAllowedHeaders(), Matchers.containsInAnyOrder("abc", "def"));
+    ArchaiusUtils.setProperty(configKey, "abc ,, def");
+    Assert.assertThat(TransportConfig.getCorsAllowedHeaders(), Matchers.containsInAnyOrder("abc", "def"));
+    ArchaiusUtils.setProperty(configKey, "");
+    Assert.assertTrue(TransportConfig.getCorsAllowedHeaders().isEmpty());
+  }
+
+  @Test
+  public void testGetCorsAllowedMethods() {
+    String configKey = "servicecomb.cors.allowedMethod";
+    Assert.assertTrue(TransportConfig.getCorsAllowedMethods().isEmpty());
+    ArchaiusUtils.setProperty(configKey, "GET");
+    Assert.assertThat(TransportConfig.getCorsAllowedMethods(), Matchers.containsInAnyOrder("GET"));
+    ArchaiusUtils.setProperty(configKey, "GET, POST");
+    Assert.assertThat(TransportConfig.getCorsAllowedMethods(), Matchers.containsInAnyOrder("GET", "POST"));
+    ArchaiusUtils.setProperty(configKey, "GET,,POST");
+    Assert.assertThat(TransportConfig.getCorsAllowedMethods(), Matchers.containsInAnyOrder("GET", "POST"));
+    ArchaiusUtils.setProperty(configKey, "");
+    Assert.assertTrue(TransportConfig.getCorsAllowedMethods().isEmpty());
+  }
+
+  @Test
+  public void testGetCorsExposedHeaders() {
+    String configKey = "servicecomb.cors.exposedHeader";
+    Assert.assertTrue(TransportConfig.getCorsExposedHeaders().isEmpty());
+    ArchaiusUtils.setProperty(configKey, "abc");
+    Assert.assertThat(TransportConfig.getCorsExposedHeaders(), Matchers.containsInAnyOrder("abc"));
+    ArchaiusUtils.setProperty(configKey, "abc, def");
+    Assert.assertThat(TransportConfig.getCorsExposedHeaders(), Matchers.containsInAnyOrder("abc", "def"));
+    ArchaiusUtils.setProperty(configKey, "abc ,, def");
+    Assert.assertThat(TransportConfig.getCorsExposedHeaders(), Matchers.containsInAnyOrder("abc", "def"));
+    ArchaiusUtils.setProperty(configKey, "");
+    Assert.assertTrue(TransportConfig.getCorsExposedHeaders().isEmpty());
+  }
+
+  @Test
+  public void testGetCorsMaxAge() {
+    Assert.assertEquals(-1, TransportConfig.getCorsMaxAge());
+    ArchaiusUtils.setProperty("servicecomb.cors.maxAge", 3600);
+    Assert.assertEquals(3600, TransportConfig.getCorsMaxAge());
   }
 }