| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| use crate::raw::*; |
| use crate::*; |
| |
| /// Layer for replacing the default HTTP client with a custom one. |
| /// |
| /// This layer allows users to provide their own HTTP client implementation |
| /// by implementing the [`HttpFetch`] trait. This is useful when you need |
| /// to customize HTTP behavior, add authentication, use a different HTTP |
| /// client library, or apply custom middleware. |
| /// |
| /// # Examples |
| /// |
| /// ```no_run |
| /// use opendal::layers::HttpClientLayer; |
| /// use opendal::services; |
| /// use opendal::Operator; |
| /// use opendal::Result; |
| /// use opendal::raw::HttpClient; |
| /// |
| /// # fn main() -> Result<()> { |
| /// // Create a custom HTTP client |
| /// let custom_client = HttpClient::new()?; |
| /// |
| /// let op = Operator::new(services::S3::default())? |
| /// .layer(HttpClientLayer::new(custom_client)) |
| /// .finish(); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// # Custom HTTP Client Implementation |
| /// |
| /// ```no_run |
| /// use opendal::raw::{HttpFetch, HttpBody}; |
| /// use opendal::Buffer; |
| /// use http::{Request, Response}; |
| /// use opendal::Result; |
| /// |
| /// struct CustomHttpClient { |
| /// // Your custom HTTP client fields |
| /// } |
| /// |
| /// impl HttpFetch for CustomHttpClient { |
| /// async fn fetch(&self, req: Request<Buffer>) -> Result<Response<HttpBody>> { |
| /// // Your custom HTTP client implementation |
| /// todo!() |
| /// } |
| /// } |
| /// ``` |
| #[derive(Clone)] |
| pub struct HttpClientLayer { |
| client: HttpClient, |
| } |
| |
| impl HttpClientLayer { |
| /// Create a new `HttpClientLayer` with the given HTTP client. |
| /// |
| /// # Arguments |
| /// |
| /// * `client` - The HTTP client to use for all HTTP requests |
| /// |
| /// # Examples |
| /// |
| /// ```no_run |
| /// use opendal::layers::HttpClientLayer; |
| /// use opendal::raw::HttpClient; |
| /// use opendal::Result; |
| /// |
| /// # fn main() -> Result<()> { |
| /// let client = HttpClient::new()?; |
| /// let layer = HttpClientLayer::new(client); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub fn new(client: HttpClient) -> Self { |
| Self { client } |
| } |
| } |
| |
| impl<A: Access> Layer<A> for HttpClientLayer { |
| type LayeredAccess = HttpClientAccessor<A>; |
| |
| fn layer(&self, inner: A) -> Self::LayeredAccess { |
| let info = inner.info(); |
| |
| // Replace the HTTP client with our custom one |
| info.update_http_client(|_| self.client.clone()); |
| |
| HttpClientAccessor { inner } |
| } |
| } |
| |
| /// The accessor returned by [`HttpClientLayer`]. |
| /// |
| /// This accessor simply passes through all operations to the inner accessor, |
| /// while the HTTP client replacement is handled at the layer level. |
| #[derive(Debug, Clone)] |
| pub struct HttpClientAccessor<A: Access> { |
| inner: A, |
| } |
| |
| impl<A: Access> LayeredAccess for HttpClientAccessor<A> { |
| type Inner = A; |
| type Reader = A::Reader; |
| type Writer = A::Writer; |
| type Lister = A::Lister; |
| type Deleter = A::Deleter; |
| |
| fn inner(&self) -> &Self::Inner { |
| &self.inner |
| } |
| |
| async fn create_dir(&self, path: &str, args: OpCreateDir) -> Result<RpCreateDir> { |
| self.inner.create_dir(path, args).await |
| } |
| |
| async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> { |
| self.inner.read(path, args).await |
| } |
| |
| async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> { |
| self.inner.write(path, args).await |
| } |
| |
| async fn copy(&self, from: &str, to: &str, args: OpCopy) -> Result<RpCopy> { |
| self.inner.copy(from, to, args).await |
| } |
| |
| async fn rename(&self, from: &str, to: &str, args: OpRename) -> Result<RpRename> { |
| self.inner.rename(from, to, args).await |
| } |
| |
| async fn stat(&self, path: &str, args: OpStat) -> Result<RpStat> { |
| self.inner.stat(path, args).await |
| } |
| |
| async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> { |
| self.inner.delete().await |
| } |
| |
| async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> { |
| self.inner.list(path, args).await |
| } |
| |
| async fn presign(&self, path: &str, args: OpPresign) -> Result<RpPresign> { |
| self.inner.presign(path, args).await |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::services; |
| |
| #[tokio::test] |
| async fn test_http_client_layer() { |
| let layer = HttpClientLayer::new(HttpClient::new().unwrap()); |
| let op = Operator::new(services::Memory::default()) |
| .unwrap() |
| .layer(layer) |
| .finish(); |
| |
| // Basic test to ensure the layer doesn't break functionality |
| op.write("test", "data").await.unwrap(); |
| let content = op.read("test").await.unwrap(); |
| assert_eq!(content.to_bytes(), "data"); |
| } |
| |
| #[tokio::test] |
| async fn test_http_client_layer_with_fetcher() { |
| struct MockFetcher; |
| |
| impl HttpFetch for MockFetcher { |
| async fn fetch(&self, _req: http::Request<Buffer>) -> Result<http::Response<HttpBody>> { |
| // For testing purposes, we just return an error since Memory service doesn't use HTTP |
| Err(Error::new(ErrorKind::Unexpected, "mock fetcher")) |
| } |
| } |
| |
| let client = HttpClient::with(MockFetcher); |
| let layer = HttpClientLayer::new(client); |
| let _op = Operator::new(services::Memory::default()) |
| .unwrap() |
| .layer(layer) |
| .finish(); |
| |
| // The layer should be created successfully even with a custom fetcher |
| } |
| } |