实现 put object
This commit is contained in:
parent
0bb6ed21f3
commit
cfa84d057a
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
|
/.env
|
||||||
|
|
|
@ -13,4 +13,7 @@ urlencoding = "2"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
hmac-sha1 = "^0.1"
|
hmac-sha1 = "^0.1"
|
||||||
rustc-serialize = "0.3.24"
|
rustc-serialize = "0.3.24"
|
||||||
base64 = "0.21.2"
|
base64 = "0.21.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
|
132
src/auth.rs
132
src/auth.rs
|
@ -1,11 +1,11 @@
|
||||||
use crate::{client::Client, error::ObsError};
|
use crate::{client::Client, config::SignatureType, error::ObsError};
|
||||||
use ::base64::{engine::general_purpose, Engine};
|
use ::base64::{engine::general_purpose, Engine};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use hmacsha1::hmac_sha1;
|
use hmacsha1::hmac_sha1;
|
||||||
use rustc_serialize::{base64, hex::ToHex};
|
use reqwest::header::{HeaderMap, HeaderValue, HeaderName};
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
pub const RFC1123: &str = "%a, %d %b %Y %H:%M:%S %Z";
|
const RFC1123 :&str = "%a, %d %b %Y %H:%M:%S GMT";
|
||||||
|
|
||||||
pub trait Authorization {
|
pub trait Authorization {
|
||||||
fn signature(
|
fn signature(
|
||||||
|
@ -19,10 +19,11 @@ pub trait Authorization {
|
||||||
fn auth(
|
fn auth(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
|
bucket:&str,
|
||||||
params: HashMap<String, String>,
|
params: HashMap<String, String>,
|
||||||
headers: HashMap<String, Vec<String>>,
|
headers: HashMap<String, Vec<String>>,
|
||||||
canonicalized_url: String,
|
canonicalized_url: String,
|
||||||
) -> Result<HashMap<String, String>, ObsError>;
|
) -> Result<HeaderMap, ObsError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Authorization for Client {
|
impl Authorization for Client {
|
||||||
|
@ -33,14 +34,17 @@ impl Authorization for Client {
|
||||||
headers: HashMap<String, Vec<String>>,
|
headers: HashMap<String, Vec<String>>,
|
||||||
canonicalized_url: String,
|
canonicalized_url: String,
|
||||||
) -> Result<String, ObsError> {
|
) -> Result<String, ObsError> {
|
||||||
|
let attach_headers = attach_headers(headers, true);
|
||||||
|
|
||||||
let string_to_sign = vec![
|
let string_to_sign = vec![
|
||||||
method,
|
method, //HTTP-Verb
|
||||||
"\n",
|
"\n",
|
||||||
&attach_headers(headers, true),
|
&attach_headers, // Content-MD5 \n Content-Type \n
|
||||||
"\n",
|
"\n",
|
||||||
&canonicalized_url,
|
&canonicalized_url,
|
||||||
]
|
]
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
let security = self.security();
|
let security = self.security();
|
||||||
match security {
|
match security {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
|
@ -54,16 +58,33 @@ impl Authorization for Client {
|
||||||
fn auth(
|
fn auth(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
|
bucket:&str,
|
||||||
params: HashMap<String, String>,
|
params: HashMap<String, String>,
|
||||||
headers: HashMap<String, Vec<String>>,
|
mut headers: HashMap<String, Vec<String>>,
|
||||||
canonicalized_url: String,
|
canonicalized_url: String,
|
||||||
) -> Result<HashMap<String, String>, ObsError> {
|
) -> Result<HeaderMap, ObsError> {
|
||||||
let sign = self.signature(method, params, headers, canonicalized_url)?;
|
let is_v4 = matches!(self.config().signature_type, SignatureType::V4);
|
||||||
|
headers.insert("Host".into(), vec![format!("{}.{}",bucket,self.config().endpoint())]);
|
||||||
|
|
||||||
|
prepare_host_and_date(&mut headers, self.config().endpoint(), is_v4);
|
||||||
|
|
||||||
|
let sign = self.signature(method, params, headers.clone(), canonicalized_url)?;
|
||||||
let security = self.security();
|
let security = self.security();
|
||||||
match security {
|
match security {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
|
|
||||||
let value = format!("OBS {}:{}", s.ak(), sign);
|
let value = format!("OBS {}:{}", s.ak(), sign);
|
||||||
let h = vec![("Authorization".into(), value)].into_iter().collect();
|
let mut h = HeaderMap::new();
|
||||||
|
h.insert(
|
||||||
|
"Authorization",
|
||||||
|
HeaderValue::from_str(value.as_str()).unwrap(),
|
||||||
|
);
|
||||||
|
for (key, value) in headers.iter() {
|
||||||
|
h.insert(
|
||||||
|
HeaderName::from_str(key).unwrap(),
|
||||||
|
HeaderValue::from_str(&value.join(",")).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(h)
|
Ok(h)
|
||||||
}
|
}
|
||||||
None => Err(ObsError::Security),
|
None => Err(ObsError::Security),
|
||||||
|
@ -72,11 +93,10 @@ impl Authorization for Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_host_and_date(
|
fn prepare_host_and_date(
|
||||||
mut headers: HashMap<String, Vec<String>>,
|
headers: &mut HashMap<String, Vec<String>>,
|
||||||
host_name: String,
|
host_name: &str,
|
||||||
is_v4: bool,
|
is_v4: bool,
|
||||||
) {
|
) {
|
||||||
headers.insert("Host".into(), vec![host_name]);
|
|
||||||
if let Some(date) = headers.get("x-amz-date") {
|
if let Some(date) = headers.get("x-amz-date") {
|
||||||
let mut flag = false;
|
let mut flag = false;
|
||||||
if date.len() == 1 {
|
if date.len() == 1 {
|
||||||
|
@ -100,42 +120,42 @@ fn prepare_host_and_date(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_headers(headers: HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
|
// fn encode_headers(headers: HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
|
||||||
headers
|
// headers
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.map(|(key, values)| {
|
// .map(|(key, values)| {
|
||||||
(
|
// (
|
||||||
key,
|
// key,
|
||||||
values
|
// values
|
||||||
.iter()
|
// .iter()
|
||||||
.map(|v| urlencoding::encode(v).to_string())
|
// .map(|v| urlencoding::encode(v).to_string())
|
||||||
.collect::<Vec<String>>(),
|
// .collect::<Vec<String>>(),
|
||||||
)
|
// )
|
||||||
})
|
// })
|
||||||
.collect::<HashMap<String, Vec<String>>>()
|
// .collect::<HashMap<String, Vec<String>>>()
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn signature_header(
|
// fn signature_header(
|
||||||
headers: HashMap<String, Vec<String>>,
|
// headers: HashMap<String, Vec<String>>,
|
||||||
) -> (Vec<String>, HashMap<String, Vec<String>>) {
|
// ) -> (Vec<String>, HashMap<String, Vec<String>>) {
|
||||||
let mut signed_headers = vec![];
|
// let mut signed_headers = vec![];
|
||||||
let mut headers2 = HashMap::with_capacity(headers.len());
|
// let mut headers2 = HashMap::with_capacity(headers.len());
|
||||||
for (key, value) in headers {
|
// for (key, value) in headers {
|
||||||
let key2 = key.trim().to_lowercase();
|
// let key2 = key.trim().to_lowercase();
|
||||||
if !key2.is_empty() {
|
// if !key2.is_empty() {
|
||||||
signed_headers.push(key2.clone());
|
// signed_headers.push(key2.clone());
|
||||||
headers2.insert(key2, value);
|
// headers2.insert(key2, value);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
signed_headers.sort();
|
// signed_headers.sort();
|
||||||
(signed_headers, headers2)
|
// (signed_headers, headers2)
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn credential(ak: &str, region: &str, short_date: &str) -> (String, String) {
|
// fn credential(ak: &str, region: &str, short_date: &str) -> (String, String) {
|
||||||
let scope = format!("{}/{}/{}/{}", short_date, region, "s3", "aws4_request");
|
// let scope = format!("{}/{}/{}/{}", short_date, region, "s3", "aws4_request");
|
||||||
// return fmt.Sprintf("%s/%s", ak, scope), scope
|
// // return fmt.Sprintf("%s/%s", ak, scope), scope
|
||||||
(format!("{}/{}", ak, &scope), scope)
|
// (format!("{}/{}", ak, &scope), scope)
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn string_to_sign(
|
fn string_to_sign(
|
||||||
keys: Vec<String>,
|
keys: Vec<String>,
|
||||||
|
@ -178,11 +198,11 @@ fn attach_headers(headers: HashMap<String, Vec<String>>, is_obs: bool) -> String
|
||||||
.collect::<_>();
|
.collect::<_>();
|
||||||
for (key, value) in headers {
|
for (key, value) in headers {
|
||||||
let _key = key.trim().to_lowercase();
|
let _key = key.trim().to_lowercase();
|
||||||
let prefixheader = if is_obs { "x-amz-" } else { "x-obs-" };
|
let prefix_header = if is_obs { "x-amz-" } else { "x-obs-" };
|
||||||
if _key == "content-md5"
|
if _key == "content-md5"
|
||||||
|| _key == "content-type"
|
|| _key == "content-type"
|
||||||
|| _key == "date"
|
|| _key == "date"
|
||||||
|| _key.starts_with(prefixheader)
|
|| _key.starts_with(prefix_header)
|
||||||
{
|
{
|
||||||
keys.push(_key.clone());
|
keys.push(_key.clone());
|
||||||
_headers.insert(_key, value);
|
_headers.insert(_key, value);
|
||||||
|
@ -199,10 +219,9 @@ fn attach_headers(headers: HashMap<String, Vec<String>>, is_obs: bool) -> String
|
||||||
let date_camel_header = if is_obs { "X-obs-Date" } else { "X-Amz-Date" };
|
let date_camel_header = if is_obs { "X-obs-Date" } else { "X-Amz-Date" };
|
||||||
let data_header = date_camel_header.to_lowercase();
|
let data_header = date_camel_header.to_lowercase();
|
||||||
|
|
||||||
if _headers.contains_key("Date")
|
if (_headers.contains_key(&data_header) || _headers.contains_key(date_camel_header))
|
||||||
&& (_headers.contains_key(&data_header) || _headers.contains_key(date_camel_header))
|
|
||||||
{
|
{
|
||||||
_headers.insert(date_camel_header.into(), vec![]);
|
_headers.insert(date_camel_header.into(), vec![rfc_1123()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
keys.sort();
|
keys.sort();
|
||||||
|
@ -218,3 +237,10 @@ fn signature(string_to_sign: &str, sk: &str) -> Result<String, ObsError> {
|
||||||
let hs = general_purpose::STANDARD.encode(hash);
|
let hs = general_purpose::STANDARD.encode(hash);
|
||||||
Ok(hs)
|
Ok(hs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn rfc_1123() -> String {
|
||||||
|
let date = Utc::now().format(RFC1123).to_string();
|
||||||
|
date
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Bucket<'a> {
|
||||||
|
name: &'a str
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Bucket<'a> {
|
||||||
|
pub fn new(name: &'a str) -> Self { Self { name } }
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::time::Duration;
|
use std::{time::Duration, collections::HashMap};
|
||||||
|
|
||||||
use reqwest::{header::HeaderMap, Body, Method, Response};
|
use reqwest::{header::{HeaderMap, HeaderValue}, Body, Method, Response};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, SignatureType},
|
config::{Config, SignatureType, SecurityHolder},
|
||||||
error::ObsError,
|
error::ObsError, auth::Authorization,
|
||||||
provider::{SecurityHolder},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -15,6 +14,7 @@ pub struct Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
/// endpoint 格式: https[http]://obs.cn-north-4.myhuaweicloud.com
|
||||||
pub fn new<S:ToString>(
|
pub fn new<S:ToString>(
|
||||||
access_key_id: S,
|
access_key_id: S,
|
||||||
secret_access_key: S,
|
secret_access_key: S,
|
||||||
|
@ -51,6 +51,7 @@ impl Client {
|
||||||
pub async fn do_action<T>(
|
pub async fn do_action<T>(
|
||||||
&self,
|
&self,
|
||||||
method: Method,
|
method: Method,
|
||||||
|
bucket_name:&str,
|
||||||
uri: &str,
|
uri: &str,
|
||||||
with_headers: Option<HeaderMap>,
|
with_headers: Option<HeaderMap>,
|
||||||
body: T,
|
body: T,
|
||||||
|
@ -58,8 +59,10 @@ impl Client {
|
||||||
where
|
where
|
||||||
T: Into<Body>,
|
T: Into<Body>,
|
||||||
{
|
{
|
||||||
let url = format!("https://{}/{}", self.config().endpoint(), uri);
|
let url = format!("https://{}.{}/{}",bucket_name, self.config().endpoint(), uri);
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
|
let canonicalized_url = self.config().canonicalized_url(bucket_name,uri);
|
||||||
|
let mut headers = self.auth(method.as_str(),bucket_name, HashMap::new(), HashMap::new(), canonicalized_url)?;
|
||||||
if let Some(wh) = with_headers {
|
if let Some(wh) = with_headers {
|
||||||
headers.extend(wh);
|
headers.extend(wh);
|
||||||
}
|
}
|
||||||
|
@ -74,6 +77,16 @@ impl Client {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PUT上传
|
||||||
|
pub async fn put_object(&self, bucket:&str,key:&str, object:&'static [u8]) ->Result<(), ObsError> {
|
||||||
|
let mut with_headers = HeaderMap::new();
|
||||||
|
with_headers.insert("Content-Length", HeaderValue::from_str(format!("{}",object.len()).as_str()).unwrap());
|
||||||
|
let resp = self.do_action(Method::PUT, bucket, key, Some(with_headers), object).await?;
|
||||||
|
let _ = resp.text().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,8 +124,19 @@ impl ClientBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 节点,支持以下三种格式:
|
||||||
|
///
|
||||||
|
/// 1. http://your-endpoint
|
||||||
|
/// 2. https://your-endpoint
|
||||||
|
/// 3. your-endpoint
|
||||||
pub fn endpoint<S: ToString>(mut self, value: S) -> ClientBuilder {
|
pub fn endpoint<S: ToString>(mut self, value: S) -> ClientBuilder {
|
||||||
self.config.set_endpoint(value.to_string());
|
let mut value = value.to_string();
|
||||||
|
if value.starts_with("https://") {
|
||||||
|
value = value.replace("https://", "");
|
||||||
|
}else if value.starts_with("http://") {
|
||||||
|
value = value.replace("http://","");
|
||||||
|
}
|
||||||
|
self.config.set_endpoint(value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +164,7 @@ impl ClientBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(self) -> Result<Client, ObsError> {
|
pub fn build(self) -> Result<Client, ObsError> {
|
||||||
let req_client = reqwest::ClientBuilder::new()
|
let req_client = reqwest::ClientBuilder::new()
|
||||||
.timeout(self.config.timeout())
|
.timeout(self.config.timeout())
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::provider::SecurityHolder;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SignatureType {
|
pub enum SignatureType {
|
||||||
V2,
|
V2,
|
||||||
|
@ -10,6 +7,34 @@ pub enum SignatureType {
|
||||||
OBS,
|
OBS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct SecurityHolder {
|
||||||
|
ak: String,
|
||||||
|
sk: String,
|
||||||
|
security_token: String,
|
||||||
|
}
|
||||||
|
impl SecurityHolder {
|
||||||
|
pub fn new(ak: String, sk: String, security_token: String) -> Self {
|
||||||
|
Self {
|
||||||
|
ak,
|
||||||
|
sk,
|
||||||
|
security_token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ak(&self) -> &str {
|
||||||
|
self.ak.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sk(&self) -> &str {
|
||||||
|
self.sk.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn security_token(&self) -> &str {
|
||||||
|
self.security_token.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub(crate) security_providers: Vec<SecurityHolder>,
|
pub(crate) security_providers: Vec<SecurityHolder>,
|
||||||
|
@ -25,13 +50,19 @@ impl Config {
|
||||||
self.security_providers.as_ref()
|
self.security_providers.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 暂时不支持自定义域名
|
||||||
pub fn canonicalized_url(&self, bucket_name: &str) -> String {
|
pub fn canonicalized_url(&self, bucket_name: &str, uri: &str) -> String {
|
||||||
if bucket_name.is_empty() {
|
if bucket_name.is_empty() {
|
||||||
String::from("/")
|
String::from("/")
|
||||||
} else {
|
} else {
|
||||||
match self.signature_type {
|
match self.signature_type {
|
||||||
SignatureType::V2 | SignatureType::OBS => format!("/{}/", bucket_name),
|
SignatureType::V2 | SignatureType::OBS => {
|
||||||
|
if uri.is_empty() {
|
||||||
|
format!("/{}/", bucket_name)
|
||||||
|
} else {
|
||||||
|
format!("/{}/{}", bucket_name, uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
SignatureType::V4 => String::from("/"),
|
SignatureType::V4 => String::from("/"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +92,6 @@ impl Config {
|
||||||
self.signature_type = signature_type;
|
self.signature_type = signature_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub(crate) fn timeout(&self) -> Duration {
|
pub(crate) fn timeout(&self) -> Duration {
|
||||||
self.timeout
|
self.timeout
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,5 @@ pub mod client;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod provider;
|
pub mod bucket;
|
||||||
|
pub mod model;
|
|
@ -1,76 +0,0 @@
|
||||||
pub trait SecurityProvider {
|
|
||||||
fn security(&self) -> SecurityHolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default,Clone)]
|
|
||||||
pub struct SecurityHolder {
|
|
||||||
ak: String,
|
|
||||||
sk: String,
|
|
||||||
security_token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SecurityHolder {
|
|
||||||
pub fn new(ak: String, sk: String, security_token: String) -> Self {
|
|
||||||
Self {
|
|
||||||
ak,
|
|
||||||
sk,
|
|
||||||
security_token,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ak(&self) -> &str {
|
|
||||||
self.ak.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sk(&self) -> &str {
|
|
||||||
self.sk.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn security_token(&self) -> &str {
|
|
||||||
self.security_token.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EnvSecurityProvider {
|
|
||||||
sh: SecurityHolder,
|
|
||||||
suffix: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EnvSecurityProvider {
|
|
||||||
pub fn new(suffix: String) -> Self {
|
|
||||||
let suffix = if !suffix.is_empty() {
|
|
||||||
format!("_{}", suffix)
|
|
||||||
} else {
|
|
||||||
suffix
|
|
||||||
};
|
|
||||||
Self {
|
|
||||||
sh: SecurityHolder::default(),
|
|
||||||
suffix,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SecurityProvider for EnvSecurityProvider {
|
|
||||||
fn security(&self) -> SecurityHolder {
|
|
||||||
let ak = format!(
|
|
||||||
"{}{}",
|
|
||||||
std::env::var("OBS_ACCESS_KEY_ID").unwrap_or_default(),
|
|
||||||
self.suffix.as_str()
|
|
||||||
);
|
|
||||||
let sk = format!(
|
|
||||||
"{}{}",
|
|
||||||
std::env::var("OBS_SECRET_ACCESS_KEY").unwrap_or_default(),
|
|
||||||
self.suffix.as_str()
|
|
||||||
);
|
|
||||||
let security_token = format!(
|
|
||||||
"{}{}",
|
|
||||||
std::env::var("OBS_SECURITY_TOKEN").unwrap_or_default(),
|
|
||||||
self.suffix.as_str()
|
|
||||||
);
|
|
||||||
SecurityHolder {
|
|
||||||
ak,
|
|
||||||
sk,
|
|
||||||
security_token,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
|
@ -0,0 +1,22 @@
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use huaweicloud_sdk_rust_obs::{client, error::ObsError};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_put_object() ->Result<(),ObsError> {
|
||||||
|
dotenvy::dotenv().unwrap();
|
||||||
|
|
||||||
|
let ak = env::var("OBS_AK").unwrap();
|
||||||
|
let sk = env::var("OBS_SK").unwrap();
|
||||||
|
let obs = client::Client::builder()
|
||||||
|
.endpoint("https://obs.ap-southeast-1.myhuaweicloud.com")
|
||||||
|
.security_provider(&ak, &sk) //ifree-test
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let object = include_bytes!("../testdata/test.jpeg");
|
||||||
|
obs.put_object("ifree-test", "obs-client-key.jpeg", object).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue