any_object_storage/
oss.rs

1use crate::auth::AuthAPI;
2use crate::request::RequestBuilder;
3use chrono::{DateTime, Utc};
4use reqwest::header::{HeaderMap, InvalidHeaderValue, AUTHORIZATION, CONTENT_TYPE, DATE};
5
6/// OSS配置
7#[derive(Debug, Clone)]
8pub struct OSS {
9    key_id: String,
10    key_secret: String,
11    endpoint: String,
12    bucket: String,
13}
14
15unsafe impl Send for OSS {}
16
17unsafe impl Sync for OSS {}
18
19pub trait OSSInfo {
20    fn endpoint(&self) -> String;
21    fn bucket(&self) -> String;
22    fn key_id(&self) -> String;
23    fn key_secret(&self) -> String;
24}
25
26pub trait API {
27    fn key_urlencode<S: AsRef<str>>(&self, key: S) -> String {
28        key.as_ref()
29            .split("/")
30            .map(|x| urlencoding::encode(x))
31            .collect::<Vec<_>>()
32            .join("/")
33    }
34
35    fn format_key<S: AsRef<str>>(&self, key: S) -> String {
36        let key = key.as_ref();
37        if key.starts_with("/") {
38            key.to_string()
39        } else {
40            format!("/{}", key)
41        }
42    }
43
44    fn format_oss_resource_str<S: AsRef<str>>(&self, bucket: S, key: S) -> String;
45}
46
47impl OSSInfo for OSS {
48    fn endpoint(&self) -> String {
49        self.endpoint.clone()
50    }
51    fn bucket(&self) -> String {
52        self.bucket.clone()
53    }
54
55    fn key_id(&self) -> String {
56        self.key_id.clone()
57    }
58
59    fn key_secret(&self) -> String {
60        self.key_secret.clone()
61    }
62}
63
64impl API for OSS {
65    fn format_oss_resource_str<S: AsRef<str>>(&self, bucket: S, key: S) -> String {
66        let bucket = bucket.as_ref();
67        if bucket == "" {
68            format!("/{}", bucket)
69        } else {
70            format!("/{}{}", bucket, key.as_ref())
71        }
72    }
73}
74
75impl<'a> OSS {
76    pub fn from_env() -> Self {
77        let key_id = std::env::var("OSS_KEY_ID").expect("OSS_KEY_ID not found");
78        let key_secret = std::env::var("OSS_KEY_SECRET").expect("OSS_KEY_SECRET not found");
79        let endpoint = std::env::var("OSS_ENDPOINT").expect("OSS_ENDPOINT not found");
80        let bucket = std::env::var("OSS_BUCKET").expect("OSS_BUCKET not found");
81        OSS::new(key_id, key_secret, endpoint, bucket)
82    }
83
84    #[cfg(feature = "debug-print")]
85    pub fn open_debug(&self) {
86        std::env::set_var("RUST_LOG", "oss=debug");
87        tracing_subscriber::fmt()
88            .with_max_level(tracing::Level::DEBUG)
89            .with_line_number(true)
90            .init();
91    }
92    #[cfg(not(feature = "debug-print"))]
93    pub fn open_debug(&self) {}
94    pub fn new<S: Into<String>>(key_id: S, key_secret: S, endpoint: S, bucket: S) -> Self {
95        OSS {
96            key_id: key_id.into(),
97            key_secret: key_secret.into(),
98            endpoint: endpoint.into(),
99            bucket: bucket.into(),
100        }
101    }
102
103    pub fn format_host<S: AsRef<str>>(&self, bucket: S, key: S, build: &RequestBuilder) -> String {
104        let key = if key.as_ref().starts_with("/") {
105            key.as_ref().to_string()
106        } else {
107            format!("/{}", key.as_ref())
108        };
109        if let Some(cdn) = &build.cdn {
110            format!("{}{}", cdn, key,)
111        } else {
112            if self.endpoint().starts_with("https") {
113                format!(
114                    "https://{}.{}{}",
115                    bucket.as_ref(),
116                    self.endpoint().replacen("https://", "", 1),
117                    key,
118                )
119            } else {
120                format!(
121                    "http://{}.{}{}",
122                    bucket.as_ref(),
123                    self.endpoint().replacen("http://", "", 1),
124                    key,
125                )
126            }
127        }
128    }
129
130    pub fn build_request<S: AsRef<str>>(
131        &self,
132        key: S,
133        build: RequestBuilder,
134    ) -> Result<(String, HeaderMap), InvalidHeaderValue> {
135        let mut build = build.clone();
136        let host = self.format_host(self.bucket(), key.as_ref().to_string(), &build);
137        let mut header = HeaderMap::new();
138        let date = self.date();
139        header.insert(DATE, date.parse()?);
140        build.headers.insert(DATE.to_string(), date);
141        let key = key.as_ref();
142        let authorization = self.oss_sign(key, &build);
143        if let Some(content_type) = build.content_type {
144            header.insert(CONTENT_TYPE, content_type.parse()?);
145        }
146        header.insert(AUTHORIZATION, authorization.parse()?);
147        Ok((host, header))
148    }
149    pub fn date(&self) -> String {
150        let now: DateTime<Utc> = Utc::now();
151        now.format("%a, %d %b %Y %T GMT").to_string()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use crate::error::OssError;
158    use std::io::Read;
159
160    fn open_file(file_name: &str) -> Result<String, OssError> {
161        let mut file = std::fs::File::open(file_name)?;
162        let mut contents = String::new();
163        file.read_to_string(&mut contents)?;
164        Ok(contents)
165    }
166
167    #[test]
168    fn test_read_file() {
169        open_file("a").unwrap();
170    }
171}