导入 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(())
}
文档反馈
(如有产品使用问题,请 提交工单)