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/servicecomb-java-chassis.git


The following commit(s) were added to refs/heads/master by this push:
     new cbc614f  [SCB-1847] create a new edge dispatcher to forward http 
requests to provider
cbc614f is described below

commit cbc614f668339b0f993374e509494e484e52fd19
Author: liubao <[email protected]>
AuthorDate: Thu Apr 2 20:21:44 2020 +0800

    [SCB-1847] create a new edge dispatcher to forward http requests to provider
---
 .../org/apache/servicecomb/core/Invocation.java    |   2 +-
 ...gumentExceptionToProducerResponseConverter.java |  46 +++++
 ...owableExceptionToProducerResponseConverter.java |  45 +++++
 .../handler/impl/ProducerOperationHandler.java     |  26 +--
 ....exception.ExceptionToProducerResponseConverter |   4 +-
 .../servicecomb/demo/edge/consumer/Consumer.java   |   6 +-
 .../demo/edge/consumer/ConsumerMain.java           |   2 +
 .../src/main/resources/microservice.yaml           |  18 ++
 .../jaxrs/client/MultiErrorCodeServiceClient.java  |   6 +-
 edge/edge-core/pom.xml                             |   5 +-
 .../edge/core/CommonHttpEdgeDispatcher.java        | 192 +++++++++++++++++++++
 .../edge/core/URLMappedEdgeDispatcher.java         |  28 ++-
 ...cecomb.transport.rest.vertx.VertxHttpDispatcher |   3 +-
 .../edge/core/TestURLMappedEdgeDispatcher.java     |   9 +
 .../tests/SpringMvcIntegrationTestBase.java        |   7 +-
 .../ExceptionToProducerResponseConverter.java      |   6 +
 .../transport/rest/vertx/RestBodyHandler.java      |  31 ++--
 17 files changed, 387 insertions(+), 49 deletions(-)

