You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by ma...@apache.org on 2019/08/07 10:41:54 UTC

[servicecomb-samples] 13/37: 增加分布式运维的例子

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

mabin pushed a commit to branch houserush-sample
in repository https://gitbox.apache.org/repos/asf/servicecomb-samples.git

commit e51b877c927d39f421ea3a215625b78012ee340b
Author: liubao <ba...@huawei.com>
AuthorDate: Fri Apr 26 17:56:08 2019 +0800

    增加分布式运维的例子
---
 .../api/{ => common/endpoint}/pom.xml              |  20 +-
 .../samples/porter/file/api/InspectorEndpoint.java | 206 +++++++++++++++++++++
 .../samples/porter/file/api/LogEndpoint.java       |  72 +++++++
 porter_lightweight/api/{ => common}/pom.xml        |   8 +-
 .../api/{ => common/service}/pom.xml               |  13 +-
 .../samples/porter/common/api/LogService.java      |  26 +++
 porter_lightweight/api/pom.xml                     |   1 +
 porter_lightweight/file-service/pom.xml            |   9 +
 .../main/resources/META-INF/spring/file.bean.xml   |   3 +-
 .../src/main/resources/microservice.yaml           |  13 +-
 porter_lightweight/gateway-service/pom.xml         |   9 +
 .../porter/gateway/CustomVertxRestDispatcher.java  | 196 ++++++++++++++++++++
 ...cecomb.transport.rest.vertx.VertxHttpDispatcher |   3 +-
 .../main/resources/META-INF/spring/cse.bean.xml}   |  16 +-
 .../src/main/resources/microservice.yaml           |  13 ++
 porter_lightweight/user-service/pom.xml            |   9 +
 .../main/resources/META-INF/spring/user.bean.xml   |   4 +-
 .../src/main/resources/microservice.yaml           |  13 +-
 18 files changed, 599 insertions(+), 35 deletions(-)

diff --git a/porter_lightweight/api/pom.xml b/porter_lightweight/api/common/endpoint/pom.xml
similarity index 77%
copy from porter_lightweight/api/pom.xml
copy to porter_lightweight/api/common/endpoint/pom.xml
index 018a879..df1628b 100644
--- a/porter_lightweight/api/pom.xml
+++ b/porter_lightweight/api/common/endpoint/pom.xml
@@ -15,22 +15,24 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
 
   <parent>
     <groupId>org.apache.servicecomb.samples.porter</groupId>
-    <artifactId>porter-application</artifactId>
+    <artifactId>common-api</artifactId>
     <version>0.0.1-SNAPSHOT</version>
   </parent>
 
-  <artifactId>porter-api</artifactId>
-  <packaging>pom</packaging>
+  <artifactId>common-api-endpoint</artifactId>
+  <packaging>jar</packaging>
 
