object_storage_lib/
lib.rs1mod action;
2mod config;
3mod context;
4
5use async_trait::async_trait;
6pub use config::Config;
7pub use config::Oss;
8use context::Context;
9use headers::{ContentType, HeaderMapExt};
10use hyper;
11use hyper::body::Incoming;
12use hyper::{Request, Response, StatusCode};
13use object_storage_sdk as sdk;
14use sdk::storage::GetReq;
15use std::collections::HashMap;
16use std::net::SocketAddr;
17use std::sync::Arc;
18use tihu::LightString;
19use tihu_native::http::Body;
20use tihu_native::http::BoxBody;
21use tihu_native::http::HttpHandler;
22use tihu_native::http::RequestData;
23
24pub struct GetMapping {
25 path_prefix: LightString, key_prefix: LightString, }
28
29pub struct UploadMapping {
30 path: LightString, key_prefix: LightString, }
33
34pub struct OssHandler {
35 context: Arc<Context>,
36 get_mapping: Vec<GetMapping>,
37 upload_mapping: Vec<UploadMapping>,
38 delete_path: Option<LightString>,
39 namespaces: Vec<LightString>,
40}
41
42impl OssHandler {
43 pub async fn try_init_from_config(
44 config: Config,
45 adjust_error_code: impl Fn(i32) -> i32 + Send + Sync + 'static,
46 ) -> Result<Self, anyhow::Error> {
47 let context = Context::try_init_from_config(config, adjust_error_code)?;
48 let context = Arc::new(context);
49 return Ok(Self {
50 context: context,
51 get_mapping: Vec::new(),
52 upload_mapping: Vec::new(),
53 delete_path: None,
54 namespaces: Vec::new(),
55 });
56 }
57
58 pub fn add_get_mapping(
59 &mut self,
60 path_prefix: LightString,
61 key_prefix: LightString,
62 ) -> &mut Self {
63 self.get_mapping.push(GetMapping {
64 path_prefix: path_prefix.clone(),
65 key_prefix: key_prefix,
66 });
67 self.namespaces.push(path_prefix);
68 return self;
69 }
70
71 pub fn add_upload_mapping(&mut self, path: LightString, key_prefix: LightString) -> &mut Self {
72 self.upload_mapping.push(UploadMapping {
73 path: path.clone(),
74 key_prefix: key_prefix,
75 });
76 self.namespaces.push(path);
77 return self;
78 }
79
80 pub fn set_delete_path(&mut self, delete_path: LightString) -> &mut Self {
81 self.delete_path.replace(delete_path);
82 return self;
83 }
84}
85
86#[async_trait]
87impl HttpHandler for OssHandler {
88 fn namespace(&self) -> &[LightString] {
89 return &self.namespaces;
90 }
91 async fn handle(
92 &self,
93 request: Request<Incoming>,
94 _remote_addr: SocketAddr,
95 _request_data: &mut RequestData,
96 prefix: Option<&str>,
97 ) -> Result<Response<BoxBody>, anyhow::Error> {
98 let prefix = prefix.unwrap_or("");
99 let (_, route) = request.uri().path().split_at(prefix.len());
100 for get_mapping in &self.get_mapping {
101 let path_prefix = &get_mapping.path_prefix;
102 let key_prefix = &get_mapping.key_prefix;
103 if route.starts_with(path_prefix.as_ref()) {
104 let key = format!(
105 "{}{}",
106 key_prefix,
107 String::from_utf8_lossy(&route.as_bytes()[path_prefix.len()..])
108 );
109 if key.is_empty() {
110 let mut response = text_response("file not found!");
111 *response.status_mut() = StatusCode::NOT_FOUND;
112 return Ok(response.map(From::from));
113 } else {
114 let query = request
115 .uri()
116 .query()
117 .map(|query| {
118 form_urlencoded::parse(query.as_bytes()).collect::<HashMap<_, _>>()
119 })
120 .unwrap_or_default();
121 let filename = query.get("filename").map(|filename| filename.as_ref());
122 let response =
123 action::storage::get(self.context.clone(), GetReq { key: key }, filename)
124 .await?;
125 return Ok(response.map(From::from));
126 }
127 }
128 }
129
130 for upload_mapping in &self.upload_mapping {
131 let path = &upload_mapping.path;
132 let key_prefix = &upload_mapping.key_prefix;
133 if route == path.as_ref() {
134 let hash = request.headers().get("X-Hash");
135 let hash = hash
136 .map(|hash| hash.to_str().map(|hash| hash.to_string()).ok())
137 .flatten();
138 if let Some(hash) = hash {
139 let response =
140 action::storage::put(self.context.clone(), request, key_prefix, hash)
141 .await?;
142 return Ok(response.map(From::from));
143 } else {
144 let response = Response::builder()
145 .status(StatusCode::BAD_REQUEST)
146 .body(Body::from("BAD REQUEST"))
147 .unwrap();
148 return Ok(response.map(From::from));
149 }
150 }
151 }
152
153 if let Some(delete_path) = self.delete_path.as_ref() {
154 if route == delete_path.as_ref() {
155 let hash = request.headers().get("X-Hash");
156 let hash = hash
157 .map(|hash| hash.to_str().map(|hash| hash.to_string()).ok())
158 .flatten();
159 if let Some(hash) = hash {
160 let response =
161 action::storage::delete(self.context.clone(), request, hash).await?;
162 return Ok(response.map(From::from));
163 } else {
164 let response = Response::builder()
165 .status(StatusCode::BAD_REQUEST)
166 .body(Body::from("BAD REQUEST"))
167 .unwrap();
168 return Ok(response.map(From::from));
169 }
170 }
171 }
172 let mut response = text_response("file not found!");
173 *response.status_mut() = StatusCode::NOT_FOUND;
174 return Ok(response.map(From::from));
175 }
176}
177
178fn text_response<T: Into<Body>>(body: T) -> Response<Body> {
179 let mut response = Response::new(body.into());
180 response
181 .headers_mut()
182 .typed_insert(ContentType::text_utf8());
183 return response;
184}