opendal/services/http/
backend.rs1use std::fmt::Debug;
19use std::sync::Arc;
20
21use http::Response;
22use http::StatusCode;
23use log::debug;
24
25use super::HTTP_SCHEME;
26use super::config::HttpConfig;
27use super::core::HttpCore;
28use super::error::parse_error;
29use crate::raw::*;
30use crate::*;
31
32#[doc = include_str!("docs.md")]
34#[derive(Default)]
35pub struct HttpBuilder {
36 pub(super) config: HttpConfig,
37
38 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
39 pub(super) http_client: Option<HttpClient>,
40}
41
42impl Debug for HttpBuilder {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("HttpBuilder")
45 .field("config", &self.config)
46 .finish_non_exhaustive()
47 }
48}
49
50impl HttpBuilder {
51 pub fn endpoint(mut self, endpoint: &str) -> Self {
55 self.config.endpoint = if endpoint.is_empty() {
56 None
57 } else {
58 Some(endpoint.to_string())
59 };
60
61 self
62 }
63
64 pub fn username(mut self, username: &str) -> Self {
68 if !username.is_empty() {
69 self.config.username = Some(username.to_owned());
70 }
71 self
72 }
73
74 pub fn password(mut self, password: &str) -> Self {
78 if !password.is_empty() {
79 self.config.password = Some(password.to_owned());
80 }
81 self
82 }
83
84 pub fn token(mut self, token: &str) -> Self {
88 if !token.is_empty() {
89 self.config.token = Some(token.to_string());
90 }
91 self
92 }
93
94 pub fn root(mut self, root: &str) -> Self {
96 self.config.root = if root.is_empty() {
97 None
98 } else {
99 Some(root.to_string())
100 };
101
102 self
103 }
104
105 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
112 #[allow(deprecated)]
113 pub fn http_client(mut self, client: HttpClient) -> Self {
114 self.http_client = Some(client);
115 self
116 }
117}
118
119impl Builder for HttpBuilder {
120 type Config = HttpConfig;
121
122 fn build(self) -> Result<impl Access> {
123 debug!("backend build started: {:?}", &self);
124
125 let endpoint = match &self.config.endpoint {
126 Some(v) => v,
127 None => {
128 return Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
129 .with_context("service", HTTP_SCHEME));
130 }
131 };
132
133 let root = normalize_root(&self.config.root.unwrap_or_default());
134 debug!("backend use root {root}");
135
136 let mut auth = None;
137 if let Some(username) = &self.config.username {
138 auth = Some(format_authorization_by_basic(
139 username,
140 self.config.password.as_deref().unwrap_or_default(),
141 )?);
142 }
143 if let Some(token) = &self.config.token {
144 auth = Some(format_authorization_by_bearer(token)?)
145 }
146
147 let info = AccessorInfo::default();
148 info.set_scheme(HTTP_SCHEME)
149 .set_root(&root)
150 .set_native_capability(Capability {
151 stat: true,
152 stat_with_if_match: true,
153 stat_with_if_none_match: true,
154
155 read: true,
156
157 read_with_if_match: true,
158 read_with_if_none_match: true,
159
160 presign: auth.is_none(),
161 presign_read: auth.is_none(),
162 presign_stat: auth.is_none(),
163
164 shared: true,
165
166 ..Default::default()
167 });
168
169 #[allow(deprecated)]
171 if let Some(client) = self.http_client {
172 info.update_http_client(|_| client);
173 }
174
175 let accessor_info = Arc::new(info);
176
177 let core = Arc::new(HttpCore {
178 info: accessor_info,
179 endpoint: endpoint.to_string(),
180 root,
181 authorization: auth,
182 });
183
184 Ok(HttpBackend { core })
185 }
186}
187
188#[derive(Clone, Debug)]
190pub struct HttpBackend {
191 core: Arc<HttpCore>,
192}
193
194impl Access for HttpBackend {
195 type Reader = HttpBody;
196 type Writer = ();
197 type Lister = ();
198 type Deleter = ();
199
200 fn info(&self) -> Arc<AccessorInfo> {
201 self.core.info.clone()
202 }
203
204 async fn stat(&self, path: &str, args: OpStat) -> Result<RpStat> {
205 if path == "/" {
207 return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
208 }
209
210 let resp = self.core.http_head(path, &args).await?;
211
212 let status = resp.status();
213
214 match status {
215 StatusCode::OK => parse_into_metadata(path, resp.headers()).map(RpStat::new),
216 StatusCode::NOT_FOUND | StatusCode::FORBIDDEN if path.ends_with('/') => {
219 Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
220 }
221 _ => Err(parse_error(resp)),
222 }
223 }
224
225 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
226 let resp = self.core.http_get(path, args.range(), &args).await?;
227
228 let status = resp.status();
229
230 match status {
231 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
232 Ok((RpRead::default(), resp.into_body()))
233 }
234 _ => {
235 let (part, mut body) = resp.into_parts();
236 let buf = body.to_buffer().await?;
237 Err(parse_error(Response::from_parts(part, buf)))
238 }
239 }
240 }
241
242 async fn presign(&self, path: &str, args: OpPresign) -> Result<RpPresign> {
243 if self.core.has_authorization() {
244 return Err(Error::new(
245 ErrorKind::Unsupported,
246 "Http doesn't support presigned request on backend with authorization",
247 ));
248 }
249
250 let req = match args.operation() {
251 PresignOperation::Stat(v) => self.core.http_head_request(path, v)?,
252 PresignOperation::Read(v) => {
253 self.core.http_get_request(path, BytesRange::default(), v)?
254 }
255 _ => {
256 return Err(Error::new(
257 ErrorKind::Unsupported,
258 "Http doesn't support presigned write",
259 ));
260 }
261 };
262
263 let (parts, _) = req.into_parts();
264
265 Ok(RpPresign::new(PresignedRequest::new(
266 parts.method,
267 parts.uri,
268 parts.headers,
269 )))
270 }
271}