You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/08/14 09:24:08 UTC

(opendal) branch main updated: feat(services/tos): support presigned URLs (#8044)

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

Xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new d2472c380 feat(services/tos): support presigned URLs (#8044)
d2472c380 is described below

commit d2472c380ec1922a8fd864e18ebc3579c86616d1
Author: Xin Sun <dd...@gmail.com>
AuthorDate: Fri Aug 14 17:24:02 2026 +0800

    feat(services/tos): support presigned URLs (#8044)
    
    feat(services/tos): add presign support
    
    Change-Id: I8876d35e13a3d60ea1f3a32b4ff9af95aaf511a0
---
 core/Cargo.lock                  |  4 ++--
 core/services/tos/Cargo.toml     |  2 +-
 core/services/tos/src/backend.rs | 40 +++++++++++++++++++++++++++++++--------
 core/services/tos/src/core.rs    | 41 ++++++++++++++++++++++++++++++++++------
 core/services/tos/src/docs.md    |  2 +-
 5 files changed, 71 insertions(+), 18 deletions(-)

diff --git a/core/Cargo.lock b/core/Cargo.lock
index a0c3db818..6d37d3f38 100644
--- a/core/Cargo.lock
+++ b/core/Cargo.lock
@@ -9324,9 +9324,9 @@ dependencies = [
 
 [[package]]
 name = "reqsign-volcengine-tos"
-version = "3.1.0"
+version = "3.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19ac4120764e6cf8b8bd431c9256bf1defeb2331ae616d353ee9d6b750c39f62"
+checksum = "173387eb5ae4cf6a0a7098665ebcc6729862819dc3d95a81aee840767803e3d9"
 dependencies = [
  "anyhow",
  "http 1.4.2",
diff --git a/core/services/tos/Cargo.toml b/core/services/tos/Cargo.toml
index 228c1adce..a688b3e54 100644
--- a/core/services/tos/Cargo.toml
+++ b/core/services/tos/Cargo.toml
@@ -40,6 +40,6 @@ opendal-core = { path = "../../core", version = "0.58.2", default-features = fal
 quick-xml = { workspace = true, features = ["serialize", "overlapped-lists"] }
 reqsign-core = { version = "3.2.0", default-features = false }
 reqsign-file-read-tokio = { version = "3.0.3", default-features = false }
-reqsign-volcengine-tos = "3.0.3"
+reqsign-volcengine-tos = "3.1.1"
 serde = { workspace = true, features = ["derive"] }
 serde_json = { workspace = true }
diff --git a/core/services/tos/src/backend.rs b/core/services/tos/src/backend.rs
index 74b1c38fd..5526cee40 100644
--- a/core/services/tos/src/backend.rs
+++ b/core/services/tos/src/backend.rs
@@ -33,7 +33,7 @@ use crate::writer::TosWriter;
 use http::StatusCode;
 use opendal_core::OperationContext;
 use opendal_core::raw::*;
-use opendal_core::{Builder, Capability, EntryMode, Error, ErrorKind, Result};
+use opendal_core::{Buffer, Builder, Capability, EntryMode, Error, ErrorKind, Result};
 use reqsign_core::{Context, OsEnv, ProvideCredentialChain, Signer};
 use reqsign_file_read_tokio::TokioFileRead;
 use reqsign_volcengine_tos::{EnvCredentialProvider, RequestSigner, StaticCredentialProvider};
@@ -215,6 +215,12 @@ impl Builder for TosBuilder {
             list_with_versions: true,
             list_with_deleted: true,
 
+            presign: true,
+            presign_stat: true,
+            presign_read: true,
+            presign_write: true,
+            presign_delete: true,
+
             stat: true,
             stat_with_if_match: true,
             stat_with_if_none_match: true,
@@ -416,13 +422,31 @@ impl Service for TosBackend {
 
     async fn presign(
         &self,
-        _ctx: &OperationContext,
-        _path: &str,
-        _args: OpPresign,
+        ctx: &OperationContext,
+        path: &str,
+        args: OpPresign,
     ) -> Result<RpPresign> {
-        Err(Error::new(
-            ErrorKind::Unsupported,
-            "operation is not supported",
-        ))
+        let (expire, op) = args.into_parts();
+        let req = match op {
+            PresignOperation::Stat(v) => self.core.tos_head_object_request(path, v),
+            PresignOperation::Read(range, v) => self.core.tos_get_object_request(path, range, &v),
+            PresignOperation::Write(v) => {
+                self.core
+                    .tos_put_object_request(path, None, &v, Buffer::new())
+            }
+            PresignOperation::Delete(v) => self.core.tos_delete_object_request(path, &v),
+            _ => Err(Error::new(
+                ErrorKind::Unsupported,
+                "operation is not supported",
+            )),
+        }?;
+        let req = self.core.sign_query(ctx, req, expire).await?;
+        let (parts, _) = req.into_parts();
+
+        Ok(RpPresign::new(PresignedRequest::new(
+            parts.method,
+            parts.uri,
+            parts.headers,
+        )))
     }
 }
diff --git a/core/services/tos/src/core.rs b/core/services/tos/src/core.rs
index f32066cd3..ed3436df6 100644
--- a/core/services/tos/src/core.rs
+++ b/core/services/tos/src/core.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 use std::fmt::Debug;
+use std::time::Duration;
 
 use http::Request;
 use http::Response;
@@ -99,6 +100,27 @@ impl TosCore {
         )
     }
 
+    pub async fn sign_query<T>(
+        &self,
+        ctx: &OperationContext,
+        req: Request<T>,
+        duration: Duration,
+    ) -> Result<Request<T>> {
+        if self.skip_signature {
+            return Ok(req);
+        }
+
+        let (mut parts, body) = req.into_parts();
+        self.signer(ctx)
+            .sign(&mut parts, Some(duration))
+            .await
+            .map_err(|e| new_request_sign_error(e.into()))?;
+
+        parts.headers.remove(HOST);
+
+        Ok(Request::from_parts(parts, body))
+    }
+
     pub async fn send(
         &self,
         ctx: &OperationContext,
@@ -413,12 +435,11 @@ impl TosCore {
         self.send(ctx, req).await
     }
 
-    pub async fn tos_delete_object(
+    pub fn tos_delete_object_request(
         &self,
-        ctx: &OperationContext,
         path: &str,
         args: &OpDelete,
-    ) -> Result<Response<Buffer>> {
+    ) -> Result<Request<Buffer>> {
         let p = build_abs_path(&self.root, path);
 
         let mut url = format!(
@@ -444,11 +465,19 @@ impl TosCore {
 
         let req = Request::delete(&url);
 
-        let req = req
-            .extension(Operation::Delete)
+        req.extension(Operation::Delete)
             .extension(ServiceOperation("DeleteObject"))
             .body(Buffer::new())
-            .map_err(new_request_build_error)?;
+            .map_err(new_request_build_error)
+    }
+
+    pub async fn tos_delete_object(
+        &self,
+        ctx: &OperationContext,
+        path: &str,
+        args: &OpDelete,
+    ) -> Result<Response<Buffer>> {
+        let req = self.tos_delete_object_request(path, args)?;
 
         self.send(ctx, req).await
     }
diff --git a/core/services/tos/src/docs.md b/core/services/tos/src/docs.md
index 01610d88c..6f688f1a6 100644
--- a/core/services/tos/src/docs.md
+++ b/core/services/tos/src/docs.md
@@ -10,7 +10,7 @@ Depending on its configuration and the backing system, this service can expose:
 - [x] list
 - [x] copy
 - [ ] rename
-- [ ] presign
+- [x] presign
 
 Inspect the effective capability set with [`opendal_core::Operator::info`] and
 [`opendal_core::OperatorInfo::capability`] after building an operator.