对象存储

  • 对象存储 > 使用指南 > 开发指南 > AWS S3 兼容 > 兼容 SDK 示例 > AWS SDK for Rust

    AWS SDK for Rust

    最近更新时间: 2024-02-19 17:03:51

    导入 AWS SDK for Rust

    确保 Rust v1.70.0 或更新版本已经安装。

    初始化 Rust 项目

    cargo init
    

    添加 AWS SDK for Rust 包

    cargo add anyhow@1.0.75
    cargo add aws-config@=1.1.5
    cargo add aws-credential-types@=1.1.5 -F hardcoded-credentials
    cargo add aws-smithy-types@=1.1.6 -F rt-tokio
    cargo add aws-sdk-s3@=1.15.0
    cargo add aws-sdk-sts@=1.13.0
    cargo add tokio@1.32.0 -F full
    

    对于之后的每个代码示例,将代码创建在 src/main.rs 后,执行

    cargo run
    

    即可执行代码。

    对象上传

    获取客户端上传 URL

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, presigning::PresigningConfig, Client};
    use std::time::Duration;
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let request = client
            .put_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .presigned(
                PresigningConfig::builder()
                    .expires_in(Duration::from_secs(3600))
                    .build()?,
            )
            .await?;
        println!("{}", request.uri());
    
        Ok(())
    }
    

    这段代码将生成一个经过预先签名的客户端上传 URL,有效期为 1 小时,客户端可以在过期时间内对该 URL 发送 HTTP PUT 请求将文件上传。

    以下是用 curl 上传文件的案例:

    curl -X PUT --upload-file "<path/to/file>" "<presigned url>"
    

    服务器端直传

    单请求上传(文件)

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, primitives::ByteStream, Client};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let output = client
            .put_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .body(ByteStream::from_path("<path/to/upload>").await?)
            .send()
            .await?;
        let etag = output.e_tag().expect("etag is expected");
        println!("ETag: {}", etag);
    
        Ok(())
    }
    

    单请求上传(数据流)

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, primitives::ByteStream, Client};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let output = client
            .put_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .body(ByteStream::from_static(b"Hello from Qiniu S3!"))
            .send()
            .await?;
        let etag = output.e_tag().expect("etag is expected");
        println!("ETag: {}", etag);
    
        Ok(())
    }
    

    分片上传(文件)

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{
        config::Region,
        primitives::ByteStream,
        types::{CompletedMultipartUpload, CompletedPart},
        Client,
    };
    use aws_smithy_types::byte_stream::Length;
    use tokio::fs::metadata;
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let create_multipart_upload_resp = client
            .create_multipart_upload()
            .bucket("<Bucket>")
            .key("<Key>")
            .send()
            .await?;
    
        const PART_SIZE: u64 = 5 * 1024 * 1024; // 分片大小为 5 MB
        let mut offset: u64 = 0;
        let file_size = metadata("<path/to/upload>").await?.len();
        let mut completed_multipart_upload_builder = CompletedMultipartUpload::builder();
        let upload_id = create_multipart_upload_resp
            .upload_id()
            .expect("upload_id is expected");
    
        // 这里给出的案例是串行分片上传。可以自行改造成并行分片上传以进一步提升上传速度
        for part_number in 1.. {
            if offset >= file_size {
                break;
            }
            let part_size = PART_SIZE.min(file_size - offset);
            let upload_part_resp = client
                .upload_part()
                .bucket("<Bucket>")
                .key("<Key>")
                .upload_id(upload_id)
                .part_number(part_number)
                .body(
                    ByteStream::read_from()
                        .path("<path/to/upload>")
                        .offset(offset)
                        .length(Length::UpTo(part_size))
                        .build()
                        .await?,
                )
                .send()
                .await?;
            let etag = upload_part_resp.e_tag().expect("etag is expected");
            completed_multipart_upload_builder = completed_multipart_upload_builder.parts(
                CompletedPart::builder()
                    .part_number(part_number)
                    .e_tag(etag)
                    .build(),
            );
            offset += part_size;
        }
    
        let complete_multipart_upload_resp = client
            .complete_multipart_upload()
            .bucket("<Bucket>")
            .key("<Key>")
            .upload_id(upload_id)
            .multipart_upload(completed_multipart_upload_builder.build())
            .send()
            .await?;
        let etag = complete_multipart_upload_resp
            .e_tag()
            .expect("etag is expected");
        println!("ETag: {}", etag);
    
        Ok(())
    }
    

    对象下载

    获取客户端下载 URL

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, presigning::PresigningConfig, Client};
    use std::time::Duration;
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let request = client
            .get_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .presigned(
                PresigningConfig::builder()
                    .expires_in(Duration::from_secs(3600))
                    .build()?,
            )
            .await?;
        println!("{}", request.uri());
    
        Ok(())
    }
    

    这段代码将生成一个经过预先签名的客户端下载 URL,有效期为 1 小时,客户端可以在过期时间内对该 URL 发送 HTTP GET 请求将文件下载。

    以下是用 curl 下载文件的案例:

    curl -o "<path/to/download>" "<presigned url>"
    

    服务器端直接下载

    创建 src/main.rs

    use std::mem::take;
    
    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, Client};
    use tokio::{fs::File, io::copy};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let mut resp = client
            .get_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .send()
            .await?;
        let mut file = File::create("<path/to/download>").await?;
        copy(&mut take(&mut resp.body).into_async_read(), &mut file).await?;
        println!("ETag: {}", resp.e_tag().expect("etag is expected"));
    
        Ok(())
    }
    

    对象管理

    获取对象信息

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, Client};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let resp = client
            .head_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .send()
            .await?;
        println!("ETag: {}", resp.e_tag().expect("etag is expected"));
    
        Ok(())
    }
    

    修改对象 MimeType

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, types::MetadataDirective, Client};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        client
            .copy_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .copy_source("/<Bucket>/<Key>")
            .content_type("<NewContentType>")
            .metadata_directive(MetadataDirective::Replace)
            .send()
            .await?;
        println!("Done");
    
        Ok(())
    }
    

    修改对象存储类型

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{
        config::Region,
        types::{MetadataDirective, StorageClass},
        Client,
    };
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        client
            .copy_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .copy_source("/<Bucket>/<Key>")
            .storage_class(StorageClass::Glacier)
            .metadata_directive(MetadataDirective::Replace)
            .send()
            .await?;
        println!("Done");
    
        Ok(())
    }
    

    复制对象副本

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, types::MetadataDirective, Client};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        client
            .copy_object()
            .bucket("<ToBucket>")
            .key("<ToKey>")
            .copy_source("/<FromBucket>/<FromKey>")
            .metadata_directive(MetadataDirective::Copy)
            .send()
            .await?;
        println!("Done");
    
        Ok(())
    }
    

    复制对象副本(大于 5GB)

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{
        config::Region,
        types::{CompletedMultipartUpload, CompletedPart},
        Client,
    };
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let head_object_resp = client
            .head_object()
            .bucket("<FromBucket>")
            .key("<FromKey>")
            .send()
            .await?;
        let content_length = u64::try_from(head_object_resp.content_length().unwrap())?;
    
        let create_multipart_upload_resp = client
            .create_multipart_upload()
            .bucket("<ToBucket>")
            .key("<ToKey>")
            .send()
            .await?;
    
        const PART_SIZE: u64 = 5 * 1024 * 1024;
        let mut offset: u64 = 0;
        let mut completed_multipart_upload_builder = CompletedMultipartUpload::builder();
        let upload_id = create_multipart_upload_resp
            .upload_id()
            .expect("upload_id is expected");
    
        for part_number in 1.. {
            if offset >= content_length {
                break;
            }
            let part_size = PART_SIZE.min(content_length - offset);
            let upload_part_copy_resp = client
                .upload_part_copy()
                .bucket("<ToBucket>")
                .key("<ToKey>")
                .upload_id(upload_id)
                .part_number(part_number)
                .copy_source("/<FromBucket>/<FromKey>")
                .copy_source_range(format!("bytes={}-{}", offset, offset + part_size - 1))
                .send()
                .await?;
            let etag = upload_part_copy_resp
                .copy_part_result()
                .expect("copy_part_result is expected")
                .e_tag()
                .expect("etag is expected");
            completed_multipart_upload_builder = completed_multipart_upload_builder.parts(
                CompletedPart::builder()
                    .part_number(part_number)
                    .e_tag(etag)
                    .build(),
            );
            offset += part_size;
        }
    
        let complete_multipart_upload_resp = client
            .complete_multipart_upload()
            .bucket("<ToBucket>")
            .key("<ToKey>")
            .upload_id(upload_id)
            .multipart_upload(completed_multipart_upload_builder.build())
            .send()
            .await?;
        let etag = complete_multipart_upload_resp
            .e_tag()
            .expect("etag is expected");
        println!("ETag: {}", etag);
    
        Ok(())
    }
    

    删除空间中的文件

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, Client};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        client
            .delete_object()
            .bucket("<Bucket>")
            .key("<Key>")
            .send()
            .await?;
        println!("Done");
    
        Ok(())
    }
    

    获取指定前缀的文件列表

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{config::Region, Client};
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        let resp = client
            .list_objects_v2()
            .bucket("<Bucket>")
            .prefix("<KeyPrefix>")
            .send()
            .await?;
        for content in resp.contents() {
            println!("Key: {}", content.key().expect("key is expected"));
            println!("ETag: {}", content.e_tag().expect("etag is expected"));
        }
    
        Ok(())
    }
    

    批量删除空间中的文件

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{provider::SharedCredentialsProvider, Credentials};
    use aws_sdk_s3::{
        config::Region,
        types::{Delete, ObjectIdentifier},
        Client,
    };
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1"))  // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com")  // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
    
        client
            .delete_objects()
            .bucket("<Bucket>")
            .delete(
                Delete::builder()
                    .objects(ObjectIdentifier::builder().key("<Key1>").build()?)
                    .objects(ObjectIdentifier::builder().key("<Key2>").build()?)
                    .objects(ObjectIdentifier::builder().key("<Key3>").build()?)
                    .build()?,
            )
            .send()
            .await?;
        println!("Done");
    
        Ok(())
    }
    

    临时安全凭证

    创建 src/main.rs

    use aws_config::{BehaviorVersion, SdkConfig};
    use aws_credential_types::{
        provider::{error::CredentialsError, ProvideCredentials, SharedCredentialsProvider},
        Credentials,
    };
    use aws_sdk_s3::{config::Region, Client as S3Client};
    use aws_sdk_sts::{
        operation::get_federation_token::builders::GetFederationTokenFluentBuilder, Client as StsClient,
    };
    
    #[derive(Debug)]
    struct StsCredentialsProvider {
        get_federation_token_builder: GetFederationTokenFluentBuilder,
    }
    
    impl ProvideCredentials for StsCredentialsProvider {
        fn provide_credentials<'a>(
            &'a self,
        ) -> aws_credential_types::provider::future::ProvideCredentials<'a>
        where
            Self: 'a,
        {
            aws_credential_types::provider::future::ProvideCredentials::new(async {
                match self.get_federation_token_builder.to_owned().send().await {
                    Ok(get_federation_token_output) => {
                        if let Some(credentials) = get_federation_token_output.credentials() {
                            Ok(Credentials::new(
                                credentials.access_key_id(),
                                credentials.secret_access_key(),
                                Some(credentials.session_token().to_owned()),
                                Some(
                                    credentials
                                        .expiration()
                                        .to_owned()
                                        .try_into()
                                        .map_err(CredentialsError::unhandled)?,
                                ),
                                "Bob",
                            ))
                        } else {
                            Err(CredentialsError::not_loaded(
                                "No credentials from get_federation_token",
                            ))
                        }
                    }
                    Err(err) => Err(CredentialsError::provider_error(err)),
                }
            })
        }
    }
    
    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let sts_client = StsClient::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1")) // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com") // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(Credentials::from_keys(
                    "<QiniuAccessKey>",
                    "<QiniuSecretKey>",
                    None,
                )))
                .build(),
        );
        let s3_client = S3Client::new(
            &SdkConfig::builder()
                .region(Region::new("cn-east-1")) // 华东-浙江区 region id
                .endpoint_url("https://s3.cn-east-1.qiniucs.com") // 华东-浙江区 endpoint
                .behavior_version(BehaviorVersion::v2023_11_09())
                .credentials_provider(SharedCredentialsProvider::new(StsCredentialsProvider {
                    get_federation_token_builder: sts_client.get_federation_token().
                        set_name(Some("Bob".to_owned())).
                        set_duration_seconds(Some(3600)).
                        set_policy(Some("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Stmt1\",\"Effect\":\"Allow\",\"Action\":[\"*\"],\"Resource\":[\"*\"]}]}".to_owned())),
                }))
                .build(),
        );
    
        // 可以使用这些临时凭证调用 S3 服务
        let list_buckets_output = s3_client.list_buckets().send().await?;
        for bucket in list_buckets_output.buckets() {
            println!("{}", bucket.name().unwrap());
        }
    
        Ok(())
    }
    
    以上内容是否对您有帮助?
  • Qvm free helper
    Close