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

fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-connectors.git


The following commit(s) were added to refs/heads/main by this push:
     new 3cb1ed8c4 Allow using SdkHttpConfigurationOption over default 
pekko-http connection settings (#827)
3cb1ed8c4 is described below

commit 3cb1ed8c479405cc94a758bee3edd694fb8ec45b
Author: João Ferreira <[email protected]>
AuthorDate: Thu Sep 26 14:02:39 2024 +0100

    Allow using SdkHttpConfigurationOption over default pekko-http connection 
settings (#827)
    
    * withConnectionPoolSettingsBuilderFromAttributeMap
    
    * scalafmt tests
    
    * remove comment
    
    * reorganize imports
    
    * DurationConverters scala 2.12
---
 .../stream/connectors/awsspi/PekkoHttpClient.scala | 44 +++++++++--
 .../connectors/awsspi/PekkoHttpClientSpec.scala    | 88 ++++++++++++++++++++++
 2 files changed, 124 insertions(+), 8 deletions(-)

diff --git 
a/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
 
b/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
index 9aacfd2b5..df57812e2 100644
--- 
a/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
+++ 
b/aws-spi-pekko-http/src/main/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClient.scala
@@ -32,17 +32,19 @@ import pekko.http.scaladsl.settings.ConnectionPoolSettings
 import pekko.stream.scaladsl.Source
 import pekko.stream.{ Materializer, SystemMaterializer }
 import pekko.util.ByteString
-import pekko.util.OptionConverters
+import pekko.util.OptionConverters._
+import pekko.util.JavaDurationConverters._
 import org.slf4j.LoggerFactory
 import software.amazon.awssdk.http.async._
-import software.amazon.awssdk.http.SdkHttpRequest
+import software.amazon.awssdk.http.{ SdkHttpConfigurationOption, 
SdkHttpRequest }
 import software.amazon.awssdk.utils.AttributeMap
 
 import scala.collection.immutable
 import scala.concurrent.duration.Duration
 import scala.concurrent.{ Await, ExecutionContext }
 
-class PekkoHttpClient(shutdownHandle: () => Unit, connectionSettings: 
ConnectionPoolSettings)(implicit
+class PekkoHttpClient(shutdownHandle: () => Unit, private[awsspi] val 
connectionSettings: ConnectionPoolSettings)(
+    implicit
     actorSystem: ActorSystem,
     ec: ExecutionContext,
     mat: Materializer) extends SdkAsyncHttpClient {
@@ -84,8 +86,7 @@ object PekkoHttpClient {
       contentType: ContentType,
       contentPublisher: SdkHttpContentPublisher): RequestEntity =
     method.requestEntityAcceptance match {
-      case Expected =>
-        OptionConverters.toScala(contentPublisher.contentLength()) match {
+      case Expected => contentPublisher.contentLength().toScala match {
           case Some(length) =>
             HttpEntity(contentType, length, 
Source.fromPublisher(contentPublisher).map(ByteString(_)))
           case None => HttpEntity(contentType, 
Source.fromPublisher(contentPublisher).map(ByteString(_)))
@@ -151,18 +152,39 @@ object PekkoHttpClient {
     else throw new RuntimeException(s"Could not parse custom content type 
'$contentTypeStr'.")
   }
 
+  private[awsspi] def buildConnectionPoolSettings(
+      base: ConnectionPoolSettings, attributeMap: AttributeMap): 
ConnectionPoolSettings = {
+    def zeroToInfinite(duration: java.time.Duration): 
scala.concurrent.duration.Duration =
+      if (duration.isZero) scala.concurrent.duration.Duration.Inf
+      else duration.asScala
+
+    base
+      .withUpdatedConnectionSettings(s =>
+        
s.withConnectingTimeout(attributeMap.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT).asScala)
+          
.withIdleTimeout(attributeMap.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).asScala))
+      
.withMaxConnections(attributeMap.get(SdkHttpConfigurationOption.MAX_CONNECTIONS).intValue())
+      
.withMaxConnectionLifetime(zeroToInfinite(attributeMap.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE)))
+  }
+
   def builder() = PekkoHttpClientBuilder()
 
   case class PekkoHttpClientBuilder(private val actorSystem: 
Option[ActorSystem] = None,
       private val executionContext: Option[ExecutionContext] = None,
-      private val connectionPoolSettings: Option[ConnectionPoolSettings] = 
None)
+      private val connectionPoolSettings: Option[ConnectionPoolSettings] = 
None,
+      private val connectionPoolSettingsBuilder: (ConnectionPoolSettings, 
AttributeMap) => ConnectionPoolSettings =
+        (c, _) => c)
       extends SdkAsyncHttpClient.Builder[PekkoHttpClientBuilder] {
-    def buildWithDefaults(attributeMap: AttributeMap): SdkAsyncHttpClient = {
+    def buildWithDefaults(serviceDefaults: AttributeMap): SdkAsyncHttpClient = 
{
       implicit val as = actorSystem.getOrElse(ActorSystem("aws-pekko-http"))
       implicit val ec = executionContext.getOrElse(as.dispatcher)
       val mat: Materializer = SystemMaterializer(as).materializer
 
-      val cps = connectionPoolSettings.getOrElse(ConnectionPoolSettings(as))
+      val resolvedOptions = 
serviceDefaults.merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS);
+
+      val cps = connectionPoolSettingsBuilder(
+        connectionPoolSettings.getOrElse(ConnectionPoolSettings(as)),
+        resolvedOptions
+      )
       val shutdownhandleF = () => {
         if (actorSystem.isEmpty) {
           Await.result(Http().shutdownAllConnectionPools().flatMap(_ => 
as.terminate()),
@@ -179,6 +201,12 @@ object PekkoHttpClient {
       copy(executionContext = Some(executionContext))
     def withConnectionPoolSettings(connectionPoolSettings: 
ConnectionPoolSettings): PekkoHttpClientBuilder =
       copy(connectionPoolSettings = Some(connectionPoolSettings))
+    def withConnectionPoolSettingsBuilder(
+        connectionPoolSettingsBuilder: (ConnectionPoolSettings, AttributeMap) 
=> ConnectionPoolSettings
+    ): PekkoHttpClientBuilder =
+      copy(connectionPoolSettingsBuilder = connectionPoolSettingsBuilder)
+    def withConnectionPoolSettingsBuilderFromAttributeMap(): 
PekkoHttpClientBuilder =
+      copy(connectionPoolSettingsBuilder = buildConnectionPoolSettings)
   }
 
   lazy val xAmzJson = ContentType(MediaType.customBinary("application", 
"x-amz-json-1.0", Compressible))
diff --git 
a/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
 
b/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
index 6046aa1b6..3ae36ed21 100644
--- 
a/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
+++ 
b/aws-spi-pekko-http/src/test/scala/org/apache/pekko/stream/connectors/awsspi/PekkoHttpClientSpec.scala
@@ -18,13 +18,20 @@
 package org.apache.pekko.stream.connectors.awsspi
 
 import java.util.Collections
+import com.typesafe.config.ConfigFactory
 
 import org.apache.pekko
 import pekko.http.scaladsl.model.headers.`Content-Type`
 import pekko.http.scaladsl.model.MediaTypes
+import pekko.http.scaladsl.settings.{ ClientConnectionSettings, 
ConnectionPoolSettings }
+import pekko.util.JavaDurationConverters._
 import org.scalatest.OptionValues
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
+import software.amazon.awssdk.http.SdkHttpConfigurationOption
+import software.amazon.awssdk.utils.AttributeMap
+
+import scala.concurrent.duration._
 
 class PekkoHttpClientSpec extends AnyWordSpec with Matchers with OptionValues {
 
@@ -47,5 +54,86 @@ class PekkoHttpClientSpec extends AnyWordSpec with Matchers 
with OptionValues {
       contentTypeHeader.value.lowercaseName() shouldBe 
`Content-Type`.lowercaseName
       reqHeaders should have size 1
     }
+    "build() should use default ConnectionPoolSettings" in {
+      val pekkoClient: PekkoHttpClient = new 
PekkoHttpAsyncHttpService().createAsyncHttpClientFactory()
+        .build()
+        .asInstanceOf[PekkoHttpClient]
+
+      pekkoClient.connectionSettings shouldBe 
ConnectionPoolSettings(ConfigFactory.load())
+    }
+
+    "withConnectionPoolSettingsBuilderFromAttributeMap().buildWithDefaults() 
should propagate configuration options" in {
+      val attributeMap = AttributeMap.builder()
+        .put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, 1.second.asJava)
+        .put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, 
2.second.asJava)
+        .put(SdkHttpConfigurationOption.MAX_CONNECTIONS, Integer.valueOf(3))
+        .put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, 
4.second.asJava)
+        .build()
+      val pekkoClient: PekkoHttpClient = new 
PekkoHttpAsyncHttpService().createAsyncHttpClientFactory()
+        .withConnectionPoolSettingsBuilderFromAttributeMap()
+        .buildWithDefaults(attributeMap)
+        .asInstanceOf[PekkoHttpClient]
+
+      pekkoClient.connectionSettings.connectionSettings.connectingTimeout 
shouldBe 1.second
+      pekkoClient.connectionSettings.connectionSettings.idleTimeout shouldBe 
2.seconds
+      pekkoClient.connectionSettings.maxConnections shouldBe 3
+      pekkoClient.connectionSettings.maxConnectionLifetime shouldBe 4.seconds
+    }
+
+    "withConnectionPoolSettingsBuilderFromAttributeMap().build() should 
fallback to GLOBAL_HTTP_DEFAULTS" in {
+      val pekkoClient: PekkoHttpClient = new 
PekkoHttpAsyncHttpService().createAsyncHttpClientFactory()
+        .withConnectionPoolSettingsBuilderFromAttributeMap()
+        .build()
+        .asInstanceOf[PekkoHttpClient]
+
+      pekkoClient.connectionSettings.connectionSettings.connectingTimeout 
shouldBe
+      
SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT).asScala
+      pekkoClient.connectionSettings.connectionSettings.idleTimeout shouldBe
+      
SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).asScala
+      pekkoClient.connectionSettings.maxConnections shouldBe
+      
SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS.get(SdkHttpConfigurationOption.MAX_CONNECTIONS).intValue()
+      infiniteToZero(pekkoClient.connectionSettings.maxConnectionLifetime) 
shouldBe
+      
SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE)
+    }
+
+    "withConnectionPoolSettingsBuilder().build() should use passed 
connectionPoolSettings builder" in {
+      val connectionPoolSettings = ConnectionPoolSettings(ConfigFactory.load())
+        .withConnectionSettings(
+          ClientConnectionSettings(ConfigFactory.load())
+            .withConnectingTimeout(1.second)
+            .withIdleTimeout(2.seconds)
+        )
+        .withMaxConnections(3)
+        .withMaxConnectionLifetime(4.seconds)
+
+      val pekkoClient: PekkoHttpClient = new 
PekkoHttpAsyncHttpService().createAsyncHttpClientFactory()
+        .withConnectionPoolSettingsBuilder((_, _) => connectionPoolSettings)
+        .build()
+        .asInstanceOf[PekkoHttpClient]
+
+      pekkoClient.connectionSettings shouldBe connectionPoolSettings
+    }
+
+    "withConnectionPoolSettings().build() should use passed 
ConnectionPoolSettings" in {
+      val connectionPoolSettings = ConnectionPoolSettings(ConfigFactory.load())
+        .withConnectionSettings(
+          ClientConnectionSettings(ConfigFactory.load())
+            .withConnectingTimeout(1.second)
+            .withIdleTimeout(2.seconds)
+        )
+        .withMaxConnections(3)
+        .withMaxConnectionLifetime(4.seconds)
+      val pekkoClient: PekkoHttpClient = new 
PekkoHttpAsyncHttpService().createAsyncHttpClientFactory()
+        .withConnectionPoolSettings(connectionPoolSettings)
+        .build()
+        .asInstanceOf[PekkoHttpClient]
+
+      pekkoClient.connectionSettings shouldBe connectionPoolSettings
+    }
+  }
+
+  private def infiniteToZero(duration: scala.concurrent.duration.Duration): 
java.time.Duration = duration match {
+    case _: scala.concurrent.duration.Duration.Infinite => 
java.time.Duration.ZERO
+    case duration: FiniteDuration                       => duration.asJava
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to