refactor: unify async synchronization with AsyncBand (#870)

## Summary

Reqsign uses Tokio and futures synchronization alongside AsyncBand,
while concurrent Azure SAS cache misses can issue identical user
delegation key requests. Use AsyncBand consistently for async
synchronization and coalesce those Azure key requests.

- Replace the Google client-side CAB cache and refresh mutexes with
`asyncband::mutex::Mutex`.
- Replace Tokio semaphores in the Azure and Google concurrency tests,
and futures oneshot channels in the core signer tests and WASM Reqwest
response bridge.
- Use `Group::try_work` for Azure user delegation keys, partitioned by
account, endpoint, source-token fingerprint, service version, and
requested key start and expiry. Retain the bounded cache and per-grant
SAS validity checks; waiting callers can retry after a failed or
cancelled key request.
- Declare AsyncBand features at their use sites and remove the replaced
Google futures and WASM futures-channel dependencies.

## Testing

The full workspace suite includes Google client-side CAB refresh
sharing, partition isolation, and cancellation tests. The four Azure
coordination tests pass; three reproduce duplicate requests against the
original implementation. The existing WASM HTTP test also passes under
Node with wasm-bindgen-test-runner 0.2.128.

- `cargo fmt --all -- --check`
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- `cargo test --workspace --all-features --no-fail-fast` (including doc
tests)
- `cargo build --manifest-path reqsign/Cargo.toml --target
wasm32-unknown-unknown --no-default-features --features
default-context,aws,aws-v4a,azure,aliyun,tencent`
- `cargo test --target wasm32-unknown-unknown -p
reqsign-http-send-reqwest --test wasm`
- `hawkeye check`

Repository-wide scanning found no remaining direct uses of Tokio or
futures synchronization primitives. Live cloud service tests were not
run locally.

AI assisted with the implementation, tests, and PR draft.
8 files changed
tree: 59adce572003c9d7841b5927feca86239a75d7c1
  1. .github/
  2. .vscode/
  3. context/
  4. core/
  5. docs/
  6. release/
  7. reqsign/
  8. services/
  9. .asf.yaml
  10. .env.example
  11. .gitignore
  12. .taplo.toml
  13. AGENTS.md
  14. azure-pipelines.yml
  15. Cargo.toml
  16. CONTRIBUTING.md
  17. LICENSE
  18. licenserc.toml
  19. NOTICE
  20. README.md
  21. rust-toolchain.toml
  22. rustfmt.toml
  23. SECURITY.md
  24. THREAT_MODEL.md
README.md

Apache OpenDAL Reqsign

Build Status Latest Version Crate Downloads

Signing API requests without effort.


Most API is simple. But they could be complicated when they are hidden from complex abstraction. reqsign bring the simple API back: build, sign, send.

Quick Start

Option 1: Use Default Signer (Recommended)

The simplest way to use reqsign is with the default signers provided by each service:

use anyhow::Result;
use reqsign::aws;

#[tokio::main]
async fn main() -> Result<()> {
    // Create a default signer for S3 in us-east-1
    // This will automatically:
    // - Load credentials from environment variables, config files, or IAM roles
    // - Set up the default HTTP client and file reader
    let signer = aws::default_signer("s3", "us-east-1");
    
    // Build your request
    let mut req = http::Request::builder()
        .method("GET")
        .uri("https://s3.amazonaws.com/testbucket")
        .body(())
        .unwrap()
        .into_parts()
        .0;
    
    // Sign the request
    signer.sign(&mut req, None).await?;
    
    // Send the request with your preferred HTTP client
    println!("Request has been signed!");
    Ok(())
}

Option 2: Custom Assembly

For more control over the components, you can manually assemble the signer:

use anyhow::Result;
use reqsign::{Context, Signer};
use reqsign_aws_v4::{DefaultCredentialProvider, RequestSigner};
use reqsign_file_read_tokio::TokioFileRead;
use reqsign_http_send_reqwest::ReqwestHttpSend;

#[tokio::main]
async fn main() -> Result<()> {
    // Build your own context with specific implementations
    let ctx = Context::new()
        .with_file_read(TokioFileRead)
        .with_http_send(ReqwestHttpSend::default())
        .with_env(reqsign::OsEnv);
    
    // Configure credential provider
    let credential_provider = DefaultCredentialProvider::new();
    
    // Configure request signer for S3
    let request_signer = RequestSigner::new("s3", "us-east-1");
    
    // Assemble the signer
    let signer = Signer::new(ctx, credential_provider, request_signer);
    
    // Build and sign the request
    let mut req = http::Request::builder()
        .method("GET")
        .uri("https://s3.amazonaws.com/testbucket")
        .body(())
        .unwrap()
        .into_parts()
        .0;
    
    // Sign the request
    signer.sign(&mut req, None).await?;
    
    println!("Request has been signed!");
    Ok(())
}

Customizing Default Signers

You can also customize the default signers using the with_* methods:

use reqsign::aws;
use reqsign_aws_v4::StaticCredentialProvider;

// Start with default signer and customize specific components
let signer = aws::default_signer("s3", "us-east-1")
    .with_credential_provider(StaticCredentialProvider::new(
        "my-access-key",
        "my-secret-key",
        None,  // Optional session token
    ));

For server applications that already hold an OIDC assertion, see Caller-provided subject tokens.

More Services Examples

AWS SigV4a

use reqsign::aws::v4a::{SigningRegionSet, default_signer};

let region_set = SigningRegionSet::new("us-east-1,us-west-2")?;
let signer = default_signer("s3", region_set);
# Ok::<(), reqsign_core::Error>(())

Azure Storage

use reqsign::azure;

// Default signer for Azure Storage
let signer = azure::default_signer();

// With custom credentials
use reqsign_azure_storage::StaticCredentialProvider;
let signer = azure::default_signer()
    .with_credential_provider(StaticCredentialProvider::new(
        "account-name",
        "account-key",
    ));

Google Cloud

use reqsign::google;

// Default signer for Google Cloud Storage
let signer = google::default_signer("storage.googleapis.com");

Aliyun OSS

use reqsign::aliyun;

// Default signer for Aliyun OSS
let signer = aliyun::default_signer();

Features

  • Pure rust with minimal dependencies.
  • Test again official SDK and services.
  • Supported services
    • Aliyun OSS: reqsign-aliyun-oss
    • AWS shared credentials and canonicalization: reqsign-aws-core
    • AWS services (SigV4): reqsign-aws-v4
    • AWS multi-region services (SigV4a): reqsign-aws-v4a
    • Azure Storage services: reqsign-azure-storage
    • Google services: reqsign-google
    • Huawei Cloud OBS: reqsign-huaweicloud-obs
    • Oracle Cloud: reqsign-oracle
    • Tencent COS: reqsign-tencent-cos

Contributing

Check out the CONTRIBUTING.md guide for more details on getting started with contributing to this project.

Getting help

Submit issues for bug report or asking questions in discussion.

Acknowledge

Inspired a lot from:

License

Licensed under Apache License, Version 2.0.