This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new a2f818afd2 [Improve][Connector-V2] Validate HTTP sink URL is not blank
(#12773)
a2f818afd2 is described below
commit a2f818afd2d98c1bc6b281ee2aa11f9138b6851d
Author: Supidan <[email protected]>
AuthorDate: Thu Oct 15 06:53:50 2026 +0000
[Improve][Connector-V2] Validate HTTP sink URL is not blank (#12773)
Co-authored-by: Supidan <[email protected]>
---
docs/en/connectors/sink/Feishu.md | 2 +-
docs/en/connectors/sink/Http.md | 2 +-
docs/zh/connectors/sink/Feishu.md | 2 +-
docs/zh/connectors/sink/Http.md | 2 +-
.../seatunnel/http/sink/HttpSinkFactory.java | 3 +-
.../connectors/seatunnel/http/HttpFactoryTest.java | 54 +++++++++++++++++++++-
6 files changed, 59 insertions(+), 6 deletions(-)
diff --git a/docs/en/connectors/sink/Feishu.md
b/docs/en/connectors/sink/Feishu.md
index 155e9dc753..d5d420f7ba 100644
--- a/docs/en/connectors/sink/Feishu.md
+++ b/docs/en/connectors/sink/Feishu.md
@@ -56,7 +56,7 @@ Feishu webhook URLs and custom authentication headers are
sensitive. Do not prin
| Name | Type | Required | Default | Description
|
|-----------------------------|---------|----------|---------|-------------------------------------------------------------------------------------------------------------------------|
-| url | String | Yes | - | Feishu webhook
URL. The current sink writer sends requests to this fixed URL and does not
replace table-name placeholders. |
+| url | String | Yes | - | Feishu webhook
URL. Must not be empty or whitespace-only. The current sink writer sends
requests to this fixed URL and does not replace table-name placeholders. |
| headers | Map | No | - | HTTP request
headers. Use it when the webhook gateway requires extra headers.
|
| params | Map | No | - | Accepted by the
option rule, but the current sink writer does not pass it to requests. Put
non-sensitive query parameters directly in `url` when needed. |
| retry | Int | No | - | The maximum
retry times when the HTTP request fails with an `IOException`.
|
diff --git a/docs/en/connectors/sink/Http.md b/docs/en/connectors/sink/Http.md
index 3e83b2fc63..9cf60a28a3 100644
--- a/docs/en/connectors/sink/Http.md
+++ b/docs/en/connectors/sink/Http.md
@@ -38,7 +38,7 @@ They can be downloaded via install-plugin.sh or from the
Maven central repositor
| Name | Type | Required | Default |
Description
|
|-----------------------------|--------|----------|---------|-------------------------------------------------------------------------------------------------------------|
-| url | String | Yes | - | Http request url
|
+| url | String | Yes | - | HTTP request
URL. Must not be empty or whitespace-only.
|
| headers | Map | No | - | Http headers
|
| params | Map | No | - | Accepted by the
option rule. For the current sink writer, put query parameters directly in
`url`; rows are posted to the final URL as the request body. |
| retry | Int | No | - | The max retry
times if request http return to `IOException`
|
diff --git a/docs/zh/connectors/sink/Feishu.md
b/docs/zh/connectors/sink/Feishu.md
index e0735cb579..f3b7543852 100644
--- a/docs/zh/connectors/sink/Feishu.md
+++ b/docs/zh/connectors/sink/Feishu.md
@@ -56,7 +56,7 @@ import ChangeLog from '../changelog/connector-http-feishu.md';
| 名称 | 类型 | 是否必需 | 默认值 | 描述
|
|-----------------------------|---------|------|-------|------------------------------------------------------------------------------------------|
-| url | String | 是 | - | 飞书 Webhook URL。当前
Sink 写入器会向这个固定 URL 发送请求,不会替换表名相关占位符。 |
+| url | String | 是 | - | 飞书 Webhook
URL,不能为空或仅包含空白字符。当前 Sink 写入器会向这个固定 URL 发送请求,不会替换表名相关占位符。 |
| headers | Map | 否 | - | HTTP 请求头。Webhook
网关需要额外请求头时使用。 |
| params | Map | 否 | - | 该参数会通过参数校验,但当前 Sink
写入器不会把它传入请求。如需查询参数,请把非敏感参数直接写在 `url` 中。 |
| retry | Int | 否 | - | HTTP 请求发生
`IOException` 时的最大重试次数。 |
diff --git a/docs/zh/connectors/sink/Http.md b/docs/zh/connectors/sink/Http.md
index e18718a15b..53140c02ca 100644
--- a/docs/zh/connectors/sink/Http.md
+++ b/docs/zh/connectors/sink/Http.md
@@ -37,7 +37,7 @@ import ChangeLog from '../changelog/connector-http.md';
| 名称 | 类型 | 是否必须 | 默认值 |
描述 |
|-----------------------------|--------|------|-------|------------------------------------------------------------|
-| url | String | 是 | - | Http 请求链接
|
+| url | String | 是 | - | HTTP
请求链接,不能为空或仅包含空白字符。 |
| headers | Map | 否 | - | Http 请求头
|
| params | Map | 否 | - | 该参数会通过参数校验。当前 Sink
写入器会把数据作为请求体 POST 到最终 URL,如需查询参数,建议直接写在 `url` 中。 |
| retry | Int | 否 | - |
如果请求http返回`IOException`的最大重试次数 |
diff --git
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java
index b0a192893b..4fb64a4fdd 100644
---
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java
+++
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java
@@ -17,6 +17,7 @@
package org.apache.seatunnel.connectors.seatunnel.http.sink;
+import org.apache.seatunnel.api.configuration.util.Conditions;
import org.apache.seatunnel.api.configuration.util.OptionRule;
import org.apache.seatunnel.api.options.SinkConnectorCommonOptions;
import org.apache.seatunnel.api.table.connector.TableSink;
@@ -42,7 +43,7 @@ public class HttpSinkFactory implements TableSinkFactory {
@Override
public OptionRule optionRule() {
return OptionRule.builder()
- .required(HttpSinkOptions.URL)
+ .required(HttpSinkOptions.URL,
Conditions.notBlank(HttpSinkOptions.URL))
.optional(HttpSinkOptions.HEADERS)
.optional(HttpSinkOptions.PARAMS)
.optional(HttpSinkOptions.RETRY)
diff --git
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpFactoryTest.java
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpFactoryTest.java
index 44b73c8757..dfca204deb 100644
---
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpFactoryTest.java
+++
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpFactoryTest.java
@@ -17,17 +17,69 @@
package org.apache.seatunnel.connectors.seatunnel.http;
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.configuration.util.ConfigValidator;
+import org.apache.seatunnel.api.configuration.util.OptionRule;
+import org.apache.seatunnel.api.configuration.util.OptionValidationException;
+import org.apache.seatunnel.connectors.seatunnel.http.config.HttpSinkOptions;
import org.apache.seatunnel.connectors.seatunnel.http.sink.HttpSinkFactory;
import org.apache.seatunnel.connectors.seatunnel.http.source.HttpSourceFactory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.Collections;
class HttpFactoryTest {
+ private final OptionRule sinkOptionRule = new
HttpSinkFactory().optionRule();
+
@Test
void optionRule() {
Assertions.assertNotNull((new HttpSourceFactory()).optionRule());
- Assertions.assertNotNull((new HttpSinkFactory()).optionRule());
+ Assertions.assertNotNull(sinkOptionRule);
+ }
+
+ @ParameterizedTest
+ @ValueSource(
+ strings = {
+ "http://localhost/test/webhook",
+ "https://example.com/webhook",
+
"http://localhost/test/${database_name}_test/${table_name}_test",
+ "http://localhost/test/${schema_name}_test/${table_name}_test",
+ " \thttp://localhost/test/webhook\r\n "
+ })
+ void testNonBlankSinkUrlAcceptedWithoutModification(String url) {
+ ReadonlyConfig config =
+
ReadonlyConfig.fromMap(Collections.singletonMap(HttpSinkOptions.URL.key(),
url));
+
+ Assertions.assertDoesNotThrow(() ->
ConfigValidator.of(config).validate(sinkOptionRule));
+ Assertions.assertEquals(url, config.get(HttpSinkOptions.URL));
+ }
+
+ @Test
+ void testMissingSinkUrlRejected() {
+ ReadonlyConfig config = ReadonlyConfig.fromMap(Collections.emptyMap());
+
+ OptionValidationException error =
+ Assertions.assertThrows(
+ OptionValidationException.class,
+ () ->
ConfigValidator.of(config).validate(sinkOptionRule));
+
Assertions.assertTrue(error.getMessage().contains(HttpSinkOptions.URL.key()));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", " ", "\t", "\n", "\r", " \t\r\n "})
+ void testBlankSinkUrlRejected(String url) {
+ ReadonlyConfig config =
+
ReadonlyConfig.fromMap(Collections.singletonMap(HttpSinkOptions.URL.key(),
url));
+
+ OptionValidationException error =
+ Assertions.assertThrows(
+ OptionValidationException.class,
+ () ->
ConfigValidator.of(config).validate(sinkOptionRule));
+
Assertions.assertTrue(error.getMessage().contains(HttpSinkOptions.URL.key()));
}
}