opendal/services/http/
backend.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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/// HTTP Read-only service support like [Nginx](https://www.nginx.com/) and [Caddy](https://caddyserver.com/).
33#[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    /// Set endpoint for http backend.
52    ///
53    /// For example: `https://example.com`
54    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    /// set username for http backend
65    ///
66    /// default: no username
67    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    /// set password for http backend
75    ///
76    /// default: no password
77    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    /// set bearer token for http backend
85    ///
86    /// default: no access token
87    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    /// Set root path of http backend.
95    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    /// Specify the http client that used by this service.
106    ///
107    /// # Notes
108    ///
109    /// This API is part of OpenDAL's Raw API. `HttpClient` could be changed
110    /// during minor updates.
111    #[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 api here for compatibility
170        #[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/// Backend is used to serve `Accessor` support for http.
189#[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        // Stat root always returns a DIR.
206        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            // HTTP Server like nginx could return FORBIDDEN if auto-index
217            // is not enabled, we should ignore them.
218            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}