From 0264a20191d0d28173b9bb1cab6e076934e3f320 Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Mon, 24 Jun 2024 23:40:22 -0700
Subject: [PATCH 01/16] Upgrade rs-consul to latest http, hyper, opentelemetry

---
 .github/workflows/format-code.yml |  2 +-
 .github/workflows/lint.yml        |  2 +-
 .github/workflows/main.yml        |  4 +-
 .github/workflows/publish.yml     |  6 +-
 CHANGELOG.md                      |  9 +++
 Cargo.toml                        | 12 ++--
 rust-toolchain                    |  2 +-
 src/hyper_wrapper.rs              |  1 -
 src/lib.rs                        | 92 ++++++++++++++++++++-----------
 9 files changed, 84 insertions(+), 46 deletions(-)

diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml
index 1c5251f..42693b3 100644
--- a/.github/workflows/format-code.yml
+++ b/.github/workflows/format-code.yml
@@ -8,7 +8,7 @@ on:
 jobs:
   format-code:
     runs-on: "ubuntu-latest"
-    container: rust:1.77
+    container: rust:1.79
 
     steps:
       - name: Checkout the code on merge
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 297fb62..9e0d1d5 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -9,7 +9,7 @@ on:
 jobs:
   lint:
     runs-on: "ubuntu-latest"
-    container: rust:1.77
+    container: rust:1.79
 
     steps:
       - uses: actions/checkout@v2
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 911ca6f..5d87f53 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -13,7 +13,7 @@ jobs:
       matrix:
         features: ["", "--no-default-features --features rustls-native"]
     runs-on: "ubuntu-latest"
-    container: rust:1.74
+    container: rust:1.79
 
     steps:
       - uses: actions/checkout@v2
@@ -26,7 +26,7 @@ jobs:
       matrix:
         features: ["", "--no-default-features --features rustls-native"]
     runs-on: "ubuntu-latest"
-    container: rust:1.77
+    container: rust:1.79
     services:
       consul:
         image: consul:1.11.11
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 9841d9e..e2a6b3d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -6,7 +6,7 @@ on:
 jobs:
   test:
     runs-on: ubuntu-latest
-    container: rust:1.77
+    container: rust:1.79
     services:
       consul:
         image: consul:1.11.11
@@ -25,7 +25,7 @@ jobs:
 
   dry-run:
     runs-on: ubuntu-latest
-    container: rust:1.77
+    container: rust:1.79
 
     steps:
       - uses: actions/checkout@v2
@@ -36,7 +36,7 @@ jobs:
   publish:
     needs: [test, dry-run]
     runs-on: ubuntu-latest
-    container: rust:1.74
+    container: rust:1.79
     environment: crates-publish
 
     steps:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8afc7bc..be5de48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 ## Unreleased
 
+## 0.7.0 - 2024-06-25
+
+### Changed
+
+- `opentelemetry` updated to version `0.23` from `0.22`.
+- `http` updated to version `1.0` from `0.2`.
+- `hyper` updated to version `1.0` from `0.14`.
+- `hyper-rustls` updated to version `0.27` from `0.24`.
+
 ## 0.6.0 - 2024-04-01
 
 ### Changed
diff --git a/Cargo.toml b/Cargo.toml
index c8a9841..c1c15fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "rs-consul"
-version = "0.6.0"
+version = "0.7.0"
 authors = ["Roblox"]
 edition = "2021"
 description = "This crate provides access to a set of strongly typed apis to interact with consul (https://www.consul.io/)"
@@ -20,11 +20,13 @@ trace = ["dep:opentelemetry"]
 [dependencies]
 base64 = "0.22"
 futures = "0.3"
-http = "0.2"
-hyper = { version = "0.14", features = ["full"] }
-hyper-rustls = { version = "0.24" }
+http = "1"
+http-body-util = "0.1"
+hyper = { version = "1", features = ["full"] }
+hyper-rustls = { version = "0.27" }
+hyper-util = { version = "0.1", features = ["client", "client-legacy", "tokio", "http2"] }
 lazy_static = { version = "1", optional = true }
-opentelemetry = { version = "0.22", optional = true }
+opentelemetry = { version = "0.23", optional = true }
 prometheus = { version = "0.13", optional = true }
 quick-error = "2"
 serde = { version = "1.0", features = ["derive"] }
diff --git a/rust-toolchain b/rust-toolchain
index f23daf4..c408301 100644
--- a/rust-toolchain
+++ b/rust-toolchain
@@ -1 +1 @@
-1.77
\ No newline at end of file
+1.79
\ No newline at end of file
diff --git a/src/hyper_wrapper.rs b/src/hyper_wrapper.rs
index b51425f..9688ede 100644
--- a/src/hyper_wrapper.rs
+++ b/src/hyper_wrapper.rs
@@ -21,7 +21,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
  */
