实现 put object
This commit is contained in:
parent
0bb6ed21f3
commit
cfa84d057a
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
/.env
|
||||
|
|
|
@ -13,4 +13,7 @@ urlencoding = "2"
|
|||
chrono = "0.4"
|
||||
hmac-sha1 = "^0.1"
|
||||
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 chrono::{TimeZone, Utc};
|
||||
use hmacsha1::hmac_sha1;
|
||||
use rustc_serialize::{base64, hex::ToHex};
|
||||
use std::collections::HashMap;
|
||||
use reqwest::header::{HeaderMap, HeaderValue, HeaderName};
|
||||
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 {
|
||||
fn signature(
|
||||
|
@ -19,10 +19,11 @@ pub trait Authorization {
|
|||
fn auth(
|
||||
&self,
|
||||
method: &str,
|
||||
bucket:&str,
|
||||
params: HashMap<String, String>,
|
||||
headers: HashMap<String, Vec<String>>,
|
||||
canonicalized_url: String,
|
||||
) -> Result<HashMap<String, String>, ObsError>;
|
||||
) -> Result<HeaderMap, ObsError>;
|
||||
}
|
||||
|
||||
impl Authorization for Client {
|
||||
|
@ -33,14 +34,17 @@ impl Authorization for Client {
|
|||
headers: HashMap<String, Vec<String>>,
|
||||
canonicalized_url: String,
|
||||
) -> Result<String, ObsError> {
|
||||
let attach_headers = attach_headers(headers, true);
|
||||
|
||||
let string_to_sign = vec![
|
||||
method,
|
||||
method, //HTTP-Verb
|
||||
"\n",
|
||||
&attach_headers(headers, true),
|
||||
&attach_headers, // Content-MD5 \n Content-Type \n
|
||||
"\n",
|
||||
&canonicalized_url,
|
||||
]
|
||||
.join("");
|
||||
|
||||
let security = self.security();
|
||||
match security {
|
||||
Some(s) => {
|
||||
|
@ -54,16 +58,33 @@ impl Authorization for Client {
|
|||
fn auth(
|
||||
&self,
|
||||
method: &str,
|
||||
bucket:&str,
|
||||
params: HashMap<String, String>,
|
||||
headers: HashMap<String, Vec<String>>,
|
||||
mut headers: HashMap<String, Vec<String>>,
|
||||
canonicalized_url: String,
|
||||
) -> Result<HashMap<String, String>, ObsError> {
|
||||
let sign = self.signature(method, params, headers, canonicalized_url)?;
|
||||
) -> Result<HeaderMap, ObsError> {
|
||||
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();
|
||||
match security {
|
||||
Some(s) => {
|
||||
|
||||
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)
|
||||
}
|
||||
None => Err(ObsError::Security),
|
||||
|
@ -72,11 +93,10 @@ impl Authorization for Client {
|
|||
}
|
||||
|
||||
fn prepare_host_and_date(
|
||||
mut headers: HashMap<String, Vec<String>>,
|
||||
host_name: String,
|
||||
headers: &mut HashMap<String, Vec<String>>,
|
||||
host_name: &str,
|
||||
is_v4: bool,
|
||||
) {
|
||||
headers.insert("Host".into(), vec![host_name]);
|
||||
if let Some(date) = headers.get("x-amz-date") {
|
||||
let mut flag = false;
|
||||
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>> {
|
||||
headers
|
||||
.into_iter()
|
||||
.map(|(key, values)| {
|
||||
(
|
||||
key,
|
||||
values
|
||||
.iter()
|
||||
.map(|v| urlencoding::encode(v).to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<String, Vec<String>>>()
|
||||
}
|
||||
// fn encode_headers(headers: HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
|
||||
// headers
|
||||
// .into_iter()
|
||||
// .map(|(key, values)| {
|
||||
// (
|
||||
// key,
|
||||
// values
|
||||
// .iter()
|
||||
// .map(|v| urlencoding::encode(v).to_string())
|
||||
// .collect::<Vec<String>>(),
|
||||
// )
|
||||
// })
|
||||
// .collect::<HashMap<String, Vec<String>>>()
|
||||
// }
|
||||
|
||||
fn signature_header(
|
||||
headers: HashMap<String, Vec<String>>,
|
||||
) -> (Vec<String>, HashMap<String, Vec<String>>) {
|
||||
let mut signed_headers = vec![];
|
||||
let mut headers2 = HashMap::with_capacity(headers.len());
|
||||
for (key, value) in headers {
|
||||
let key2 = key.trim().to_lowercase();
|
||||
if !key2.is_empty() {
|
||||
signed_headers.push(key2.clone());
|
||||
headers2.insert(key2, value);
|
||||
}
|
||||
}
|
||||
signed_headers.sort();
|
||||
(signed_headers, headers2)
|
||||
}
|
||||
// fn signature_header(
|
||||
// headers: HashMap<String, Vec<String>>,
|
||||
// ) -> (Vec<String>, HashMap<String, Vec<String>>) {
|
||||
// let mut signed_headers = vec![];
|
||||
// let mut headers2 = HashMap::with_capacity(headers.len());
|
||||
// for (key, value) in headers {
|
||||
// let key2 = key.trim().to_lowercase();
|
||||
// if !key2.is_empty() {
|
||||
// signed_headers.push(key2.clone());
|
||||
// headers2.insert(key2, value);
|
||||
// }
|
||||
// }
|
||||
// signed_headers.sort();
|
||||
// (signed_headers, headers2)
|
||||
// }
|
||||
|
||||
fn credential(ak: &str, region: &str, short_date: &str) -> (String, String) {
|
||||
let scope = format!("{}/{}/{}/{}", short_date, region, "s3", "aws4_request");
|
||||
// return fmt.Sprintf("%s/%s", ak, scope), scope
|
||||
(format!("{}/{}", ak, &scope), scope)
|
||||
}
|
||||
// fn credential(ak: &str, region: &str, short_date: &str) -> (String, String) {
|
||||
// let scope = format!("{}/{}/{}/{}", short_date, region, "s3", "aws4_request");
|
||||
// // return fmt.Sprintf("%s/%s", ak, scope), scope
|
||||
// (format!("{}/{}", ak, &scope), scope)
|
||||
// }
|
||||
|
||||
fn string_to_sign(
|
||||
keys: Vec<String>,
|
||||
|
@ -178,11 +198,11 @@ fn attach_headers(headers: HashMap<String, Vec<String>>, is_obs: bool) -> String
|
|||
.collect::<_>();
|
||||
for (key, value) in headers {
|
||||
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"
|
||||
|| _key == "content-type"
|
||||
|| _key == "date"
|
||||
|| _key.starts_with(prefixheader)
|
||||
|| _key.starts_with(prefix_header)
|
||||
{
|
||||
keys.push(_key.clone());
|
||||
_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 data_header = date_camel_header.to_lowercase();
|
||||
|
||||
if _headers.contains_key("Date")
|
||||
&& (_headers.contains_key(&data_header) || _headers.contains_key(date_camel_header))
|
||||
if (_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();
|
||||
|
@ -218,3 +237,10 @@ fn signature(string_to_sign: &str, sk: &str) -> Result<String, ObsError> {
|
|||
let hs = general_purpose::STANDARD.encode(hash);
|
||||
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::{
|
||||
config::{Config, SignatureType},
|
||||
error::ObsError,
|
||||
provider::{SecurityHolder},
|
||||
config::{Config, SignatureType, SecurityHolder},
|
||||
error::ObsError, auth::Authorization,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -15,6 +14,7 @@ pub struct Client {
|
|||
}
|
||||
|
||||
impl Client {
|
||||
/// endpoint 格式: https[http]://obs.cn-north-4.myhuaweicloud.com
|
||||
pub fn new<S:ToString>(
|
||||
access_key_id: S,
|
||||
secret_access_key: S,
|
||||
|
@ -51,6 +51,7 @@ impl Client {
|
|||
pub async fn do_action<T>(
|
||||
&self,
|
||||
method: Method,
|
||||
bucket_name:&str,
|
||||
uri: &str,
|
||||
with_headers: Option<HeaderMap>,
|
||||
body: T,
|
||||
|
@ -58,8 +59,10 @@ impl Client {
|
|||
where
|
||||
T: Into<Body>,
|
||||
{
|
||||
let url = format!("https://{}/{}", self.config().endpoint(), uri);
|
||||
let mut headers = HeaderMap::new();
|
||||
let url = format!("https://{}.{}/{}",bucket_name, self.config().endpoint(), uri);
|
||||
|
||||
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 {
|
||||
headers.extend(wh);
|
||||
}
|
||||
|
@ -74,6 +77,16 @@ impl Client {
|
|||
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
|
||||
}
|
||||
|
||||
/// 节点,支持以下三种格式:
|
||||
///
|
||||
/// 1. http://your-endpoint
|
||||
/// 2. https://your-endpoint
|
||||
/// 3. your-endpoint
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -140,7 +164,7 @@ impl ClientBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
fn build(self) -> Result<Client, ObsError> {
|
||||
pub fn build(self) -> Result<Client, ObsError> {
|
||||
let req_client = reqwest::ClientBuilder::new()
|
||||
.timeout(self.config.timeout())
|
||||
.build();
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::provider::SecurityHolder;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SignatureType {
|
||||
V2,
|
||||
|
@ -10,6 +7,34 @@ pub enum SignatureType {
|
|||
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)]
|
||||
pub struct Config {
|
||||
pub(crate) security_providers: Vec<SecurityHolder>,
|
||||
|
@ -25,13 +50,19 @@ impl Config {
|
|||
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() {
|
||||
String::from("/")
|
||||
} else {
|
||||
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("/"),
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +92,6 @@ impl Config {
|
|||
self.signature_type = signature_type;
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn timeout(&self) -> Duration {
|
||||
self.timeout
|
||||
}
|
||||
|
@ -69,4 +99,4 @@ impl Config {
|
|||
pub fn endpoint(&self) -> &str {
|
||||
self.endpoint.as_ref()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@ pub mod client;
|
|||
pub mod config;
|
||||
pub mod error;
|
||||
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