diff --git a/core/src/main/java/org/apache/servicecomb/core/Invocation.java 
b/core/src/main/java/org/apache/servicecomb/core/Invocation.java
index 049316c..8a36c66 100644
--- a/core/src/main/java/org/apache/servicecomb/core/Invocation.java
+++ b/core/src/main/java/org/apache/servicecomb/core/Invocation.java
@@ -144,7 +144,7 @@ public class Invocation extends SwaggerInvocation {
 
   public Invocation() {
     // An empty invocation, used to mock or some other scenario do not need 
operation information.
-       traceIdLogger = new TraceIdLogger(this);
+    traceIdLogger = new TraceIdLogger(this);
   }
 
   public Invocation(ReferenceConfig referenceConfig, OperationMeta 
operationMeta,
diff --git 
a/core/src/main/java/org/apache/servicecomb/core/exception/IllegalArgumentExceptionToProducerResponseConverter.java
 
b/core/src/main/java/org/apache/servicecomb/core/exception/IllegalArgumentExceptionToProducerResponseConverter.java
new file mode 100644
index 0000000..c3c8d2e
--- /dev/null
+++ 
b/core/src/main/java/org/apache/servicecomb/core/exception/IllegalArgumentExceptionToProducerResponseConverter.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.core.exception;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.SwaggerInvocation;
+import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
+import 
org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+
+public class IllegalArgumentExceptionToProducerResponseConverter implements
+    ExceptionToProducerResponseConverter<IllegalArgumentException> {
+  @Override
+  public Class<IllegalArgumentException> getExceptionClass() {
+    return IllegalArgumentException.class;
+  }
+
+  @Override
+  public int getOrder() {
+    return 10000;
+  }
+
+  @Override
+  public Response convert(SwaggerInvocation swaggerInvocation, 
IllegalArgumentException e) {
+    InvocationException invocationException = new 
InvocationException(Status.BAD_REQUEST.getStatusCode(), "",
+        new CommonExceptionData("Parameters not valid or types not match."), 
e);
+    return Response.failResp(invocationException);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/servicecomb/core/exception/ThrowableExceptionToProducerResponseConverter.java
 
b/core/src/main/java/org/apache/servicecomb/core/exception/ThrowableExceptionToProducerResponseConverter.java
new file mode 100644
index 0000000..4ffa2ec
--- /dev/null
+++ 
b/core/src/main/java/org/apache/servicecomb/core/exception/ThrowableExceptionToProducerResponseConverter.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.core.exception;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.SwaggerInvocation;
+import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
+import 
org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+
+public class ThrowableExceptionToProducerResponseConverter implements 
ExceptionToProducerResponseConverter<Throwable> {
+  @Override
+  public Class<Throwable> getExceptionClass() {
+    return Throwable.class;
+  }
+
+  @Override
+  public int getOrder() {
+    return 20000;
+  }
+
+  @Override
+  public Response convert(SwaggerInvocation swaggerInvocation, Throwable e) {
+    InvocationException invocationException = new 
InvocationException(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "",
+        new CommonExceptionData("Unexpected exception when processing the 
request."), e);
+    return Response.failResp(invocationException);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/servicecomb/core/handler/impl/ProducerOperationHandler.java
 
b/core/src/main/java/org/apache/servicecomb/core/handler/impl/ProducerOperationHandler.java
index 22df2f9..3845e6b 100644
--- 
a/core/src/main/java/org/apache/servicecomb/core/handler/impl/ProducerOperationHandler.java
+++ 
b/core/src/main/java/org/apache/servicecomb/core/handler/impl/ProducerOperationHandler.java
@@ -20,8 +20,6 @@ package org.apache.servicecomb.core.handler.impl;
 import java.lang.reflect.InvocationTargetException;
 import java.util.concurrent.CompletableFuture;
 
-import javax.ws.rs.core.Response.Status;
-
 import org.apache.servicecomb.core.Handler;
 import org.apache.servicecomb.core.Invocation;
 import org.apache.servicecomb.core.exception.ExceptionUtils;
@@ -30,7 +28,6 @@ import 
org.apache.servicecomb.swagger.invocation.AsyncResponse;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.SwaggerInvocation;
 import org.apache.servicecomb.swagger.invocation.context.ContextUtils;
-import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
 import org.apache.servicecomb.swagger.invocation.exception.ExceptionFactory;
 import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
 import 
org.apache.servicecomb.swagger.invocation.extension.ProducerInvokeExtension;
@@ -93,17 +90,11 @@ public class ProducerOperationHandler implements Handler {
 
         asyncResp.handle(processException(invocation, ex));
       });
-    } catch (IllegalArgumentException ae) {
-      invocation.getTraceIdLogger().error(LOGGER, "Parameters not valid or 
types not match {},",
-          invocation.getInvocationQualifiedName(), ae);
-      invocation.onBusinessMethodFinish();
-      invocation.onBusinessFinish();
-      asyncResp.handle(processException(invocation,
-          new InvocationException(Status.BAD_REQUEST.getStatusCode(), "",
-              new CommonExceptionData("Parameters not valid or types not 
match."), ae)));
     } catch (Throwable e) {
-      invocation.getTraceIdLogger().error(LOGGER, "unexpected error {},",
-          invocation.getInvocationQualifiedName(), e);
+      if (shouldPrintErrorLog(e)) {
+        invocation.getTraceIdLogger().error(LOGGER, "unexpected error {},",
+            invocation.getInvocationQualifiedName(), e);
+      }
       invocation.onBusinessMethodFinish();
       invocation.onBusinessFinish();
       asyncResp.handle(processException(invocation, e));
@@ -132,15 +123,6 @@ public class ProducerOperationHandler implements Handler {
 
       invocation.onBusinessMethodFinish();
       invocation.onBusinessFinish();
-    } catch (IllegalArgumentException ae) {
-      invocation.getTraceIdLogger().error(LOGGER, "Parameters not valid or 
types not match {},",
-          invocation.getInvocationQualifiedName(), ae);
-      invocation.onBusinessMethodFinish();
-      invocation.onBusinessFinish();
-      // ae.getMessage() is always null. Give a custom error message.
-      response = processException(invocation,
-          new InvocationException(Status.BAD_REQUEST.getStatusCode(), "",
-              new CommonExceptionData("Parameters not valid or types not 
match."), ae));
     } catch (Throwable e) {
       if (shouldPrintErrorLog(e)) {
         invocation.getTraceIdLogger().error(LOGGER, "unexpected error {},",
diff --git 
a/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
 
b/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter
similarity index 81%
copy from 
edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
copy to 
core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter
index d81bb22..f703b90 100644
--- 
a/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
+++ 
b/core/src/main/resources/META-INF/services/org.apache.servicecomb.swagger.invocation.exception.ExceptionToProducerResponseConverter
@@ -15,5 +15,5 @@
 # limitations under the License.
 #
 
-org.apache.servicecomb.edge.core.DefaultEdgeDispatcher
-org.apache.servicecomb.edge.core.URLMappedEdgeDispatcher
\ No newline at end of file
+org.apache.servicecomb.core.exception.IllegalArgumentExceptionToProducerResponseConverter
+org.apache.servicecomb.core.exception.ThrowableExceptionToProducerResponseConverter
\ No newline at end of file
diff --git 
a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/Consumer.java
 
b/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/Consumer.java
index 7d5d567..973e556 100644
--- 
a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/Consumer.java
+++ 
b/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/Consumer.java
@@ -168,8 +168,12 @@ public class Consumer {
     int response = template.getForObject(url + "?x=2&y=3", Integer.class);
     Assert.isTrue(response == 5, "not get 5.");
 
+    try {
     Map raw = template.getForObject(url + "?x=99&y=3", Map.class);
-    Assert.isTrue(raw.get("message").equals("Cse Internal Server Error"), 
"x99");
+    } catch (HttpServerErrorException e) {
+      Assert.isTrue(e.getRawStatusCode() == 500, "x99");
+      Assert.isTrue(e.getResponseBodyAsString().contains("Unexpected exception 
when processing the request"), "x99");
+    }
 
     try {
       template.getForObject(url + "?x=88&y=3", Map.class);
diff --git 
a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java
 
b/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java
index b4378b5..1bcfc30 100644
--- 
a/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java
+++ 
b/demo/demo-edge/consumer/src/main/java/org/apache/servicecomb/demo/edge/consumer/ConsumerMain.java
@@ -33,6 +33,8 @@ public class ConsumerMain {
     new Consumer().run("rest");
     System.out.println("Running url dispatcher.");
     new Consumer().run("url");
+    System.out.println("Running http dispatcher.");
+    new Consumer().run("http");
 
     System.out.println("All test case finished.");
   }
diff --git a/demo/demo-edge/edge-service/src/main/resources/microservice.yaml 
b/demo/demo-edge/edge-service/src/main/resources/microservice.yaml
index beaa93b..f4f2489 100644
--- a/demo/demo-edge/edge-service/src/main/resources/microservice.yaml
+++ b/demo/demo-edge/edge-service/src/main/resources/microservice.yaml
@@ -56,3 +56,21 @@ servicecomb:
               path: "/url/business/v2/.*"
               microserviceName: business
               versionRule: 2.0.0-3.0.0
+        http:
+          enabled: true
+          mappings:
+            businessV2:
+              prefixSegmentCount: 1
+              path: "/http/business/v2/.*"
+              microserviceName: business
+              versionRule: 2.0.0
+            businessV1:
+              prefixSegmentCount: 1
+              path: "/http/business/v1/add.*"
+              microserviceName: business
+              versionRule: 1.0.0-1.2.0
+            businessV1_1:
+              prefixSegmentCount: 1
+              path: "/http/business/v1/dec.*"
+              microserviceName: business
+              versionRule: 1.1.0
\ No newline at end of file
diff --git 
a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java
 
b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java
index 3a7ec70..2ff8d0a 100644
--- 
a/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java
+++ 
b/demo/demo-jaxrs/jaxrs-client/src/main/java/org/apache/servicecomb/demo/jaxrs/client/MultiErrorCodeServiceClient.java
@@ -41,8 +41,8 @@ import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
 import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
 import org.springframework.web.client.RestTemplate;
-import org.springframework.web.client.UnknownHttpStatusCodeException;
 
 import io.vertx.core.json.Json;
 import io.vertx.core.json.JsonObject;
@@ -110,8 +110,8 @@ public class MultiErrorCodeServiceClient implements 
CategorizedTestCase {
       result = template
           .postForEntity(serverDirectURL + "/MultiErrorCodeService/errorCode", 
entity, MultiResponse200.class);
       TestMgr.check(590, 200);
-    } catch (UnknownHttpStatusCodeException e) {
-      TestMgr.check(e.getRawStatusCode(), 590);
+    } catch (HttpServerErrorException e) {
+      TestMgr.check(e.getRawStatusCode(), 500);
     }
 
     // not recommend
diff --git a/edge/edge-core/pom.xml b/edge/edge-core/pom.xml
index e50c5c1..86454f5 100644
--- a/edge/edge-core/pom.xml
+++ b/edge/edge-core/pom.xml
@@ -31,7 +31,10 @@
       <groupId>org.apache.servicecomb</groupId>
       <artifactId>transport-rest-vertx</artifactId>
     </dependency>
-
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>handler-loadbalance</artifactId>
+    </dependency>
     <dependency>
       <groupId>io.vertx</groupId>
       <artifactId>vertx-codegen</artifactId>
diff --git 
a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/CommonHttpEdgeDispatcher.java
 
b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/CommonHttpEdgeDispatcher.java
new file mode 100644
index 0000000..ac8f30d
--- /dev/null
+++ 
b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/CommonHttpEdgeDispatcher.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.servicecomb.edge.core;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.foundation.common.cache.VersionedCache;
+import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
+import org.apache.servicecomb.foundation.common.net.URIEndpointObject;
+import org.apache.servicecomb.foundation.vertx.client.http.HttpClients;
+import org.apache.servicecomb.loadbalance.ExtensionsManager;
+import org.apache.servicecomb.loadbalance.LoadBalancer;
+import org.apache.servicecomb.loadbalance.LoadbalanceHandler;
+import org.apache.servicecomb.loadbalance.RuleExt;
+import org.apache.servicecomb.loadbalance.ServiceCombServer;
+import org.apache.servicecomb.loadbalance.filter.ServerDiscoveryFilter;
+import org.apache.servicecomb.serviceregistry.RegistryUtils;
+import org.apache.servicecomb.serviceregistry.discovery.DiscoveryContext;
+import org.apache.servicecomb.serviceregistry.discovery.DiscoveryTree;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.netflix.config.ConcurrentCompositeConfiguration;
+import com.netflix.config.DynamicPropertyFactory;
+
+import io.vertx.core.http.HttpClient;
+import io.vertx.core.http.HttpClientRequest;
+import io.vertx.core.http.RequestOptions;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+
+/**
+ * This dispatcher forwards requests to any http servers, includes 
java-chassis providers and other,
+ * provided the server is registered to service center.
+ *
+ * This dispatcher using loadbalance handler to choose the target server. So 
any functions
+ * provided by loadbalancer handler is available, excluding retrying.
+ */
+public class CommonHttpEdgeDispatcher extends AbstractEdgeDispatcher {
+  private static final Logger LOG = 
LoggerFactory.getLogger(CommonHttpEdgeDispatcher.class);
+
+  private static final String KEY_ENABLED = 
"servicecomb.http.dispatcher.edge.http.enabled";
+
+  private static final String KEY_ORDER = 
"servicecomb.http.dispatcher.edge.http.order";
+
+  private static final String KEY_PATTERN = 
"servicecomb.http.dispatcher.edge.http.pattern";
+
+  private static final String PATTERN_ANY = "/(.*)";
+
+  private static final String KEY_MAPPING_PREFIX = 
"servicecomb.http.dispatcher.edge.http.mappings";
+
+  private Map<String, LoadBalancer> loadBalancerMap = new 
ConcurrentHashMapEx<>();
+
+  private Map<String, URLMappedConfigurationItem> configurations = new 
HashMap<>();
+
+  private DiscoveryTree discoveryTree;
+
+  public CommonHttpEdgeDispatcher() {
+    if (this.enabled()) {
+      loadConfigurations();
+      discoveryTree = new DiscoveryTree();
+      discoveryTree.addFilter(new ServerDiscoveryFilter());
+    }
+  }
+
+  @Override
+  public int getOrder() {
+    return DynamicPropertyFactory.getInstance().getIntProperty(KEY_ORDER, 
40_000).get();
+  }
+
+  @Override
+  public boolean enabled() {
+    return 
DynamicPropertyFactory.getInstance().getBooleanProperty(KEY_ENABLED, 
false).get();
+  }
+
+  @Override
+  public void init(Router router) {
+    String pattern = 
DynamicPropertyFactory.getInstance().getStringProperty(KEY_PATTERN, 
PATTERN_ANY).get();
+    
router.routeWithRegex(pattern).failureHandler(this::onFailure).handler(this::onRequest);
+  }
+
+  private void loadConfigurations() {
+    ConcurrentCompositeConfiguration config = 
(ConcurrentCompositeConfiguration) DynamicPropertyFactory
+        .getBackingConfigurationSource();
+    configurations = URLMappedConfigurationLoader.loadConfigurations(config, 
KEY_MAPPING_PREFIX);
+    config.addConfigurationListener(event -> {
+      if (event.getPropertyName().startsWith(KEY_MAPPING_PREFIX)) {
+        LOG.info("Map rule have been changed. Reload configurations. Event=" + 
event.getType());
+        configurations = 
URLMappedConfigurationLoader.loadConfigurations(config, KEY_MAPPING_PREFIX);
+      }
+    });
+  }
+
+  @SuppressWarnings("deprecation")
+  protected void onRequest(RoutingContext context) {
+    URLMappedConfigurationItem configurationItem = 
findConfigurationItem(context.request().uri());
+    if (configurationItem == null) {
+      context.next();
+      return;
+    }
+
+    String uri = Utils.findActualPath(context.request().uri(), 
configurationItem.getPrefixSegmentCount());
+
+    Invocation invocation = new Invocation() {
+      @Override
+      public String getConfigTransportName() {
+        return "rest";
+      }
+    };
+
+    LoadBalancer loadBalancer = getOrCreateLoadBalancer(invocation, 
configurationItem.getMicroserviceName(),
+        configurationItem.getVersionRule());
+    ServiceCombServer server = loadBalancer.chooseServer(invocation);
+    URIEndpointObject endpointObject = new 
URIEndpointObject(server.getEndpoint().getEndpoint());
+
+    RequestOptions requestOptions = new RequestOptions();
+    requestOptions.setHost(endpointObject.getHostOrIp())
+        .setPort(endpointObject.getPort())
+        .setSsl(endpointObject.isSslEnabled())
+        .setURI(uri);
+
+    // TODO: now use registry client, after next PR transport client is fixed, 
using that.
+    HttpClient httpClient = HttpClients.getClient("registry").getHttpClient();
+    HttpClientRequest httpClientRequest = httpClient
+        .request(context.request().method(), requestOptions, 
httpClientResponse -> {
+          context.response().setStatusCode(httpClientResponse.statusCode());
+          httpClientResponse.headers().forEach((header) -> {
+            // any headers need to exclude can add here
+//              if ("Content-Length".equalsIgnoreCase(header.getKey())) {
+//                return;
+//              }
+            context.response().headers().set(header.getKey(), 
header.getValue());
+          });
+          httpClientResponse.handler(data -> {
+            context.response().write(data);
+          });
+          httpClientResponse.endHandler((v) -> context.response().end());
+        });
+    context.request().headers().forEach((header) -> {
+      // any headers need to exclude can add here
+//              if ("Content-Length".equalsIgnoreCase(header.getKey())) {
+//                return;
+//              }
+      httpClientRequest.headers().set(header.getKey(), header.getValue());
+    });
+    context.request().handler(data -> httpClientRequest.write(data));
+    context.request().endHandler((v) -> httpClientRequest.end());
+  }
+
+  protected LoadBalancer getOrCreateLoadBalancer(Invocation invocation, String 
microserviceName, String versionRule) {
+    DiscoveryContext context = new DiscoveryContext();
+    context.setInputParameters(invocation);
+    VersionedCache serversVersionedCache = discoveryTree.discovery(context,
+        RegistryUtils.getAppId(),
+        microserviceName,
+        versionRule);
+    invocation.addLocalContext(LoadbalanceHandler.CONTEXT_KEY_SERVER_LIST, 
serversVersionedCache.data());
+    return loadBalancerMap
+        .computeIfAbsent(microserviceName, name -> 
createLoadBalancer(microserviceName));
+  }
+
+  private LoadBalancer createLoadBalancer(String microserviceName) {
+    RuleExt rule = ExtensionsManager.createLoadBalancerRule(microserviceName);
+    return new LoadBalancer(rule, microserviceName);
+  }
+
+  private URLMappedConfigurationItem findConfigurationItem(String path) {
+    for (URLMappedConfigurationItem item : configurations.values()) {
+      if (item.getPattern().matcher(path).matches()) {
+        return item;
+      }
+    }
+    return null;
+  }
+}
diff --git 
a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/URLMappedEdgeDispatcher.java
 
b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/URLMappedEdgeDispatcher.java
index 7bb1a88..014c1e9 100644
--- 
a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/URLMappedEdgeDispatcher.java
+++ 
b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/URLMappedEdgeDispatcher.java
@@ -20,6 +20,7 @@ package org.apache.servicecomb.edge.core;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.servicecomb.transport.rest.vertx.RestBodyHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,6 +36,8 @@ import io.vertx.ext.web.RoutingContext;
 public class URLMappedEdgeDispatcher extends AbstractEdgeDispatcher {
   private static final Logger LOG = 
LoggerFactory.getLogger(URLMappedEdgeDispatcher.class);
 
+  public static final String CONFIGURATION_ITEM = "URLMappedConfigurationItem";
+
   private static final String PATTERN_ANY = "/(.*)";
 
   private static final String KEY_ORDER = 
"servicecomb.http.dispatcher.edge.url.order";
@@ -67,8 +70,10 @@ public class URLMappedEdgeDispatcher extends 
AbstractEdgeDispatcher {
   public void init(Router router) {
     // cookies handler are enabled by default start from 3.8.3
     String pattern = 
DynamicPropertyFactory.getInstance().getStringProperty(KEY_PATTERN, 
PATTERN_ANY).get();
-    router.routeWithRegex(pattern).handler(createBodyHandler());
-    
router.routeWithRegex(pattern).failureHandler(this::onFailure).handler(this::onRequest);
+    router.routeWithRegex(pattern).failureHandler(this::onFailure)
+        .handler(this::preCheck)
+        .handler(createBodyHandler())
+        .handler(this::onRequest);
   }
 
   private void loadConfigurations() {
@@ -83,14 +88,29 @@ public class URLMappedEdgeDispatcher extends 
AbstractEdgeDispatcher {
     });
   }
 
-
-  protected void onRequest(RoutingContext context) {
+  protected void preCheck(RoutingContext context) {
     URLMappedConfigurationItem configurationItem = 
findConfigurationItem(context.request().path());
     if (configurationItem == null) {
+      // by pass body handler flag
+      context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.TRUE);
+      context.next();
+      return;
+    }
+    context.put(CONFIGURATION_ITEM, configurationItem);
+    context.next();
+  }
+
+  protected void onRequest(RoutingContext context) {
+    Boolean bypass = context.get(RestBodyHandler.BYPASS_BODY_HANDLER);
+    if (Boolean.TRUE.equals(bypass)) {
+      // clear flag
+      context.put(RestBodyHandler.BYPASS_BODY_HANDLER, Boolean.FALSE);
       context.next();
       return;
     }
 
+    URLMappedConfigurationItem configurationItem = 
context.get(CONFIGURATION_ITEM);
+
     String path = Utils.findActualPath(context.request().path(), 
configurationItem.getPrefixSegmentCount());
 
     EdgeInvocation edgeInvocation = createEdgeInvocation();
diff --git 
a/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
 
b/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
index d81bb22..0f1d5d6 100644
--- 
a/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
+++ 
b/edge/edge-core/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
@@ -16,4 +16,5 @@
 #
 
 org.apache.servicecomb.edge.core.DefaultEdgeDispatcher
-org.apache.servicecomb.edge.core.URLMappedEdgeDispatcher
\ No newline at end of file
+org.apache.servicecomb.edge.core.URLMappedEdgeDispatcher
+org.apache.servicecomb.edge.core.CommonHttpEdgeDispatcher
\ No newline at end of file
diff --git 
a/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestURLMappedEdgeDispatcher.java
 
b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestURLMappedEdgeDispatcher.java
index b575c2d..a209a40 100644
--- 
a/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestURLMappedEdgeDispatcher.java
+++ 
b/edge/edge-core/src/test/java/org/apache/servicecomb/edge/core/TestURLMappedEdgeDispatcher.java
@@ -20,6 +20,7 @@ package org.apache.servicecomb.edge.core;
 import java.util.Map;
 
 import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
+import org.apache.servicecomb.transport.rest.vertx.RestBodyHandler;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -54,6 +55,8 @@ public class TestURLMappedEdgeDispatcher {
 
     new Expectations() {
       {
+        context.get(RestBodyHandler.BYPASS_BODY_HANDLER);
+        result = Boolean.TRUE;
         context.next();
       }
     };
@@ -81,8 +84,14 @@ public class TestURLMappedEdgeDispatcher {
     Assert.assertEquals(item.getStringPattern(), "/a/b/c/.*");
     Assert.assertEquals(item.getVersionRule(), "2.0.0+");
 
+    URLMappedConfigurationItem finalItem = item;
     new Expectations() {
       {
+        context.get(RestBodyHandler.BYPASS_BODY_HANDLER);
+        result = Boolean.FALSE;
+        context.get(URLMappedEdgeDispatcher.CONFIGURATION_ITEM);
+        result = finalItem;
+
         context.request();
         result = requst;
         requst.path();
diff --git 
a/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcIntegrationTestBase.java
 
b/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcIntegrationTestBase.java
index 12a1356..e2a4efb 100644
--- 
a/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcIntegrationTestBase.java
+++ 
b/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcIntegrationTestBase.java
@@ -68,6 +68,7 @@ import org.springframework.util.MultiValueMap;
 import org.springframework.util.concurrent.ListenableFuture;
 import org.springframework.util.concurrent.ListenableFutureCallback;
 import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
 import org.springframework.web.client.RestClientException;
 import org.springframework.web.client.RestTemplate;
 import org.springframework.web.client.UnknownHttpStatusCodeException;
@@ -639,9 +640,9 @@ public class SpringMvcIntegrationTestBase {
       restTemplate
           .getForEntity(controllerUrl + "sayhi?name=throwexception", 
String.class);
       assertFalse(true);
-    } catch (UnknownHttpStatusCodeException e) {
-      assertThat(e.getRawStatusCode(), is(590));
-      assertThat(e.getResponseBodyAsString(), is("{\"message\":\"Cse Internal 
Server Error\"}"));
+    } catch (HttpServerErrorException e) {
+      assertThat(e.getRawStatusCode(), is(500));
+      assertThat(e.getResponseBodyAsString(), is("{\"message\":\"Unexpected 
exception when processing the request.\"}"));
     }
   }
 
diff --git 
a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverter.java
 
b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverter.java
index a74b471..5bb9382 100644
--- 
a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverter.java
+++ 
b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/exception/ExceptionToProducerResponseConverter.java
@@ -19,6 +19,12 @@ package org.apache.servicecomb.swagger.invocation.exception;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.SwaggerInvocation;
 
+/**
+ *  ExceptionToProducerResponseConverter are used to convert provider 
Exceptions to user friendly messages.
+ *  They are called when :
+ *  1. exception happens executing business logic
+ *  2. exception happens in ProducerInvokeExtension.beforeMethodInvoke(e.g. 
parameter validation)
+ */
 public interface ExceptionToProducerResponseConverter<T extends Throwable> {
   Class<T> getExceptionClass();
 
diff --git 
a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java
 
b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java
index 257d95d..be4e774 100644
--- 
a/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java
+++ 
b/transports/transport-rest/transport-rest-vertx/src/main/java/org/apache/servicecomb/transport/rest/vertx/RestBodyHandler.java
@@ -60,15 +60,21 @@ public class RestBodyHandler implements BodyHandler {
   private static final String BODY_HANDLED = "__body-handled";
 
   private long bodyLimit = DEFAULT_BODY_LIMIT;
+
   private boolean handleFileUploads;
+
   private String uploadsDir;
 
   private boolean mergeFormAttributes = DEFAULT_MERGE_FORM_ATTRIBUTES;
 
   private boolean deleteUploadedFilesOnEnd = 
DEFAULT_DELETE_UPLOADED_FILES_ON_END;
+
   private boolean isPreallocateBodyBuffer = DEFAULT_PREALLOCATE_BODY_BUFFER;
+
   private static final int DEFAULT_INITIAL_BODY_BUFFER_SIZE = 1024; //bytes
 
+  public static final String BYPASS_BODY_HANDLER = "__bypass_body_handler";
+
   public RestBodyHandler() {
     this(true, DEFAULT_UPLOADS_DIRECTORY);
   }
@@ -94,6 +100,12 @@ public class RestBodyHandler implements BodyHandler {
       return;
     }
 
+    Boolean bypass = context.get(BYPASS_BODY_HANDLER);
+    if (Boolean.TRUE.equals(bypass)) {
+      context.next();
+      return;
+    }
+
     // we need to keep state since we can be called again on reroute
     Boolean handled = context.get(BODY_HANDLED);
     if (handled == null || !handled) {
@@ -150,14 +162,13 @@ public class RestBodyHandler implements BodyHandler {
 
   private long parseContentLengthHeader(HttpServerRequest request) {
     String contentLength = request.getHeader(HttpHeaders.CONTENT_LENGTH);
-    if(contentLength == null || contentLength.isEmpty()) {
+    if (contentLength == null || contentLength.isEmpty()) {
       return -1;
     }
     try {
       long parsedContentLength = Long.parseLong(contentLength);
-      return  parsedContentLength < 0 ? null : parsedContentLength;
-    }
-    catch (NumberFormatException ex) {
+      return parsedContentLength < 0 ? null : parsedContentLength;
+    } catch (NumberFormatException ex) {
       return -1;
     }
   }
@@ -246,18 +257,16 @@ public class RestBodyHandler implements BodyHandler {
 
     private void initBodyBuffer(long contentLength) {
       int initialBodyBufferSize;
-      if(contentLength < 0) {
+      if (contentLength < 0) {
         initialBodyBufferSize = DEFAULT_INITIAL_BODY_BUFFER_SIZE;
-      }
-      else if(contentLength > MAX_PREALLOCATED_BODY_BUFFER_BYTES) {
+      } else if (contentLength > MAX_PREALLOCATED_BODY_BUFFER_BYTES) {
         initialBodyBufferSize = MAX_PREALLOCATED_BODY_BUFFER_BYTES;
-      }
-      else {
+      } else {
         initialBodyBufferSize = (int) contentLength;
       }
 
-      if(bodyLimit != -1) {
-        initialBodyBufferSize = (int)Math.min(initialBodyBufferSize, 
bodyLimit);
+      if (bodyLimit != -1) {
+        initialBodyBufferSize = (int) Math.min(initialBodyBufferSize, 
bodyLimit);
       }
 
       this.body = Buffer.buffer(initialBodyBufferSize);

Reply via email to