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

mxsm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/eventmesh.git


The following commit(s) were added to refs/heads/master by this push:
     new 05b1f435e [ISSUE #4824] Add HTTP Sink Connector (#4837)
05b1f435e is described below

commit 05b1f435e2f0570fb5aea03ffe6f68868534a095
Author: Zaki <[email protected]>
AuthorDate: Wed May 8 22:47:01 2024 +0800

    [ISSUE #4824] Add HTTP Sink Connector (#4837)
    
    * feat: Add HTTP Sink Connector
    
    * refactor: Replace okHttpClient with vertx.WebClient
    
    * fix: Resolving dependency conflicts
    
    * test: Add HttpSinkConnectorTest
    
    * fix: Add License
    
    * fix: Solving dependency issues
    
    * fix: License Check
    
    * feat: Add HTTPS/SSL support
    
    * fix: Optimize logging
    
    * feat: Add webhook functionality
    
    * fix: Fix some bugs
    
    * test: add callback test
    
    * refactor: Add webhook Support
    
    * fix: Optimization tests and configuration additions
    
    * fix: code style
    
    * feat: rebuild WebhookHttpSinkHandler and add RetryHttpSinkHandler
    
    * fix: fix ci
    
    * refactor: Use failsafe alternative resilience4j and optimize webhook 
functionality
    
    * fix: fix License Check
    
    * fix: update something
    
    * fix: fix ci
    
    * fix: update something
    
    * fix: Optimized naming
    
    * fix: fix ci
    
    * fix: fix style check error
    
    * test: update HttpSinkConnectorTest
---
 .../eventmesh-connector-http/build.gradle          |   7 +-
 .../connector/http/server/HttpConnectServer.java   |   4 +-
 .../connector/http/sink/HttpSinkConnector.java     | 124 ++++++++
 .../http/sink/config/HttpRetryConfig.java}         |  60 ++--
 .../http/sink/config/HttpSinkConfig.java}          |  57 ++--
 .../http/sink/config/HttpWebhookConfig.java}       |  64 ++--
 .../http/sink/config/SinkConnectorConfig.java      |  73 +++++
 .../http/sink/data/HttpConnectRecord.java          |  67 ++++
 .../http/sink/data/HttpExportMetadata.java}        |  71 +++--
 .../http/sink/data/HttpExportRecord.java}          |  60 ++--
 .../http/sink/data/HttpExportRecordPage.java}      |  65 ++--
 .../http/sink/handle/CommonHttpSinkHandler.java    | 181 +++++++++++
 .../http/sink/handle/HttpSinkHandler.java          |  74 +++++
 .../http/sink/handle/RetryHttpSinkHandler.java     | 206 ++++++++++++
 .../http/sink/handle/WebhookHttpSinkHandler.java   | 348 +++++++++++++++++++++
 .../eventmesh/connector/http/util/HttpUtils.java}  |  59 ++--
 .../src/main/resources/sink-config.yml             |  46 +++
 .../source/connector/HttpSinkConnectorTest.java    | 170 ++++++++++
 .../src/test/resources/sink-config.yml             |  46 +++
 tools/dependency-check/known-dependencies.txt      |   4 +
 20 files changed, 1595 insertions(+), 191 deletions(-)

diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/build.gradle
index 734b2fc62..786ac4518 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ b/eventmesh-connectors/eventmesh-connector-http/build.gradle
@@ -18,10 +18,15 @@
 dependencies {
     api project(":eventmesh-openconnect:eventmesh-openconnect-java")
     implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
+
+    implementation 'io.cloudevents:cloudevents-http-vertx:3.0.0'
     implementation 'io.vertx:vertx-web:4.4.6'
+    implementation 'io.vertx:vertx-web-client:4.4.6'
+    implementation 'dev.failsafe:failsafe:3.3.2'
 
     testImplementation "org.apache.httpcomponents:httpclient"
+    testImplementation 'org.mock-server:mockserver-netty:5.15.0'
+    testImplementation 'com.squareup.okhttp3:okhttp:4.12.0'
     compileOnly 'org.projectlombok:lombok'
     annotationProcessor 'org.projectlombok:lombok'
 }
\ No newline at end of file
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/server/HttpConnectServer.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/server/HttpConnectServer.java
index bd94fed12..8d753d281 100644
--- 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/server/HttpConnectServer.java
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/server/HttpConnectServer.java
@@ -18,6 +18,7 @@
 package org.apache.eventmesh.connector.http.server;
 
 import org.apache.eventmesh.connector.http.config.HttpServerConfig;
+import org.apache.eventmesh.connector.http.sink.HttpSinkConnector;
 import 
org.apache.eventmesh.connector.http.source.connector.HttpSourceConnector;
 import org.apache.eventmesh.openconnect.Application;
 import org.apache.eventmesh.openconnect.util.ConfigUtil;