-  <modules>
-    <module>file-service</module>
-    <module>user-service</module>
-  </modules>
-</project>
\ No newline at end of file
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.servicecomb.samples.porter</groupId>
+      <artifactId>common-api-service</artifactId>
+      <version>${project.parent.version}</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/porter_lightweight/api/common/endpoint/src/main/java/org/apache/servicecomb/samples/porter/file/api/InspectorEndpoint.java b/porter_lightweight/api/common/endpoint/src/main/java/org/apache/servicecomb/samples/porter/file/api/InspectorEndpoint.java
new file mode 100644
index 0000000..731f6ac
--- /dev/null
+++ b/porter_lightweight/api/common/endpoint/src/main/java/org/apache/servicecomb/samples/porter/file/api/InspectorEndpoint.java
@@ -0,0 +1,206 @@
+/*
+ * 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.samples.porter.file.api;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Map.Entry;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.servlet.http.Part;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.servicecomb.common.rest.resource.ClassPathStaticResourceHandler;
+import org.apache.servicecomb.common.rest.resource.StaticResourceHandler;
+import org.apache.servicecomb.config.inject.ConfigObjectFactory;
+import org.apache.servicecomb.foundation.common.part.InputStreamPart;
+import org.apache.servicecomb.inspector.internal.InspectorConfig;
+import org.apache.servicecomb.inspector.internal.swagger.AppendStyleProcessor;
+import org.apache.servicecomb.inspector.internal.swagger.SchemaFormat;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.apache.servicecomb.serviceregistry.RegistryUtils;
+import org.apache.servicecomb.swagger.SwaggerUtils;
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.Asciidoctor.Factory;
+import org.asciidoctor.Attributes;
+import org.asciidoctor.AttributesBuilder;
+import org.asciidoctor.OptionsBuilder;
+import org.asciidoctor.SafeMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Ordering;
+
+import io.github.swagger2markup.Swagger2MarkupConfig;
+import io.github.swagger2markup.Swagger2MarkupConverter;
+import io.github.swagger2markup.Swagger2MarkupConverter.Builder;
+import io.github.swagger2markup.builder.Swagger2MarkupConfigBuilder;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.models.parameters.Parameter;
+
+// copied from org.apache.servicecomb.inspector.internal.InspectorImpl;
+@RestSchema(schemaId = "inspector")
+@Path("/inspector")
+public class InspectorEndpoint {
+  private static final Logger LOGGER = LoggerFactory.getLogger(InspectorEndpoint.class);
+
+  private InspectorConfig inspectorConfig;
+
+  private volatile Asciidoctor asciidoctor;
+
+  private StaticResourceHandler resourceHandler = new ClassPathStaticResourceHandler();
+
+  public InspectorEndpoint() {
+    this.inspectorConfig = new ConfigObjectFactory().create(InspectorConfig.class);
+  }
+
+  @Path("/schemas")
+  @GET
+  public Collection<String> getSchemaIds() {
+    return RegistryUtils.getServiceRegistry().getMicroservice().getSchemaMap().keySet();
+  }
+
+  @Path("/download/schemas")
+  @GET
+  @ApiResponse(code = 200, message = "", response = File.class)
+  public Response downloadSchemas(@QueryParam("format") SchemaFormat format) {
+    if (format == null) {
+      format = SchemaFormat.SWAGGER;
+    }
+
+    // normally, schema will not be too big, just save them in memory temporarily
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    try (ZipOutputStream zos = new ZipOutputStream(os)) {
+      for (Entry<String, String> entry : RegistryUtils.getServiceRegistry().getMicroservice().getSchemaMap().entrySet()) {
+        // begin writing a new ZIP entry, positions the stream to the start of the entry data
+        zos.putNextEntry(new ZipEntry(entry.getKey() + format.getSuffix()));
+
+        String content = entry.getValue();
+        if (SchemaFormat.HTML.equals(format)) {
+          content = swaggerToHtml(content);
+        }
+        zos.write(content.getBytes(StandardCharsets.UTF_8));
+        zos.closeEntry();
+      }
+    } catch (Throwable e) {
+      String msg = "failed to create schemas zip file, format=" + format + ".";
+      LOGGER.error(msg, e);
+      return Response.failResp(new InvocationException(Status.INTERNAL_SERVER_ERROR, msg));
+    }
+
+    Part part = new InputStreamPart(null, new ByteArrayInputStream(os.toByteArray()))
+        .setSubmittedFileName(RegistryUtils.getMicroservice().getServiceName() + format.getSuffix() + ".zip");
+    return Response.ok(part);
+  }
+
+  @Path("/schemas/{schemaId}")
+  @GET
+  @ApiResponse(code = 200, message = "", response = File.class)
+  public Response getSchemaContentById(@PathParam("schemaId") String schemaId,
+      @QueryParam("format") SchemaFormat format, @QueryParam("download") boolean download) {
+    String swaggerContent = RegistryUtils.getServiceRegistry().getMicroservice().getSchemaMap().get(schemaId);
+    if (swaggerContent == null) {
+      return Response.failResp(new InvocationException(Status.NOT_FOUND, Status.NOT_FOUND.getReasonPhrase()));
+    }
+
+    if (format == null) {
+      format = SchemaFormat.SWAGGER;
+    }
+
+    byte[] bytes;
+    if (SchemaFormat.HTML.equals(format)) {
+      String html = swaggerToHtml(swaggerContent);
+      bytes = html.getBytes(StandardCharsets.UTF_8);
+    } else {
+      bytes = swaggerContent.getBytes(StandardCharsets.UTF_8);
+    }
+
+    Part part = new InputStreamPart(null, new ByteArrayInputStream(bytes))
+        .setSubmittedFileName(schemaId + format.getSuffix());
+
+    Response response = Response.ok(part);
+    if (!download) {
+      response.getHeaders().addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
+    }
+    response.getHeaders().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
+    return response;
+  }
+
+  // swagger not support cookie parameter
+  // so if swaggerContent contains cookie parameter, will cause problem.
+  private String swaggerToHtml(String swaggerContent) {
+    if (asciidoctor == null) {
+      synchronized (this) {
+        if (asciidoctor == null) {
+          // very slow, need a few seconds
+          LOGGER.info("create AsciiDoctor start.");
+          asciidoctor = Factory.create();
+          asciidoctor.javaExtensionRegistry().docinfoProcessor(AppendStyleProcessor.class);
+          LOGGER.info("create AsciiDoctor end.");
+        }
+      }
+    }
+
+    // swagger to markup
+    Builder markupBuilder = Swagger2MarkupConverter.from(SwaggerUtils.parseSwagger(swaggerContent));
+    // default not support cookie parameter
+    // so must customize config
+    Swagger2MarkupConfig markupConfig = new Swagger2MarkupConfigBuilder()
+        .withParameterOrdering(Ordering
+            .explicit("path", "query", "header", "cookie", "formData", "body")
+            .onResultOf(Parameter::getIn))
+        .build();
+    String markup = markupBuilder.withConfig(markupConfig).build().toString();
+
+    // markup to html
+    OptionsBuilder builder = OptionsBuilder.options();
+    builder.docType("book")
+        .backend("html5")
+        .headerFooter(true)
+        .safe(SafeMode.UNSAFE)
+        .attributes(AttributesBuilder.attributes()
+            .attribute("toclevels", 3)
+            .attribute(Attributes.TOC_2, true)
+            .attribute(Attributes.TOC_POSITION, "left")
+            .attribute(Attributes.LINK_CSS, true)
+            .attribute(Attributes.STYLESHEET_NAME, inspectorConfig.getAsciidoctorCss())
+            .attribute(Attributes.SECTION_NUMBERS, true)
+            .attribute(Attributes.SECT_NUM_LEVELS, 4));
+    return asciidoctor.convert(markup, builder.asMap());
+  }
+
+  @Path("/{path : .+}")
+  @GET
+  @ApiResponse(code = 200, message = "", response = File.class)
+  public Response getStaticResource(@PathParam("path") String path) {
+    return resourceHandler.handle(path);
+  }
+}
+
diff --git a/porter_lightweight/api/common/endpoint/src/main/java/org/apache/servicecomb/samples/porter/file/api/LogEndpoint.java b/porter_lightweight/api/common/endpoint/src/main/java/org/apache/servicecomb/samples/porter/file/api/LogEndpoint.java
new file mode 100644
index 0000000..8e49a6e
--- /dev/null
+++ b/porter_lightweight/api/common/endpoint/src/main/java/org/apache/servicecomb/samples/porter/file/api/LogEndpoint.java
@@ -0,0 +1,72 @@
+/*
+ * 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.samples.porter.file.api;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.apache.servicecomb.samples.porter.common.api.LogService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import com.netflix.config.DynamicPropertyFactory;
+
+@RestSchema(schemaId = "log")
+@RequestMapping(path = "/v1/log")
+public class LogEndpoint implements LogService {
+  // protect your file in real applications
+  private static final File LOG_DIR =
+      new File(DynamicPropertyFactory.getInstance().getStringProperty("servicecomb.samples.logdir", ".").get());
+
+  private static final String FILE_POST_FIX = ".log";
+
+  @Override
+  @GetMapping(path = "/getLogFileList")
+  public List<String> getLogFileList() {
+    File[] files = LOG_DIR.listFiles(new FileFilter() {
+      @Override
+      public boolean accept(File file) {
+        return isLogFile(file);
+      }
+    });
+
+    List<String> result = new ArrayList<>(files.length);
+    for (int i = 0; i < files.length; i++) {
+      result.add(files[i].getName());
+    }
+    return result;
+  }
+
+  @Override
+  @GetMapping(path = "/getLogFileContent")
+  public File getLogFileContent(@RequestParam(name = "fileName") String fileName) {
+    File file = new File(LOG_DIR, fileName);
+    if (isLogFile(file)) {
+      return file;
+    }
+    return null;
+  }
+
+  private boolean isLogFile(File file) {
+    return file.isFile() && file.canRead() && file.getName().endsWith(FILE_POST_FIX);
+  }
+}
diff --git a/porter_lightweight/api/pom.xml b/porter_lightweight/api/common/pom.xml
similarity index 88%
copy from porter_lightweight/api/pom.xml
copy to porter_lightweight/api/common/pom.xml
index 018a879..5c28887 100644
--- a/porter_lightweight/api/pom.xml
+++ b/porter_lightweight/api/common/pom.xml
@@ -22,15 +22,15 @@
 
   <parent>
     <groupId>org.apache.servicecomb.samples.porter</groupId>
-    <artifactId>porter-application</artifactId>
+    <artifactId>porter-api</artifactId>
     <version>0.0.1-SNAPSHOT</version>
   </parent>
 
-  <artifactId>porter-api</artifactId>
+  <artifactId>common-api</artifactId>
   <packaging>pom</packaging>
 
   <modules>
-    <module>file-service</module>
-    <module>user-service</module>
+    <module>service</module>
+    <module>endpoint</module>
   </modules>
 </project>
\ No newline at end of file
diff --git a/porter_lightweight/api/pom.xml b/porter_lightweight/api/common/service/pom.xml
similarity index 84%
copy from porter_lightweight/api/pom.xml
copy to porter_lightweight/api/common/service/pom.xml
index 018a879..41a7d85 100644
--- a/porter_lightweight/api/pom.xml
+++ b/porter_lightweight/api/common/service/pom.xml
@@ -15,22 +15,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
 
   <parent>
     <groupId>org.apache.servicecomb.samples.porter</groupId>
-    <artifactId>porter-application</artifactId>
+    <artifactId>common-api</artifactId>
     <version>0.0.1-SNAPSHOT</version>
   </parent>
 
-  <artifactId>porter-api</artifactId>
-  <packaging>pom</packaging>
+  <artifactId>common-api-service</artifactId>
+  <packaging>jar</packaging>
 
-  <modules>
-    <module>file-service</module>
-    <module>user-service</module>
-  </modules>
-</project>
\ No newline at end of file
+</project>
diff --git a/porter_lightweight/api/common/service/src/main/java/org/apache/servicecomb/samples/porter/common/api/LogService.java b/porter_lightweight/api/common/service/src/main/java/org/apache/servicecomb/samples/porter/common/api/LogService.java
new file mode 100644
index 0000000..9991543
--- /dev/null
+++ b/porter_lightweight/api/common/service/src/main/java/org/apache/servicecomb/samples/porter/common/api/LogService.java
@@ -0,0 +1,26 @@
+/*
+ * 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.samples.porter.common.api;
+
+import java.io.File;
+import java.util.List;
+
+public interface LogService {
+  List<String> getLogFileList();
+  File getLogFileContent(String fileName);
+}
diff --git a/porter_lightweight/api/pom.xml b/porter_lightweight/api/pom.xml
index 018a879..faa2d56 100644
--- a/porter_lightweight/api/pom.xml
+++ b/porter_lightweight/api/pom.xml
@@ -30,6 +30,7 @@
   <packaging>pom</packaging>
 
   <modules>
+    <module>common</module>
     <module>file-service</module>
     <module>user-service</module>
   </modules>
diff --git a/porter_lightweight/file-service/pom.xml b/porter_lightweight/file-service/pom.xml
index 6ae53d6..02eea87 100644
--- a/porter_lightweight/file-service/pom.xml
+++ b/porter_lightweight/file-service/pom.xml
@@ -30,10 +30,19 @@
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>inspector</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.servicecomb.samples.porter</groupId>
       <artifactId>file-service-api-endpoint</artifactId>
       <version>${project.parent.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb.samples.porter</groupId>
+      <artifactId>common-api-endpoint</artifactId>
+      <version>${project.parent.version}</version>
+    </dependency>
   </dependencies>
   <properties>
     <main.class>org.apache.servicecomb.samples.porter.file.FileMain</main.class>
diff --git a/porter_lightweight/file-service/src/main/resources/META-INF/spring/file.bean.xml b/porter_lightweight/file-service/src/main/resources/META-INF/spring/file.bean.xml
index c679960..692102e 100644
--- a/porter_lightweight/file-service/src/main/resources/META-INF/spring/file.bean.xml
+++ b/porter_lightweight/file-service/src/main/resources/META-INF/spring/file.bean.xml
@@ -23,5 +23,6 @@
     xsi:schemaLocation="
         http://www.springframework.org/schema/beans classpath:org/springframework/beans/factory/xml/spring-beans-3.0.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
-
+  <bean id="servicecomb.samples.executor.groupThreadPool" class="org.apache.servicecomb.core.executor.GroupExecutor"
+    init-method="init"/>
 </beans>
\ No newline at end of file
diff --git a/porter_lightweight/file-service/src/main/resources/microservice.yaml b/porter_lightweight/file-service/src/main/resources/microservice.yaml
index dc4a845..f473dbe 100644
--- a/porter_lightweight/file-service/src/main/resources/microservice.yaml
+++ b/porter_lightweight/file-service/src/main/resources/microservice.yaml
@@ -36,4 +36,15 @@ servicecomb:
     address: 0.0.0.0:9091
 
   uploads:
-    directory: tmp_for_upload_file
\ No newline at end of file
+    directory: tmp_for_upload_file
+
+  samples:
+    logdir: D:\code\servicecomb-samples\porter_lightweight\file-service
+    
+  inspector:
+    enabled: false
+
+  executors:
+   Provider:
+     log: servicecomb.samples.executor.groupThreadPool
+     inspector: servicecomb.samples.executor.groupThreadPool
\ No newline at end of file
diff --git a/porter_lightweight/gateway-service/pom.xml b/porter_lightweight/gateway-service/pom.xml
index 252935e..343beb3 100644
--- a/porter_lightweight/gateway-service/pom.xml
+++ b/porter_lightweight/gateway-service/pom.xml
@@ -34,11 +34,20 @@
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>inspector</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.servicecomb.samples.porter</groupId>
       <artifactId>user-service-api-service</artifactId>
       <version>${project.parent.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.apache.servicecomb.samples.porter</groupId>
+      <artifactId>common-api-endpoint</artifactId>
+      <version>${project.parent.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.apache.servicecomb</groupId>
       <artifactId>edge-core</artifactId>
     </dependency>
diff --git a/porter_lightweight/gateway-service/src/main/java/org/apache/servicecomb/samples/porter/gateway/CustomVertxRestDispatcher.java b/porter_lightweight/gateway-service/src/main/java/org/apache/servicecomb/samples/porter/gateway/CustomVertxRestDispatcher.java
new file mode 100644
index 0000000..245e0af
--- /dev/null
+++ b/porter_lightweight/gateway-service/src/main/java/org/apache/servicecomb/samples/porter/gateway/CustomVertxRestDispatcher.java
@@ -0,0 +1,196 @@
+/*
+ * 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.samples.porter.gateway;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.Response.Status.Family;
+
+import org.apache.servicecomb.common.rest.AbstractRestInvocation;
+import org.apache.servicecomb.common.rest.RestConst;
+import org.apache.servicecomb.common.rest.VertxRestInvocation;
+import org.apache.servicecomb.core.Const;
+import org.apache.servicecomb.core.CseContext;
+import org.apache.servicecomb.core.Transport;
+import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
+import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
+import org.apache.servicecomb.foundation.vertx.http.VertxServerRequestToHttpServletRequest;
+import org.apache.servicecomb.foundation.vertx.http.VertxServerResponseToHttpServletResponse;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.apache.servicecomb.transport.rest.vertx.AbstractVertxHttpDispatcher;
+import org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.CookieHandler;
+
+// copied from org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher
+public class CustomVertxRestDispatcher extends AbstractVertxHttpDispatcher {
+  private static final Logger LOGGER = LoggerFactory.getLogger(VertxRestDispatcher.class);
+
+  private Transport transport;
+
+  @Override
+  public int getOrder() {
+    return Integer.MAX_VALUE;
+  }
+
+  @Override
+  public boolean enabled() {
+    return true;
+  }
+
+  @Override
+  public void init(Router router) {
+    String regex = "[/v1/log/|/inspector](.*)";
+    router.routeWithRegex(regex).handler(CookieHandler.create());
+    router.routeWithRegex(regex).handler(createBodyHandler());
+    router.routeWithRegex(regex).failureHandler(this::failureHandler).handler(this::onRequest);
+  }
+
+  private void failureHandler(RoutingContext context) {
+    LOGGER.error("http server failed.", context.failure());
+
+    AbstractRestInvocation restProducerInvocation = context.get(RestConst.REST_PRODUCER_INVOCATION);
+    Throwable e = context.failure();
+    if (ErrorDataDecoderException.class.isInstance(e)) {
+      Throwable cause = e.getCause();
+      if (InvocationException.class.isInstance(cause)) {
+        e = cause;
+      }
+    }
+
+    // only when unexpected exception happens, it will run into here.
+    // the connection should be closed.
+    handleFailureAndClose(context, restProducerInvocation, e);
+  }
+
+  /**
+   * Try to find out the failure information and send it in response.
+   */
+  private void handleFailureAndClose(RoutingContext context, AbstractRestInvocation restProducerInvocation,
+      Throwable e) {
+    if (null != restProducerInvocation) {
+      // if there is restProducerInvocation, let it send exception in response. The exception is allowed to be null.
+      sendFailResponseByInvocation(context, restProducerInvocation, e);
+      return;
+    }
+
+    if (null != e) {
+      // if there exists exception, try to send this exception by RoutingContext
+      sendExceptionByRoutingContext(context, e);
+      return;
+    }
+
+    // if there is no exception, the response is determined by status code.
+    sendFailureRespDeterminedByStatus(context);
+  }
+
+  /**
+   * Try to determine response by status code, and send response.
+   */
+  private void sendFailureRespDeterminedByStatus(RoutingContext context) {
+    Family statusFamily = Family.familyOf(context.statusCode());
+    if (Family.CLIENT_ERROR.equals(statusFamily) || Family.SERVER_ERROR.equals(statusFamily) || Family.OTHER
+        .equals(statusFamily)) {
+      context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
+          .setStatusCode(context.statusCode()).end();
+    } else {
+      // it seems the status code is not set properly
+      context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
+          .setStatusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode())
+          .setStatusMessage(Status.INTERNAL_SERVER_ERROR.getReasonPhrase())
+          .end(wrapResponseBody(Status.INTERNAL_SERVER_ERROR.getReasonPhrase()));
+    }
+    context.response().close();
+  }
+
+  /**
+   * Use routingContext to send failure information in throwable.
+   */
+  private void sendExceptionByRoutingContext(RoutingContext context, Throwable e) {
+    if (InvocationException.class.isInstance(e)) {
+      InvocationException invocationException = (InvocationException) e;
+      context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
+          .setStatusCode(invocationException.getStatusCode()).setStatusMessage(invocationException.getReasonPhrase())
+          .end(wrapResponseBody(invocationException.getReasonPhrase()));
+    } else {
+      context.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.WILDCARD)
+          .setStatusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode()).end(wrapResponseBody(e.getMessage()));
+    }
+    context.response().close();
+  }
+
+  /**
+   * Consumer will treat the response body as json by default, so it's necessary to wrap response body as Json string
+   * to avoid deserialization error.
+   *
+   * @param message response body
+   * @return response body wrapped as Json string
+   */
+  String wrapResponseBody(String message) {
+    if (isValidJson(message)) {
+      return message;
+    }
+
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.put("message", message);
+
+    return jsonObject.toString();
+  }
+
+  /**
+   * Check if the message is a valid Json string.
+   * @param message the message to be checked.
+   * @return true if message is a valid Json string, otherwise false.
+   */
+  private boolean isValidJson(String message) {
+    try {
+      new JsonObject(message);
+    } catch (Exception ignored) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Use restProducerInvocation to send failure message. The throwable is allowed to be null.
+   */
+  private void sendFailResponseByInvocation(RoutingContext context, AbstractRestInvocation restProducerInvocation,
+      Throwable e) {
+    restProducerInvocation.sendFailResponse(e);
+    context.response().close();
+  }
+
+  private void onRequest(RoutingContext context) {
+    if (transport == null) {
+      transport = CseContext.getInstance().getTransportManager().findTransport(Const.RESTFUL);
+    }
+    HttpServletRequestEx requestEx = new VertxServerRequestToHttpServletRequest(context);
+    HttpServletResponseEx responseEx = new VertxServerResponseToHttpServletResponse(context.response());
+
+    VertxRestInvocation vertxRestInvocation = new VertxRestInvocation();
+    context.put(RestConst.REST_PRODUCER_INVOCATION, vertxRestInvocation);
+    vertxRestInvocation.invoke(transport, requestEx, responseEx, httpServerFilters);
+  }
+}
diff --git a/porter_lightweight/gateway-service/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher b/porter_lightweight/gateway-service/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
index 71009db..bada1de 100644
--- a/porter_lightweight/gateway-service/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
+++ b/porter_lightweight/gateway-service/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher
@@ -16,4 +16,5 @@
 #
 
 org.apache.servicecomb.samples.porter.gateway.ApiDispatcher
