This is an automated email from the ASF dual-hosted git repository.
CalvinKirs pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 07509a2dc67 [feature](fs-spi) add HTTP filesystem module, capability
framework and URI validation (#64853)
07509a2dc67 is described below
commit 07509a2dc67f12b9a2af318fb5a2a137a4af89c1
Author: Calvin Kirs <[email protected]>
AuthorDate: Wed Jul 8 21:08:04 2026 +0800
[feature](fs-spi) add HTTP filesystem module, capability framework and URI
validation (#64853)
### What problem does this PR solve?
Related PR: #xxx
Problem Summary:
An additive slice of the `fe-filesystem` SPI migration. It touches
**only `fe-filesystem`**
(zero `fe-core` footprint) and only lays down SPI geography that later
consumer-migration PRs
depend on:
1. **HTTP filesystem module** (`fe-filesystem-http`): typed
`HttpFileSystemProperties`
(validates `http://` / `https://` / `hf://` URIs, extracts
`http.header.*`) and
`HttpFileSystemProvider` registered via SPI. HTTP has no real FileSystem
— reads happen via the
`http()` TVF (FE `HttpUtils` / BE `TFileType.FILE_HTTP`) — so `create()`
throws; callers gate
via capabilities instead.
2. **Capability vocabulary**: `FileSystemCapability` enum
(READ/WRITE/LIST/DELETE/ATOMIC_RENAME/
HIERARCHICAL_NAMESPACE/APPEND/CONDITIONAL_WRITE) plus a config-aware
negotiation method on the
SPI, `FileSystemProvider.capabilities(P bound)` (with a
`capabilities(Map)` bridge that binds
first). Capability is negotiated against the resolved config, not the
provider type alone
(e.g. Ozone via S3 gateway has no ATOMIC_RENAME; via ofs:// it does). No
`fe-core` wiring /
selection shortcut is added here — that lands together with the first
real consumer, so no
speculative API ships without a caller.
3. **URI validation on the properties model**:
`FileSystemProperties.validateAndNormalizeUri()` /
`validateAndGetUri()` defaults. Pure config logic (no I/O); lives on the
properties model on
purpose — many callers validate a URI before, or without ever, creating
a FileSystem.
### Release note
None
### Check List (For Author)
- Test
- [x] Unit Test (`fe-filesystem-http`, `fe-filesystem-spi`,
`fe-filesystem-api` module tests)
- Behavior changed:
- [x] No. Additive SPI scaffolding; `fe-core` is untouched, no existing
path is rerouted.
- Does this need documentation?
- [x] No.
---
build.sh | 4 +-
.../properties/FileSystemCapability.java | 53 ++++++++++
.../properties/FileSystemProperties.java | 28 ++++++
fe/fe-filesystem/fe-filesystem-http/pom.xml | 74 ++++++++++++++
.../src/main/assembly/plugin-zip.xml | 65 ++++++++++++
.../filesystem/http/HttpFileSystemProperties.java | 109 +++++++++++++++++++++
.../filesystem/http/HttpFileSystemProvider.java | 73 ++++++++++++++
....apache.doris.filesystem.spi.FileSystemProvider | 18 ++++
.../http/HttpFileSystemPropertiesTest.java | 90 +++++++++++++++++
.../http/HttpFileSystemProviderTest.java | 80 +++++++++++++++
.../doris/filesystem/spi/FileSystemProvider.java | 19 ++++
fe/fe-filesystem/pom.xml | 1 +
12 files changed, 612 insertions(+), 2 deletions(-)
diff --git a/build.sh b/build.sh
index e7dfb3d531c..7bcd0dc7439 100755
--- a/build.sh
+++ b/build.sh
@@ -717,7 +717,7 @@ if [[ "${BUILD_FE}" -eq 1 ]]; then
# Filesystem API and SPI plugin modules (loaded at runtime as plugins)
modules+=("fe-filesystem/fe-filesystem-api")
modules+=("fe-filesystem/fe-filesystem-spi")
- for _fs_mod in s3 oss cos obs azure hdfs local broker; do
+ for _fs_mod in s3 oss cos obs azure hdfs local broker http; do
if [[ -d "${DORIS_HOME}/fe/fe-filesystem/fe-filesystem-${_fs_mod}" ]];
then
modules+=("fe-filesystem/fe-filesystem-${_fs_mod}")
fi
@@ -1049,7 +1049,7 @@ if [[ "${BUILD_FE}" -eq 1 ]]; then
# Deploy filesystem provider plugins as independent plugin directories
# Each sub-directory is one storage backend loaded at runtime by
FileSystemPluginManager.
FS_PLUGIN_DIR="${DORIS_OUTPUT}/fe/plugins/filesystem"
- for fs_module in s3 azure oss cos obs hdfs local broker; do
+ for fs_module in s3 azure oss cos obs hdfs local broker http; do
fs_plugin_target="${FS_PLUGIN_DIR}/${fs_module}"
fs_module_dir="${DORIS_HOME}/fe/fe-filesystem/fe-filesystem-${fs_module}"
if [ ! -d "${fs_module_dir}" ]; then
diff --git
a/fe/fe-filesystem/fe-filesystem-api/src/main/java/org/apache/doris/filesystem/properties/FileSystemCapability.java
b/fe/fe-filesystem/fe-filesystem-api/src/main/java/org/apache/doris/filesystem/properties/FileSystemCapability.java
new file mode 100644
index 00000000000..f2080624550
--- /dev/null
+++
b/fe/fe-filesystem/fe-filesystem-api/src/main/java/org/apache/doris/filesystem/properties/FileSystemCapability.java
@@ -0,0 +1,53 @@
+// 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.doris.filesystem.properties;
+
+/**
+ * Capabilities a filesystem provider may support.
+ *
+ * <p>Declared per provider (the storage type is the source of truth) so that
framework code can
+ * gate operations before invoking them, instead of calling and catching
UnsupportedOperationException.
+ */
+public enum FileSystemCapability {
+ /** Read an object. */
+ READ,
+ /** Write / upload an object. */
+ WRITE,
+ /** List / glob a prefix. */
+ LIST,
+ /** Delete an object. */
+ DELETE,
+ /**
+ * Rename is atomic and cheap. True filesystems (HDFS, local) provide
this; object stores
+ * emulate rename as copy+delete, which is neither atomic nor cheap, and
must not declare it.
+ */
+ ATOMIC_RENAME,
+ /**
+ * A real hierarchical namespace with directories (mkdirs /
renameDirectory / listDirectories
+ * carry meaning). Object stores only emulate this via key prefixes and
must not declare it.
+ */
+ HIERARCHICAL_NAMESPACE,
+ /** Append to an existing object. Supported by HDFS; not by object stores.
*/
+ APPEND,
+ /**
+ * Conditional write such as put-if-absent or if-match (used to build
commit locks). Supported
+ * by some object stores via conditional puts; true filesystems achieve
the same via
+ * {@link #ATOMIC_RENAME} instead.
+ */
+ CONDITIONAL_WRITE
+}
diff --git
a/fe/fe-filesystem/fe-filesystem-api/src/main/java/org/apache/doris/filesystem/properties/FileSystemProperties.java
b/fe/fe-filesystem/fe-filesystem-api/src/main/java/org/apache/doris/filesystem/properties/FileSystemProperties.java
index a0002ad6bf1..e75f5225129 100644
---
a/fe/fe-filesystem/fe-filesystem-api/src/main/java/org/apache/doris/filesystem/properties/FileSystemProperties.java
+++
b/fe/fe-filesystem/fe-filesystem-api/src/main/java/org/apache/doris/filesystem/properties/FileSystemProperties.java
@@ -66,6 +66,34 @@ public interface FileSystemProperties extends
StorageProperties {
*/
Map<String, String> matchedProperties();
+ /**
+ * Validates and normalizes a single storage URI against this provider's
configuration.
+ *
+ * <p>This is pure configuration logic (no I/O) and lives on the
properties model rather than
+ * on {@link org.apache.doris.filesystem.FileSystem} on purpose: many
callers validate a URI
+ * before — or without ever — creating a FileSystem (e.g. HTTP has no
FileSystem). Providers
+ * with scheme/endpoint/path-style specific rules override this; the
default returns the URI
+ * unchanged.
+ *
+ * @throws IllegalArgumentException if the URI is invalid for this provider
+ */
+ default String validateAndNormalizeUri(String uri) {
+ return uri;
+ }
+
+ /**
+ * Extracts the storage URI from the given load properties and validates
it via
+ * {@link #validateAndNormalizeUri(String)}.
+ *
+ * <p>The default looks up the {@code "uri"} key. Providers whose URI
lives under a different
+ * key, or that need extra extraction logic (e.g. HDFS nameservices),
override this.
+ *
+ * @throws IllegalArgumentException if the URI is missing or invalid for
this provider
+ */
+ default String validateAndGetUri(Map<String, String> loadProperties) {
+ return validateAndNormalizeUri(loadProperties == null ? null :
loadProperties.get("uri"));
+ }
+
/**
* Converts to backend storage properties if this provider supports BE
access.
*/
diff --git a/fe/fe-filesystem/fe-filesystem-http/pom.xml
b/fe/fe-filesystem/fe-filesystem-http/pom.xml
new file mode 100644
index 00000000000..c71bc769f80
--- /dev/null
+++ b/fe/fe-filesystem/fe-filesystem-http/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.doris</groupId>
+ <artifactId>fe-filesystem</artifactId>
+ <version>${revision}</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>fe-filesystem-http</artifactId>
+ <name>Doris FE Filesystem - HTTP</name>
+ <description>HTTP storage properties provider. Property binding only; HTTP
has no FileSystem
+ implementation (reads happen via the http() TVF on FE and
TFileType.FILE_HTTP on BE).</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.doris</groupId>
+ <artifactId>fe-filesystem-spi</artifactId>
+ <version>${revision}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>doris-fe-filesystem-http</finalName>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <appendAssemblyId>false</appendAssemblyId>
+ <descriptors>
+
<descriptor>src/main/assembly/plugin-zip.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git
a/fe/fe-filesystem/fe-filesystem-http/src/main/assembly/plugin-zip.xml
b/fe/fe-filesystem/fe-filesystem-http/src/main/assembly/plugin-zip.xml
new file mode 100644
index 00000000000..e65d143ff9b
--- /dev/null
+++ b/fe/fe-filesystem/fe-filesystem-http/src/main/assembly/plugin-zip.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<!--
+ Plugin zip layout expected by DirectoryPluginRuntimeManager:
+ <finalName>.jar (the plugin jar at root — discovered for service
registration)
+ lib/
+ *.jar (all runtime deps: direct + transitive)
+
+ The plugin jar is placed at root via <files> so that service-discovery scans
+ only root-level jars. All dependencies (including intra-doris modules such as
+ fe-filesystem-s3 inside fe-filesystem-cos) land in lib/ and are available for
+ class loading but are NOT scanned for FileSystemProvider registrations.
+
+ Jars already present in fe-core classpath are excluded from lib/:
+ fe-filesystem-api, fe-filesystem-spi, fe-extension-spi
+-->
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0
+ https://maven.apache.org/xsd/assembly-2.2.0.xsd">
+ <id>plugin</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <!-- Plugin jar at the root of the zip (service-discovery target) -->
+ <files>
+ <file>
+
<source>${project.build.directory}/${project.build.finalName}.jar</source>
+ <outputDirectory>/</outputDirectory>
+ </file>
+ </files>
+
+ <dependencySets>
+ <!-- All runtime deps (direct + transitive) in lib/ -->
+ <dependencySet>
+ <outputDirectory>/lib</outputDirectory>
+ <useProjectArtifact>false</useProjectArtifact>
+ <scope>runtime</scope>
+ <excludes>
+ <exclude>org.apache.doris:fe-filesystem-api</exclude>
+ <exclude>org.apache.doris:fe-filesystem-spi</exclude>
+ <exclude>org.apache.doris:fe-extension-spi</exclude>
+ </excludes>
+ </dependencySet>
+ </dependencySets>
+</assembly>
diff --git
a/fe/fe-filesystem/fe-filesystem-http/src/main/java/org/apache/doris/filesystem/http/HttpFileSystemProperties.java
b/fe/fe-filesystem/fe-filesystem-http/src/main/java/org/apache/doris/filesystem/http/HttpFileSystemProperties.java
new file mode 100644
index 00000000000..be13901f0dc
--- /dev/null
+++
b/fe/fe-filesystem/fe-filesystem-http/src/main/java/org/apache/doris/filesystem/http/HttpFileSystemProperties.java
@@ -0,0 +1,109 @@
+// 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.doris.filesystem.http;
+
+import org.apache.doris.filesystem.FileSystemType;
+import org.apache.doris.filesystem.properties.FileSystemProperties;
+import org.apache.doris.filesystem.properties.StorageKind;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Typed properties for HTTP storage. HTTP is not a real filesystem: reads
happen via the
+ * http() TVF on FE (HttpUtils) and TFileType.FILE_HTTP on BE. This class only
validates the URI,
+ * extracts http.header.* headers, and passes the raw map through for BE
backend config.
+ */
+public class HttpFileSystemProperties implements FileSystemProperties {
+
+ public static final String URI_KEY = "uri";
+ public static final String HEADER_PREFIX = "http.header.";
+
+ private final Map<String, String> rawProperties;
+ private final String uri;
+ private final Map<String, String> headers;
+ private final Map<String, String> matchedProperties;
+
+ private HttpFileSystemProperties(Map<String, String> rawProperties) {
+ this.rawProperties = Collections.unmodifiableMap(new
HashMap<>(rawProperties));
+ this.uri = validateUri(rawProperties.get(URI_KEY));
+ Map<String, String> hdrs = new LinkedHashMap<>();
+ Map<String, String> matched = new LinkedHashMap<>();
+ matched.put(URI_KEY, this.uri);
+ for (Map.Entry<String, String> e : rawProperties.entrySet()) {
+ if (e.getKey().toLowerCase().startsWith(HEADER_PREFIX)) {
+ hdrs.put(e.getKey().substring(HEADER_PREFIX.length()),
e.getValue());
+ matched.put(e.getKey(), e.getValue());
+ }
+ }
+ this.headers = Collections.unmodifiableMap(hdrs);
+ this.matchedProperties = Collections.unmodifiableMap(matched);
+ }
+
+ public static HttpFileSystemProperties of(Map<String, String> properties) {
+ return new HttpFileSystemProperties(properties);
+ }
+
+ private static String validateUri(String url) {
+ if (url == null
+ || (!url.startsWith("http://") && !url.startsWith("https://")
&& !url.startsWith("hf://"))) {
+ throw new IllegalArgumentException("Invalid http/hf url: " + url);
+ }
+ return url;
+ }
+
+ @Override
+ public String validateAndNormalizeUri(String url) {
+ return validateUri(url);
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public Map<String, String> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public String providerName() {
+ return "HTTP";
+ }
+
+ @Override
+ public StorageKind kind() {
+ return StorageKind.HTTP;
+ }
+
+ @Override
+ public FileSystemType type() {
+ return FileSystemType.HTTP;
+ }
+
+ @Override
+ public Map<String, String> rawProperties() {
+ return rawProperties;
+ }
+
+ @Override
+ public Map<String, String> matchedProperties() {
+ return matchedProperties;
+ }
+}
diff --git
a/fe/fe-filesystem/fe-filesystem-http/src/main/java/org/apache/doris/filesystem/http/HttpFileSystemProvider.java
b/fe/fe-filesystem/fe-filesystem-http/src/main/java/org/apache/doris/filesystem/http/HttpFileSystemProvider.java
new file mode 100644
index 00000000000..0ebeb002e60
--- /dev/null
+++
b/fe/fe-filesystem/fe-filesystem-http/src/main/java/org/apache/doris/filesystem/http/HttpFileSystemProvider.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.doris.filesystem.http;
+
+import org.apache.doris.filesystem.FileSystem;
+import org.apache.doris.filesystem.properties.FileSystemCapability;
+import org.apache.doris.filesystem.spi.FileSystemProvider;
+
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * SPI provider for HTTP storage (http://, https://, hf:// URIs).
+ *
+ * <p>HTTP has no FileSystem implementation: the http() TVF reads via
HttpUtils on FE and
+ * TFileType.FILE_HTTP on BE. This provider only binds and validates
properties; {@link #create}
+ * throws. Use {@link #capabilities(HttpFileSystemProperties)} (READ) to gate
operations
+ * instead of calling create().
+ */
+public class HttpFileSystemProvider implements
FileSystemProvider<HttpFileSystemProperties> {
+
+ @Override
+ public boolean supports(Map<String, String> properties) {
+ if ("HTTP".equalsIgnoreCase(properties.get("_STORAGE_TYPE_"))) {
+ return true;
+ }
+ String uri = properties.getOrDefault("uri", "");
+ return uri.startsWith("http://") || uri.startsWith("https://") ||
uri.startsWith("hf://");
+ }
+
+ @Override
+ public HttpFileSystemProperties bind(Map<String, String> properties) {
+ return HttpFileSystemProperties.of(properties);
+ }
+
+ @Override
+ public FileSystem create(HttpFileSystemProperties properties) {
+ throw new UnsupportedOperationException(
+ "HTTP has no FileSystem; read via the http() TVF (FE HttpUtils
/ BE FILE_HTTP).");
+ }
+
+ @Override
+ public FileSystem create(Map<String, String> properties) {
+ throw new UnsupportedOperationException(
+ "HTTP has no FileSystem; read via the http() TVF (FE HttpUtils
/ BE FILE_HTTP).");
+ }
+
+ @Override
+ public Set<FileSystemCapability> capabilities(HttpFileSystemProperties
boundProperties) {
+ return EnumSet.of(FileSystemCapability.READ);
+ }
+
+ @Override
+ public String name() {
+ return "HTTP";
+ }
+}
diff --git
a/fe/fe-filesystem/fe-filesystem-http/src/main/resources/META-INF/services/org.apache.doris.filesystem.spi.FileSystemProvider
b/fe/fe-filesystem/fe-filesystem-http/src/main/resources/META-INF/services/org.apache.doris.filesystem.spi.FileSystemProvider
new file mode 100644
index 00000000000..239cf71ddc9
--- /dev/null
+++
b/fe/fe-filesystem/fe-filesystem-http/src/main/resources/META-INF/services/org.apache.doris.filesystem.spi.FileSystemProvider
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+#
+org.apache.doris.filesystem.http.HttpFileSystemProvider
diff --git
a/fe/fe-filesystem/fe-filesystem-http/src/test/java/org/apache/doris/filesystem/http/HttpFileSystemPropertiesTest.java
b/fe/fe-filesystem/fe-filesystem-http/src/test/java/org/apache/doris/filesystem/http/HttpFileSystemPropertiesTest.java
new file mode 100644
index 00000000000..b9f68f46343
--- /dev/null
+++
b/fe/fe-filesystem/fe-filesystem-http/src/test/java/org/apache/doris/filesystem/http/HttpFileSystemPropertiesTest.java
@@ -0,0 +1,90 @@
+// 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.doris.filesystem.http;
+
+import org.apache.doris.filesystem.FileSystemType;
+import org.apache.doris.filesystem.properties.StorageKind;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class HttpFileSystemPropertiesTest {
+
+ private Map<String, String> props(String uri) {
+ Map<String, String> m = new HashMap<>();
+ m.put("uri", uri);
+ return m;
+ }
+
+ @Test
+ public void testAcceptsHttpHttpsHfSchemes() {
+ Assertions.assertEquals("http://h/a.csv",
HttpFileSystemProperties.of(props("http://h/a.csv")).getUri());
+ Assertions.assertEquals("https://h/a.csv",
HttpFileSystemProperties.of(props("https://h/a.csv")).getUri());
+ Assertions.assertEquals("hf://ds/a",
HttpFileSystemProperties.of(props("hf://ds/a")).getUri());
+ }
+
+ @Test
+ public void testRejectsOtherSchemes() {
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> HttpFileSystemProperties.of(props("s3://b/k")));
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> HttpFileSystemProperties.of(props(null)));
+ }
+
+ @Test
+ public void testExtractsHeaders() {
+ Map<String, String> m = props("http://h/a.csv");
+ m.put("http.header.Authorization", "Bearer x");
+ m.put("http.header.Accept", "text/csv");
+ m.put("format", "csv");
+ Map<String, String> headers =
HttpFileSystemProperties.of(m).getHeaders();
+ Assertions.assertEquals(2, headers.size());
+ Assertions.assertEquals("Bearer x", headers.get("Authorization"));
+ Assertions.assertEquals("text/csv", headers.get("Accept"));
+ }
+
+ @Test
+ public void testValidateAndNormalizeUri() {
+ HttpFileSystemProperties p =
HttpFileSystemProperties.of(props("http://h/a.csv"));
+ Assertions.assertEquals("https://h/b.csv",
p.validateAndNormalizeUri("https://h/b.csv"));
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
p.validateAndNormalizeUri("s3://b/k"));
+ }
+
+ @Test
+ public void testValidateAndGetUri() {
+ HttpFileSystemProperties p =
HttpFileSystemProperties.of(props("http://h/a.csv"));
+ Map<String, String> load = new HashMap<>();
+ load.put("uri", "hf://ds/x");
+ Assertions.assertEquals("hf://ds/x", p.validateAndGetUri(load));
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
p.validateAndGetUri(new HashMap<>()));
+ }
+
+ @Test
+ public void testTypeAndKindAndRaw() {
+ Map<String, String> m = props("http://h/a.csv");
+ HttpFileSystemProperties p = HttpFileSystemProperties.of(m);
+ Assertions.assertEquals("HTTP", p.providerName());
+ Assertions.assertEquals(FileSystemType.HTTP, p.type());
+ Assertions.assertEquals(StorageKind.HTTP, p.kind());
+ Assertions.assertEquals("http://h/a.csv",
p.rawProperties().get("uri"));
+ Assertions.assertTrue(p.matchedProperties().containsKey("uri"));
+ }
+}
diff --git
a/fe/fe-filesystem/fe-filesystem-http/src/test/java/org/apache/doris/filesystem/http/HttpFileSystemProviderTest.java
b/fe/fe-filesystem/fe-filesystem-http/src/test/java/org/apache/doris/filesystem/http/HttpFileSystemProviderTest.java
new file mode 100644
index 00000000000..6d6f6b311ed
--- /dev/null
+++
b/fe/fe-filesystem/fe-filesystem-http/src/test/java/org/apache/doris/filesystem/http/HttpFileSystemProviderTest.java
@@ -0,0 +1,80 @@
+// 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.doris.filesystem.http;
+
+import org.apache.doris.filesystem.properties.FileSystemCapability;
+import org.apache.doris.filesystem.properties.FileSystemProperties;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+public class HttpFileSystemProviderTest {
+
+ private final HttpFileSystemProvider provider = new
HttpFileSystemProvider();
+
+ private Map<String, String> props(String key, String value) {
+ Map<String, String> m = new HashMap<>();
+ m.put(key, value);
+ return m;
+ }
+
+ @Test
+ public void testSupportsByScheme() {
+ Assertions.assertTrue(provider.supports(props("uri",
"http://h/a.csv")));
+ Assertions.assertTrue(provider.supports(props("uri",
"https://h/a.csv")));
+ Assertions.assertTrue(provider.supports(props("uri", "hf://ds/a")));
+ Assertions.assertFalse(provider.supports(props("uri", "s3://b/k")));
+ Assertions.assertFalse(provider.supports(new HashMap<>()));
+ }
+
+ @Test
+ public void testSupportsByMarker() {
+ Assertions.assertTrue(provider.supports(props("_STORAGE_TYPE_",
"HTTP")));
+ }
+
+ @Test
+ public void testBindReturnsHttpProperties() {
+ Map<String, String> m = props("uri", "http://h/a.csv");
+ m.put("http.header.Accept", "text/csv");
+ FileSystemProperties fp = provider.bind(m);
+ Assertions.assertTrue(fp instanceof HttpFileSystemProperties);
+ Assertions.assertEquals("http://h/a.csv", ((HttpFileSystemProperties)
fp).getUri());
+ Assertions.assertEquals("text/csv", ((HttpFileSystemProperties)
fp).getHeaders().get("Accept"));
+ }
+
+ @Test
+ public void testCreateThrows() {
+ Assertions.assertThrows(UnsupportedOperationException.class,
+ () -> provider.create(props("uri", "http://h/a.csv")));
+ }
+
+ @Test
+ public void testCapabilitiesReadOnly() {
+ HttpFileSystemProperties bound = provider.bind(props("uri",
"http://h/a.csv"));
+ Assertions.assertEquals(EnumSet.of(FileSystemCapability.READ),
provider.capabilities(bound));
+ }
+
+ @Test
+ public void testName() {
+ Assertions.assertEquals("HTTP", provider.name());
+ }
+}
diff --git
a/fe/fe-filesystem/fe-filesystem-spi/src/main/java/org/apache/doris/filesystem/spi/FileSystemProvider.java
b/fe/fe-filesystem/fe-filesystem-spi/src/main/java/org/apache/doris/filesystem/spi/FileSystemProvider.java
index a44a005ed61..d6bde78fce3 100644
---
a/fe/fe-filesystem/fe-filesystem-spi/src/main/java/org/apache/doris/filesystem/spi/FileSystemProvider.java
+++
b/fe/fe-filesystem/fe-filesystem-spi/src/main/java/org/apache/doris/filesystem/spi/FileSystemProvider.java
@@ -20,10 +20,12 @@ package org.apache.doris.filesystem.spi;
import org.apache.doris.extension.spi.Plugin;
import org.apache.doris.extension.spi.PluginFactory;
import org.apache.doris.filesystem.FileSystem;
+import org.apache.doris.filesystem.properties.FileSystemCapability;
import org.apache.doris.filesystem.properties.FileSystemProperties;
import java.io.IOException;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
@@ -109,6 +111,23 @@ public interface FileSystemProvider<P extends
FileSystemProperties> extends Plug
return Collections.emptySet();
}
+ /**
+ * Negotiates the capabilities this provider exposes for the given bound
configuration.
+ *
+ * <p>Capability is a function of the resolved configuration, not of the
provider type alone:
+ * the same provider may expose different capabilities depending on the
config (e.g. Ozone via
+ * the S3 gateway has no {@link FileSystemCapability#ATOMIC_RENAME}, but
Ozone via {@code ofs://}
+ * does). Defaults to the empty set; providers override to declare what
they support.
+ *
+ * <p>Capability negotiation is intentionally typed: the caller binds the
raw property map via
+ * {@link #bind(Map)} first, then negotiates against the resulting
configuration. There is no
+ * raw-map bridge here — a legacy provider that has not migrated to {@link
#bind(Map)} cannot be
+ * negotiated against, and a silent fallback would hide that instead of
surfacing it.
+ */
+ default Set<FileSystemCapability> capabilities(P boundProperties) {
+ return EnumSet.noneOf(FileSystemCapability.class);
+ }
+
/**
* Human-readable name for logging/diagnostics (e.g., "S3", "HDFS",
"Azure").
*/
diff --git a/fe/fe-filesystem/pom.xml b/fe/fe-filesystem/pom.xml
index e49b52aaf8a..7333615dbd5 100644
--- a/fe/fe-filesystem/pom.xml
+++ b/fe/fe-filesystem/pom.xml
@@ -54,6 +54,7 @@ under the License.
<module>fe-filesystem-azure</module>
<module>fe-filesystem-hdfs</module>
<module>fe-filesystem-broker</module>
+ <module>fe-filesystem-http</module>
</modules>
<build>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]