[SCB-2671]support beforeSendRequestAsync so that HttpClient filter ca… (#3291)

diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java
index a139712..9fe6d1b 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilter.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.common.rest.filter;
 
+import java.util.concurrent.CompletableFuture;
+
 import org.apache.servicecomb.core.Invocation;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
@@ -29,7 +31,30 @@
 
   int getOrder();
 
-  void beforeSendRequest(Invocation invocation, HttpServletRequestEx requestEx);
+  /**
+   * callback method before send a client request.
+   *
+   * @Deprecated this method may be called in an event-loop thread, do not add blocking
+   * methods. Implement #beforeSendRequestAsync instead.
+   */
+  @Deprecated
+  default void beforeSendRequest(Invocation invocation, HttpServletRequestEx requestEx) {
+
+  }
+
+  /**
+   *  callback method before send a client request.
+   */
+  default CompletableFuture<Void> beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) {
+    CompletableFuture<Void> future = new CompletableFuture<>();
+    try {
+      beforeSendRequest(invocation, requestEx);
+      future.complete(null);
+    } catch (Throwable e) {
+      future.completeExceptionally(e);
+    }
+    return future;
+  }
 
   // if finished, then return a none null response
   // if return a null response, then sdk will call next filter.afterReceive
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBeforeSendRequestExecutor.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBeforeSendRequestExecutor.java
new file mode 100644
index 0000000..eab6217
--- /dev/null
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBeforeSendRequestExecutor.java
@@ -0,0 +1,92 @@
+/*
+ * 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.common.rest.filter;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
+
+public class HttpClientFilterBeforeSendRequestExecutor {
+  private final List<HttpClientFilter> httpClientFilters;
+
+  private final Invocation invocation;
+
+  private final HttpServletRequestEx requestEx;
+
+  private int currentIndex;
+
+  private final CompletableFuture<Void> future = new CompletableFuture<>();
+
+  public HttpClientFilterBeforeSendRequestExecutor(List<HttpClientFilter> httpClientFilters, Invocation invocation,
+      HttpServletRequestEx requestEx) {
+    this.httpClientFilters = httpClientFilters;
+    this.invocation = invocation;
+    this.requestEx = requestEx;
+  }
+
+  public CompletableFuture<Void> run() {
+    doRun();
+
+    return future;
+  }
+
+  protected CompletableFuture<Void> safeInvoke(HttpClientFilter httpClientFilter) {
+    try {
+      if (httpClientFilter.enabled()) {
+        CompletableFuture<Void> future = httpClientFilter.beforeSendRequestAsync(invocation, requestEx);
+        if (future == null) {
+          future = new CompletableFuture<>();
+          future.completeExceptionally(new IllegalStateException(
+              "HttpClientFilter beforeSendRequestAsync can not return null, do not override it. Class="
+                  + httpClientFilter.getClass()
+                  .getName()));
+        }
+        return future;
+      } else {
+        CompletableFuture<Void> eFuture = new CompletableFuture<>();
+        eFuture.complete(null);
+        return eFuture;
+      }
+    } catch (Throwable e) {
+      CompletableFuture<Void> eFuture = new CompletableFuture<>();
+      eFuture.completeExceptionally(e);
+      return eFuture;
+    }
+  }
+
+  protected void doRun() {
+    if (currentIndex == httpClientFilters.size()) {
+      future.complete(null);
+      return;
+    }
+
+    HttpClientFilter httpServerFilter = httpClientFilters.get(currentIndex);
+    currentIndex++;
+
+    CompletableFuture<Void> stepFuture = safeInvoke(httpServerFilter);
+    stepFuture.whenComplete((v, e) -> {
+      if (e == null) {
+        doRun();
+        return;
+      }
+
+      future.completeExceptionally(e);
+    });
+  }
+}
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java
index b809660..639bd55 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/HttpServerFilter.java
@@ -43,7 +43,7 @@
   Response afterReceiveRequest(Invocation invocation, HttpServletRequestEx requestEx);
 
   /**
-   * @param invocation maybe null
+   * callback method before send a server response.
    */
   default CompletableFuture<Void> beforeSendResponseAsync(Invocation invocation, HttpServletResponseEx responseEx) {
     CompletableFuture<Void> future = new CompletableFuture<>();
@@ -57,8 +57,12 @@
   }
 
   /**
-   * @param invocation maybe null
+   * callback method before send a server response.
+   *
+   * @Deprecated this method may be called in an event-loop thread, do not add blocking
+   * methods. Implement #beforeSendResponseAsync instead.
    */
