This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/dev/pr-12072-c2bd543ffccacd50f7d55425f6118f7771ba00a1 in repository https://gitbox.apache.org/repos/asf/seatunnel.git
commit 76c22220d7e6611495f174678bfbcc8191bb8727 Author: SEZ <[email protected]> AuthorDate: Sat Sep 5 13:40:05 2026 +0000 [Improve][Zeta] Bound the size of a config file uploaded to the REST server (#12072) --- docs/en/engines/zeta/rest-api-v2.md | 21 ++++++++++++++++++++- docs/zh/engines/zeta/rest-api-v2.md | 19 ++++++++++++++++++- .../config/YamlSeaTunnelDomConfigProcessor.java | 18 ++++++++++++++++++ .../engine/common/config/server/HttpConfig.java | 8 ++++++++ .../common/config/server/ServerConfigOptions.java | 14 ++++++++++++++ .../seatunnel/engine/server/JettyService.java | 13 ++++++++++++- 6 files changed, 90 insertions(+), 3 deletions(-) diff --git a/docs/en/engines/zeta/rest-api-v2.md b/docs/en/engines/zeta/rest-api-v2.md index 47bc667fdf..efe4d04a3e 100644 --- a/docs/en/engines/zeta/rest-api-v2.md +++ b/docs/en/engines/zeta/rest-api-v2.md @@ -16,7 +16,7 @@ The v2 API and the Web UI are both served by the embedded Jetty server. Jetty st There are two different "default" sources that are easy to mix up: -- Code defaults: `enable-http = false`, `enable-https = false`, `port = 8080`, `context-path = ""`, `enable-dynamic-port = false`, `port-range = 100` +- Code defaults: `enable-http = false`, `enable-https = false`, `port = 8080`, `context-path = ""`, `enable-dynamic-port = false`, `port-range = 100`, `upload-max-file-size-mb = 10`, `upload-max-request-size-mb = 10` - The packaged `seatunnel.yaml` example: it already sets `enable-http: true` and `port: 8080` As a result, if you start SeaTunnel with the packaged configuration, the Web UI and REST API usually @@ -60,6 +60,21 @@ seatunnel: context-path: /seatunnel ``` +The size of an uploaded config file is bounded, so a single request cannot fill the master's temp +directory or exhaust its heap. Both limits apply to `/submit-job/upload` only, and a value of `0` +or below means unlimited: + +```yaml + +seatunnel: + engine: + http: + enable-http: true + port: 8080 + upload-max-file-size-mb: 10 + upload-max-request-size-mb: 10 +``` + ## Web UI and Port 8080 Troubleshooting - If `http://<host>:8080/` is unreachable, first check whether `seatunnel.engine.http.enable-http` or `enable-https` is actually enabled. The `network.rest-api.enabled` setting in `hazelcast.yaml` does not replace the Jetty switch. @@ -1028,6 +1043,10 @@ The name of the uploaded file key is config_file, and supports the following for - `.conf` or `.config` files: parsed in HOCON format - `.sql` files: parsed in SQL format, supports CREATE TABLE and INSERT INTO syntax +The upload is limited to `seatunnel.engine.http.upload-max-file-size-mb` (10 MB by default) per +file and `upload-max-request-size-mb` (10 MB by default) per request. A larger upload is rejected +before the config is parsed. + curl Example : ```bash # Upload HOCON config file diff --git a/docs/zh/engines/zeta/rest-api-v2.md b/docs/zh/engines/zeta/rest-api-v2.md index d5eda567bd..c2e4fabd88 100644 --- a/docs/zh/engines/zeta/rest-api-v2.md +++ b/docs/zh/engines/zeta/rest-api-v2.md @@ -13,7 +13,7 @@ v2 版本的 API 和 Web UI 都由内嵌 Jetty 提供,与 v1 版本保持相 这里需要区分两个容易混淆的“默认值”来源: -- 代码默认值:`enable-http = false`、`enable-https = false`、`port = 8080`、`context-path = ""`、`enable-dynamic-port = false`、`port-range = 100` +- 代码默认值:`enable-http = false`、`enable-https = false`、`port = 8080`、`context-path = ""`、`enable-dynamic-port = false`、`port-range = 100`、`upload-max-file-size-mb = 10`、`upload-max-request-size-mb = 10` - 发行包自带的 `seatunnel.yaml` 示例:默认写入了 `enable-http: true` 和 `port: 8080` 因此,直接使用发行包自带配置启动时,Web UI 和 REST API 通常会监听 @@ -56,6 +56,20 @@ seatunnel: context-path: /seatunnel ``` +上传的配置文件有大小上限,避免单个请求把 Master 的临时目录写满或把堆内存吃光。这两个配置只作用于 +`/submit-job/upload`,取值小于等于 `0` 表示不限制: + +```yaml + +seatunnel: + engine: + http: + enable-http: true + port: 8080 + upload-max-file-size-mb: 10 + upload-max-request-size-mb: 10 +``` + ## Web UI 与 8080 排查 - 如果 `http://<host>:8080/` 打不开,先检查 `seatunnel.engine.http.enable-http` 或 `enable-https` 是否真的开启;仅配置 `hazelcast.yaml` 中的 `network.rest-api.enabled` 不能替代 Jetty 开关。 @@ -1002,6 +1016,9 @@ INSERT INTO console_sink SELECT * FROM fake_source; - `.conf` 或 `.config` 文件:按照 HOCON 格式解析 - `.sql` 文件:按照 SQL 格式解析,支持 CREATE TABLE 和 INSERT INTO 语法 +单个文件大小受 `seatunnel.engine.http.upload-max-file-size-mb`(默认 10 MB)限制,单个请求的总大小受 +`upload-max-request-size-mb`(默认 10 MB)限制。超出上限的请求会在解析配置之前被拒绝。 + curl Example ```bash diff --git a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java index 5e69c3ecbf..f2826a8e0a 100644 --- a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java +++ b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/YamlSeaTunnelDomConfigProcessor.java @@ -658,6 +658,24 @@ public class YamlSeaTunnelDomConfigProcessor extends AbstractDomConfigProcessor .key() .equals(name)) { httpConfig.setBasicAuthPassword(getTextContent(node)); + } else if (ServerConfigOptions.MasterServerConfigOptions.UPLOAD_MAX_FILE_SIZE_MB + .key() + .equals(name)) { + httpConfig.setUploadMaxFileSizeMb( + getIntegerValue( + ServerConfigOptions.MasterServerConfigOptions + .UPLOAD_MAX_FILE_SIZE_MB + .key(), + getTextContent(node))); + } else if (ServerConfigOptions.MasterServerConfigOptions.UPLOAD_MAX_REQUEST_SIZE_MB + .key() + .equals(name)) { + httpConfig.setUploadMaxRequestSizeMb( + getIntegerValue( + ServerConfigOptions.MasterServerConfigOptions + .UPLOAD_MAX_REQUEST_SIZE_MB + .key(), + getTextContent(node))); } else { LOGGER.warning("Unrecognized element: " + name); } diff --git a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java index fe7324bf31..36e860dfac 100644 --- a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java +++ b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/HttpConfig.java @@ -78,6 +78,14 @@ public class HttpConfig implements Serializable { private String basicAuthPassword = ServerConfigOptions.MasterServerConfigOptions.BASIC_AUTH_PASSWORD.defaultValue(); + /** The maximum size in MB of a single uploaded file. A value <= 0 means unlimited. */ + private int uploadMaxFileSizeMb = + ServerConfigOptions.MasterServerConfigOptions.UPLOAD_MAX_FILE_SIZE_MB.defaultValue(); + + /** The maximum total size in MB of a multipart request. A value <= 0 means unlimited. */ + private int uploadMaxRequestSizeMb = + ServerConfigOptions.MasterServerConfigOptions.UPLOAD_MAX_REQUEST_SIZE_MB.defaultValue(); + public void setPort(int port) { checkPositive(port, ServerConfigOptions.MasterServerConfigOptions.HTTP + " must be > 0"); this.port = port; diff --git a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java index a99596331d..903e1d9e1a 100644 --- a/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java +++ b/seatunnel-engine/seatunnel-engine-common/src/main/java/org/apache/seatunnel/engine/common/config/server/ServerConfigOptions.java @@ -374,6 +374,20 @@ public class ServerConfigOptions { .defaultValue("admin") .withDescription("The password for basic authentication."); + public static final Option<Integer> UPLOAD_MAX_FILE_SIZE_MB = + Options.key("upload-max-file-size-mb") + .intType() + .defaultValue(10) + .withDescription( + "The maximum size in MB of a single file uploaded to the http server. A value <= 0 means unlimited."); + + public static final Option<Integer> UPLOAD_MAX_REQUEST_SIZE_MB = + Options.key("upload-max-request-size-mb") + .intType() + .defaultValue(10) + .withDescription( + "The maximum total size in MB of a multipart request sent to the http server. A value <= 0 means unlimited."); + public static final Option<HttpConfig> HTTP = Options.key("http") .type(new TypeReference<HttpConfig>() {}) diff --git a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java index 8734e12ba8..a7866cad48 100644 --- a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java +++ b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/JettyService.java @@ -160,6 +160,12 @@ public class JettyService { log.info("SeaTunnel REST service will start on https port {}", httpsPort); } + private static long toBytes(int megabytes) { + // Jetty reads a negative limit as unlimited, which is what this server did before the + // limit became configurable. Keep that available instead of scaling it into bytes. + return megabytes <= 0 ? -1L : megabytes * 1024L * 1024L; + } + public void createJettyServer() { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); @@ -238,7 +244,12 @@ public class JettyService { context.addServlet(jobInfoHolder, convertUrlToPath(REST_URL_JOB_INFO)); context.addServlet(jobInfoHolder, convertUrlToPath(REST_URL_RUNNING_JOB)); context.addServlet(threadDumpHolder, convertUrlToPath(REST_URL_THREAD_DUMP)); - MultipartConfigElement multipartConfigElement = new MultipartConfigElement(""); + // Bound the upload: the whole part is buffered by Jetty and then read into a String, so an + // unbounded body lets a single request fill the temp directory and exhaust the master heap. + long maxFileSize = toBytes(httpConfig.getUploadMaxFileSizeMb()); + long maxRequestSize = toBytes(httpConfig.getUploadMaxRequestSizeMb()); + MultipartConfigElement multipartConfigElement = + new MultipartConfigElement("", maxFileSize, maxRequestSize, 0); submitJobByUploadFileHolder.getRegistration().setMultipartConfig(multipartConfigElement); context.addServlet( submitJobByUploadFileHolder, convertUrlToPath(REST_URL_SUBMIT_JOB_BY_UPLOAD_FILE));