-#![cfg(feature = "trace")]
 use hyper::Version;
 use opentelemetry::{
     global::{BoxedSpan, BoxedTracer},
diff --git a/src/lib.rs b/src/lib.rs
index 343bd64..a8d21d0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27,12 +27,18 @@ SOFTWARE.
 //! This crate provides access to a set of strongly typed apis to interact with consul (https://www.consul.io/)
 #![deny(missing_docs)]
 
+use http_body_util::BodyExt;
 use std::collections::HashMap;
+use std::convert::Infallible;
 use std::time::{Duration, Instant};
 use std::{env, str::Utf8Error};
 
 use base64::Engine;
-use hyper::{body::Buf, client::HttpConnector, Body, Method};
+use http_body_util::combinators::BoxBody;
+use http_body_util::{Empty, Full};
+use hyper::body::Bytes;
+use hyper::{body::Buf, Method};
+use hyper_util::client::legacy::{connect::HttpConnector, Builder, Client};
 #[cfg(any(feature = "rustls-native", feature = "rustls-webpki"))]
 #[cfg(feature = "metrics")]
 use lazy_static::lazy_static;
@@ -66,7 +72,7 @@ quick_error! {
         /// The request was invalid and could not be converted into a proper http request.
         RequestError(err: http::Error) {}
         /// The consul server response could not be converted into a proper http response.
-        ResponseError(err: hyper::Error) {}
+        ResponseError(err: hyper_util::client::legacy::Error) {}
         /// The consul server response was invalid.
         InvalidResponse(err: hyper::Error) {}
         /// The consul server response could not be deserialized from json.
@@ -151,7 +157,7 @@ const GET_SESSION_METHOD_NAME: &str = "get_session";
 pub(crate) type Result<T> = std::result::Result<T, ConsulError>;
 
 /// The config necessary to create a new consul client.
-#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct Config {
     /// The address of the consul server. This must include the protocol to connect over eg. http or https.
     pub address: String,
@@ -160,7 +166,12 @@ pub struct Config {
 
     /// The hyper builder for the internal http client.
     #[serde(skip)]
-    pub hyper_builder: hyper::client::Builder,
+    #[serde(default = "default_builder")]
+    pub hyper_builder: hyper_util::client::legacy::Builder,
+}
+
+fn default_builder() -> Builder {
+    Builder::new(hyper_util::rt::TokioExecutor::new())
 }
 
 impl Config {
@@ -176,7 +187,7 @@ impl Config {
         Config {
             address: addr,
             token: Some(token),
-            hyper_builder: Default::default(),
+            hyper_builder: Builder::new(hyper_util::rt::TokioExecutor::new()),
         }
     }
 }
@@ -224,26 +235,26 @@ impl Drop for Lock<'_> {
 #[derive(Debug)]
 /// This struct defines the consul client and allows access to the consul api via method syntax.
 pub struct Consul {
-    https_client: hyper::Client<hyper_rustls::HttpsConnector<HttpConnector>, Body>,
+    https_client: Client<hyper_rustls::HttpsConnector<HttpConnector>, BoxBody<Bytes, Infallible>>,
     config: Config,
     #[cfg(feature = "trace")]
     tracer: BoxedTracer,
 }
 
-fn https_connector() -> hyper_rustls::HttpsConnector<HttpConnector> {
+fn https_connector() -> Result<hyper_rustls::HttpsConnector<HttpConnector>> {
     #[cfg(feature = "rustls-webpki")]
-    return hyper_rustls::HttpsConnectorBuilder::new()
+    return Ok(hyper_rustls::HttpsConnectorBuilder::new()
         .with_webpki_roots()
         .https_or_http()
         .enable_http1()
-        .build();
+        .build());
     #[allow(unreachable_code)]
     // Clippy doesn't realize if the feature is disabled, this code would execute.
-    hyper_rustls::HttpsConnectorBuilder::new()
-        .with_native_roots()
+    Ok(hyper_rustls::HttpsConnectorBuilder::new()
+        .with_native_roots()?
         .https_or_http()
         .enable_http1()
-        .build()
+        .build())
 }
 
 impl Consul {
@@ -251,15 +262,17 @@ impl Consul {
     /// This is the entry point for this crate.
     /// #Arguments:
     /// - [Config](consul::Config)
-    pub fn new(config: Config) -> Self {
-        let https = https_connector();
-        let https_client = config.hyper_builder.build::<_, hyper::Body>(https);
-        Consul {
+    pub fn new(config: Config) -> Result<Self> {
+        let https = https_connector()?;
+        let https_client = config
+            .hyper_builder
+            .build::<_, BoxBody<Bytes, Infallible>>(https);
+        Ok(Consul {
             https_client,
             config,
             #[cfg(feature = "trace")]
             tracer: global::tracer("consul"),
-        }
+        })
     }
 
     /// Reads a key from Consul's KV store. See the [consul docs](https://www.consul.io/api-docs/kv#read-key) for more information.
@@ -270,7 +283,12 @@ impl Consul {
     pub async fn read_key(&self, request: ReadKeyRequest<'_>) -> Result<Vec<ReadKeyResponse>> {
         let req = self.build_read_key_req(request);
         let (mut response_body, _index) = self
-            .execute_request(req, hyper::Body::empty(), None, READ_KEY_METHOD_NAME)
+            .execute_request(
+                req,
+                BoxBody::new(http_body_util::Empty::<Bytes>::new()),
+                None,
+                READ_KEY_METHOD_NAME,
+            )
             .await?;
         let bytes = response_body.copy_to_bytes(response_body.remaining());
         serde_json::from_slice::<Vec<ReadKeyResponse>>(&bytes)
@@ -310,7 +328,7 @@ impl Consul {
         let (mut response_body, index) = self
             .execute_request(
                 req,
-                Body::from(value),
+                BoxBody::new(Full::<Bytes>::new(Bytes::from(value))),
                 None,
                 CREATE_OR_UPDATE_KEY_METHOD_NAME,
             )
@@ -398,7 +416,12 @@ impl Consul {
         url = add_namespace_and_datacenter(url, request.namespace, request.datacenter);
         req = req.uri(url);
         let (mut response_body, _index) = self
-            .execute_request(req, hyper::Body::empty(), None, DELETE_KEY_METHOD_NAME)
+            .execute_request(
+                req,
+                BoxBody::new(Empty::<Bytes>::new()),
+                None,
+                DELETE_KEY_METHOD_NAME,
+            )
             .await?;
         let bytes = response_body.copy_to_bytes(response_body.remaining());
         serde_json::from_slice(&bytes).map_err(ConsulError::ResponseDeserializationFailed)
@@ -444,7 +467,7 @@ impl Consul {
             let (_watch, index) = self
                 .execute_request(
                     lock_index_req,
-                    hyper::Body::empty(),
+                    BoxBody::new(http_body_util::Empty::<Bytes>::new()),
                     None,
                     GET_LOCK_METHOD_NAME,
                 )
@@ -486,7 +509,7 @@ impl Consul {
         let payload = serde_json::to_string(payload).map_err(ConsulError::InvalidRequest)?;
         self.execute_request(
             request,
-            payload.into(),
+            BoxBody::new(Full::<Bytes>::new(Bytes::from(payload.into_bytes()))),
             Some(Duration::from_secs(5)),
             REGISTER_ENTITY_METHOD_NAME,
         )
@@ -506,7 +529,7 @@ impl Consul {
         let payload = serde_json::to_string(payload).map_err(ConsulError::InvalidRequest)?;
         self.execute_request(
             request,
-            payload.into(),
+            BoxBody::new(Full::<Bytes>::new(Bytes::from(payload.into_bytes()))),
             Some(Duration::from_secs(5)),
             DEREGISTER_ENTITY_METHOD_NAME,
         )
@@ -534,7 +557,7 @@ impl Consul {
         let (mut response_body, index) = self
             .execute_request(
                 request,
-                hyper::Body::empty(),
+                BoxBody::new(Empty::<Bytes>::new()),
                 query_opts.timeout,
                 GET_ALL_REGISTERED_SERVICE_NAMES_METHOD_NAME,
             )
@@ -566,7 +589,7 @@ impl Consul {
         let (mut response_body, index) = self
             .execute_request(
                 req,
-                hyper::Body::empty(),
+                BoxBody::new(Empty::<Bytes>::new()),
                 query_opts.timeout,
                 GET_SERVICE_NODES_METHOD_NAME,
             )
@@ -684,7 +707,9 @@ impl Consul {
         let (mut response_body, _index) = self
             .execute_request(
                 req,
-                hyper::Body::from(create_session_json),
+                BoxBody::new(Full::<Bytes>::new(Bytes::from(
+                    create_session_json.into_bytes(),
+                ))),
                 None,
                 GET_SESSION_METHOD_NAME,
             )
@@ -718,7 +743,7 @@ impl Consul {
     async fn execute_request<'a>(
         &self,
         req: http::request::Builder,
-        body: hyper::Body,
+        body: BoxBody<Bytes, Infallible>,
         duration: Option<std::time::Duration>,
         request_name: &str,
     ) -> Result<(Box<dyn Buf>, u64)> {
@@ -764,9 +789,12 @@ impl Consul {
         if status != hyper::StatusCode::OK {
             record_failure_metric_if_enabled(&method, request_name);
 
-            let mut response_body = hyper::body::aggregate(response.into_body())
+            let mut response_body = response
+                .into_body()
+                .collect()
                 .await
-                .map_err(|e| ConsulError::UnexpectedResponseCode(status, e.to_string()))?;
+                .map_err(|e| ConsulError::UnexpectedResponseCode(status, e.to_string()))?
+                .aggregate();
             let bytes = response_body.copy_to_bytes(response_body.remaining());
             let resp = std::str::from_utf8(&bytes)
                 .map_err(|e| ConsulError::UnexpectedResponseCode(status, e.to_string()))?;
@@ -780,7 +808,7 @@ impl Consul {
             None => 0,
         };
 
-        match hyper::body::aggregate(response.into_body()).await {
+        match response.into_body().collect().await.map(|b| b.aggregate()) {
             Ok(body) => Ok((Box::new(body), index)),
             Err(e) => {
                 record_failure_metric_if_enabled(&method, request_name);
@@ -974,7 +1002,7 @@ mod tests {
             .iter()
             .map(|sn| sn.service.address.clone())
             .collect();
-        let expected_addresses = vec![
+        let expected_addresses = [
             "1.1.1.1".to_string(),
             "2.2.2.2".to_string(),
             "3.3.3.3".to_string(),
@@ -1261,7 +1289,7 @@ mod tests {
 
     fn get_client() -> Consul {
         let conf: Config = Config::from_env();
-        Consul::new(conf)
+        Consul::new(conf).unwrap()
     }
 
     async fn create_or_update_key_value(

From f800ef56a295e4abe5b0a23f086e39aaef52841d Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Tue, 25 Jun 2024 21:48:30 -0700
Subject: [PATCH 02/16] Fix possible hang from hyper-util

---
 src/lib.rs | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/lib.rs b/src/lib.rs
index a8d21d0..a90305a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -171,7 +171,21 @@ pub struct Config {
 }
 
 fn default_builder() -> Builder {
+    // https://github.com/hyperium/hyper/issues/2312
     Builder::new(hyper_util::rt::TokioExecutor::new())
+        .pool_idle_timeout(std::time::Duration::from_millis(0))
+        .pool_max_idle_per_host(0)
+        .to_owned()
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        Config {
+            address: String::default(),
+            token: None,
+            hyper_builder: default_builder(),
+        }
+    }
 }
 
 impl Config {

From af0841618c1ea6fdd999e1a2d35bd7cc451c50e7 Mon Sep 17 00:00:00 2001
From: Kushagra Udai <112903599+kushudai@users.noreply.github.com>
Date: Mon, 1 Jul 2024 00:42:07 -0700
Subject: [PATCH 03/16] Update src/lib.rs

---
 src/lib.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/lib.rs b/src/lib.rs
index a90305a..058049e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -201,7 +201,7 @@ impl Config {
         Config {
             address: addr,
             token: Some(token),
-            hyper_builder: Builder::new(hyper_util::rt::TokioExecutor::new()),
+            hyper_builder: default_builder(),
         }
     }
 }

From c824e3adcabc9ab6d47c1b2aaadaff51e3952913 Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Mon, 1 Jul 2024 01:03:58 -0700
Subject: [PATCH 04/16] Remove unused dep

---
 Cargo.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index c1c15fb..7ec5934 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,6 @@ trace = ["dep:opentelemetry"]
 # keep this list sorted!
 [dependencies]
 base64 = "0.22"
-futures = "0.3"
 http = "1"
 http-body-util = "0.1"
 hyper = { version = "1", features = ["full"] }

From e5b9c23f1cffda101a9476396ffe765d47125ebb Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Fri, 19 Jul 2024 23:00:24 -0700
Subject: [PATCH 05/16] cleanup

---
 src/lib.rs | 44 +++++++++++++++++++++-----------------------
 1 file changed, 21 insertions(+), 23 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 54c1c20..765e3c3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -296,7 +296,7 @@ impl Consul {
     /// [ConsulError](consul::ConsulError) describes all possible errors returned by this api.
     pub async fn read_key(&self, request: ReadKeyRequest<'_>) -> Result<Vec<ReadKeyResponse>> {
         let req = self.build_read_key_req(request);
-        let (mut response_body, _index) = self
+        let (response_body, _index) = self
             .execute_request(
                 req,
                 BoxBody::new(http_body_util::Empty::<Bytes>::new()),
@@ -304,8 +304,7 @@ impl Consul {
                 READ_KEY_METHOD_NAME,
             )
             .await?;
-        let bytes = response_body.copy_to_bytes(response_body.remaining());
-        serde_json::from_slice::<Vec<ReadKeyResponse>>(&bytes)
+        serde_json::from_reader::<_, Vec<ReadKeyResponse>>(response_body.reader())
             .map_err(ConsulError::ResponseDeserializationFailed)?
             .into_iter()
             .map(|mut r| {
@@ -339,7 +338,7 @@ impl Consul {
     ) -> Result<(bool, u64)> {
         let url = self.build_create_or_update_url(request);
         let req = hyper::Request::builder().method(Method::PUT).uri(url);
-        let (mut response_body, index) = self
+        let (response_body, index) = self
             .execute_request(
                 req,
                 BoxBody::new(Full::<Bytes>::new(Bytes::from(value))),
@@ -347,9 +346,9 @@ impl Consul {
                 CREATE_OR_UPDATE_KEY_METHOD_NAME,
             )
             .await?;
-        let bytes = response_body.copy_to_bytes(response_body.remaining());
         Ok((
-            serde_json::from_slice(&bytes).map_err(ConsulError::ResponseDeserializationFailed)?,
+            serde_json::from_reader(response_body.reader())
+                .map_err(ConsulError::ResponseDeserializationFailed)?,
             index,
         ))
     }
@@ -429,7 +428,7 @@ impl Consul {
 
         url = add_namespace_and_datacenter(url, request.namespace, request.datacenter);
         req = req.uri(url);
-        let (mut response_body, _index) = self
+        let (response_body, _index) = self
             .execute_request(
                 req,
                 BoxBody::new(Empty::<Bytes>::new()),
@@ -437,8 +436,8 @@ impl Consul {
                 DELETE_KEY_METHOD_NAME,
             )
             .await?;
-        let bytes = response_body.copy_to_bytes(response_body.remaining());
-        serde_json::from_slice(&bytes).map_err(ConsulError::ResponseDeserializationFailed)
+        serde_json::from_reader(response_body.reader())
+            .map_err(ConsulError::ResponseDeserializationFailed)
     }
 
     /// Obtains a lock against a specific key in consul. See the [consul docs](https://learn.hashicorp.com/tutorials/consul/application-leader-elections?in=consul/developer-configuration) for more information.
@@ -568,7 +567,7 @@ impl Consul {
         let request = hyper::Request::builder()
             .method(Method::GET)
             .uri(uri.clone());
-        let (mut response_body, index) = self
+        let (response_body, index) = self
             .execute_request(
                 request,
                 BoxBody::new(Empty::<Bytes>::new()),
@@ -576,9 +575,9 @@ impl Consul {
                 GET_ALL_REGISTERED_SERVICE_NAMES_METHOD_NAME,
             )
             .await?;
-        let bytes = response_body.copy_to_bytes(response_body.remaining());
-        let service_tags_by_name = serde_json::from_slice::<HashMap<String, Vec<String>>>(&bytes)
-            .map_err(ConsulError::ResponseDeserializationFailed)?;
+        let service_tags_by_name =
+            serde_json::from_reader::<_, HashMap<String, Vec<String>>>(response_body.reader())
+                .map_err(ConsulError::ResponseDeserializationFailed)?;
 
         Ok(ResponseMeta {
             response: service_tags_by_name.keys().cloned().collect(),
@@ -600,7 +599,7 @@ impl Consul {
     ) -> Result<ResponseMeta<GetServiceNodesResponse>> {
         let query_opts = query_opts.unwrap_or_default();
         let req = self.build_get_service_nodes_req(request, &query_opts);
-        let (mut response_body, index) = self
+        let (response_body, index) = self
             .execute_request(
                 req,
                 BoxBody::new(Empty::<Bytes>::new()),
@@ -608,9 +607,9 @@ impl Consul {
                 GET_SERVICE_NODES_METHOD_NAME,
             )
             .await?;
-        let bytes = response_body.copy_to_bytes(response_body.remaining());
-        let response = serde_json::from_slice::<GetServiceNodesResponse>(&bytes)
-            .map_err(ConsulError::ResponseDeserializationFailed)?;
+        let response =
+            serde_json::from_reader::<_, GetServiceNodesResponse>(response_body.reader())
+                .map_err(ConsulError::ResponseDeserializationFailed)?;
         Ok(ResponseMeta { response, index })
     }
 
@@ -718,7 +717,7 @@ impl Consul {
         req = req.uri(url);
         let create_session_json =
             serde_json::to_string(&session_req).map_err(ConsulError::InvalidRequest)?;
-        let (mut response_body, _index) = self
+        let (response_body, _index) = self
             .execute_request(
                 req,
                 BoxBody::new(Full::<Bytes>::new(Bytes::from(
@@ -728,8 +727,8 @@ impl Consul {
                 GET_SESSION_METHOD_NAME,
             )
             .await?;
-        let bytes = response_body.copy_to_bytes(response_body.remaining());
-        serde_json::from_slice(&bytes).map_err(ConsulError::ResponseDeserializationFailed)
+        serde_json::from_reader(response_body.reader())
+            .map_err(ConsulError::ResponseDeserializationFailed)
     }
 
     fn build_get_service_nodes_req(
@@ -1027,10 +1026,9 @@ mod tests {
 
         let tags: Vec<String> = response
             .iter()
-            .map(|sn| sn.service.tags.clone().into_iter())
-            .flatten()
+            .flat_map(|sn| sn.service.tags.clone().into_iter())
             .collect();
-        let expected_tags = vec![
+        let expected_tags = [
             "first".to_string(),
             "second".to_string(),
             "third".to_string(),

From 9ffd39dc44a2e6a8e10bd2fc644cdd2807abe0a7 Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Fri, 19 Jul 2024 23:16:02 -0700
Subject: [PATCH 06/16] Upgrade test, changelog

---
 CHANGELOG.md |  4 +++-
 Cargo.toml   |  2 +-
 src/lib.rs   | 11 +++++++++--
 3 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index be5de48..3d40fb4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,10 +10,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 ### Changed
 
-- `opentelemetry` updated to version `0.23` from `0.22`.
+- `opentelemetry` updated to version `0.24` from `0.22`.
 - `http` updated to version `1.0` from `0.2`.
 - `hyper` updated to version `1.0` from `0.14`.
 - `hyper-rustls` updated to version `0.27` from `0.24`.
+- `get_service_nodes` now supports tags thanks to @gautamg795
+- `read_key` now also returns the index thanks to @badalex
 
 ## 0.6.0 - 2024-04-01
 
diff --git a/Cargo.toml b/Cargo.toml
index 7ec5934..31f3460 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,7 +25,7 @@ hyper = { version = "1", features = ["full"] }
 hyper-rustls = { version = "0.27" }
 hyper-util = { version = "0.1", features = ["client", "client-legacy", "tokio", "http2"] }
 lazy_static = { version = "1", optional = true }
-opentelemetry = { version = "0.23", optional = true }
+opentelemetry = { version = "0.24", optional = true }
 prometheus = { version = "0.13", optional = true }
 quick-error = "2"
 serde = { version = "1.0", features = ["derive"] }
diff --git a/src/lib.rs b/src/lib.rs
index 246e662..1de0f00 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -961,8 +961,15 @@ mod tests {
         let res = create_or_update_key_value(&consul, key, string_value).await;
         assert_expected_result_with_index(res);
 
-        let res = read_key(&consul, key).await;
-        verify_single_value_matches(res, string_value);
+        let res = read_key(&consul, key).await.unwrap();
+        let index = res.index;
+        verify_single_value_matches(Ok(res), string_value);
+
+        let res = read_key(&consul, key).await.unwrap();
+        assert_eq!(res.index, index);
+        create_or_update_key_value(&consul, key, "This is a new test").await.unwrap();
+        let res = read_key(&consul, key).await.unwrap();
+        assert!(res.index > index);
     }
 
     #[tokio::test(flavor = "multi_thread")]

From 90cf56e2257d25fcbd4c7f2e027bfd9c6c547519 Mon Sep 17 00:00:00 2001
From: GitHub Action Bot <kushudai@users.noreply.github.com>
Date: Sat, 20 Jul 2024 06:16:50 +0000
Subject: [PATCH 07/16] Commit from GitHub Actions (Format Code)

---
 src/lib.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/lib.rs b/src/lib.rs
index 1de0f00..382c343 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -967,7 +967,9 @@ mod tests {
 
         let res = read_key(&consul, key).await.unwrap();
         assert_eq!(res.index, index);
-        create_or_update_key_value(&consul, key, "This is a new test").await.unwrap();
+        create_or_update_key_value(&consul, key, "This is a new test")
+            .await
+            .unwrap();
         let res = read_key(&consul, key).await.unwrap();
         assert!(res.index > index);
     }

From 9739dbed65771bfb104f33dbf43cd65de59da81f Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Fri, 19 Jul 2024 23:18:43 -0700
Subject: [PATCH 08/16] Better test

---
 src/lib.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/lib.rs b/src/lib.rs
index 1de0f00..c02d57f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -967,6 +967,8 @@ mod tests {
 
         let res = read_key(&consul, key).await.unwrap();
         assert_eq!(res.index, index);
+        create_or_update_key_value(&consul, key, string_value).await.unwrap();
+        assert_eq!(res.index, index);
         create_or_update_key_value(&consul, key, "This is a new test").await.unwrap();
         let res = read_key(&consul, key).await.unwrap();
         assert!(res.index > index);

From c452ee2420b40e90c70f5a2dd49c305e5f141545 Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Sat, 20 Jul 2024 12:15:20 -0700
Subject: [PATCH 09/16] Change default and add ring

---
 Cargo.toml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 31f3460..3607644 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,7 +10,7 @@ license-file = "LICENSE"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [features]
-default = ["rustls-native"]
+default = ["rustls-webpki"]
 metrics = ["prometheus", "lazy_static"]
 rustls-native = ["hyper-rustls/rustls-native-certs"]
 rustls-webpki = ["hyper-rustls/webpki-roots"]
@@ -22,7 +22,7 @@ base64 = "0.22"
 http = "1"
 http-body-util = "0.1"
 hyper = { version = "1", features = ["full"] }
-hyper-rustls = { version = "0.27" }
+hyper-rustls = { version = "0.27", features = ["ring"] }
 hyper-util = { version = "0.1", features = ["client", "client-legacy", "tokio", "http2"] }
 lazy_static = { version = "1", optional = true }
 opentelemetry = { version = "0.24", optional = true }

From 4eff4f50e7dac7bc4a52f58a6ea8ab3c115495ab Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Sat, 20 Jul 2024 12:20:17 -0700
Subject: [PATCH 10/16] Try

---
 Cargo.toml | 3 +++
 src/lib.rs | 1 +
 2 files changed, 4 insertions(+)

diff --git a/Cargo.toml b/Cargo.toml
index 3607644..92b616f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,3 +34,6 @@ slog-scope = "4"
 smart-default = "0.7"
 tokio = { version = "1", features = ["full"] }
 ureq = { version = "2", features = ["json"] }
+
+[dev-dependencies]
+rustls = { version = "0.23", features = ["ring"] }
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 0ad1449..d292d02 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1334,6 +1334,7 @@ mod tests {
     }
 
     fn get_client() -> Consul {
+        rustls::crypto::ring::default_provider().install_default().expect("Failed to install rustls crypto provider");
         let conf: Config = Config::from_env();
         Consul::new(conf).unwrap()
     }

From b04f308a5b91a402b0748daf5649dcbe07183859 Mon Sep 17 00:00:00 2001
From: GitHub Action Bot <kushudai@users.noreply.github.com>
Date: Sat, 20 Jul 2024 19:20:57 +0000
Subject: [PATCH 11/16] Commit from GitHub Actions (Format Code)

---
 src/lib.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/lib.rs b/src/lib.rs
index d292d02..7d75ff9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1334,7 +1334,9 @@ mod tests {
     }
 
     fn get_client() -> Consul {
-        rustls::crypto::ring::default_provider().install_default().expect("Failed to install rustls crypto provider");
+        rustls::crypto::ring::default_provider()
+            .install_default()
+            .expect("Failed to install rustls crypto provider");
         let conf: Config = Config::from_env();
         Consul::new(conf).unwrap()
     }

From 881cb1bb904848cdd6b489f0650bf817ea3e169d Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Sat, 20 Jul 2024 12:26:29 -0700
Subject: [PATCH 12/16] Rerun

---
 Cargo.toml | 2 +-
 src/lib.rs | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 92b616f..b315332 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,4 +36,4 @@ tokio = { version = "1", features = ["full"] }
 ureq = { version = "2", features = ["json"] }
 
 [dev-dependencies]
-rustls = { version = "0.23", features = ["ring"] }
\ No newline at end of file
+rustls = { version = "0.23", features = ["ring"] }
diff --git a/src/lib.rs b/src/lib.rs
index 7d75ff9..229d25e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -949,6 +949,7 @@ fn record_duration_metric_if_enabled(_method: &Method, _function: &str, _duratio
 mod tests {
     use std::time::Duration;
 
+    use rustls::crypto::ring::default_provider;
     use tokio::time::sleep;
 
     use super::*;
@@ -1334,7 +1335,7 @@ mod tests {
     }
 
     fn get_client() -> Consul {
-        rustls::crypto::ring::default_provider()
+        default_provider()
             .install_default()
             .expect("Failed to install rustls crypto provider");
         let conf: Config = Config::from_env();

From 13f05b8bb0123e36d31dea082dbee8331a51772c Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Sat, 20 Jul 2024 12:39:48 -0700
Subject: [PATCH 13/16] Fix non-additive features

---
 .github/workflows/main.yml |  2 +-
 CHANGELOG.md               |  1 +
 Cargo.toml                 |  9 ++-------
 src/lib.rs                 | 15 +--------------
 4 files changed, 5 insertions(+), 22 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c0af32c..5d1766f 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -24,7 +24,7 @@ jobs:
   test:
     strategy:
       matrix:
-        features: ["", "--no-default-features --features rustls-native"]
+        features: [""]
     runs-on: "ubuntu-latest"
     container: rust:1.79
     services:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d40fb4..6dfbdfd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 - `hyper-rustls` updated to version `0.27` from `0.24`.
 - `get_service_nodes` now supports tags thanks to @gautamg795
 - `read_key` now also returns the index thanks to @badalex
+- Removed `rustls-native-roots` feature and now defaults to `rustls-webpki-roots`. This addresses the bug that features were not additive.
 
 ## 0.6.0 - 2024-04-01
 
diff --git a/Cargo.toml b/Cargo.toml
index b315332..bc71f88 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,10 +10,8 @@ license-file = "LICENSE"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [features]
-default = ["rustls-webpki"]
+default = []
 metrics = ["prometheus", "lazy_static"]
-rustls-native = ["hyper-rustls/rustls-native-certs"]
-rustls-webpki = ["hyper-rustls/webpki-roots"]
 trace = ["dep:opentelemetry"]
 
 # keep this list sorted!
@@ -22,7 +20,7 @@ base64 = "0.22"
 http = "1"
 http-body-util = "0.1"
 hyper = { version = "1", features = ["full"] }
-hyper-rustls = { version = "0.27", features = ["ring"] }
+hyper-rustls = { version = "0.27", features = ["webpki-roots"] }
 hyper-util = { version = "0.1", features = ["client", "client-legacy", "tokio", "http2"] }
 lazy_static = { version = "1", optional = true }
 opentelemetry = { version = "0.24", optional = true }
@@ -34,6 +32,3 @@ slog-scope = "4"
 smart-default = "0.7"
 tokio = { version = "1", features = ["full"] }
 ureq = { version = "2", features = ["json"] }
-
-[dev-dependencies]
-rustls = { version = "0.23", features = ["ring"] }
diff --git a/src/lib.rs b/src/lib.rs
index 229d25e..7577f8a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -39,7 +39,6 @@ use http_body_util::{Empty, Full};
 use hyper::body::Bytes;
 use hyper::{body::Buf, Method};
 use hyper_util::client::legacy::{connect::HttpConnector, Builder, Client};
-#[cfg(any(feature = "rustls-native", feature = "rustls-webpki"))]
 #[cfg(feature = "metrics")]
 use lazy_static::lazy_static;
 use quick_error::quick_error;
@@ -256,16 +255,8 @@ pub struct Consul {
 }
 
 fn https_connector() -> Result<hyper_rustls::HttpsConnector<HttpConnector>> {
-    #[cfg(feature = "rustls-webpki")]
-    return Ok(hyper_rustls::HttpsConnectorBuilder::new()
-        .with_webpki_roots()
-        .https_or_http()
-        .enable_http1()
-        .build());
-    #[allow(unreachable_code)]
-    // Clippy doesn't realize if the feature is disabled, this code would execute.
     Ok(hyper_rustls::HttpsConnectorBuilder::new()
-        .with_native_roots()?
+        .with_webpki_roots()
         .https_or_http()
         .enable_http1()
         .build())
@@ -949,7 +940,6 @@ fn record_duration_metric_if_enabled(_method: &Method, _function: &str, _duratio
 mod tests {
     use std::time::Duration;
 
-    use rustls::crypto::ring::default_provider;
     use tokio::time::sleep;
 
     use super::*;
@@ -1335,9 +1325,6 @@ mod tests {
     }
 
     fn get_client() -> Consul {
-        default_provider()
-            .install_default()
-            .expect("Failed to install rustls crypto provider");
         let conf: Config = Config::from_env();
         Consul::new(conf).unwrap()
     }

From 348553cc438296c39ca3aad401967e7b56b32687 Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Sat, 20 Jul 2024 12:48:59 -0700
Subject: [PATCH 14/16] Missed one

---
 .github/workflows/main.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5d1766f..a6364d1 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -11,7 +11,7 @@ jobs:
   build:
     strategy:
       matrix:
-        features: ["", "--no-default-features --features rustls-native"]
+        features: [""]
     runs-on: "ubuntu-latest"
     container: rust:1.79
 

From 2c42dbbae37480c99c0502eb4b53ef98ace7ea06 Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Sat, 20 Jul 2024 13:01:47 -0700
Subject: [PATCH 15/16] Fix features

---
 Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index bc71f88..678f5c9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,7 +20,7 @@ base64 = "0.22"
 http = "1"
 http-body-util = "0.1"
 hyper = { version = "1", features = ["full"] }
-hyper-rustls = { version = "0.27", features = ["webpki-roots"] }
+hyper-rustls = { version = "0.27", default-features = false, features = ["webpki-roots", "ring", "http1"] }
 hyper-util = { version = "0.1", features = ["client", "client-legacy", "tokio", "http2"] }
 lazy_static = { version = "1", optional = true }
 opentelemetry = { version = "0.24", optional = true }

From 77b202e5e5e3bf38ec68e90dd52f65eec247572d Mon Sep 17 00:00:00 2001
From: Kushagra Udai <kudai@roblox.com>
Date: Sat, 20 Jul 2024 13:16:47 -0700
Subject: [PATCH 16/16] Changelog

---
 CHANGELOG.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6dfbdfd..c7df596 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 - `hyper-rustls` updated to version `0.27` from `0.24`.
 - `get_service_nodes` now supports tags thanks to @gautamg795
 - `read_key` now also returns the index thanks to @badalex
-- Removed `rustls-native-roots` feature and now defaults to `rustls-webpki-roots`. This addresses the bug that features were not additive.
+- Allow configuring `Consul` with a custom http client thanks to @LeonHartley
+- Removed `rustls-native-roots` feature and now defaults to `rustls-webpki-roots` (which has been removed). This addresses the bug that features were not additive.
 
 ## 0.6.0 - 2024-04-01