+  @Deprecated
   default void beforeSendResponse(Invocation invocation, HttpServletResponseEx responseEx) {
 
   }
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java
index 8babd97..a532833 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ClientRestArgsFilter.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.common.rest.filter.inner;
 
+import java.util.concurrent.CompletableFuture;
+
 import org.apache.servicecomb.common.rest.RestConst;
 import org.apache.servicecomb.common.rest.codec.RestCodec;
 import org.apache.servicecomb.common.rest.codec.param.RestClientRequestImpl;
@@ -27,7 +29,6 @@
 import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
 import org.apache.servicecomb.swagger.invocation.Response;
-import org.apache.servicecomb.swagger.invocation.exception.ExceptionFactory;
 
 public class ClientRestArgsFilter implements HttpClientFilter {
 
@@ -37,8 +38,8 @@
   }
 
   @Override
-  @SuppressWarnings("unchecked")
-  public void beforeSendRequest(Invocation invocation, HttpServletRequestEx requestEx) {
+  public CompletableFuture<Void> beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) {
+    CompletableFuture<Void> result = new CompletableFuture<>();
     RestClientRequestImpl restClientRequest = (RestClientRequestImpl) invocation.getHandlerContext()
         .get(RestConst.INVOCATION_HANDLER_REQUESTCLIENT);
     OperationMeta operationMeta = invocation.getOperationMeta();
@@ -47,9 +48,11 @@
       RestCodec.argsToRest(invocation.getSwaggerArguments(), swaggerRestOperation,
           restClientRequest);
       requestEx.setBodyBuffer(restClientRequest.getBodyBuffer());
+      result.complete(null);
     } catch (Throwable e) {
-      throw ExceptionFactory.convertConsumerException(e);
+      result.completeExceptionally(e);
     }