-org.apache.servicecomb.samples.porter.gateway.StaticWebpageDispatcher
\ No newline at end of file
+org.apache.servicecomb.samples.porter.gateway.StaticWebpageDispatcher
+org.apache.servicecomb.samples.porter.gateway.CustomVertxRestDispatcher
\ No newline at end of file
diff --git a/porter_lightweight/file-service/src/main/resources/META-INF/spring/file.bean.xml b/porter_lightweight/gateway-service/src/main/resources/META-INF/spring/cse.bean.xml
similarity index 60%
copy from porter_lightweight/file-service/src/main/resources/META-INF/spring/file.bean.xml
copy to porter_lightweight/gateway-service/src/main/resources/META-INF/spring/cse.bean.xml
index c679960..b5816c3 100644
--- a/porter_lightweight/file-service/src/main/resources/META-INF/spring/file.bean.xml
+++ b/porter_lightweight/gateway-service/src/main/resources/META-INF/spring/cse.bean.xml
@@ -16,12 +16,12 @@
   ~ limitations under the License.
   -->
 
-<beans xmlns="http://www.springframework.org/schema/beans"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
-    xmlns:util="http://www.springframework.org/schema/util"
-    xmlns:context="http://www.springframework.org/schema/context"
-    xsi:schemaLocation="
-        http://www.springframework.org/schema/beans classpath:org/springframework/beans/factory/xml/spring-beans-3.0.xsd
-        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="
+		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
-</beans>
\ No newline at end of file
+  <bean id="servicecomb.samples.executor.groupThreadPool" class="org.apache.servicecomb.core.executor.GroupExecutor"
+    init-method="init"/>
+</beans>
diff --git a/porter_lightweight/gateway-service/src/main/resources/microservice.yaml b/porter_lightweight/gateway-service/src/main/resources/microservice.yaml
index 2462c7f..c05dc5a 100644
--- a/porter_lightweight/gateway-service/src/main/resources/microservice.yaml
+++ b/porter_lightweight/gateway-service/src/main/resources/microservice.yaml
@@ -42,6 +42,19 @@ servicecomb:
   uploads:
     directory: tmp_for_upload_gateway
 