@@ -33,7 +34,8 @@ public class HttpConnectServer {
         }
 
         if (serverConfig.isSinkEnable()) {
-            // TODO support sink connector
+            Application httpSinkApp = new Application();
+            httpSinkApp.run(HttpSinkConnector.class);
         }
     }
 
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/HttpSinkConnector.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/HttpSinkConnector.java
new file mode 100644
index 000000000..23d09fa14
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/HttpSinkConnector.java
@@ -0,0 +1,124 @@
+/*
+ * 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.eventmesh.connector.http.sink;
+
+import org.apache.eventmesh.connector.http.sink.config.HttpSinkConfig;
+import org.apache.eventmesh.connector.http.sink.config.SinkConnectorConfig;
+import org.apache.eventmesh.connector.http.sink.handle.CommonHttpSinkHandler;
+import org.apache.eventmesh.connector.http.sink.handle.HttpSinkHandler;
+import org.apache.eventmesh.connector.http.sink.handle.RetryHttpSinkHandler;
+import org.apache.eventmesh.connector.http.sink.handle.WebhookHttpSinkHandler;
+import org.apache.eventmesh.openconnect.api.config.Config;
+import org.apache.eventmesh.openconnect.api.connector.ConnectorContext;
+import org.apache.eventmesh.openconnect.api.connector.SinkConnectorContext;
+import org.apache.eventmesh.openconnect.api.sink.Sink;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.util.List;
+import java.util.Objects;
+
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class HttpSinkConnector implements Sink {
+
+    private HttpSinkConfig httpSinkConfig;
+
+    @Getter
+    private HttpSinkHandler sinkHandler;
+
+    @Override
+    public Class<? extends Config> configClass() {
+        return HttpSinkConfig.class;
+    }
+
+    @Override
+    public void init(Config config) throws Exception {
+        this.httpSinkConfig = (HttpSinkConfig) config;
+        doInit();
+    }
+
+    @Override
+    public void init(ConnectorContext connectorContext) throws Exception {
+        SinkConnectorContext sinkConnectorContext = (SinkConnectorContext) 
connectorContext;
+        this.httpSinkConfig = (HttpSinkConfig) 
sinkConnectorContext.getSinkConfig();
+        doInit();
+    }
+
+    @SneakyThrows
+    private void doInit() {
+        // Fill default values if absent
+        
SinkConnectorConfig.populateFieldsWithDefaults(this.httpSinkConfig.connectorConfig);
+        // Create different handlers for different configurations
+        HttpSinkHandler nonRetryHandler;
+        if 
(this.httpSinkConfig.connectorConfig.getWebhookConfig().isActivate()) {
+            nonRetryHandler = new 
WebhookHttpSinkHandler(this.httpSinkConfig.connectorConfig);
+        } else {
+            nonRetryHandler = new 
CommonHttpSinkHandler(this.httpSinkConfig.connectorConfig);
+        }
+
+        int maxRetries = 
this.httpSinkConfig.connectorConfig.getRetryConfig().getMaxRetries();
+        if (maxRetries == 0) {
+            // Use the original sink handler
+            this.sinkHandler = nonRetryHandler;
+        } else if (maxRetries > 0) {
+            // Wrap the sink handler with a retry handler
+            this.sinkHandler = new 
RetryHttpSinkHandler(this.httpSinkConfig.connectorConfig, nonRetryHandler);
+        } else {
+            throw new IllegalArgumentException("Max retries must be greater 
than or equal to 0.");
+        }
+    }
+
+    @Override
+    public void start() throws Exception {
+        this.sinkHandler.start();
+    }
+
+    @Override
+    public void commit(ConnectRecord record) {
+
+    }
+
+    @Override
+    public String name() {
+        return this.httpSinkConfig.connectorConfig.getConnectorName();
+    }
+
+    @Override
+    public void stop() throws Exception {
+        this.sinkHandler.stop();
+    }
+
+    @Override
+    public void put(List<ConnectRecord> sinkRecords) {
+        for (ConnectRecord sinkRecord : sinkRecords) {
+            try {
+                if (Objects.isNull(sinkRecord)) {
+                    log.warn("ConnectRecord data is null, ignore.");
+                    continue;
+                }
+                // Handle the ConnectRecord
+                this.sinkHandler.handle(sinkRecord);
+            } catch (Exception e) {
+                log.error("Failed to sink message via HTTP. ", e);
+            }
+        }
+    }
+}
diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpRetryConfig.java
similarity index 59%
copy from eventmesh-connectors/eventmesh-connector-http/build.gradle
copy to 
eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpRetryConfig.java
index 734b2fc62..cf6d5adcb 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpRetryConfig.java
@@ -1,27 +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.
- */
-
-dependencies {
-    api project(":eventmesh-openconnect:eventmesh-openconnect-java")
-    implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
-    implementation 'io.vertx:vertx-web:4.4.6'
-
-    testImplementation "org.apache.httpcomponents:httpclient"
-    compileOnly 'org.projectlombok:lombok'
-    annotationProcessor 'org.projectlombok:lombok'
-}
\ No newline at end of file
+/*
+ * 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.eventmesh.connector.http.sink.config;
+
+import lombok.Data;
+
+@Data
+public class HttpRetryConfig {
+    // maximum number of retries, default 3, minimum 0
+    private int maxRetries = 3;
+
+    // retry interval, default 2000ms
+    private int interval = 2000;
+
+    // Default value is false, indicating that only requests with 
network-level errors will be retried.
+    // If set to true, all failed requests will be retried, including 
network-level errors and non-2xx responses.
+    private boolean retryOnNonSuccess = false;
+}
diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpSinkConfig.java
similarity index 65%
copy from eventmesh-connectors/eventmesh-connector-http/build.gradle
copy to 
eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpSinkConfig.java
index 734b2fc62..3dd0c2b6a 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpSinkConfig.java
@@ -1,27 +1,30 @@
-/*
- * 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.
- */
-
-dependencies {
-    api project(":eventmesh-openconnect:eventmesh-openconnect-java")
-    implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
-    implementation 'io.vertx:vertx-web:4.4.6'
-
-    testImplementation "org.apache.httpcomponents:httpclient"
-    compileOnly 'org.projectlombok:lombok'
-    annotationProcessor 'org.projectlombok:lombok'
-}
\ No newline at end of file
+/*
+ * 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.eventmesh.connector.http.sink.config;
+
+import org.apache.eventmesh.openconnect.api.config.SinkConfig;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class HttpSinkConfig extends SinkConfig {
+
+    public SinkConnectorConfig connectorConfig;
+}
diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpWebhookConfig.java
similarity index 65%
copy from eventmesh-connectors/eventmesh-connector-http/build.gradle
copy to 
eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpWebhookConfig.java
index 734b2fc62..f15bac456 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/HttpWebhookConfig.java
@@ -1,27 +1,37 @@
-/*
- * 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.
- */
-
-dependencies {
-    api project(":eventmesh-openconnect:eventmesh-openconnect-java")
-    implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
-    implementation 'io.vertx:vertx-web:4.4.6'
-
-    testImplementation "org.apache.httpcomponents:httpclient"
-    compileOnly 'org.projectlombok:lombok'
-    annotationProcessor 'org.projectlombok:lombok'
-}
\ No newline at end of file
+/*
+ * 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.eventmesh.connector.http.sink.config;
+
+import lombok.Data;
+
+@Data
+public class HttpWebhookConfig {
+
+    private boolean activate = false;
+
+    // Path to display/export callback data
+    private String exportPath = "/export";
+
+    private int port;
+
+    // timeunit: ms
+    private int serverIdleTimeout = 5000;
+
+    // max size of the storage queue
+    private int maxStorageSize = 5000;
+}
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/SinkConnectorConfig.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/SinkConnectorConfig.java
new file mode 100644
index 000000000..9bb338cce
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/config/SinkConnectorConfig.java
@@ -0,0 +1,73 @@
+/*
+ * 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.eventmesh.connector.http.sink.config;
+
+import io.vertx.core.http.HttpClientOptions;
+
+import lombok.Data;
+
+@Data
+public class SinkConnectorConfig {
+
+    private String connectorName;
+
+    private String[] urls;
+
+    // keepAlive, default true
+    private boolean keepAlive = HttpClientOptions.DEFAULT_KEEP_ALIVE;
+
+    // timeunit: ms, default 60000ms
+    private int keepAliveTimeout = 
HttpClientOptions.DEFAULT_KEEP_ALIVE_TIMEOUT * 1000; // Keep units consistent
+
+    // timeunit: ms, default 5000ms, recommended scope: 5000ms - 10000ms
+    private int connectionTimeout = 5000;
+
+    // timeunit: ms, default 5000ms
+    private int idleTimeout;
+
+    // maximum number of HTTP/1 connections a client will pool, default 5
+    private int maxConnectionPoolSize = 
HttpClientOptions.DEFAULT_MAX_POOL_SIZE;
+
+    // retry config
+    private HttpRetryConfig retryConfig = new HttpRetryConfig();
+
+    // webhook config
+    private HttpWebhookConfig webhookConfig = new HttpWebhookConfig();
+
+
+    /**
+     * Fill default values if absent (When there are multiple default values 
for a field)
+     *
+     * @param config SinkConnectorConfig
+     */
+    public static void populateFieldsWithDefaults(SinkConnectorConfig config) {
+        /*
+         * set default values for idleTimeout
+         * recommended scope: common(5s - 10s), webhook(15s - 30s)
+         */
+        final int commonHttpIdleTimeout = 5000;
+        final int webhookHttpIdleTimeout = 15000;
+
+        // Set default values for idleTimeout
+        if (config.getIdleTimeout() == 0) {
+            int idleTimeout = config.webhookConfig.isActivate() ? 
webhookHttpIdleTimeout : commonHttpIdleTimeout;
+            config.setIdleTimeout(idleTimeout);
+        }
+
+    }
+}
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpConnectRecord.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpConnectRecord.java
new file mode 100644
index 000000000..1bfd22307
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpConnectRecord.java
@@ -0,0 +1,67 @@
+/*
+ * 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.eventmesh.connector.http.sink.data;
+
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+import java.util.UUID;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * a special ConnectRecord for HttpSinkConnector
+ */
+@Data
+@Builder
+public class HttpConnectRecord {
+
+    private String type;
+
+    private String time;
+
+    private String uuid;
+
+    private String eventId;
+
+    private ConnectRecord data;
+
+    /**
+     * Convert ConnectRecord to HttpConnectRecord
+     *
+     * @param record the ConnectRecord to convert
+     * @return the converted HttpConnectRecord
+     */
+    public static HttpConnectRecord convertConnectRecord(ConnectRecord record, 
String type) {
+        Map<String, ?> offsetMap = 
record.getPosition().getOffset().getOffset();
+        String offset = "0";
+        if (!offsetMap.isEmpty()) {
+            offset = offsetMap.values().iterator().next().toString();
+        }
+        return HttpConnectRecord.builder()
+            .type(type)
+            .time(LocalDateTime.now().toString())
+            .uuid(UUID.randomUUID().toString())
+            .eventId(type + "-" + offset)
+            .data(record)
+            .build();
+    }
+
+}
diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportMetadata.java
similarity index 65%
copy from eventmesh-connectors/eventmesh-connector-http/build.gradle
copy to 
eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportMetadata.java
index 734b2fc62..848012f15 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportMetadata.java
@@ -1,27 +1,44 @@
-/*
- * 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.
- */
-
-dependencies {
-    api project(":eventmesh-openconnect:eventmesh-openconnect-java")
-    implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
-    implementation 'io.vertx:vertx-web:4.4.6'
-
-    testImplementation "org.apache.httpcomponents:httpclient"
-    compileOnly 'org.projectlombok:lombok'
-    annotationProcessor 'org.projectlombok:lombok'
-}
\ No newline at end of file
+/*
+ * 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.eventmesh.connector.http.sink.data;
+
+import java.time.LocalDateTime;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Metadata for an HTTP export operation.
+ */
+@Data
+@Builder
+public class HttpExportMetadata {
+    private String url;
+
+    private int code;
+
+    private String message;
+
+    private LocalDateTime receivedTime;
+
+    private String uuid;
+
+    private String retriedBy;
+
+    private int retryNum;
+}
diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportRecord.java
similarity index 65%
copy from eventmesh-connectors/eventmesh-connector-http/build.gradle
copy to 
eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportRecord.java
index 734b2fc62..b6382aee7 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportRecord.java
@@ -1,27 +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.
- */
-
-dependencies {
-    api project(":eventmesh-openconnect:eventmesh-openconnect-java")
-    implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
-    implementation 'io.vertx:vertx-web:4.4.6'
-
-    testImplementation "org.apache.httpcomponents:httpclient"
-    compileOnly 'org.projectlombok:lombok'
-    annotationProcessor 'org.projectlombok:lombok'
-}
\ No newline at end of file
+/*
+ * 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.eventmesh.connector.http.sink.data;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Represents an HTTP export record containing metadata and data to be 
exported.
+ */
+@Data
+@AllArgsConstructor
+public class HttpExportRecord {
+
+    private HttpExportMetadata metadata;
+
+    private Object data;
+}
diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportRecordPage.java
similarity index 65%
copy from eventmesh-connectors/eventmesh-connector-http/build.gradle
copy to 
eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportRecordPage.java
index 734b2fc62..5c44eb3b7 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/data/HttpExportRecordPage.java
@@ -1,27 +1,38 @@
-/*
- * 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.
- */
-
-dependencies {
-    api project(":eventmesh-openconnect:eventmesh-openconnect-java")
-    implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
-    implementation 'io.vertx:vertx-web:4.4.6'
-
-    testImplementation "org.apache.httpcomponents:httpclient"
-    compileOnly 'org.projectlombok:lombok'
-    annotationProcessor 'org.projectlombok:lombok'
-}
\ No newline at end of file
+/*
+ * 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.eventmesh.connector.http.sink.data;
+
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Represents a page of HTTP export records.
+ */
+@Data
+@AllArgsConstructor
+public class HttpExportRecordPage {
+
+    private int pageNum;
+
+    private int pageSize;
+
+    private List<HttpExportRecord> pageItems;
+
+}
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/CommonHttpSinkHandler.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/CommonHttpSinkHandler.java
new file mode 100644
index 000000000..e21046c4d
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/CommonHttpSinkHandler.java
@@ -0,0 +1,181 @@
+/*
+ * 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.eventmesh.connector.http.sink.handle;
+
+import org.apache.eventmesh.connector.http.sink.config.SinkConnectorConfig;
+import org.apache.eventmesh.connector.http.sink.data.HttpConnectRecord;
+import org.apache.eventmesh.connector.http.util.HttpUtils;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.client.HttpResponse;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.client.WebClientOptions;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Common HTTP/HTTPS Sink Handler implementation to handle ConnectRecords by 
sending them over HTTP or HTTPS to configured URLs.
+ *
+ * <p>This handler initializes a WebClient for making HTTP requests based on 
the provided SinkConnectorConfig.
+ * It handles processing ConnectRecords by converting them to 
HttpConnectRecord and sending them asynchronously to each configured URL using 
the
+ * WebClient.</p>
+ *
+ * <p>The handler uses Vert.x's WebClient to perform HTTP/HTTPS requests. It 
initializes the WebClient in the {@link #start()}
+ * method and closes it in the {@link #stop()} method to manage resources 
efficiently.</p>
+ *
+ * <p>Each ConnectRecord is processed and sent to all configured URLs 
concurrently using asynchronous HTTP requests.</p>
+ */
+@Slf4j
+@Getter
+public class CommonHttpSinkHandler implements HttpSinkHandler {
+
+    private final SinkConnectorConfig connectorConfig;
+
+    private final List<URI> urls;
+
+    private WebClient webClient;
+
+
+    public CommonHttpSinkHandler(SinkConnectorConfig sinkConnectorConfig) {
+        this.connectorConfig = sinkConnectorConfig;
+        // Initialize URLs
+        String[] urlStrings = sinkConnectorConfig.getUrls();
+        this.urls = Arrays.stream(urlStrings)
+            .map(URI::create)
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * Initializes the WebClient for making HTTP requests based on the 
provided SinkConnectorConfig.
+     */
+    @Override
+    public void start() {
+        // Create WebClient
+        doInitWebClient();
+    }
+
+    /**
+     * Initializes the WebClient with the provided configuration options.
+     */
+    private void doInitWebClient() {
+        final Vertx vertx = Vertx.vertx();
+        WebClientOptions options = new WebClientOptions()
+            .setKeepAlive(this.connectorConfig.isKeepAlive())
+            .setKeepAliveTimeout(this.connectorConfig.getKeepAliveTimeout() / 
1000)
+            .setIdleTimeout(this.connectorConfig.getIdleTimeout())
+            .setIdleTimeoutUnit(TimeUnit.MILLISECONDS)
+            .setConnectTimeout(this.connectorConfig.getConnectionTimeout())
+            .setMaxPoolSize(this.connectorConfig.getMaxConnectionPoolSize());
+        this.webClient = WebClient.create(vertx, options);
+    }
+
+    /**
+     * Processes a ConnectRecord by sending it over HTTP or HTTPS. This method 
should be called for each ConnectRecord that needs to be processed.
+     *
+     * @param record the ConnectRecord to process
+     */
+    @Override
+    public void handle(ConnectRecord record) {
+        for (URI url : this.urls) {
+            // convert ConnectRecord to HttpConnectRecord
+            String type = String.format("%s.%s.%s", 
connectorConfig.getConnectorName(), url.getScheme(), "common");
+            HttpConnectRecord httpConnectRecord = 
HttpConnectRecord.convertConnectRecord(record, type);
+            deliver(url, httpConnectRecord);
+        }
+    }
+
+
+    /**
+     * Processes HttpConnectRecord on specified URL while returning its own 
processing logic.
+     * This method sends the HttpConnectRecord to the specified URL using the 
WebClient.
+     *
+     * @param url               URI to which the HttpConnectRecord should be 
sent
+     * @param httpConnectRecord HttpConnectRecord to process
+     * @return processing chain
+     */
+    @Override
+    public Future<HttpResponse<Buffer>> deliver(URI url, HttpConnectRecord 
httpConnectRecord) {
+        // create headers
+        MultiMap headers = HttpHeaders.headers()
+            .set(HttpHeaderNames.CONTENT_TYPE, "application/json; 
charset=utf-8")
+            .set(HttpHeaderNames.ACCEPT, "application/json; charset=utf-8");
+
+        // get timestamp and offset
+        Long timestamp = httpConnectRecord.getData().getTimestamp();
+        Map<String, ?> offset = 
httpConnectRecord.getData().getPosition().getOffset().getOffset();
+
+        // send the request
+        return this.webClient.post(url.getPath())
+            .host(url.getHost())
+            .port(url.getPort() == -1 ? (Objects.equals(url.getScheme(), 
"https") ? 443 : 80) : url.getPort())
+            .putHeaders(headers)
+            .ssl(Objects.equals(url.getScheme(), "https"))
+            .sendJson(httpConnectRecord)
+            .onSuccess(res -> {
+                log.info("Request sent successfully. Record: timestamp={}, 
offset={}", timestamp, offset);
+                // log the response
+                if (HttpUtils.is2xxSuccessful(res.statusCode())) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Received successful response: 
statusCode={}. Record: timestamp={}, offset={}, responseBody={}",
+                            res.statusCode(), timestamp, offset, 
res.bodyAsString());
+                    } else {
+                        log.info("Received successful response: statusCode={}. 
Record: timestamp={}, offset={}", res.statusCode(), timestamp, offset);
+                    }
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.warn("Received non-2xx response: statusCode={}. 
Record: timestamp={}, offset={}, responseBody={}",
+                            res.statusCode(), timestamp, offset, 
res.bodyAsString());
+                    } else {
+                        log.warn("Received non-2xx response: statusCode={}. 
Record: timestamp={}, offset={}", res.statusCode(), timestamp, offset);
+                    }
+                }
+
+            })
+            .onFailure(err -> log.error("Request failed to send. Record: 
timestamp={}, offset={}", timestamp, offset, err));
+    }
+
+
+    /**
+     * Cleans up and releases resources used by the HTTP/HTTPS handler.
+     */
+    @Override
+    public void stop() {
+        if (this.webClient != null) {
+            this.webClient.close();
+        } else {
+            log.warn("WebClient is null, ignore.");
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/HttpSinkHandler.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/HttpSinkHandler.java
new file mode 100644
index 000000000..09fd66a76
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/HttpSinkHandler.java
@@ -0,0 +1,74 @@
+/*
+ * 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.eventmesh.connector.http.sink.handle;
+
+import org.apache.eventmesh.connector.http.sink.data.HttpConnectRecord;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.net.URI;
+
+import io.vertx.core.Future;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.ext.web.client.HttpResponse;
+
+/**
+ * Interface for handling ConnectRecords via HTTP or HTTPS. Classes 
implementing this interface are responsible for processing ConnectRecords by
+ * sending them over HTTP or HTTPS, with additional support for handling 
multiple requests and asynchronous processing.
+ *
+ * <p>Any class that needs to process ConnectRecords via HTTP or HTTPS should 
implement this interface.
+ * Implementing classes must provide implementations for the {@link #start()}, 
{@link #handle(ConnectRecord)},
+ * {@link #deliver(URI, HttpConnectRecord)}, and {@link #stop()} methods.</p>
+ *
+ * <p>Implementing classes should ensure thread safety and handle HTTP/HTTPS 
communication efficiently.
+ * The {@link #start()} method initializes any necessary resources for 
HTTP/HTTPS communication. The {@link #handle(ConnectRecord)} method processes a
+ * ConnectRecord by sending it over HTTP or HTTPS. The {@link #deliver(URI, 
HttpConnectRecord)} method processes HttpConnectRecord on specified URL
+ * while returning its own processing logic {@link #stop()} method releases 
any resources used for HTTP/HTTPS communication.</p>
+ *
+ * <p>It's recommended to handle exceptions gracefully within the {@link 
#deliver(URI, HttpConnectRecord)} method
+ * to prevent message loss or processing interruptions.</p>
+ */
+public interface HttpSinkHandler {
+
+    /**
+     * Initializes the HTTP/HTTPS handler. This method should be called before 
using the handler.
+     */
+    void start();
+
+    /**
+     * Processes a ConnectRecord by sending it over HTTP or HTTPS. This method 
should be called for each ConnectRecord that needs to be processed.
+     *
+     * @param record the ConnectRecord to process
+     */
+    void handle(ConnectRecord record);
+
+
+    /**
+     * Processes HttpConnectRecord on specified URL while returning its own 
processing logic
+     *
+     * @param url               URI to which the HttpConnectRecord should be 
sent
+     * @param httpConnectRecord HttpConnectRecord to process
+     * @return processing chain
+     */
+    Future<HttpResponse<Buffer>> deliver(URI url, HttpConnectRecord 
httpConnectRecord);
+
+    /**
+     * Cleans up and releases resources used by the HTTP/HTTPS handler. This 
method should be called when the handler is no longer needed.
+     */
+    void stop();
+}
+
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/RetryHttpSinkHandler.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/RetryHttpSinkHandler.java
new file mode 100644
index 000000000..06700261d
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/RetryHttpSinkHandler.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.eventmesh.connector.http.sink.handle;
+
+import org.apache.eventmesh.connector.http.sink.config.HttpRetryConfig;
+import org.apache.eventmesh.connector.http.sink.config.SinkConnectorConfig;
+import org.apache.eventmesh.connector.http.sink.data.HttpConnectRecord;
+import org.apache.eventmesh.connector.http.sink.data.HttpExportMetadata;
+import org.apache.eventmesh.connector.http.sink.data.HttpExportRecord;
+import org.apache.eventmesh.connector.http.util.HttpUtils;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import java.net.ConnectException;
+import java.net.URI;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import io.vertx.core.Future;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.ext.web.client.HttpResponse;
+
+import lombok.extern.slf4j.Slf4j;
+
+import dev.failsafe.Failsafe;
+import dev.failsafe.RetryPolicy;
+import dev.failsafe.RetryPolicyBuilder;
+import dev.failsafe.event.ExecutionEvent;
+
+
+@Slf4j
+public class RetryHttpSinkHandler implements HttpSinkHandler {
+
+    private final SinkConnectorConfig connectorConfig;
+
+    // Retry policy builder
+    private RetryPolicyBuilder<HttpResponse<Buffer>> retryPolicyBuilder;
+
+    private final List<URI> urls;
+
+    private final HttpSinkHandler sinkHandler;
+
+
+    public RetryHttpSinkHandler(SinkConnectorConfig connectorConfig, 
HttpSinkHandler sinkHandler) {
+        this.connectorConfig = connectorConfig;
+        this.sinkHandler = sinkHandler;
+
+        // Initialize retry
+        initRetry();
+
+        // Initialize URLs
+        String[] urlStrings = connectorConfig.getUrls();
+        this.urls = Arrays.stream(urlStrings)
+            .map(URI::create)
+            .collect(Collectors.toList());
+    }
+
+    private void initRetry() {
+        HttpRetryConfig httpRetryConfig = 
this.connectorConfig.getRetryConfig();
+
+        this.retryPolicyBuilder = RetryPolicy.<HttpResponse<Buffer>>builder()
+            .handleIf(e -> e instanceof ConnectException)
+            .handleResultIf(response -> httpRetryConfig.isRetryOnNonSuccess() 
&& !HttpUtils.is2xxSuccessful(response.statusCode()))
+            .withMaxRetries(httpRetryConfig.getMaxRetries())
+            .withDelay(Duration.ofMillis(httpRetryConfig.getInterval()));
+    }
+
+
+    /**
+     * Initializes the WebClient for making HTTP requests based on the 
provided SinkConnectorConfig.
+     */
+    @Override
+    public void start() {
+        sinkHandler.start();
+    }
+
+
+    /**
+     * Processes a ConnectRecord by sending it over HTTP or HTTPS. This method 
should be called for each ConnectRecord that needs to be processed.
+     *
+     * @param record the ConnectRecord to process
+     */
+    @Override
+    public void handle(ConnectRecord record) {
+        for (URI url : this.urls) {
+            // convert ConnectRecord to HttpConnectRecord
+            String type = String.format("%s.%s.%s",
+                this.connectorConfig.getConnectorName(), url.getScheme(),
+                this.connectorConfig.getWebhookConfig().isActivate() ? 
"webhook" : "common");
+            HttpConnectRecord httpConnectRecord = 
HttpConnectRecord.convertConnectRecord(record, type);
+            // handle the HttpConnectRecord
+            deliver(url, httpConnectRecord);
+        }
+    }
+
+
+    /**
+     * Processes HttpConnectRecord on specified URL while returning its own 
processing logic This method provides the retry power to process the
+     * HttpConnectRecord
+     *
+     * @param url               URI to which the HttpConnectRecord should be 
sent
+     * @param httpConnectRecord HttpConnectRecord to process
+     * @return processing chain
+     */
+    @Override
+    public Future<HttpResponse<Buffer>> deliver(URI url, HttpConnectRecord 
httpConnectRecord) {
+        // Only webhook mode needs to use the UUID to identify the request
+        String id = httpConnectRecord.getUuid();
+
+        // Build the retry policy
+        RetryPolicy<HttpResponse<Buffer>> retryPolicy = retryPolicyBuilder
+            .onSuccess(event -> {
+                if (connectorConfig.getWebhookConfig().isActivate()) {
+                    // convert the result to an HttpExportRecord
+                    HttpExportRecord exportRecord = 
covertToExportRecord(httpConnectRecord, event, event.getResult(), 
event.getException(), url, id);
+                    // add the data to the queue
+                    ((WebhookHttpSinkHandler) 
sinkHandler).addDataToQueue(exportRecord);
+                }
+            })
+            .onRetry(event -> {
+                if (log.isDebugEnabled()) {
+                    log.warn("Retrying the request to {} for the {} time. 
HttpConnectRecord= {}", url, event.getAttemptCount(), httpConnectRecord);
+                } else {
+                    log.warn("Retrying the request to {} for the {} time.", 
url, event.getAttemptCount());
+                }
+                if (connectorConfig.getWebhookConfig().isActivate()) {
+                    HttpExportRecord exportRecord =
+                        covertToExportRecord(httpConnectRecord, event, 
event.getLastResult(), event.getLastException(), url, id);
+                    ((WebhookHttpSinkHandler) 
sinkHandler).addDataToQueue(exportRecord);
+                }
+                // update the HttpConnectRecord
+                httpConnectRecord.setTime(LocalDateTime.now().toString());
+                httpConnectRecord.setUuid(UUID.randomUUID().toString());
+            })
+            .onFailure(event -> {
+                if (log.isDebugEnabled()) {
+                    log.error("Failed to send the request to {} after {} 
attempts. HttpConnectRecord= {}", url, event.getAttemptCount(),
+                        httpConnectRecord, event.getException());
+                } else {
+                    log.error("Failed to send the request to {} after {} 
attempts.", url, event.getAttemptCount(), event.getException());
+                }
+                if (connectorConfig.getWebhookConfig().isActivate()) {
+                    HttpExportRecord exportRecord = 
covertToExportRecord(httpConnectRecord, event, event.getResult(), 
event.getException(), url, id);
+                    ((WebhookHttpSinkHandler) 
sinkHandler).addDataToQueue(exportRecord);
+                }
+            }).build();
+
+        // Handle the HttpConnectRecord with retry
+        Failsafe.with(retryPolicy)
+            .getStageAsync(() -> sinkHandler.deliver(url, 
httpConnectRecord).toCompletionStage());
+
+        return null;
+    }
+
+    /**
+     * Converts the ExecutionCompletedEvent to an HttpExportRecord.
+     *
+     * @param httpConnectRecord HttpConnectRecord
+     * @param event             ExecutionEvent
+     * @param response          the response of the request, may be null
+     * @param e                 the exception thrown during the request, may 
be null
+     * @param url               the URL the request was sent to
+     * @param id                UUID
+     * @return the converted HttpExportRecord
+     */
+    private HttpExportRecord covertToExportRecord(HttpConnectRecord 
httpConnectRecord, ExecutionEvent event, HttpResponse<Buffer> response,
+        Throwable e, URI url, String id) {
+
+        HttpExportMetadata httpExportMetadata = HttpExportMetadata.builder()
+            .url(url.toString())
+            .code(response != null ? response.statusCode() : -1)
+            .message(response != null ? response.statusMessage() : 
e.getMessage())
+            .receivedTime(LocalDateTime.now())
+            .uuid(httpConnectRecord.getUuid())
+            .retriedBy(event.getAttemptCount() > 1 ? id : null)
+            .retryNum(event.getAttemptCount() - 1).build();
+
+        return new HttpExportRecord(httpExportMetadata, response == null ? 
null : response.bodyAsString());
+    }
+
+    /**
+     * Cleans up and releases resources used by the HTTP/HTTPS handler.
+     */
+    @Override
+    public void stop() {
+        sinkHandler.stop();
+    }
+}
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/WebhookHttpSinkHandler.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/WebhookHttpSinkHandler.java
new file mode 100644
index 000000000..e07683fcf
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/sink/handle/WebhookHttpSinkHandler.java
@@ -0,0 +1,348 @@
+/*
+ * 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.eventmesh.connector.http.sink.handle;
+
+import org.apache.eventmesh.common.exception.EventMeshException;
+import org.apache.eventmesh.connector.http.sink.config.HttpWebhookConfig;
+import org.apache.eventmesh.connector.http.sink.config.SinkConnectorConfig;
+import org.apache.eventmesh.connector.http.sink.data.HttpConnectRecord;
+import org.apache.eventmesh.connector.http.sink.data.HttpExportMetadata;
+import org.apache.eventmesh.connector.http.sink.data.HttpExportRecord;
+import org.apache.eventmesh.connector.http.sink.data.HttpExportRecordPage;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.net.URI;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpMethod;
+import io.vertx.core.http.HttpServer;
+import io.vertx.core.http.HttpServerOptions;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.client.HttpResponse;
+import io.vertx.ext.web.handler.LoggerHandler;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Extends CommonHttpSinkHandler to provide additional functionality for 
handling webhook features, including sending requests to callback servers,
+ * allowing longer response wait times, storing responses returned from 
callback servers, and exposing received data through an HTTP service.
+ */
+@Slf4j
+public class WebhookHttpSinkHandler extends CommonHttpSinkHandler {
+
+    private final SinkConnectorConfig sinkConnectorConfig;
+
+    // the configuration for webhook
+    private final HttpWebhookConfig webhookConfig;
+
+    // the server for exporting the received data
+    private HttpServer exportServer;
+
+    // store the received data, when webhook is enabled
+    private final ConcurrentLinkedQueue<HttpExportRecord> receivedDataQueue;
+
+    // the maximum queue size
+    private final int maxQueueSize;
+
+    // the current queue size
+    private final AtomicInteger currentQueueSize;
+
+    public WebhookHttpSinkHandler(SinkConnectorConfig sinkConnectorConfig) {
+        super(sinkConnectorConfig);
+        this.sinkConnectorConfig = sinkConnectorConfig;
+        this.webhookConfig = sinkConnectorConfig.getWebhookConfig();
+        this.maxQueueSize = this.webhookConfig.getMaxStorageSize();
+        this.currentQueueSize = new AtomicInteger(0);
+        this.receivedDataQueue = new ConcurrentLinkedQueue<>();
+        // init the export server
+        doInitExportServer();
+    }
+
+    /**
+     * Initialize the server for exporting the received data
+     */
+    private void doInitExportServer() {
+        final Vertx vertx = Vertx.vertx();
+        final Router router = Router.router(vertx);
+        // add logger handler
+        router.route().handler(LoggerHandler.create());
+        // add export handler
+        router.route()
+            .path(this.webhookConfig.getExportPath())
+            .method(HttpMethod.GET)
+            .produces("application/json")
+            .handler(ctx -> {
+                // Validate the request parameters
+                MultiMap params = ctx.request().params();
+                String pageNumStr = params.get(ParamEnum.PAGE_NUM.getValue());
+                String pageSizeStr = 
params.get(ParamEnum.PAGE_SIZE.getValue());
+                String type = params.get(ParamEnum.TYPE.getValue());
+
+                // 1. type must be "poll" or "peek" or null
+                // 2. if type is "peek", pageNum must be greater than 0
+                // 3. pageSize must be greater than 0
+                if ((type != null && !Objects.equals(type, 
TypeEnum.PEEK.getValue()) && !Objects.equals(type, TypeEnum.POLL.getValue()))
+                    || (Objects.equals(type, TypeEnum.PEEK.getValue()) && 
(StringUtils.isBlank(pageNumStr) || Integer.parseInt(pageNumStr) < 1))
+                    || (StringUtils.isBlank(pageSizeStr) || 
Integer.parseInt(pageSizeStr) < 1)) {
+
+                    // Return 400 Bad Request if the request parameters are 
invalid
+                    ctx.response()
+                        .putHeader(HttpHeaders.CONTENT_TYPE, 
"application/json; charset=utf-8")
+                        .setStatusCode(HttpResponseStatus.BAD_REQUEST.code())
+                        .end();
+                    log.info("Invalid request parameters. pageNum: {}, 
pageSize: {}, type: {}", pageNumStr, pageSizeStr, type);
+                    return;
+                }
+
+                // Parse the request parameters
+                if (type == null) {
+                    type = TypeEnum.PEEK.getValue();
+                }
+                int pageNum = StringUtils.isBlank(pageNumStr) ? 1 : 
Integer.parseInt(pageNumStr);
+                int pageSize = Integer.parseInt(pageSizeStr);
+
+                if (currentQueueSize.get() == 0) {
+                    ctx.response()
+                        .putHeader(HttpHeaders.CONTENT_TYPE, 
"application/json; charset=utf-8")
+                        .setStatusCode(HttpResponseStatus.NO_CONTENT.code())
+                        .end();
+                    log.info("No callback data to export.");
+                    return;
+                }
+
+                // Get the received data
+                List<HttpExportRecord> exportRecords;
+                if (Objects.equals(type, TypeEnum.POLL.getValue())) {
+                    // If the type is poll, only the first page of data is 
exported and removed
+                    exportRecords = getDataFromQueue(0, pageSize, true);
+                } else {
+                    // If the type is peek, the specified page of data is 
exported without removing
+                    int startIndex = (pageNum - 1) * pageSize;
+                    int endIndex = startIndex + pageSize;
+                    exportRecords = getDataFromQueue(startIndex, endIndex, 
false);
+                }
+
+                // Create HttpExportRecordPage
+                HttpExportRecordPage page = new HttpExportRecordPage(pageNum, 
exportRecords.size(), exportRecords);
+
+                // export the received data
+                ctx.response()
+                    .putHeader(HttpHeaders.CONTENT_TYPE, "application/json; 
charset=utf-8")
+                    .setStatusCode(HttpResponseStatus.OK.code())
+                    .send(JSON.toJSONString(page, 
JSONWriter.Feature.WriteMapNullValue));
+                if (log.isDebugEnabled()) {
+                    log.debug("Succeed to export callback data. Data: {}", 
page);
+                } else {
+                    log.info("Succeed to export callback data.");
+                }
+            });
+        // create the export server
+        this.exportServer = vertx.createHttpServer(new HttpServerOptions()
+            .setPort(this.webhookConfig.getPort())
+            .setIdleTimeout(this.webhookConfig.getServerIdleTimeout())
+            .setIdleTimeoutUnit(TimeUnit.MILLISECONDS)).requestHandler(router);
+    }
+
+    /**
+     * Starts the HTTP/HTTPS handler by creating a WebClient with configured 
options and starting the export server.
+     */
+    @Override
+    public void start() {
+        // start the webclient
+        super.start();
+        // start the export server
+        Throwable t = this.exportServer.listen().cause();
+        if (t != null) {
+            throw new EventMeshException("Failed to start Vertx server. ", t);
+        }
+    }
+
+    /**
+     * Processes a ConnectRecord by sending it over HTTP or HTTPS. This method 
should be called for each ConnectRecord that needs to be processed.
+     *
+     * @param record the ConnectRecord to process
+     */
+    @Override
+    public void handle(ConnectRecord record) {
+        for (URI url : super.getUrls()) {
+            // convert ConnectRecord to HttpConnectRecord
+            String type = String.format("%s.%s.%s", 
this.getConnectorConfig().getConnectorName(), url.getScheme(), "webhook");
+            HttpConnectRecord httpConnectRecord = 
HttpConnectRecord.convertConnectRecord(record, type);
+            // handle the HttpConnectRecord
+            deliver(url, httpConnectRecord);
+        }
+    }
+
+
+    /**
+     * Processes HttpConnectRecord on specified URL while returning its own 
processing logic This method sends the HttpConnectRecord to the specified
+     * URL by super class method and stores the received data.
+     *
+     * @param url               URI to which the HttpConnectRecord should be 
sent
+     * @param httpConnectRecord HttpConnectRecord to process
+     * @return processing chain
+     */
+    @Override
+    public Future<HttpResponse<Buffer>> deliver(URI url, HttpConnectRecord 
httpConnectRecord) {
+        // send the request
+        Future<HttpResponse<Buffer>> responseFuture = super.deliver(url, 
httpConnectRecord);
+        // store the received data
+        return responseFuture.onComplete(arr -> {
+            // If open retry, return directly and handled by 
RetryHttpSinkHandler
+            if (sinkConnectorConfig.getRetryConfig().getMaxRetries() > 0) {
+                return;
+            }
+            // create ExportMetadataBuilder
+            HttpResponse<Buffer> response = arr.succeeded() ? arr.result() : 
null;
+
+            HttpExportMetadata httpExportMetadata = 
HttpExportMetadata.builder()
+                .url(url.toString())
+                .code(response != null ? response.statusCode() : -1)
+                .message(response != null ? response.statusMessage() : 
arr.cause().getMessage())
+                .receivedTime(LocalDateTime.now())
+                .retriedBy(null)
+                .uuid(httpConnectRecord.getUuid())
+                .retryNum(0)
+                .build();
+
+            // create ExportRecord
+            HttpExportRecord exportRecord = new 
HttpExportRecord(httpExportMetadata, arr.succeeded() ? 
arr.result().bodyAsString() : null);
+            // add the data to the queue
+            addDataToQueue(exportRecord);
+        });
+    }
+
+
+    /**
+     * Adds the received data to the queue.
+     *
+     * @param exportRecord the received data to add to the queue
+     */
+    public void addDataToQueue(HttpExportRecord exportRecord) {
+        // If the current queue size is greater than or equal to the maximum 
queue size, remove the oldest element
+        if (currentQueueSize.get() >= maxQueueSize) {
+            Object removedData = receivedDataQueue.poll();
+            if (log.isDebugEnabled()) {
+                log.debug("The queue is full, remove the oldest element: {}", 
removedData);
+            } else {
+                log.info("The queue is full, remove the oldest element");
+            }
+            currentQueueSize.decrementAndGet();
+        }
+        // Try to put the received data into the queue
+        if (receivedDataQueue.offer(exportRecord)) {
+            currentQueueSize.incrementAndGet();
+            log.debug("Successfully put the received data into the queue: {}", 
exportRecord);
+        } else {
+            log.error("Failed to put the received data into the queue: {}", 
exportRecord);
+        }
+    }
+
+    /**
+     * Gets the received data from the queue.
+     *
+     * @param startIndex the start index of the data to get
+     * @param endIndex   the end index of the data to get
+     * @param removed    whether to remove the data from the queue
+     * @return the received data
+     */
+    private List<HttpExportRecord> getDataFromQueue(int startIndex, int 
endIndex, boolean removed) {
+        Iterator<HttpExportRecord> iterator = receivedDataQueue.iterator();
+
+        List<HttpExportRecord> pageItems = new ArrayList<>(endIndex - 
startIndex);
+        int count = 0;
+        while (iterator.hasNext() && count < endIndex) {
+            HttpExportRecord item = iterator.next();
+            if (count >= startIndex) {
+                pageItems.add(item);
+                if (removed) {
+                    iterator.remove();
+                    currentQueueSize.decrementAndGet();
+                }
+            }
+            count++;
+        }
+        return pageItems;
+    }
+
+    /**
+     * Cleans up and releases resources used by the HTTP/HTTPS handler.
+     */
+    @Override
+    public void stop() {
+        // stop the webclient
+        super.stop();
+        // stop the export server
+        if (this.exportServer != null) {
+            Throwable t = this.exportServer.close().cause();
+            if (t != null) {
+                throw new EventMeshException("Failed to stop Vertx server. ", 
t);
+            }
+        } else {
+            log.warn("Callback server is null, ignore.");
+        }
+    }
+
+
+    @Getter
+    public enum ParamEnum {
+        PAGE_NUM("pageNum"),
+        PAGE_SIZE("pageSize"),
+        TYPE("type");
+
+        private final String value;
+
+        ParamEnum(String value) {
+            this.value = value;
+        }
+
+    }
+
+
+    @Getter
+    public enum TypeEnum {
+        POLL("poll"),
+        PEEK("peek");
+
+        private final String value;
+
+        TypeEnum(String value) {
+            this.value = value;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/eventmesh-connectors/eventmesh-connector-http/build.gradle 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/util/HttpUtils.java
similarity index 65%
copy from eventmesh-connectors/eventmesh-connector-http/build.gradle
copy to 
eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/util/HttpUtils.java
index 734b2fc62..79f9fd120 100644
--- a/eventmesh-connectors/eventmesh-connector-http/build.gradle
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/java/org/apache/eventmesh/connector/http/util/HttpUtils.java
@@ -1,27 +1,32 @@
-/*
- * 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.
- */
-
-dependencies {
-    api project(":eventmesh-openconnect:eventmesh-openconnect-java")
-    implementation project(":eventmesh-common")
-    implementation 'io.cloudevents:cloudevents-http-vertx:2.3.0'
-    implementation 'io.vertx:vertx-web:4.4.6'
-
-    testImplementation "org.apache.httpcomponents:httpclient"
-    compileOnly 'org.projectlombok:lombok'
-    annotationProcessor 'org.projectlombok:lombok'
-}
\ No newline at end of file
+/*
+ * 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.eventmesh.connector.http.util;
+
+public class HttpUtils {
+
+    /**
+     * Checks if the status code represents a successful response (2xx).
+     *
+     * @param statusCode the HTTP status code to check
+     * @return true if the status code is 2xx, false otherwise
+     */
+    public static boolean is2xxSuccessful(int statusCode) {
+        int seriesCode = statusCode / 100;
+        return seriesCode == 2;
+    }
+}
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/main/resources/sink-config.yml
 
b/eventmesh-connectors/eventmesh-connector-http/src/main/resources/sink-config.yml
new file mode 100644
index 000000000..f740cf7cd
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/main/resources/sink-config.yml
@@ -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.
+#
+
+pubSubConfig:
+  meshAddress: 127.0.0.1:10000
+  subject: TopicTest
+  idc: FT
+  env: PRD
+  group: httpSink
+  appId: 5032
+  userName: httpSinkUser
+  passWord: httpPassWord
+connectorConfig:
+  connectorName: httpSink
+  urls:
+   - http://127.0.0.1:8987/test
+  ssl: false
+  keepAlive: true
+  keepAliveTimeout: 60000
+  idleTimeout: 5000   # timeunit: ms, recommended scope: common(5s - 10s), 
webhook(15s - 60s)
+  connectionTimeout: 5000   # timeunit: ms, recommended scope: 5 - 10s
+  maxConnectionPoolSize: 5
+  retryConfig:
+    maxRetries: 2
+    interval: 1000
+    retryOnNonSuccess: false
+  webhookConfig:
+    activate: false
+    exportPath: /export
+    port: 8988
+    serverIdleTimeout: 5000
+    maxStorageSize: 5000
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/test/java/org/apache/eventmesh/connector/http/source/connector/HttpSinkConnectorTest.java
 
b/eventmesh-connectors/eventmesh-connector-http/src/test/java/org/apache/eventmesh/connector/http/source/connector/HttpSinkConnectorTest.java
new file mode 100644
index 000000000..738df6430
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/test/java/org/apache/eventmesh/connector/http/source/connector/HttpSinkConnectorTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.eventmesh.connector.http.source.connector;
+
+import static org.mockserver.model.HttpRequest.request;
+
+import org.apache.eventmesh.connector.http.sink.HttpSinkConnector;
+import org.apache.eventmesh.connector.http.sink.config.HttpSinkConfig;
+import org.apache.eventmesh.connector.http.sink.config.HttpWebhookConfig;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.ConnectRecord;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.RecordOffset;
+import org.apache.eventmesh.openconnect.offsetmgmt.api.data.RecordPartition;
+import org.apache.eventmesh.openconnect.util.ConfigUtil;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.HttpRequest;
+import org.mockserver.model.HttpResponse;
+import org.mockserver.model.MediaType;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+public class HttpSinkConnectorTest {
+
+    private HttpSinkConnector sinkConnector;
+
+    private HttpSinkConfig sinkConfig;
+
+    private URI severUri;
+
+    private ClientAndServer mockServer;
+
+
+    @BeforeEach
+    void before() throws Exception {
+        // init sinkConnector
+        this.sinkConnector = new HttpSinkConnector();
+        this.sinkConfig = (HttpSinkConfig) 
ConfigUtil.parse(sinkConnector.configClass());
+        this.sinkConnector.init(this.sinkConfig);
+        this.sinkConnector.start();
+
+        this.severUri = URI.create(sinkConfig.connectorConfig.getUrls()[0]);
+        // start mockServer
+        mockServer = ClientAndServer.startClientAndServer(severUri.getPort());
+        mockServer.reset()
+            .when(
+                request()
+                    .withMethod("POST")
+                    .withPath(severUri.getPath())
+            )
+            .respond(
+                httpRequest -> {
+                    JSONObject requestBody = 
JSON.parseObject(httpRequest.getBodyAsString());
+                    return HttpResponse.response()
+                        .withContentType(MediaType.APPLICATION_JSON)
+                        .withStatusCode(200)
+                        .withBody(new JSONObject()
+                            .fluentPut("code", 0)
+                            .fluentPut("message", "success")
+                            .fluentPut("data", 
requestBody.getJSONObject("data").get("data"))
+                            .toJSONString()
+                        ); // .withDelay(TimeUnit.SECONDS, 10);
+                }
+            );
+    }
+
+    @AfterEach
+    void after() throws Exception {
+        this.sinkConnector.stop();
+        this.mockServer.close();
+    }
+
+    @Test
+    void testPut() throws Exception {
+        // Create a list of ConnectRecord
+        final int times = 10;
+        List<ConnectRecord> connectRecords = new ArrayList<>();
+        for (int i = 0; i < times; i++) {
+            ConnectRecord record = createConnectRecord();
+            connectRecords.add(record);
+        }
+        // Put ConnectRecord
+        sinkConnector.put(connectRecords);
+
+        // sleep 5s
+        Thread.sleep(5000);
+
+        // verify request
+        HttpRequest[] recordedRequests = 
mockServer.retrieveRecordedRequests(null);
+        assert recordedRequests.length == times;
+
+        // verify response
+        HttpWebhookConfig webhookConfig = 
sinkConfig.connectorConfig.getWebhookConfig();
+        String url = new HttpUrl.Builder()
+            .scheme("http")
+            .host(severUri.getHost())
+            .port(webhookConfig.getPort())
+            .addPathSegments(webhookConfig.getExportPath())
+            .addQueryParameter("pageNum", "1")
+            .addQueryParameter("pageSize", "10")
+            .addQueryParameter("type", "poll")
+            .build().toString();
+
+        // build request
+        Request request = new Request.Builder()
+            .url(url)
+            .addHeader("Content-Type", "application/json")
+            .build();
+
+        OkHttpClient client = new OkHttpClient();
+        try (Response response = client.newCall(request).execute()) {
+            // check response code
+            if (!response.isSuccessful()) {
+                throw new RuntimeException("Unexpected response code: " + 
response);
+            }
+            // check response body
+            ResponseBody responseBody = response.body();
+            if (responseBody != null) {
+                JSONObject jsonObject = 
JSON.parseObject(responseBody.string());
+                JSONArray pageItems = jsonObject.getJSONArray("pageItems");
+
+                assert pageItems != null && pageItems.size() == times;
+
+                for (int i = 0; i < times; i++) {
+                    JSONObject pageItem = pageItems.getJSONObject(i);
+                    assert pageItem != null;
+                    assert pageItem.getJSONObject("data") != null;
+                    assert pageItem.getJSONObject("metadata") != null;
+                }
+            }
+        }
+    }
+
+    private ConnectRecord createConnectRecord() {
+        RecordPartition partition = new RecordPartition();
+        RecordOffset offset = new RecordOffset();
+        long timestamp = System.currentTimeMillis();
+        return new ConnectRecord(partition, offset, timestamp, 
UUID.randomUUID().toString());
+    }
+}
diff --git 
a/eventmesh-connectors/eventmesh-connector-http/src/test/resources/sink-config.yml
 
b/eventmesh-connectors/eventmesh-connector-http/src/test/resources/sink-config.yml
new file mode 100644
index 000000000..149ad7681
--- /dev/null
+++ 
b/eventmesh-connectors/eventmesh-connector-http/src/test/resources/sink-config.yml
@@ -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.
+#
+
+pubSubConfig:
+  meshAddress: 127.0.0.1:10000
+  subject: TopicTest
+  idc: FT
+  env: PRD
+  group: httpSink
+  appId: 5032
+  userName: httpSinkUser
+  passWord: httpPassWord
+connectorConfig:
+  connectorName: httpSink
+  urls:
+    - http://127.0.0.1:8987/test
+  ssl: false
+  keepAlive: true
+  keepAliveTimeout: 60000
+  idleTimeout: 15000   # timeunit: ms, recommended scope: common(5s - 10s), 
webhook(15s - 60s)
+  connectionTimeout: 5000   # timeunit: ms, recommended scope: 5 - 10s
+  maxConnectionPoolSize: 10
+  retryConfig:
+    maxRetries: 2
+    interval: 1000
+    retryOnNonSuccess: true
+  webhookConfig:
+    activate: true
+    exportPath: /export
+    port: 8988
+    serverIdleTimeout: 5000
+    maxStorageSize: 5000
\ No newline at end of file
diff --git a/tools/dependency-check/known-dependencies.txt 
b/tools/dependency-check/known-dependencies.txt
index b5918031e..4df5d0fc6 100644
--- a/tools/dependency-check/known-dependencies.txt
+++ b/tools/dependency-check/known-dependencies.txt
@@ -48,6 +48,7 @@ client-0.18.2.jar
 cloudevents-api-2.4.2.jar
 cloudevents-core-2.4.2.jar
 cloudevents-http-vertx-2.3.0.jar
+cloudevents-http-vertx-3.0.0.jar
 cloudevents-json-jackson-2.4.2.jar
 cloudevents-kafka-2.4.2.jar
 cloudevents-protobuf-2.4.2.jar
@@ -79,6 +80,7 @@ endpoint-util-0.0.7.jar
 endpoints-spi-2.20.29.jar
 error_prone_annotations-2.9.0.jar
 eventstream-1.0.1.jar
+failsafe-3.3.2.jar
 failureaccess-1.0.1.jar
 fastjson-1.2.69_noneautotype.jar
 fastjson2-2.0.48.jar
@@ -352,7 +354,9 @@ vertx-bridge-common-4.4.6.jar
 vertx-core-4.4.6.jar
 vertx-web-4.4.6.jar
 vertx-web-client-4.0.0.jar
+vertx-web-client-4.4.6.jar
 vertx-web-common-4.4.6.jar
+vertx-uri-template-4.4.6.jar
 xpp3-1.1.4c.jar
 xsdlib-2013.6.1.jar
 zipkin-2.27.1.jar


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to