+    return result;
   }
 
   @Override
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java
index 0b7734d..75728a3 100644
--- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java
@@ -85,6 +85,7 @@
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
+@SuppressWarnings("deprecation")
 public class TestAbstractRestInvocation {
 
   HttpServletRequestEx requestEx = Mockito.mock(HttpServletRequestEx.class);
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBaseForTest.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBaseForTest.java
new file mode 100644
index 0000000..7903b19
--- /dev/null
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/HttpClientFilterBaseForTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.common.rest.filter;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
+import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
+import org.apache.servicecomb.swagger.invocation.Response;
+
+public class HttpClientFilterBaseForTest implements HttpClientFilter {
+  @Override
+  public int getOrder() {
+    return 0;
+  }
+
+  @Override
+  public CompletableFuture<Void> beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) {
+    return null;
+  }
+
+  @Override
+  public Response afterReceiveResponse(Invocation invocation, HttpServletResponseEx responseEx) {
+    return null;
+  }
+}
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/TestHttpClientFilter.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/TestHttpClientFilter.java
new file mode 100644
index 0000000..afc788b
--- /dev/null
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/TestHttpClientFilter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.common.rest.filter;
+
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestHttpClientFilter {
+  @Test
+  public void asyncFailed() {
+    HttpClientFilter filter = new HttpClientFilterBaseForTest();
+    HttpClientFilterBeforeSendRequestExecutor executor =
+        new HttpClientFilterBeforeSendRequestExecutor(Arrays.asList(filter), null, null);
+    Assertions.assertThrows(ExecutionException.class, () -> executor.run().get());
+  }
+}
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/TestHttpServerFilter.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/TestHttpServerFilter.java
index 584a35a..37449a3 100644
--- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/TestHttpServerFilter.java
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/TestHttpServerFilter.java
@@ -39,6 +39,7 @@
   public void asyncFailed() throws InterruptedException, ExecutionException {
     HttpServerFilter filter = new HttpServerFilterBaseForTest() {
       @Override
+      @SuppressWarnings("deprecation")
       public void beforeSendResponse(Invocation invocation, HttpServletResponseEx responseEx) {
         throw new RuntimeExceptionWithoutStackTrace();
       }
diff --git a/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java b/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java
index 10c443b..ea2a539 100644
--- a/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java
+++ b/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java
@@ -493,7 +493,7 @@
     long start = System.currentTimeMillis();
     while (true) {
       long remaining = invocationStartedCounter.get() - invocationFinishedCounter.get();
-      if (remaining == 0) {
+      if (remaining <= 0) {
         return;
       }
 
diff --git a/demo/demo-edge/edge-service/src/main/java/org/apache/servicecomb/demo/edge/service/encrypt/filter/EdgeSignatureResponseFilter.java b/demo/demo-edge/edge-service/src/main/java/org/apache/servicecomb/demo/edge/service/encrypt/filter/EdgeSignatureResponseFilter.java
index 4d9a380..87d3e21 100644
--- a/demo/demo-edge/edge-service/src/main/java/org/apache/servicecomb/demo/edge/service/encrypt/filter/EdgeSignatureResponseFilter.java
+++ b/demo/demo-edge/edge-service/src/main/java/org/apache/servicecomb/demo/edge/service/encrypt/filter/EdgeSignatureResponseFilter.java
@@ -17,6 +17,7 @@
 package org.apache.servicecomb.demo.edge.service.encrypt.filter;
 
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CompletableFuture;
 
 import org.apache.servicecomb.common.rest.filter.HttpServerFilter;
 import org.apache.servicecomb.core.Invocation;
@@ -48,14 +49,14 @@
   }
 
   @Override
-  public void beforeSendResponse(Invocation invocation, HttpServletResponseEx responseEx) {
+  public CompletableFuture<Void> beforeSendResponseAsync(Invocation invocation, HttpServletResponseEx responseEx) {
     if (invocation == null) {
-      return;
+      return CompletableFuture.completedFuture(null);
     }
 
     EncryptContext encryptContext = (EncryptContext) invocation.getHandlerContext().get(EdgeConst.ENCRYPT_CONTEXT);
     if (encryptContext == null) {
-      return;
+      return CompletableFuture.completedFuture(null);
     }
     Hcr hcr = encryptContext.getHcr();
 
@@ -72,5 +73,7 @@
       body = body.substring(0, body.length() - 1) + ",\"signature\":\"" + signature + "\"}";
       responseEx.setBodyBuffer(Buffer.buffer(body));
     }
+
+    return CompletableFuture.completedFuture(null);
   }
 }
diff --git a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/JaxrsDemoHttpServerFilter.java b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/JaxrsDemoHttpServerFilter.java
index 0258474..e89d3da 100644
--- a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/JaxrsDemoHttpServerFilter.java
+++ b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/JaxrsDemoHttpServerFilter.java
@@ -39,6 +39,7 @@
   }
 
   @Override
+  @SuppressWarnings("deprecation")
   public void beforeSendResponse(Invocation invocation, HttpServletResponseEx responseEx) {
     // in 404 situation, invocation is null and a NPE is thrown
     LOGGER.info("JaxrsDemoHttpServerFilter is called, operation=[{}]", invocation.getOperationName());
diff --git a/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ClientSignature.java b/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ClientSignature.java
index aa715d2..84bbe67 100644
--- a/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ClientSignature.java
+++ b/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ClientSignature.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.demo.signature;
 
+import java.util.concurrent.CompletableFuture;
+
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.servicecomb.common.rest.filter.HttpClientFilter;
@@ -36,9 +38,10 @@
   }
 
   @Override
-  public void beforeSendRequest(Invocation invocation, HttpServletRequestEx requestEx) {
+  public CompletableFuture<Void> beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) {
     String signature = SignatureUtils.genSignature(requestEx);
     requestEx.setHeader("signature", signature);
+    return CompletableFuture.completedFuture(null);
   }
 
   @Override
diff --git a/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ServerSignature.java b/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ServerSignature.java
index 8f6e181..1b623c3 100644
--- a/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ServerSignature.java
+++ b/demo/demo-signature/src/main/java/org/apache/servicecomb/demo/signature/ServerSignature.java
@@ -57,6 +57,7 @@
   }
 
   @Override