+  samples:
+    logdir: D:\code\servicecomb-samples\porter_lightweight\gateway-service
+    
+  inspector:
+    enabled: false
+
+  executors:
+   Provider:
+     log: servicecomb.samples.executor.groupThreadPool
+     inspector: servicecomb.samples.executor.groupThreadPool
+
 servicecomb.http.dispatcher.edge.default.enabled: false
+# StaticWebpageDispatcher checking file exists is async, and will mark request status to ended, and VertxRestDispatcher read 
+# body will print exception. 
 servicecomb.http.dispatcher.rest.enabled: false
 gateway.webroot: /code/servicecomb-samples/porter_lightweight/gateway-service/src/main/resources
diff --git a/porter_lightweight/user-service/pom.xml b/porter_lightweight/user-service/pom.xml
index 7e8c20f..100bdcf 100644
--- a/porter_lightweight/user-service/pom.xml
+++ b/porter_lightweight/user-service/pom.xml
@@ -35,11 +35,20 @@
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>inspector</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.servicecomb.samples.porter</groupId>
       <artifactId>user-service-api-endpoint</artifactId>
       <version>${project.parent.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.apache.servicecomb.samples.porter</groupId>
+      <artifactId>common-api-endpoint</artifactId>
+      <version>${project.parent.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.mybatis</groupId>
       <artifactId>mybatis</artifactId>
     </dependency>
diff --git a/porter_lightweight/user-service/src/main/resources/META-INF/spring/user.bean.xml b/porter_lightweight/user-service/src/main/resources/META-INF/spring/user.bean.xml
index 3cc2dd2..661608f 100644
--- a/porter_lightweight/user-service/src/main/resources/META-INF/spring/user.bean.xml
+++ b/porter_lightweight/user-service/src/main/resources/META-INF/spring/user.bean.xml
@@ -25,7 +25,9 @@
 		http://www.springframework.org/schema/beans classpath:org/springframework/beans/factory/xml/spring-beans-3.0.xsd
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
 		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
-
+  <bean id="servicecomb.samples.executor.groupThreadPool" class="org.apache.servicecomb.core.executor.GroupExecutor"
+    init-method="init"/>
+    
     <bean id="dataSource"
         class="org.apache.commons.dbcp2.BasicDataSource"
         destroy-method="close">
diff --git a/porter_lightweight/user-service/src/main/resources/microservice.yaml b/porter_lightweight/user-service/src/main/resources/microservice.yaml
index 5daaea5..af37379 100644
--- a/porter_lightweight/user-service/src/main/resources/microservice.yaml
+++ b/porter_lightweight/user-service/src/main/resources/microservice.yaml
@@ -32,4 +32,15 @@ servicecomb:
         watch: false
 
   rest:
-    address: 0.0.0.0:9093
\ No newline at end of file
+    address: 0.0.0.0:9093
+
+  samples:
+    logdir: D:\code\servicecomb-samples\porter_lightweight\user-service
+    
+  inspector:
+    enabled: false
+
+  executors:
+   Provider:
+     log: servicecomb.samples.executor.groupThreadPool
+     inspector: servicecomb.samples.executor.groupThreadPool
\ No newline at end of file