This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 82f23b48 feat: add http if_none_match support (#1995)
82f23b48 is described below

commit 82f23b482fb548685d05f96f53f8640868292277
Author: Yingjie Qiao <[email protected]>
AuthorDate: Fri Apr 14 19:12:22 2023 +0800

    feat: add http if_none_match support (#1995)
    
    * feat: add http if_none_match support
    
    * fix lint & check
---
 core/src/services/http/backend.rs   | 84 ++++++++++++++++++++++++++++++--
 core/src/types/operator/operator.rs | 96 +++++++++++++++++++++++++++++++++++--
 2 files changed, 171 insertions(+), 9 deletions(-)

diff --git a/core/src/services/http/backend.rs 
b/core/src/services/http/backend.rs
index 39fb10f5..c4c0bdbc 100644
--- a/core/src/services/http/backend.rs
+++ b/core/src/services/http/backend.rs
@@ -263,7 +263,9 @@ impl Accessor for HttpBackend {
     }
 
     async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, 
Self::Reader)> {
-        let resp = self.http_get(path, args.range()).await?;
+        let resp = self
+            .http_get(path, args.range(), args.if_none_match())
+            .await?;
 
         let status = resp.status();
 
@@ -276,13 +278,13 @@ impl Accessor for HttpBackend {
         }
     }
 
-    async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
+    async fn stat(&self, path: &str, args: OpStat) -> Result<RpStat> {
         // Stat root always returns a DIR.
         if path == "/" {
             return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
         }
 
-        let resp = self.http_head(path).await?;
+        let resp = self.http_head(path, args.if_none_match()).await?;
 
         let status = resp.status();
 
@@ -299,13 +301,22 @@ impl Accessor for HttpBackend {
 }
 
 impl HttpBackend {
-    async fn http_get(&self, path: &str, range: BytesRange) -> 
Result<Response<IncomingAsyncBody>> {
+    async fn http_get(
+        &self,
+        path: &str,
+        range: BytesRange,
+        if_none_match: Option<&str>,
+    ) -> Result<Response<IncomingAsyncBody>> {
         let p = build_rooted_abs_path(&self.root, path);
 
         let url = format!("{}{}", self.endpoint, percent_encode_path(&p));
 
         let mut req = Request::get(&url);
 
+        if let Some(if_none_match) = if_none_match {
+            req = req.header(http::header::IF_NONE_MATCH, if_none_match);
+        }
+
         if let Some(auth) = &self.authorization {
             req = req.header(header::AUTHORIZATION, auth.clone())
         }
@@ -321,13 +332,21 @@ impl HttpBackend {
         self.client.send(req).await
     }
 
-    async fn http_head(&self, path: &str) -> 
Result<Response<IncomingAsyncBody>> {
+    async fn http_head(
+        &self,
+        path: &str,
+        if_none_match: Option<&str>,
+    ) -> Result<Response<IncomingAsyncBody>> {
         let p = build_rooted_abs_path(&self.root, path);
 
         let url = format!("{}{}", self.endpoint, percent_encode_path(&p));
 
         let mut req = Request::head(&url);
 
+        if let Some(if_none_match) = if_none_match {
+            req = req.header(http::header::IF_NONE_MATCH, if_none_match);
+        }
+
         if let Some(auth) = &self.authorization {
             req = req.header(header::AUTHORIZATION, auth.clone())
         }
@@ -345,6 +364,7 @@ mod tests {
     use anyhow::Result;
     use wiremock::matchers::basic_auth;
     use wiremock::matchers::bearer_token;
+    use wiremock::matchers::headers;
     use wiremock::matchers::method;
     use wiremock::matchers::path;
     use wiremock::Mock;
@@ -461,4 +481,58 @@ mod tests {
         assert_eq!(bs.content_length(), 128);
         Ok(())
     }
+
+    #[tokio::test]
+    async fn test_read_with() -> Result<()> {
+        let _ = env_logger::builder().is_test(true).try_init();
+
+        let mock_server = MockServer::start().await;
+        Mock::given(method("GET"))
+            .and(path("/hello"))
+            .and(headers("if-none-match", vec!["*"]))
+            .respond_with(
+                ResponseTemplate::new(200)
+                    .insert_header("content-length", "13")
+                    .set_body_string("Hello, World!"),
+            )
+            .mount(&mock_server)
+            .await;
+
+        let mut builder = HttpBuilder::default();
+        builder.endpoint(&mock_server.uri());
+        builder.root("/");
+        let op = Operator::new(builder)?.finish();
+
+        let match_bs = op
+            .read_with("hello", OpRead::new().with_if_none_match("*"))
+            .await?;
+        assert_eq!(match_bs, b"Hello, World!");
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_stat_with() -> Result<()> {
+        let _ = env_logger::builder().is_test(true).try_init();
+
+        let mock_server = MockServer::start().await;
+        Mock::given(method("HEAD"))
+            .and(path("/hello"))
+            .and(headers("if-none-match", vec!["*"]))
+            
.respond_with(ResponseTemplate::new(200).insert_header("content-length", "128"))
+            .mount(&mock_server)
+            .await;
+
+        let mut builder = HttpBuilder::default();
+        builder.endpoint(&mock_server.uri());
+        builder.root("/");
+        let op = Operator::new(builder)?.finish();
+        let bs = op
+            .stat_with("hello", OpStat::new().with_if_none_match("*"))
+            .await?;
+
+        assert_eq!(bs.mode(), EntryMode::FILE);
+        assert_eq!(bs.content_length(), 128);
+        Ok(())
+    }
 }
diff --git a/core/src/types/operator/operator.rs 
b/core/src/types/operator/operator.rs
index a85b963e..fed7e285 100644
--- a/core/src/types/operator/operator.rs
+++ b/core/src/types/operator/operator.rs
@@ -180,9 +180,45 @@ impl Operator {
     /// # }
     /// ```
     pub async fn stat(&self, path: &str) -> Result<Metadata> {
+        self.stat_with(path, OpStat::new()).await
+    }
+
+    /// Get current path's metadata **without cache** directly with extra 
options.
+    ///
+    /// # Notes
+    ///
+    /// Use `stat` if you:
+    ///
+    /// - Want detect the outside changes of path.
+    /// - Don't want to read from cached metadata.
+    ///
+    /// You may want to use `metadata` if you are working with entries
+    /// returned by [`Lister`]. It's highly possible that metadata
+    /// you want has already been cached.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use anyhow::Result;
+    /// # use futures::io;
+    /// # use opendal::Operator;
+    /// # use opendal::ops::OpStat;
+    /// use opendal::ErrorKind;
+    /// #
+    /// # #[tokio::main]
+    /// # async fn test(op: Operator) -> Result<()> {
+    /// if let Err(e) = op.stat_with("test", OpStat::new()).await {
+    ///     if e.kind() == ErrorKind::NotFound {
+    ///         println!("file not exist")
+    ///     }
+    /// }
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub async fn stat_with(&self, path: &str, args: OpStat) -> 
Result<Metadata> {
         let path = normalize_path(path);
 
-        let rp = self.inner().stat(&path, OpStat::new()).await?;
+        let rp = self.inner().stat(&path, args).await?;
         let meta = rp.into_metadata();
 
         Ok(meta)
@@ -382,6 +418,28 @@ impl Operator {
         self.range_read(path, ..).await
     }
 
+    /// Read the whole path into a bytes with extra options.
+    ///
+    /// This function will allocate a new bytes internally. For more precise 
memory control or
+    /// reading data lazily, please use [`Operator::reader`]
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use std::io::Result;
+    /// # use opendal::Operator;
+    /// # use opendal::ops::OpRead;
+    /// # use futures::TryStreamExt;
+    /// # #[tokio::main]
+    /// # async fn test(op: Operator) -> Result<()> {
+    /// let bs = op.read_with("path/to/file", OpRead::new()).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub async fn read_with(&self, path: &str, args: OpRead) -> Result<Vec<u8>> 
{
+        self.range_read_with(path, .., args).await
+    }
+
     /// Read the specified range of path into a bytes.
     ///
     /// This function will allocate a new bytes internally. For more precise 
memory control or
@@ -396,6 +454,7 @@ impl Operator {
     /// ```
     /// # use std::io::Result;
     /// # use opendal::Operator;
+    /// # use opendal::ops::OpRead;
     /// # use futures::TryStreamExt;
     /// # #[tokio::main]
     /// # async fn test(op: Operator) -> Result<()> {
@@ -404,6 +463,37 @@ impl Operator {
     /// # }
     /// ```
     pub async fn range_read(&self, path: &str, range: impl RangeBounds<u64>) 
-> Result<Vec<u8>> {
+        self.range_read_with(path, range, OpRead::new()).await
+    }
+
+    /// Read the specified range of path into a bytes with extra options..
+    ///
+    /// This function will allocate a new bytes internally. For more precise 
memory control or
+    /// reading data lazily, please use [`Operator::range_reader`]
+    ///
+    /// # Notes
+    ///
+    /// - The returning content's length may be smaller than the range 
specified.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use std::io::Result;
+    /// # use opendal::Operator;
+    /// # use opendal::ops::OpRead;
+    /// # use futures::TryStreamExt;
+    /// # #[tokio::main]
+    /// # async fn test(op: Operator) -> Result<()> {
+    /// let bs = op.range_read_with("path/to/file", 1024..2048, 
OpRead::new()).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub async fn range_read_with(
+        &self,
+        path: &str,
+        range: impl RangeBounds<u64>,
+        args: OpRead,
+    ) -> Result<Vec<u8>> {
         let path = normalize_path(path);
 
         if !validate_path(&path, EntryMode::FILE) {
@@ -417,9 +507,7 @@ impl Operator {
 
         let br = BytesRange::from(range);
 
-        let op = OpRead::new().with_range(br);
-
-        let (rp, mut s) = self.inner().read(&path, op).await?;
+        let (rp, mut s) = self.inner().read(&path, args.with_range(br)).await?;
 
         let length = rp.into_metadata().content_length() as usize;
         let mut buffer = Vec::with_capacity(length);

Reply via email to