+  @SuppressWarnings("deprecation")
   public void beforeSendResponse(Invocation invocation, HttpServletResponseEx responseEx) {
     String signature = SignatureUtils.genSignature(responseEx);
     responseEx.addHeader("signature", signature);
diff --git a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java
index c315393..49258fa 100644
--- a/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java
+++ b/edge/edge-core/src/main/java/org/apache/servicecomb/edge/core/EdgeAddHeaderClientFilter.java
@@ -19,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
 import org.apache.commons.lang3.StringUtils;
@@ -77,8 +78,9 @@
   }
 
   @Override
-  public void beforeSendRequest(Invocation invocation, HttpServletRequestEx requestEx) {
+  public CompletableFuture<Void> beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) {
     addHeaders(invocation, requestEx::addHeader);
+    return CompletableFuture.completedFuture(null);
   }
 
   public void addHeaders(Invocation invocation, BiConsumer<String, String> headerAdder) {
diff --git a/integration-tests/it-edge/src/main/java/org/apache/servicecomb/it/edge/encrypt/filter/EdgeSignatureResponseFilter.java b/integration-tests/it-edge/src/main/java/org/apache/servicecomb/it/edge/encrypt/filter/EdgeSignatureResponseFilter.java
index b625135..281b112 100644
--- a/integration-tests/it-edge/src/main/java/org/apache/servicecomb/it/edge/encrypt/filter/EdgeSignatureResponseFilter.java
+++ b/integration-tests/it-edge/src/main/java/org/apache/servicecomb/it/edge/encrypt/filter/EdgeSignatureResponseFilter.java
@@ -17,6 +17,7 @@
 package org.apache.servicecomb.it.edge.encrypt.filter;
 
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CompletableFuture;
 
 import org.apache.servicecomb.common.rest.filter.HttpServerFilter;
 import org.apache.servicecomb.core.Invocation;
@@ -48,14 +49,14 @@
   }
 
   @Override
-  public void beforeSendResponse(Invocation invocation, HttpServletResponseEx responseEx) {
+  public CompletableFuture<Void> beforeSendResponseAsync(Invocation invocation, HttpServletResponseEx responseEx) {
     if (invocation == null) {
-      return;
+      return CompletableFuture.completedFuture(null);
     }
 
     EncryptContext encryptContext = (EncryptContext) invocation.getHandlerContext().get(EdgeConst.ENCRYPT_CONTEXT);
     if (encryptContext == null) {
-      return;
+      return CompletableFuture.completedFuture(null);
     }
     Hcr hcr = encryptContext.getHcr();
 
@@ -72,5 +73,6 @@
       body = body.substring(0, body.length() - 1) + ",\"signature\":\"" + signature + "\"}";
       responseEx.setBodyBuffer(Buffer.buffer(body));
     }
+    return CompletableFuture.completedFuture(null);
   }
 }
diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java
index c37a3c3..e8b674d 100644
--- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java
+++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/RestTemplateCopyHeaderFilter.java
@@ -17,6 +17,8 @@
 
 package org.apache.servicecomb.provider.springmvc.reference;
 
+import java.util.concurrent.CompletableFuture;
+
 import org.apache.servicecomb.common.rest.RestConst;
 import org.apache.servicecomb.common.rest.filter.HttpClientFilter;
 import org.apache.servicecomb.core.Invocation;
@@ -36,10 +38,10 @@
   }
 
   @Override
-  public void beforeSendRequest(Invocation invocation, HttpServletRequestEx requestEx) {
+  public CompletableFuture<Void> beforeSendRequestAsync(Invocation invocation, HttpServletRequestEx requestEx) {
     HttpHeaders httpHeaders = (HttpHeaders) invocation.getHandlerContext().get(RestConst.CONSUMER_HEADER);
     if (httpHeaders == null) {
-      return;
+      return CompletableFuture.completedFuture(null);
     }
 
     httpHeaders.forEach((key, values) -> {
@@ -57,6 +59,7 @@
         requestEx.addHeader(key, value);
       }
     });
+    return CompletableFuture.completedFuture(null);
   }
 
   @Override
diff --git a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestRestTemplateCopyHeaderFilter.java b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestRestTemplateCopyHeaderFilter.java
index e94bb9f..7e02c75 100644
--- a/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestRestTemplateCopyHeaderFilter.java
+++ b/providers/provider-springmvc/src/test/java/org/apache/servicecomb/provider/springmvc/reference/TestRestTemplateCopyHeaderFilter.java
@@ -20,6 +20,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
 
 import org.apache.servicecomb.common.rest.RestConst;
 import org.apache.servicecomb.core.Invocation;
@@ -42,7 +43,7 @@
   }
 
   @Test
-  public void beforeSendRequestNoHeader(@Mocked Invocation invocation) {
+  public void beforeSendRequestNoHeader(@Mocked Invocation invocation) throws ExecutionException, InterruptedException {
     Map<String, Object> context = new HashMap<>();
     new Expectations() {
       {
@@ -52,12 +53,13 @@
     };
 
     HttpServletRequestEx requestEx = new CommonToHttpServletRequest(null, null, new HttpHeaders(), null, false);
-    filter.beforeSendRequest(invocation, requestEx);
+    filter.beforeSendRequestAsync(invocation, requestEx).get();
     Assertions.assertFalse(requestEx.getHeaderNames().hasMoreElements());
   }
 
   @Test
-  public void beforeSendRequestWithNullHeader(@Mocked Invocation invocation) {
+  public void beforeSendRequestWithNullHeader(@Mocked Invocation invocation)
+      throws ExecutionException, InterruptedException {
     Map<String, Object> context = new HashMap<>(1);
     HttpHeaders httpHeaders = new HttpHeaders();
     context.put(RestConst.CONSUMER_HEADER, httpHeaders);
@@ -72,14 +74,15 @@
     };
 
     HttpServletRequestEx requestEx = new CommonToHttpServletRequest(null, null, new HttpHeaders(), null, false);
-    filter.beforeSendRequest(invocation, requestEx);
+    filter.beforeSendRequestAsync(invocation, requestEx).get();
     Assertions.assertEquals("headerValue0", requestEx.getHeader("headerName0"));
     Assertions.assertEquals("headerValue2", requestEx.getHeader("headerName2"));
     Assertions.assertNull(requestEx.getHeader("headerName1"));
   }
 
   @Test
-  public void beforeSendRequestHaveHeader(@Mocked Invocation invocation) {
+  public void beforeSendRequestHaveHeader(@Mocked Invocation invocation)
+      throws ExecutionException, InterruptedException {
     HttpHeaders httpHeaders = new HttpHeaders();
     httpHeaders.add("name", "value");
 
@@ -93,12 +96,13 @@
     };
 
     HttpServletRequestEx requestEx = new CommonToHttpServletRequest(null, null, new HttpHeaders(), null, false);
-    filter.beforeSendRequest(invocation, requestEx);
+    filter.beforeSendRequestAsync(invocation, requestEx).get();
     MatcherAssert.assertThat(Collections.list(requestEx.getHeaders("name")), Matchers.contains("value"));
   }
 
   @Test
-  public void beforeSendRequestSkipContentLength(@Mocked Invocation invocation) {
+  public void beforeSendRequestSkipContentLength(@Mocked Invocation invocation)
+      throws ExecutionException, InterruptedException {
     HttpHeaders httpHeaders = new HttpHeaders();
     httpHeaders.add(HttpHeaders.CONTENT_LENGTH, "0");
 
@@ -112,7 +116,7 @@
     };
 
     HttpServletRequestEx requestEx = new CommonToHttpServletRequest(null, null, new HttpHeaders(), null, false);
-    filter.beforeSendRequest(invocation, requestEx);
+    filter.beforeSendRequestAsync(invocation, requestEx).get();
     Assertions.assertNull((requestEx.getHeader(HttpHeaders.CONTENT_LENGTH)));
   }
 
diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java
index d41b156..30cc9b7 100644
--- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java
+++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java
@@ -28,7 +28,6 @@
 import org.apache.servicecomb.common.rest.filter.HttpClientFilter;
 import org.apache.servicecomb.core.Invocation;
 import org.apache.servicecomb.core.definition.OperationMeta;
-import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.context.HttpStatus;
@@ -56,11 +55,6 @@
     return enabled;
   }
 
-  @Override
-  public void beforeSendRequest(Invocation invocation, HttpServletRequestEx requestEx) {
-
-  }
-
   protected ProduceProcessor findProduceProcessor(RestOperationMeta restOperation,
       HttpServletResponseEx responseEx) {
     String contentType = responseEx.getHeader(HttpHeaders.CONTENT_TYPE);
diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java
index 18339c1..6ddf1e2 100644
--- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java
+++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java
@@ -18,6 +18,7 @@
 package org.apache.servicecomb.transport.rest.client.http;
 
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeoutException;
 
 import javax.ws.rs.core.Response.Status;
@@ -27,6 +28,7 @@
 import org.apache.servicecomb.common.rest.codec.param.RestClientRequestImpl;
 import org.apache.servicecomb.common.rest.definition.RestOperationMeta;
 import org.apache.servicecomb.common.rest.filter.HttpClientFilter;
+import org.apache.servicecomb.common.rest.filter.HttpClientFilterBeforeSendRequestExecutor;
 import org.apache.servicecomb.core.Const;
 import org.apache.servicecomb.core.Invocation;
 import org.apache.servicecomb.core.definition.OperationConfig;
@@ -121,28 +123,25 @@
       }
       HttpServletRequestEx requestEx = new VertxClientRequestToHttpServletRequest(clientRequest, requestBodyBuffer);
       invocation.getInvocationStageTrace().startClientFiltersRequest();
-      for (HttpClientFilter filter : httpClientFilters) {
-        if (filter.enabled()) {
-          filter.beforeSendRequest(invocation, requestEx);
-        }
-      }
 
-      // 从业务线程转移到网络线程中去发送
-      invocation.onStartSendRequest();
-      httpClientWithContext.runOnContext(httpClient -> {
-        clientRequest.setTimeout(operationMeta.getConfig().getMsRequestTimeout());
-        clientRequest.response().onComplete(asyncResult -> {
-          if (asyncResult.failed()) {
-            fail(asyncResult.cause());
-            return;
-          }
-          handleResponse(asyncResult.result());
+      return Future.fromCompletionStage(executeHttpClientFilters(requestEx).thenCompose((v) -> {
+        // 从业务线程转移到网络线程中去发送
+        invocation.onStartSendRequest();
+        httpClientWithContext.runOnContext(httpClient -> {
+          clientRequest.setTimeout(operationMeta.getConfig().getMsRequestTimeout());
+          clientRequest.response().onComplete(asyncResult -> {
+            if (asyncResult.failed()) {
+              fail(asyncResult.cause());
+              return;
+            }
+            handleResponse(asyncResult.result());
+          });
+          processServiceCombHeaders(invocation, operationMeta);
+          restClientRequest.end()
+              .onComplete((t) -> invocation.getInvocationStageTrace().finishWriteToBuffer(System.nanoTime()));
         });
-        processServiceCombHeaders(invocation, operationMeta);
-        restClientRequest.end()
-            .onComplete((t) -> invocation.getInvocationStageTrace().finishWriteToBuffer(System.nanoTime()));
-      });
-      return Future.succeededFuture();
+        return CompletableFuture.completedFuture((Void) null);
+      }));
     }).onFailure(failure -> {
       invocation.getTraceIdLogger()
           .error(LOGGER, "Failed to send request, alreadyFailed:{}, local:{}, remote:{}, message={}.",
@@ -152,6 +151,13 @@
     });
   }
 
+  private CompletableFuture<Void> executeHttpClientFilters(HttpServletRequestEx requestEx) {
+    HttpClientFilterBeforeSendRequestExecutor exec =
+        new HttpClientFilterBeforeSendRequestExecutor(httpClientFilters, invocation, requestEx);
+    return exec.run();
+  }
+
+
   /**
    * If this is a 3rd party invocation, ServiceComb related headers should be removed by default to hide inner
    * implementation. Otherwise, the InvocationContext will be set into the request headers.