From ff83e4e69a329355d12518a993ee992a8269b66f Mon Sep 17 00:00:00 2001 From: "H. Habighorst" Date: Sat, 19 Jul 2025 18:24:06 +0200 Subject: [PATCH] ASync 3 client upgrade, removal of stream / oauth feature --- build.sbt | 38 +---- .../api/libs/ws/ahc/AhcWSClientSpec.scala | 23 +-- .../libs/ws/ahc/AhcWSRequestFilterSpec.scala | 55 ------- .../play/libs/ws/ahc/AhcWSClientSpec.scala | 18 +-- .../libs/ws/ahc/AhcWSRequestFilterSpec.scala | 51 ------ .../src/main/java/play/libs/oauth/OAuth.java | 152 ------------------ .../libs/ws/ahc/StandaloneAhcWSClient.java | 78 +-------- .../libs/ws/ahc/StandaloneAhcWSRequest.java | 53 +----- .../scala/play/api/libs/oauth/OAuth.scala | 144 ----------------- .../play/api/libs/ws/ahc/AhcConfig.scala | 36 +++-- .../libs/ws/ahc/StandaloneAhcWSClient.scala | 68 +------- .../libs/ws/ahc/StandaloneAhcWSRequest.scala | 44 +---- .../scala/play/api/libs/ws/ahc/Streamed.scala | 92 ----------- .../api/libs/ws/ahc/StreamedResponse.scala | 124 -------------- .../libs/ws/ahc/cache/CacheableResponse.scala | 27 ++-- .../ws/ahc/cache/CachingAsyncHttpClient.scala | 12 +- .../scala/play/api/libs/oauth/OAuthSpec.scala | 15 -- .../libs/ws/ahc/AhcConfigBuilderSpec.scala | 27 ++-- .../api/libs/ws/ahc/AhcWSRequestSpec.scala | 80 +-------- .../api/libs/ws/ahc/AhcWSResponseSpec.scala | 20 +-- .../play/libs/ws/ahc/AhcWSRequestSpec.scala | 73 ++------- .../play/libs/ws/StandaloneWSRequest.java | 9 -- .../api/libs/ws/StandaloneWSRequest.scala | 6 - project/Dependencies.scala | 8 +- 24 files changed, 112 insertions(+), 1141 deletions(-) delete mode 100644 play-ahc-ws-standalone/src/main/java/play/libs/oauth/OAuth.java delete mode 100644 play-ahc-ws-standalone/src/main/scala/play/api/libs/oauth/OAuth.scala delete mode 100644 play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/Streamed.scala delete mode 100644 play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StreamedResponse.scala delete mode 100644 play-ahc-ws-standalone/src/test/scala/play/api/libs/oauth/OAuthSpec.scala diff --git a/build.sbt b/build.sbt index a5aa6c78..9392c69a 100644 --- a/build.sbt +++ b/build.sbt @@ -176,7 +176,11 @@ lazy val `shaded-asynchttpclient` = project assembly / logLevel := Level.Error, assembly / assemblyMergeStrategy := { val NettyPropertiesPath = "META-INF" + File.separator + "io.netty.versions.properties" + val ModuleInfoClass = "META-INF" + File.separator + "versions" + File.separator + "9" + File.separator + "module-info.class" val mergeStrategy: String => MergeStrategy = { + case ModuleInfoClass => + MergeStrategy.discard + case NettyPropertiesPath => MergeStrategy.first @@ -212,47 +216,18 @@ lazy val `shaded-asynchttpclient` = project Compile / packageBin := assembly.value ) -//--------------------------------------------------------------- -// Shaded oauth -//--------------------------------------------------------------- - -lazy val `shaded-oauth` = project - .in(file("shaded/oauth")) - .disablePlugins(MimaPlugin) - .settings(commonSettings) - .settings(shadeAssemblySettings) - .settings( - libraryDependencies ++= oauth, - name := "shaded-oauth", - // logLevel in assembly := Level.Debug, - assembly / assemblyShadeRules := Seq( - ShadeRule.rename("oauth.**" -> "play.shaded.oauth.@0").inAll, - ShadeRule.rename("org.apache.commons.**" -> "play.shaded.oauth.@0").inAll - ), - // https://stackoverflow.com/questions/24807875/how-to-remove-projectdependencies-from-pom - // Remove dependencies from the POM because we have a FAT jar here. - makePomConfiguration := makePomConfiguration.value.withProcess(process = dependenciesFilter), - assembly / assemblyOption := (assembly / assemblyOption).value.withIncludeBin(false).withIncludeScala(false), - Compile / packageBin := assembly.value - ) - // Make the shaded version of AHC available downstream val shadedAhcSettings = Seq( Compile / unmanagedJars += (`shaded-asynchttpclient` / Compile / packageBin).value ) -val shadedOAuthSettings = Seq( - Compile / unmanagedJars += (`shaded-oauth` / Compile / packageBin).value -) - //--------------------------------------------------------------- // Shaded aggregate project //--------------------------------------------------------------- lazy val shaded = Project(id = "shaded", base = file("shaded")) .aggregate( - `shaded-asynchttpclient`, - `shaded-oauth` + `shaded-asynchttpclient` ) .disablePlugins(sbtassembly.AssemblyPlugin, HeaderPlugin, MimaPlugin) .settings( @@ -296,7 +271,7 @@ def addShadedDeps(deps: Seq[xml.Node], node: xml.Node): xml.Node = { lazy val `play-ahc-ws-standalone` = project .in(file("play-ahc-ws-standalone")) .settings( - commonSettings ++ shadedAhcSettings ++ shadedOAuthSettings ++ Seq( + commonSettings ++ shadedAhcSettings ++ Seq( Test / fork := true, Test / testOptions := Seq(Tests.Argument(TestFrameworks.JUnit, "-a", "-v")), libraryDependencies ++= standaloneAhcWSDependencies, @@ -381,7 +356,6 @@ lazy val `integration-tests` = project libraryDependencies ++= backendServerTestDependencies ++ testDependencies, ) .settings(shadedAhcSettings) - .settings(shadedOAuthSettings) .dependsOn( `play-ahc-ws-standalone`, `play-ws-standalone-json`, diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSClientSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSClientSpec.scala index a74b0258..eeaf278a 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSClientSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSClientSpec.scala @@ -4,10 +4,7 @@ package play.api.libs.ws.ahc -import org.apache.pekko.stream.scaladsl.Sink -import org.apache.pekko.util.ByteString -import org.specs2.concurrent.ExecutionEnv -import org.specs2.concurrent.FutureAwait +import org.specs2.concurrent.{ExecutionEnv, FutureAwait} import org.specs2.execute.Result import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification @@ -15,10 +12,7 @@ import play.NettyServerProvider import play.api.BuiltInComponents import play.api.http.Status.MOVED_PERMANENTLY import play.api.libs.ws._ -import play.api.mvc.Cookie -import play.api.mvc.Handler -import play.api.mvc.RequestHeader -import play.api.mvc.Results +import play.api.mvc.{Cookie, Handler, RequestHeader, Results} import play.api.routing.sird._ import play.shaded.ahc.org.asynchttpclient.handler.MaxRedirectException @@ -133,7 +127,7 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) case class Foo(body: String) implicit val fooBodyReadable: BodyReadable[Foo] = BodyReadable[Foo] { response => - import play.shaded.ahc.org.asynchttpclient.{ Response => AHCResponse } + import play.shaded.ahc.org.asynchttpclient.{Response => AHCResponse} val ahcResponse = response.asInstanceOf[StandaloneAhcWSResponse].underlying[AHCResponse] Foo(ahcResponse.getResponseBody) } @@ -147,17 +141,6 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) } } - "request a url as a stream" in { - withClient() { client => - val resultSource = Await.result( - client.url(s"http://localhost:$testServerPort/index").stream().map(_.bodyAsSource), - defaultTimeout - ) - val bytes: ByteString = Await.result(resultSource.runWith(Sink.head), defaultTimeout) - bytes.utf8String must beEqualTo("Say hello to play") - } - } - "when following redirect" in { "honor the number of redirects allowed" in { diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestFilterSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestFilterSpec.scala index d1fd61ca..70b922e5 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestFilterSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestFilterSpec.scala @@ -72,20 +72,6 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) .await(retries = 0, timeout = defaultTimeout) } - "stream with adhoc request filter" in withClient() { client => - client - .url(s"http://localhost:$testServerPort") - .withRequestFilter(WSRequestFilter { e => - WSRequestExecutor(r => e.apply(r.withQueryStringParameters("key" -> "some string"))) - }) - .withMethod("GET") - .stream() - .map { response => - response.body[String] must contain("some string") - } - .await(retries = 0, timeout = defaultTimeout) - } - "work with one request filter" in withClient() { client => val callList = scala.collection.mutable.ArrayBuffer[Int]() client @@ -98,19 +84,6 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) .await(retries = 0, timeout = defaultTimeout) } - "stream with one request filter" in withClient() { client => - val callList = scala.collection.mutable.ArrayBuffer[Int]() - client - .url(s"http://localhost:$testServerPort") - .withRequestFilter(new CallbackRequestFilter(callList, 1)) - .withMethod("GET") - .stream() - .map { _ => - callList must contain(1) - } - .await(retries = 0, timeout = defaultTimeout) - } - "work with three request filter" in withClient() { client => val callList = scala.collection.mutable.ArrayBuffer[Int]() client @@ -125,21 +98,6 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) .await(retries = 0, timeout = defaultTimeout) } - "stream with three request filters" in withClient() { client => - val callList = scala.collection.mutable.ArrayBuffer[Int]() - client - .url(s"http://localhost:$testServerPort") - .withRequestFilter(new CallbackRequestFilter(callList, 1)) - .withRequestFilter(new CallbackRequestFilter(callList, 2)) - .withRequestFilter(new CallbackRequestFilter(callList, 3)) - .withMethod("GET") - .stream() - .map { _ => - callList must containTheSameElementsAs(Seq(1, 2, 3)) - } - .await(retries = 0, timeout = defaultTimeout) - } - "should allow filters to modify the request" in withClient() { client => val appendedHeader = "X-Request-Id" val appendedHeaderValue = "someid" @@ -153,18 +111,5 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) .await(retries = 0, timeout = defaultTimeout) } - "allow filters to modify the streaming request" in withClient() { client => - val appendedHeader = "X-Request-Id" - val appendedHeaderValue = "someid" - client - .url(s"http://localhost:$testServerPort") - .withRequestFilter(new HeaderAppendingFilter(appendedHeader, appendedHeaderValue)) - .withMethod("GET") - .stream() - .map { response => - response.headers("X-Request-Id").head must be_==("someid") - } - .await(retries = 0, timeout = defaultTimeout) - } } } diff --git a/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSClientSpec.scala b/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSClientSpec.scala index 0cbc2183..7bfd9ae3 100644 --- a/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSClientSpec.scala +++ b/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSClientSpec.scala @@ -4,22 +4,17 @@ package play.libs.ws.ahc -import org.apache.pekko.stream.javadsl.Sink -import org.apache.pekko.util.ByteString import org.specs2.concurrent.ExecutionEnv import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification import play.NettyServerProvider import play.api.BuiltInComponents -import play.api.mvc.AnyContentAsText -import play.api.mvc.AnyContentAsXml -import play.api.mvc.Results +import play.api.mvc.{AnyContentAsText, AnyContentAsXml, Results} import play.api.routing.sird._ import play.libs.ws._ -import scala.jdk.FutureConverters._ -import scala.concurrent.Future import scala.concurrent.duration._ +import scala.jdk.FutureConverters._ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specification @@ -63,15 +58,6 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) .await(retries = 0, timeout = 5.seconds) } - "source successfully" in withClient() { client => - val future = client.url(s"http://localhost:$testServerPort").stream().asScala - val result: Future[ByteString] = future.flatMap { (response: StandaloneWSResponse) => - response.getBodyAsSource.runWith(Sink.head[ByteString](), materializer).asScala - } - val expected: ByteString = ByteString.fromString("

Say hello to play

") - result must be_==(expected).await(retries = 0, timeout = 5.seconds) - } - "round trip XML successfully" in withClient() { client => val document = XML.fromString(""" | diff --git a/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSRequestFilterSpec.scala b/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSRequestFilterSpec.scala index 42d10bd0..7b53f77d 100644 --- a/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSRequestFilterSpec.scala +++ b/integration-tests/src/test/scala/play/libs/ws/ahc/AhcWSRequestFilterSpec.scala @@ -50,22 +50,6 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) .await(retries = 0, timeout = 5.seconds) } - "stream with one request filter" in withClient() { client => - import scala.jdk.CollectionConverters._ - val callList = new java.util.ArrayList[Integer]() - val responseFuture = - client - .url(s"http://localhost:$testServerPort") - .setRequestFilter(new CallbackRequestFilter(callList, 1)) - .stream() - .asScala - responseFuture - .map { _ => - callList.asScala must contain(1) - } - .await(retries = 0, timeout = 5.seconds) - } - "work with three request filter" in withClient() { client => import scala.jdk.CollectionConverters._ val callList = new java.util.ArrayList[Integer]() @@ -84,24 +68,6 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) .await(retries = 0, timeout = 5.seconds) } - "stream with three request filters" in withClient() { client => - import scala.jdk.CollectionConverters._ - val callList = new java.util.ArrayList[Integer]() - val responseFuture = - client - .url(s"http://localhost:$testServerPort") - .setRequestFilter(new CallbackRequestFilter(callList, 1)) - .setRequestFilter(new CallbackRequestFilter(callList, 2)) - .setRequestFilter(new CallbackRequestFilter(callList, 3)) - .stream() - .asScala - responseFuture - .map { _ => - callList.asScala must containTheSameElementsAs(Seq(1, 2, 3)) - } - .await(retries = 0, timeout = 5.seconds) - } - "should allow filters to modify the request" in withClient() { client => val appendedHeader = "X-Request-Id" val appendedHeaderValue = "someid" @@ -118,22 +84,5 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) } .await(retries = 0, timeout = 5.seconds) } - - "allow filters to modify the streaming request" in withClient() { client => - val appendedHeader = "X-Request-Id" - val appendedHeaderValue = "someid" - val responseFuture = - client - .url(s"http://localhost:$testServerPort") - .setRequestFilter(new HeaderAppendingFilter(appendedHeader, appendedHeaderValue)) - .stream() - .asScala - - responseFuture - .map { response => - response.getHeaders.get("X-Request-Id").get(0) must be_==("someid") - } - .await(retries = 0, timeout = 5.seconds) - } } } diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/oauth/OAuth.java b/play-ahc-ws-standalone/src/main/java/play/libs/oauth/OAuth.java deleted file mode 100644 index 689a591c..00000000 --- a/play-ahc-ws-standalone/src/main/java/play/libs/oauth/OAuth.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. - */ - -package play.libs.oauth; - -import play.shaded.oauth.oauth.signpost.OAuthConsumer; -import play.shaded.oauth.oauth.signpost.OAuthProvider; -import play.shaded.oauth.oauth.signpost.basic.DefaultOAuthConsumer; -import play.shaded.oauth.oauth.signpost.basic.DefaultOAuthProvider; -import play.shaded.oauth.oauth.signpost.exception.OAuthException; -import play.shaded.ahc.org.asynchttpclient.oauth.OAuthSignatureCalculator; -import play.libs.ws.WSSignatureCalculator; - -public class OAuth { - - private ServiceInfo info; - private OAuthProvider provider; - - public OAuth(ServiceInfo info) { - this(info, true); - } - - public OAuth(ServiceInfo info, boolean use10a) { - this.info = info; - this.provider = new DefaultOAuthProvider(info.requestTokenURL, info.accessTokenURL, info.authorizationURL); - this.provider.setOAuth10a(use10a); - } - - public ServiceInfo getInfo() { - return info; - } - - public OAuthProvider getProvider() { - return provider; - } - - /** - * Request the request token and secret. - * - * @param callbackURL the URL where the provider should redirect to (usually a URL on the current app) - * @return A Right(RequestToken) in case of success, Left(OAuthException) otherwise - */ - public RequestToken retrieveRequestToken(String callbackURL) { - OAuthConsumer consumer = new DefaultOAuthConsumer(info.key.key, info.key.secret); - try { - provider.retrieveRequestToken(consumer, callbackURL); - return new RequestToken(consumer.getToken(), consumer.getTokenSecret()); - } catch (OAuthException ex) { - throw new RuntimeException(ex); - } - } - - /** - * Exchange a request token for an access token. - * - * @param token the token/secret pair obtained from a previous call - * @param verifier a string you got through your user, with redirection - * @return A Right(RequestToken) in case of success, Left(OAuthException) otherwise - */ - public RequestToken retrieveAccessToken(RequestToken token, String verifier) { - OAuthConsumer consumer = new DefaultOAuthConsumer(info.key.key, info.key.secret); - consumer.setTokenWithSecret(token.token, token.secret); - try { - provider.retrieveAccessToken(consumer, verifier); - return new RequestToken(consumer.getToken(), consumer.getTokenSecret()); - } catch (OAuthException ex) { - throw new RuntimeException(ex); - } - } - - /** - * The URL where the user needs to be redirected to grant authorization to your application. - * - * @param token request token - * @return the url - */ - public String redirectUrl(String token) { - return play.shaded.oauth.oauth.signpost.OAuth.addQueryParameters( - provider.getAuthorizationWebsiteUrl(), - play.shaded.oauth.oauth.signpost.OAuth.OAUTH_TOKEN, - token - ); - } - - /** - * A consumer key / consumer secret pair that the OAuth provider gave you, to identify your application. - */ - public static class ConsumerKey { - public String key; - public String secret; - - public ConsumerKey(String key, String secret) { - this.key = key; - this.secret = secret; - } - } - - /** - * A request token / token secret pair, to be used for a specific user. - */ - public static class RequestToken { - public String token; - public String secret; - - public RequestToken(String token, String secret) { - this.token = token; - this.secret = secret; - } - } - - /** - * The information identifying a oauth provider: URLs and the consumer key / consumer secret pair. - */ - public static class ServiceInfo { - public String requestTokenURL; - public String accessTokenURL; - public String authorizationURL; - public ConsumerKey key; - - public ServiceInfo(String requestTokenURL, String accessTokenURL, String authorizationURL, ConsumerKey key) { - this.requestTokenURL = requestTokenURL; - this.accessTokenURL = accessTokenURL; - this.authorizationURL = authorizationURL; - this.key = key; - } - } - - /** - * A signature calculator for the Play WS API. - *

- * Example: - * {{{ - * WS.url("http://example.com/protected").sign(OAuthCalculator(service, token)).get() - * }}} - */ - public static class OAuthCalculator implements WSSignatureCalculator { - - private OAuthSignatureCalculator calculator; - - public OAuthCalculator(ConsumerKey consumerKey, RequestToken token) { - play.shaded.ahc.org.asynchttpclient.oauth.ConsumerKey ahcConsumerKey = new play.shaded.ahc.org.asynchttpclient.oauth.ConsumerKey(consumerKey.key, consumerKey.secret); - play.shaded.ahc.org.asynchttpclient.oauth.RequestToken ahcRequestToken = new play.shaded.ahc.org.asynchttpclient.oauth.RequestToken(token.token, token.secret); - calculator = new OAuthSignatureCalculator(ahcConsumerKey, ahcRequestToken); - } - - public OAuthSignatureCalculator getCalculator() { - return calculator; - } - } - -} diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSClient.java b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSClient.java index 355bb4f4..b479503d 100644 --- a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSClient.java +++ b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSClient.java @@ -4,38 +4,28 @@ package play.libs.ws.ahc; -import org.apache.pekko.Done; +import com.typesafe.sslconfig.ssl.SystemConfiguration; +import jakarta.inject.Inject; import org.apache.pekko.stream.Materializer; import org.apache.pekko.stream.javadsl.Source; import org.apache.pekko.util.ByteString; import org.apache.pekko.util.ByteStringBuilder; -import com.typesafe.sslconfig.ssl.SystemConfiguration; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; import org.slf4j.LoggerFactory; -import play.api.libs.ws.ahc.*; +import play.api.libs.ws.ahc.AhcConfigBuilder; +import play.api.libs.ws.ahc.AhcLoggerFactory; +import play.api.libs.ws.ahc.AhcWSClientConfig; import play.api.libs.ws.ahc.cache.AhcHttpCache; import play.api.libs.ws.ahc.cache.CachingAsyncHttpClient; import play.libs.ws.StandaloneWSClient; import play.libs.ws.StandaloneWSResponse; -import play.shaded.ahc.org.asynchttpclient.AsyncCompletionHandler; -import play.shaded.ahc.org.asynchttpclient.AsyncHttpClient; -import play.shaded.ahc.org.asynchttpclient.DefaultAsyncHttpClient; -import play.shaded.ahc.org.asynchttpclient.DefaultAsyncHttpClientConfig; -import play.shaded.ahc.org.asynchttpclient.Request; -import play.shaded.ahc.org.asynchttpclient.Response; import play.shaded.ahc.org.asynchttpclient.*; -import scala.jdk.javaapi.FutureConverters; -import scala.concurrent.ExecutionContext; import scala.concurrent.Future; import scala.concurrent.Promise; +import scala.jdk.javaapi.FutureConverters; -import jakarta.inject.Inject; import java.io.IOException; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; -import java.util.function.Function; /** * A WS asyncHttpClient backed by an AsyncHttpClient instance. @@ -86,62 +76,6 @@ CompletionStage execute(Request request) { return FutureConverters.asJava(future); } - CompletionStage executeStream(Request request, ExecutionContext ec) { - final Promise streamStarted = scala.concurrent.Promise$.MODULE$.apply(); - final Promise streamCompletion = scala.concurrent.Promise$.MODULE$.apply(); - - Function f = state -> { - Publisher publisher = state.publisher(); - Publisher wrap = new Publisher() { - @Override - public void subscribe(Subscriber s) { - publisher.subscribe( - new Subscriber() { - @Override - public void onSubscribe(Subscription sub) { - s.onSubscribe(sub); - } - - @Override - public void onNext(HttpResponseBodyPart httpResponseBodyPart) { - s.onNext(httpResponseBodyPart); - } - - @Override - public void onError(Throwable t) { - s.onError(t); - } - - @Override - public void onComplete() { - FutureConverters.asJava(streamCompletion.future()) - .handle((d, t) -> { - if (d != null) s.onComplete(); - else s.onError(t); - return null; - }); - } - } - ); - } - }; - - return new StreamedResponse(this, - state.statusCode(), - state.statusText(), - state.uriOption().get(), - state.responseHeaders(), - wrap, - asyncHttpClient.getConfig().isUseLaxCookieEncoder()); - }; - - asyncHttpClient.executeRequest(request, new DefaultStreamedAsyncHandler<>(f, - streamStarted, - streamCompletion - )); - return FutureConverters.asJava(streamStarted.future()); - } - /** * A convenience method for creating a StandaloneAhcWSClient from configuration. * diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSRequest.java b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSRequest.java index 591aafe9..dfd97837 100644 --- a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSRequest.java +++ b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/StandaloneAhcWSRequest.java @@ -6,12 +6,10 @@ import org.apache.pekko.stream.Materializer; import org.apache.pekko.stream.javadsl.AsPublisher; -import org.apache.pekko.stream.javadsl.Sink; import org.apache.pekko.stream.javadsl.Source; import org.apache.pekko.util.ByteString; import org.reactivestreams.Publisher; import play.api.libs.ws.ahc.FormUrlEncodedParser; -import play.libs.oauth.OAuth; import play.libs.ws.*; import play.shaded.ahc.io.netty.buffer.ByteBuf; import play.shaded.ahc.io.netty.buffer.Unpooled; @@ -20,12 +18,7 @@ import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaders; import play.shaded.ahc.io.netty.handler.codec.http.cookie.Cookie; import play.shaded.ahc.io.netty.handler.codec.http.cookie.DefaultCookie; -import play.shaded.ahc.org.asynchttpclient.AsyncHttpClient; -import play.shaded.ahc.org.asynchttpclient.Realm; -import play.shaded.ahc.org.asynchttpclient.Request; -import play.shaded.ahc.org.asynchttpclient.RequestBuilder; -import play.shaded.ahc.org.asynchttpclient.SignatureCalculator; - +import play.shaded.ahc.org.asynchttpclient.*; import play.shaded.ahc.org.asynchttpclient.util.HttpUtils; import java.net.MalformedURLException; @@ -33,15 +26,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.CompletionStage; import static java.util.Collections.singletonList; @@ -399,16 +384,6 @@ public CompletionStage execute() { return executor.apply(this); } - @Override - public CompletionStage stream() { - WSRequestExecutor executor = foldRight(r -> { - StandaloneAhcWSRequest ahcWsRequest = (StandaloneAhcWSRequest) r; - Request ahcRequest = ahcWsRequest.buildRequest(); - return client.executeStream(ahcRequest, materializer.executionContext()); - }, filters.iterator()); - return executor.apply(this); - } - private WSRequestExecutor foldRight(WSRequestExecutor executor, Iterator iterator) { if (!iterator.hasNext()) { return executor; @@ -471,19 +446,6 @@ Request buildRequest() { builder.setBody(stringBody); } } - } else if (bodyWritable instanceof SourceBodyWritable) { - // If the bodyWritable has a streaming interface it should be up to the user to provide a manual Content-Length - // else every content would be Transfer-Encoding: chunked - // If the Content-Length is -1 Async-Http-Client sets a Transfer-Encoding: chunked - // If the Content-Length is great than -1 Async-Http-Client will use the correct Content-Length - long contentLength = Optional.ofNullable(possiblyModifiedHeaders.get(CONTENT_LENGTH.toString())) - .map(Long::valueOf).orElse(-1L); - possiblyModifiedHeaders.remove(CONTENT_LENGTH.toString()); - - @SuppressWarnings("unchecked") Source sourceBody = ((SourceBodyWritable) bodyWritable).body().get(); - Publisher publisher = sourceBody.map(bs -> Unpooled.wrappedBuffer(bs.toByteBuffer())) - .runWith(Sink.asPublisher(AsPublisher.WITHOUT_FANOUT), materializer); - builder.setBody(publisher, contentLength); } else { throw new IllegalStateException("Unknown body writable: " + bodyWritable); } @@ -492,9 +454,9 @@ Request buildRequest() { builder.setHeaders(possiblyModifiedHeaders); if (this.timeout.isNegative()) { - builder.setRequestTimeout(((int) INFINITE.toMillis())); + builder.setRequestTimeout(INFINITE); } else if (this.timeout.compareTo(Duration.ZERO) > 0) { - builder.setRequestTimeout(((int) this.timeout.toMillis())); + builder.setRequestTimeout(this.timeout); } getFollowRedirects().ifPresent(builder::setFollowRedirect); @@ -504,14 +466,11 @@ Request buildRequest() { this.getAuth().ifPresent(auth -> builder.setRealm(auth(auth.getUsername(), auth.getPassword(), auth.getScheme()))); if (this.calculator != null) { - if (this.calculator instanceof OAuth.OAuthCalculator) { - SignatureCalculator calc = ((OAuth.OAuthCalculator) this.calculator).getCalculator(); - builder.setSignatureCalculator(calc); - } else if (this.calculator instanceof SignatureCalculator) { + if (this.calculator instanceof SignatureCalculator) { SignatureCalculator calc = ((SignatureCalculator) this.calculator); builder.setSignatureCalculator(calc); } else { - throw new IllegalStateException("Use OAuth.OAuthCalculator"); + throw new IllegalStateException("Unsupported signature calculator provided."); } } diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/oauth/OAuth.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/oauth/OAuth.scala deleted file mode 100644 index 3c76155e..00000000 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/oauth/OAuth.scala +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. - */ - -package play.api.libs.oauth - -import play.shaded.oauth.oauth.signpost.basic.DefaultOAuthConsumer -import play.shaded.oauth.oauth.signpost.basic.DefaultOAuthProvider -import play.shaded.oauth.oauth.signpost.exception.OAuthException -import play.shaded.ahc.org.asynchttpclient.oauth.OAuthSignatureCalculator -import play.shaded.ahc.org.asynchttpclient.Request -import play.shaded.ahc.org.asynchttpclient.RequestBuilderBase -import play.shaded.ahc.org.asynchttpclient.SignatureCalculator -import play.api.libs.ws.WSSignatureCalculator - -import play.shaded.ahc.org.asynchttpclient.oauth.{ ConsumerKey => AHCConsumerKey } -import play.shaded.ahc.org.asynchttpclient.oauth.{ RequestToken => AHCRequestToken } - -/** - * Library to access resources protected by OAuth 1.0a. - * - * @param info the service information, including the required URLs and the application id and secret - * @param use10a whether the service should use the 1.0 version of the spec, or the 1.0a version fixing a security issue. - * You must use the version corresponding to the - */ -case class OAuth(info: ServiceInfo, use10a: Boolean = true) { - - private val provider = { - val p = new DefaultOAuthProvider(info.requestTokenURL, info.accessTokenURL, info.authorizationURL) - p.setOAuth10a(use10a) - p - } - - /** - * Request the request token and secret. - * - * @param callbackURL the URL where the provider should redirect to (usually a URL on the current app) - * @return A Right(RequestToken) in case of success, Left(OAuthException) otherwise - */ - def retrieveRequestToken(callbackURL: String): Either[OAuthException, RequestToken] = { - val consumer = new DefaultOAuthConsumer(info.key.key, info.key.secret) - try { - provider.retrieveRequestToken(consumer, callbackURL) - Right(RequestToken(consumer.getToken(), consumer.getTokenSecret())) - } catch { - case e: OAuthException => Left(e) - } - } - - /** - * Exchange a request token for an access token. - * - * @param token the token/secret pair obtained from a previous call - * @param verifier a string you got through your user, with redirection - * @return A Right(RequestToken) in case of success, Left(OAuthException) otherwise - */ - def retrieveAccessToken(token: RequestToken, verifier: String): Either[OAuthException, RequestToken] = { - val consumer = new DefaultOAuthConsumer(info.key.key, info.key.secret) - consumer.setTokenWithSecret(token.token, token.secret) - try { - provider.retrieveAccessToken(consumer, verifier) - Right(RequestToken(consumer.getToken(), consumer.getTokenSecret())) - } catch { - case e: OAuthException => Left(e) - } - } - - /** - * The URL where the user needs to be redirected to grant authorization to your application. - * - * @param token request token - */ - def redirectUrl(token: String): String = { - import play.shaded.oauth.oauth.signpost.{ OAuth => O } - O.addQueryParameters( - provider.getAuthorizationWebsiteUrl(), - O.OAUTH_TOKEN, - token - ) - } - -} - -/** - * A consumer key / consumer secret pair that the OAuth provider gave you, to identify your application. - */ -case class ConsumerKey(key: String, secret: String) - -/** - * A request token / token secret pair, to be used for a specific user. - */ -case class RequestToken(token: String, secret: String) - -/** - * The information identifying a oauth provider: URLs and the consumer key / consumer secret pair. - */ -case class ServiceInfo(requestTokenURL: String, accessTokenURL: String, authorizationURL: String, key: ConsumerKey) - -/** - * The public AsyncHttpClient implementation of WSSignatureCalculator. - */ -class OAuthCalculator(consumerKey: ConsumerKey, requestToken: RequestToken) - extends WSSignatureCalculator - with SignatureCalculator { - - private val ahcConsumerKey = new AHCConsumerKey(consumerKey.key, consumerKey.secret) - private val ahcRequestToken = new AHCRequestToken(requestToken.token, requestToken.secret) - private val calculator = new OAuthSignatureCalculator(ahcConsumerKey, ahcRequestToken) - - override def calculateAndAddSignature(request: Request, requestBuilder: RequestBuilderBase[?]): Unit = { - calculator.calculateAndAddSignature(request, requestBuilder) - } -} - -/** - * Object for creating signature calculator for the Play WS API. - * - * Example: - * {{{ - * import play.api.libs.oauth.{ ConsumerKey, OAuthCalculator, RequestToken } - * import play.api.libs.ws.ahc.StandaloneAhcWSClient - * - * def example( - * twitterConsumerKey: String, - * twitterConsumerSecret: String, - * accessTokenKey: String, - * accessTokenSecret: String, - * ws: StandaloneAhcWSClient) = { - * val consumerKey: ConsumerKey = - * ConsumerKey(twitterConsumerKey, twitterConsumerSecret) - * - * val requestToken: RequestToken = - * RequestToken(accessTokenKey, accessTokenSecret) - * - * ws.url("http://example.com/protected"). - * sign(OAuthCalculator(consumerKey, requestToken)).get() - * } - * }}} - */ -object OAuthCalculator { - def apply(consumerKey: ConsumerKey, token: RequestToken): WSSignatureCalculator = { - new OAuthCalculator(consumerKey, token) - } -} diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcConfig.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcConfig.scala index de0795fb..f1af15ea 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcConfig.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcConfig.scala @@ -4,25 +4,25 @@ package play.api.libs.ws.ahc -import jakarta.inject.Inject -import jakarta.inject.Provider -import jakarta.inject.Singleton -import javax.net.ssl._ - import com.typesafe.config.Config import com.typesafe.config.ConfigException import com.typesafe.config.ConfigFactory import com.typesafe.sslconfig.ssl._ +import jakarta.inject.Inject +import jakarta.inject.Provider +import jakarta.inject.Singleton import org.slf4j.LoggerFactory import play.api.libs.ws.WSClientConfig import play.api.libs.ws.WSConfigParser import play.shaded.ahc.io.netty.handler.ssl.SslContextBuilder import play.shaded.ahc.io.netty.handler.ssl.util.InsecureTrustManagerFactory -import play.shaded.ahc.org.asynchttpclient.netty.ssl.JsseSslEngineFactory import play.shaded.ahc.org.asynchttpclient.AsyncHttpClientConfig import play.shaded.ahc.org.asynchttpclient.DefaultAsyncHttpClientConfig +import play.shaded.ahc.org.asynchttpclient.netty.ssl.JsseSslEngineFactory +import javax.net.ssl._ import scala.concurrent.duration._ +import scala.jdk.javaapi.DurationConverters.toJava /** * Ahc client config. @@ -194,15 +194,17 @@ class AhcConfigBuilder(ahcConfig: AhcWSClientConfig = AhcWSClientConfig()) { def configureWS(ahcConfig: AhcWSClientConfig): Unit = { val config = ahcConfig.wsClientConfig - def toMillis(duration: Duration): Int = { - if (duration.isFinite) duration.toMillis.toInt - else -1 + def toJavaDuration(duration: Duration): java.time.Duration = { + if (duration.isFinite) + toJava(Duration.fromNanos(duration.toNanos)) + else + java.time.Duration.ZERO } builder - .setConnectTimeout(toMillis(config.connectionTimeout)) - .setReadTimeout(toMillis(config.idleTimeout)) - .setRequestTimeout(toMillis(config.requestTimeout)) + .setConnectTimeout(toJavaDuration(config.connectionTimeout)) + .setReadTimeout(toJavaDuration(config.idleTimeout)) + .setRequestTimeout(toJavaDuration(config.requestTimeout)) .setFollowRedirect(config.followRedirects) .setUseProxyProperties(config.useProxyProperties) .setCompressionEnforced(config.compressionEnabled) @@ -211,9 +213,9 @@ class AhcConfigBuilder(ahcConfig: AhcWSClientConfig = AhcWSClientConfig()) { builder.setMaxConnectionsPerHost(ahcConfig.maxConnectionsPerHost) builder.setMaxConnections(ahcConfig.maxConnectionsTotal) - builder.setConnectionTtl(toMillis(ahcConfig.maxConnectionLifetime)) - builder.setPooledConnectionIdleTimeout(toMillis(ahcConfig.idleConnectionInPoolTimeout)) - builder.setConnectionPoolCleanerPeriod(toMillis(ahcConfig.connectionPoolCleanerPeriod)) + builder.setConnectionTtl(toJavaDuration(ahcConfig.maxConnectionLifetime)) + builder.setPooledConnectionIdleTimeout(toJavaDuration(ahcConfig.idleConnectionInPoolTimeout)) + builder.setConnectionPoolCleanerPeriod(toJavaDuration(ahcConfig.connectionPoolCleanerPeriod)) builder.setMaxRedirects(ahcConfig.maxNumberOfRedirects) builder.setMaxRequestRetry(ahcConfig.maxRequestRetry) builder.setDisableUrlEncodingForBoundRequests(ahcConfig.disableUrlEncoding) @@ -225,8 +227,8 @@ class AhcConfigBuilder(ahcConfig: AhcWSClientConfig = AhcWSClientConfig()) { // The proper solution is to make these parameters configurable, so that they can be set // to 0 when running tests, and keep sensible defaults otherwise. AHC defaults are // shutdownQuiet=2000 (milliseconds) and shutdownTimeout=15000 (milliseconds). - builder.setShutdownQuietPeriod(0) - builder.setShutdownTimeout(0) + builder.setShutdownQuietPeriod(java.time.Duration.ZERO) + builder.setShutdownTimeout(java.time.Duration.ZERO) builder.setUseLaxCookieEncoder(ahcConfig.useLaxCookieEncoder) if (!ahcConfig.useCookieStore) { diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSClient.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSClient.scala index a931c36d..0dfe5f1f 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSClient.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSClient.scala @@ -4,31 +4,22 @@ package play.api.libs.ws.ahc -import org.apache.pekko.Done +import com.typesafe.sslconfig.ssl.SystemConfiguration import jakarta.inject.Inject import org.apache.pekko.stream.Materializer import org.apache.pekko.stream.scaladsl.Source import org.apache.pekko.util.ByteString -import com.typesafe.sslconfig.ssl.SystemConfiguration -import org.reactivestreams.Publisher -import org.reactivestreams.Subscriber -import org.reactivestreams.Subscription -import play.api.libs.ws.ahc.cache._ import play.api.libs.ws.EmptyBody import play.api.libs.ws.StandaloneWSClient import play.api.libs.ws.StandaloneWSRequest +import play.api.libs.ws.ahc.cache._ import play.shaded.ahc.org.asynchttpclient.uri.Uri -import play.shaded.ahc.org.asynchttpclient.{ Response => AHCResponse } -import play.shaded.ahc.org.asynchttpclient._ -import java.util.function.{ Function => JFunction } +import play.shaded.ahc.org.asynchttpclient.{ Response => AHCResponse, _ } import scala.collection.immutable.TreeMap -import scala.jdk.FunctionConverters._ import scala.concurrent.Await import scala.concurrent.Future import scala.concurrent.Promise -import scala.util.Failure -import scala.util.Success /** * A WS client backed by an AsyncHttpClient. @@ -102,59 +93,6 @@ class StandaloneAhcWSClient @Inject() (asyncHttpClient: AsyncHttpClient)(implici } } - private[ahc] def executeStream(request: Request): Future[StreamedResponse] = { - val streamStarted = Promise[StreamedResponse]() - val streamCompletion = Promise[Done]() - - val client = this - - val function: JFunction[StreamedState, StreamedResponse] = { (state: StreamedState) => - val publisher = state.publisher - - val wrap = new Publisher[HttpResponseBodyPart]() { - override def subscribe( - s: Subscriber[? >: HttpResponseBodyPart] - ): Unit = { - publisher.subscribe(new Subscriber[HttpResponseBodyPart] { - override def onSubscribe(sub: Subscription): Unit = - s.onSubscribe(sub) - - override def onNext(t: HttpResponseBodyPart): Unit = s.onNext(t) - - override def onError(t: Throwable): Unit = s.onError(t) - - override def onComplete(): Unit = { - streamCompletion.future.onComplete { - case Success(_) => s.onComplete() - case Failure(t) => s.onError(t) - }(materializer.executionContext) - } - }) - } - - } - new StreamedResponse( - client, - state.statusCode, - state.statusText, - state.uriOption.get, - state.responseHeaders, - wrap, - asyncHttpClient.getConfig.isUseLaxCookieEncoder - ) - - }.asJava - asyncHttpClient.executeRequest( - request, - new DefaultStreamedAsyncHandler[StreamedResponse]( - function, - streamStarted, - streamCompletion - ) - ) - streamStarted.future - } - private[ahc] def blockingToByteString(bodyAsSource: Source[ByteString, ?]) = { StandaloneAhcWSClient.logger.warn( s"blockingToByteString is a blocking and unsafe operation!" diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSRequest.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSRequest.scala index fc1250f0..6e0d9f6b 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSRequest.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StandaloneAhcWSRequest.scala @@ -4,14 +4,8 @@ package play.api.libs.ws.ahc -import java.io.UnsupportedEncodingException -import java.net.URI -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets - import org.apache.pekko.stream.Materializer import org.apache.pekko.stream.scaladsl.Sink -import play.api.libs.ws.StandaloneWSRequest import play.api.libs.ws._ import play.shaded.ahc.io.netty.buffer.Unpooled import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaders @@ -20,10 +14,15 @@ import play.shaded.ahc.org.asynchttpclient._ import play.shaded.ahc.org.asynchttpclient.proxy.{ ProxyServer => AHCProxyServer } import play.shaded.ahc.org.asynchttpclient.util.HttpUtils -import scala.jdk.CollectionConverters._ +import java.io.UnsupportedEncodingException +import java.net.URI +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets import scala.collection.immutable.TreeMap import scala.concurrent.Future import scala.concurrent.duration.Duration +import scala.jdk.CollectionConverters._ +import scala.jdk.javaapi.DurationConverters.toJava /** * A Ahc WS Request. @@ -242,14 +241,6 @@ case class StandaloneAhcWSRequest( filters.foldRight(next)((filter, executor) => filter.apply(executor)) } - override def stream(): Future[Response] = { - val executor = filterWSRequestExecutor(WSRequestExecutor { request => - client.executeStream(request.asInstanceOf[StandaloneAhcWSRequest].buildRequest()) - }) - - executor(this) - } - /** * Returns the HTTP header given by name, using the request builder. This may be signed, * so may return extra headers that were not directly input. @@ -311,9 +302,9 @@ case class StandaloneAhcWSRequest( proxyServer.foreach(p => builder.setProxyServer(createProxy(p))) requestTimeout.foreach { case d if d == Duration.Inf => - builder.setRequestTimeout(-1) + builder.setRequestTimeout(java.time.Duration.ZERO) case d => - builder.setRequestTimeout(d.toMillis.toInt) + builder.setRequestTimeout(toJava(Duration.fromNanos(d.toNanos))) } val (builderWithBody, updatedHeaders) = body match { @@ -360,25 +351,6 @@ case class StandaloneAhcWSRequest( } (builder, h) - case SourceBody(source) => - // If the body has a streaming interface it should be up to the user to provide a manual Content-Length - // else every content would be Transfer-Encoding: chunked - // If the Content-Length is -1 Async-Http-Client sets a Transfer-Encoding: chunked - // If the Content-Length is great than -1 Async-Http-Client will use the correct Content-Length - val filteredHeaders = this.headers.filterNot { case (k, v) => - k.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) - } - val contentLength = this.headers - .find { case (k, _) => k.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) } - .map(_._2.head.toLong) - - ( - builder.setBody( - source.map(bs => Unpooled.wrappedBuffer(bs.toByteBuffer)).runWith(Sink.asPublisher(false)), - contentLength.getOrElse(-1L) - ), - filteredHeaders - ) } // headers diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/Streamed.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/Streamed.scala deleted file mode 100644 index 850dfcdf..00000000 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/Streamed.scala +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. - */ - -package play.api.libs.ws.ahc - -import java.net.URI - -import org.reactivestreams.Subscriber -import org.reactivestreams.Subscription -import org.reactivestreams.Publisher -import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaders -import org.apache.pekko.Done -import play.shaded.ahc.org.asynchttpclient.AsyncHandler.State -import play.shaded.ahc.org.asynchttpclient._ -import play.shaded.ahc.org.asynchttpclient.handler.StreamedAsyncHandler - -import scala.concurrent.Promise - -case class StreamedState( - statusCode: Int = -1, - statusText: String = "", - uriOption: Option[URI] = None, - responseHeaders: Map[String, scala.collection.Seq[String]] = Map.empty, - publisher: Publisher[HttpResponseBodyPart] = EmptyPublisher -) - -class DefaultStreamedAsyncHandler[T]( - f: java.util.function.Function[StreamedState, T], - streamStarted: Promise[T], - streamDone: Promise[Done] -) extends StreamedAsyncHandler[Unit] - with AhcUtilities { - private var state = StreamedState() - - def onStream(publisher: Publisher[HttpResponseBodyPart]): State = { - if (this.state.publisher != EmptyPublisher) State.ABORT - else { - this.state = state.copy(publisher = publisher) - streamStarted.success(f(state)) - State.CONTINUE - } - } - - override def onStatusReceived(status: HttpResponseStatus): State = { - if (this.state.publisher != EmptyPublisher) State.ABORT - else { - state = state.copy( - statusCode = status.getStatusCode, - statusText = status.getStatusText, - uriOption = Option(status.getUri.toJavaNetURI) - ) - State.CONTINUE - } - } - - override def onHeadersReceived(h: HttpHeaders): State = { - if (this.state.publisher != EmptyPublisher) State.ABORT - else { - state = state.copy(responseHeaders = headersToMap(h)) - State.CONTINUE - } - } - - override def onBodyPartReceived(bodyPart: HttpResponseBodyPart): State = - throw new IllegalStateException("Should not have received bodypart") - - override def onCompleted(): Unit = { - // EmptyPublisher can be replaces with `Source.empty` when we carry out the refactoring - // mentioned in the `execute2` method. - streamStarted.trySuccess(f(state.copy(publisher = EmptyPublisher))) - streamDone.trySuccess(Done) - } - - override def onThrowable(t: Throwable): Unit = { - streamStarted.tryFailure(t) - streamDone.tryFailure(t) - } -} - -private case object EmptyPublisher extends Publisher[HttpResponseBodyPart] { - def subscribe(s: Subscriber[? >: HttpResponseBodyPart]): Unit = { - if (s eq null) - throw new NullPointerException("Subscriber must not be null, rule 1.9") - s.onSubscribe(CancelledSubscription) - s.onComplete() - } - private case object CancelledSubscription extends Subscription { - override def request(elements: Long): Unit = () - override def cancel(): Unit = () - } -} diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StreamedResponse.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StreamedResponse.scala deleted file mode 100644 index 1001a535..00000000 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/StreamedResponse.scala +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. - */ - -package play.api.libs.ws.ahc - -import org.apache.pekko.stream.scaladsl.Source -import org.apache.pekko.util.ByteString -import org.reactivestreams.Publisher -import play.api.libs.ws.StandaloneWSResponse -import play.api.libs.ws.WSCookie -import play.shaded.ahc.org.asynchttpclient.HttpResponseBodyPart - -import scala.collection.immutable.TreeMap -import scala.collection.mutable - -/** - * A streamed response containing a response header and a streamable body. - * - * Note that this is only usable with a stream call, i.e. - * - * {{{ - * import scala.concurrent.{ ExecutionContext, Future } - * - * import org.apache.pekko.util.ByteString - * import org.apache.pekko.stream.scaladsl.Source - * - * import play.api.libs.ws.DefaultBodyReadables._ - * import play.api.libs.ws.ahc.StandaloneAhcWSClient - * - * class MyClass(ws: StandaloneAhcWSClient) { - * def doIt(implicit ec: ExecutionContext): Future[String] = - * ws.url("http://example.com").stream().map { response => - * val _ = response.body[Source[ByteString, _]] - * ??? // process source to String - * } - * } - * }}} - */ -class StreamedResponse( - client: StandaloneAhcWSClient, - val status: Int, - val statusText: String, - val uri: java.net.URI, - publisher: Publisher[HttpResponseBodyPart], - val useLaxCookieEncoder: Boolean -) extends StandaloneWSResponse - with CookieBuilder { - - def this( - client: StandaloneAhcWSClient, - status: Int, - statusText: String, - uri: java.net.URI, - headers: Map[String, scala.collection.Seq[String]], - publisher: Publisher[HttpResponseBodyPart], - useLaxCookieEncoder: Boolean - ) = { - this( - client, - status, - statusText, - uri, - publisher, - useLaxCookieEncoder - ) - origHeaders = headers - } - - private var origHeaders: Map[String, scala.collection.Seq[String]] = Map.empty - - /** - * Get the underlying response object. - */ - override def underlying[T]: T = publisher.asInstanceOf[T] - - override lazy val headers: Map[String, scala.collection.Seq[String]] = { - val mutableMap = mutable.TreeMap[String, scala.collection.Seq[String]]()(CaseInsensitiveOrdered) - origHeaders.keys.foreach { name => - mutableMap.updateWith(name) { - case Some(value) => Some(value ++ origHeaders.getOrElse(name, Seq.empty)) - case None => Some(origHeaders.getOrElse(name, Seq.empty)) - } - } - TreeMap[String, scala.collection.Seq[String]]()(CaseInsensitiveOrdered) ++ mutableMap - } - - /** - * Get all the cookies. - */ - override lazy val cookies: scala.collection.Seq[WSCookie] = buildCookies(headers) - - /** - * Get only one cookie, using the cookie name. - */ - override def cookie(name: String): Option[WSCookie] = cookies.find(_.name == name) - - /** - * THIS IS A BLOCKING OPERATION. It should not be used in production. - * - * Note that this is not a charset aware operation, as the stream does not have access to the underlying machinery - * that disambiguates responses. - * - * @return the body as a String - */ - override lazy val body: String = bodyAsBytes.decodeString(AhcWSUtils.getCharset(contentType)) - - /** - * THIS IS A BLOCKING OPERATION. It should not be used in production. - * - * Note that this is not a charset aware operation, as the stream does not have access to the underlying machinery - * that disambiguates responses. - * - * @return the body as a ByteString - */ - override lazy val bodyAsBytes: ByteString = client.blockingToByteString(bodyAsSource) - - override lazy val bodyAsSource: Source[ByteString, ?] = { - Source - .fromPublisher(publisher) - .map((bodyPart: HttpResponseBodyPart) => ByteString.fromArray(bodyPart.getBodyPartBytes)) - } - -} diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CacheableResponse.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CacheableResponse.scala index 16603ed0..64171a8b 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CacheableResponse.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CacheableResponse.scala @@ -4,6 +4,17 @@ package play.api.libs.ws.ahc.cache +import org.slf4j.LoggerFactory +import play.shaded.ahc.io.netty.buffer.ByteBuf +import play.shaded.ahc.io.netty.buffer.Unpooled +import play.shaded.ahc.io.netty.handler.codec.http.DefaultHttpHeaders +import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaders +import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaders.Names._ +import play.shaded.ahc.io.netty.handler.codec.http.cookie.Cookie +import play.shaded.ahc.org.asynchttpclient._ +import play.shaded.ahc.org.asynchttpclient.uri.Uri +import play.shaded.ahc.org.asynchttpclient.util.HttpUtils._ + import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream @@ -14,15 +25,6 @@ import java.nio.charset.Charset import java.nio.charset.StandardCharsets import java.util -import org.slf4j.LoggerFactory -import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaders.Names._ -import play.shaded.ahc.io.netty.handler.codec.http.cookie.Cookie -import play.shaded.ahc.io.netty.handler.codec.http.DefaultHttpHeaders -import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaders -import play.shaded.ahc.org.asynchttpclient._ -import play.shaded.ahc.org.asynchttpclient.uri.Uri -import play.shaded.ahc.org.asynchttpclient.util.HttpUtils._ - class CacheableResponseBuilder(ahcConfig: AsyncHttpClientConfig) { private var bodyParts: List[CacheableHttpResponseBodyPart] = Nil @@ -203,8 +205,9 @@ case class CacheableResponse( } private def buildCookies: util.List[Cookie] = { - import play.shaded.ahc.org.asynchttpclient.util.MiscUtils.isNonEmpty import play.shaded.ahc.io.netty.handler.codec.http.cookie.ClientCookieDecoder + import play.shaded.ahc.org.asynchttpclient.util.MiscUtils.isNonEmpty + import java.util.Collections var setCookieHeaders = headers.getAll(SET_COOKIE2) @@ -230,6 +233,8 @@ case class CacheableResponse( override def getLocalAddress: SocketAddress = status.getLocalAddress override def getRemoteAddress: SocketAddress = status.getRemoteAddress + + override def getResponseBodyAsByteBuf: ByteBuf = Unpooled.wrappedBuffer(getResponseBodyAsByteBuffer()) } object CacheableResponse { @@ -290,6 +295,8 @@ class CacheableHttpResponseBodyPart(chunk: Array[Byte], last: Boolean) extends H override def getBodyByteBuffer: ByteBuffer = ByteBuffer.wrap(chunk) + override def getBodyByteBuf: ByteBuf = Unpooled.wrappedBuffer(getBodyByteBuffer()) + override def isLast: Boolean = super.isLast override def length(): Int = if (chunk != null) chunk.length else 0 diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CachingAsyncHttpClient.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CachingAsyncHttpClient.scala index 5f59775b..b15bcc6f 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CachingAsyncHttpClient.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/cache/CachingAsyncHttpClient.scala @@ -4,15 +4,13 @@ package play.api.libs.ws.ahc.cache -import java.io._ -import java.util.function.Predicate -import java.time.ZonedDateTime - import org.slf4j.LoggerFactory import play.shaded.ahc.io.netty.handler.codec.http.DefaultHttpHeaders -import play.shaded.ahc.org.asynchttpclient.handler.StreamedAsyncHandler import play.shaded.ahc.org.asynchttpclient.{ Response => AHCResponse, _ } +import java.io._ +import java.time.ZonedDateTime +import java.util.function.Predicate import scala.concurrent.Await import scala.concurrent.ExecutionContext @@ -53,10 +51,6 @@ class CachingAsyncHttpClient(underlying: AsyncHttpClient, ahcHttpCache: AhcHttpC case asyncCompletionHandler: AsyncCompletionHandler[T] => execute(request, asyncCompletionHandler, null)(ahcHttpCache.executionContext) - case streamedHandler: StreamedAsyncHandler[T] => - // Streamed requests don't go through the cache - underlying.executeRequest(request, streamedHandler) - case other => throw new IllegalStateException(s"Unknown handler type ${other.getClass.getName}") } diff --git a/play-ahc-ws-standalone/src/test/scala/play/api/libs/oauth/OAuthSpec.scala b/play-ahc-ws-standalone/src/test/scala/play/api/libs/oauth/OAuthSpec.scala deleted file mode 100644 index cbe5f748..00000000 --- a/play-ahc-ws-standalone/src/test/scala/play/api/libs/oauth/OAuthSpec.scala +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. - */ - -package play.api.libs.oauth - -import org.specs2.mutable.Specification - -class OAuthSpec extends Specification { - "OAuth" should { - "be able to use signpost OAuth" in { - Class.forName("play.shaded.oauth.oauth.signpost.OAuth") must not(throwA[ClassNotFoundException]) - } - } -} diff --git a/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcConfigBuilderSpec.scala b/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcConfigBuilderSpec.scala index c597fcdb..776d5934 100644 --- a/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcConfigBuilderSpec.scala +++ b/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcConfigBuilderSpec.scala @@ -4,17 +4,16 @@ package play.api.libs.ws.ahc -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.sslconfig.ssl.Protocols -import com.typesafe.sslconfig.ssl.SSLConfigFactory -import com.typesafe.sslconfig.ssl.SSLConfigSettings +import com.typesafe.config.{Config, ConfigFactory} +import com.typesafe.sslconfig.ssl.{Protocols, SSLConfigFactory, SSLConfigSettings} import org.specs2.mutable.Specification import play.api.libs.ws.WSClientConfig import play.shaded.ahc.org.asynchttpclient.proxy.ProxyServerSelector import play.shaded.ahc.org.asynchttpclient.util.ProxyUtils +import java.time.temporal.ChronoUnit import scala.concurrent.duration._ +import scala.jdk.javaapi.DurationConverters.toJava /** */ @@ -37,9 +36,9 @@ class AhcConfigBuilderSpec extends Specification { .build() ahcConfig.isCompressionEnforced must beFalse ahcConfig.isFollowRedirect must beFalse - ahcConfig.getConnectTimeout must_== 120000 - ahcConfig.getRequestTimeout must_== 120000 - ahcConfig.getReadTimeout must_== 120000 + ahcConfig.getConnectTimeout must_== java.time.Duration.of(120000, ChronoUnit.MILLIS) + ahcConfig.getRequestTimeout must_== java.time.Duration.of(120000, ChronoUnit.MILLIS) + ahcConfig.getReadTimeout must_== java.time.Duration.of(120000, ChronoUnit.MILLIS) } "with basic options" should { @@ -49,9 +48,9 @@ class AhcConfigBuilderSpec extends Specification { val builder = new AhcConfigBuilder(config) val actual = builder.build() - actual.getReadTimeout must_== defaultWsConfig.idleTimeout.toMillis - actual.getRequestTimeout must_== defaultWsConfig.requestTimeout.toMillis - actual.getConnectTimeout must_== defaultWsConfig.connectionTimeout.toMillis + actual.getReadTimeout must_== toJava(Duration.fromNanos(defaultWsConfig.idleTimeout.toNanos)) + actual.getRequestTimeout must_== toJava(Duration.fromNanos(defaultWsConfig.requestTimeout.toNanos)) + actual.getConnectTimeout must_== toJava(Duration.fromNanos(defaultWsConfig.connectionTimeout.toNanos)) actual.isFollowRedirect must_== defaultWsConfig.followRedirects actual.getCookieStore must_== null @@ -64,7 +63,7 @@ class AhcConfigBuilderSpec extends Specification { val builder = new AhcConfigBuilder(config) val actual = builder.build() - actual.getReadTimeout must_== 42L + actual.getReadTimeout must_== java.time.Duration.of(42, ChronoUnit.MILLIS) } "use an explicit request timeout" in { @@ -73,7 +72,7 @@ class AhcConfigBuilderSpec extends Specification { val builder = new AhcConfigBuilder(config) val actual = builder.build() - actual.getRequestTimeout must_== 47L + actual.getRequestTimeout must_== java.time.Duration.of(47, ChronoUnit.MILLIS) } "use an explicit connection timeout" in { @@ -82,7 +81,7 @@ class AhcConfigBuilderSpec extends Specification { val builder = new AhcConfigBuilder(config) val actual = builder.build() - actual.getConnectTimeout must_== 99L + actual.getConnectTimeout must_== java.time.Duration.of(99, ChronoUnit.MILLIS) } "use an explicit followRedirects option" in { diff --git a/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestSpec.scala b/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestSpec.scala index 6dc7d9b4..8efc0296 100644 --- a/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestSpec.scala +++ b/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSRequestSpec.scala @@ -4,28 +4,21 @@ package play.api.libs.ws.ahc -import scala.jdk.CollectionConverters._ -import scala.concurrent.duration._ - import org.apache.pekko.actor.ActorSystem import org.apache.pekko.stream.Materializer import org.apache.pekko.util.ByteString - import org.mockito.Mockito import org.specs2.execute.Result import org.specs2.mutable.Specification import org.specs2.specification.AfterAll - -import play.api.libs.oauth.ConsumerKey -import play.api.libs.oauth.RequestToken -import play.api.libs.oauth.OAuthCalculator import play.api.libs.ws._ import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaderNames import play.shaded.ahc.org.asynchttpclient.Realm.AuthScheme -import play.shaded.ahc.org.asynchttpclient.SignatureCalculator -import play.shaded.ahc.org.asynchttpclient.Param -import play.shaded.ahc.org.asynchttpclient.{ Request => AHCRequest } +import play.shaded.ahc.org.asynchttpclient.{Param, SignatureCalculator, Request => AHCRequest} +import java.time.temporal.ChronoUnit +import scala.concurrent.duration._ +import scala.jdk.CollectionConverters._ import scala.reflect.ClassTag class AhcWSRequestSpec extends Specification with AfterAll with DefaultBodyReadables with DefaultBodyWritables { @@ -356,45 +349,6 @@ class AhcWSRequestSpec extends Specification with AfterAll with DefaultBodyReada } } - "Have form params for content type application/x-www-form-urlencoded when signed" in { - withClient { client => - import scala.jdk.CollectionConverters._ - val consumerKey = ConsumerKey("key", "secret") - val requestToken = RequestToken("token", "secret") - val calc = OAuthCalculator(consumerKey, requestToken) - val req: AHCRequest = client - .url("http://playframework.com/") - .withBody(Map("param1" -> Seq("value1"))) - .sign(calc) - .asInstanceOf[StandaloneAhcWSRequest] - .buildRequest() - // Note we use getFormParams instead of getByteData here. - req.getFormParams.asScala must containTheSameElementsAs( - List(new play.shaded.ahc.org.asynchttpclient.Param("param1", "value1")) - ) - req.getByteData must beNull // should NOT result in byte data. - - val headers = req.getHeaders - headers.get("Content-Length") must beNull - } - } - - "Parse no params for empty params map" in { - withClient { client => - val consumerKey = ConsumerKey("key", "secret") - val requestToken = RequestToken("token", "secret") - val calc = OAuthCalculator(consumerKey, requestToken) - val reqEmptyParams: AHCRequest = client - .url("http://playframework.com/") - .withBody(Map.empty[String, Seq[String]]) - .sign(calc) - .asInstanceOf[StandaloneAhcWSRequest] - .buildRequest() - - reqEmptyParams.getFormParams.asScala must beEmpty - } - } - "Have form body for content type text/plain" in { withClient { client => val req: AHCRequest = client @@ -595,7 +549,7 @@ class AhcWSRequestSpec extends Specification with AfterAll with DefaultBodyReada .withRequestTimeout(1000.millis) .asInstanceOf[StandaloneAhcWSRequest] .buildRequest() - (req.getRequestTimeout must be).equalTo(1000) + (req.getRequestTimeout must be).equalTo(java.time.Duration.of(1000, ChronoUnit.MILLIS)) } "infinite timeout" in withClient { client => @@ -604,7 +558,7 @@ class AhcWSRequestSpec extends Specification with AfterAll with DefaultBodyReada .withRequestTimeout(Duration.Inf) .asInstanceOf[StandaloneAhcWSRequest] .buildRequest() - (req.getRequestTimeout must be).equalTo(-1) + (req.getRequestTimeout must be).equalTo(java.time.Duration.ZERO) } "no negative timeout" in withClient { client => @@ -652,27 +606,6 @@ class AhcWSRequestSpec extends Specification with AfterAll with DefaultBodyReada headers.get("Content-Length") must_== "9001" } - "Remove a user defined content length header if we are parsing body explicitly when signed" in withClient { client => - import scala.jdk.CollectionConverters._ - val consumerKey = ConsumerKey("key", "secret") - val requestToken = RequestToken("token", "secret") - val calc = OAuthCalculator(consumerKey, requestToken) - val req: AHCRequest = client - .url("http://playframework.com/") - .withBody(Map("param1" -> Seq("value1"))) - .withHttpHeaders("Content-Length" -> "9001") // add a meaningless content length here... - .sign(calc) // this is signed, so content length is no longer valid per #5221 - .asInstanceOf[StandaloneAhcWSRequest] - .buildRequest() - - val headers = req.getHeaders - req.getByteData must beNull // should NOT result in byte data. - req.getFormParams.asScala must containTheSameElementsAs( - List(new play.shaded.ahc.org.asynchttpclient.Param("param1", "value1")) - ) - headers.get("Content-Length") must beNull // no content length! - } - "Verify Content-Type header is passed through correctly" in withClient { client => import scala.jdk.CollectionConverters._ val req: AHCRequest = client @@ -683,5 +616,4 @@ class AhcWSRequestSpec extends Specification with AfterAll with DefaultBodyReada .buildRequest() req.getHeaders.getAll(HttpHeaderNames.CONTENT_TYPE.toString()).asScala must_== Seq("text/plain; charset=US-ASCII") } - } diff --git a/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSResponseSpec.scala b/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSResponseSpec.scala index c3ad346d..b58561f7 100644 --- a/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSResponseSpec.scala +++ b/play-ahc-ws-standalone/src/test/scala/play/api/libs/ws/ahc/AhcWSResponseSpec.scala @@ -4,19 +4,17 @@ package play.api.libs.ws.ahc -import java.nio.charset.StandardCharsets -import java.util - import org.apache.pekko.util.ByteString -import org.mockito.Mockito.when import org.mockito.Mockito +import org.mockito.Mockito.when import org.specs2.mutable.Specification import play.api.libs.ws._ import play.shaded.ahc.io.netty.handler.codec.http.DefaultHttpHeaders -import play.shaded.ahc.io.netty.handler.codec.http.cookie.DefaultCookie -import play.shaded.ahc.io.netty.handler.codec.http.cookie.{ Cookie => AHCCookie } -import play.shaded.ahc.org.asynchttpclient.{ Response => AHCResponse } +import play.shaded.ahc.io.netty.handler.codec.http.cookie.{DefaultCookie, Cookie => AHCCookie} +import play.shaded.ahc.org.asynchttpclient.{Response => AHCResponse} +import java.nio.charset.StandardCharsets +import java.util import scala.reflect.ClassTag class AhcWSResponseSpec extends Specification with DefaultBodyReadables with DefaultBodyWritables { @@ -128,14 +126,6 @@ class AhcWSResponseSpec extends Specification with DefaultBodyReadables with Def headers.contains("Bar") must beTrue } - "get headers map which retrieves headers case insensitively (for streamed responses)" in { - val srcHeaders = Map("Foo" -> Seq("a"), "foo" -> Seq("b"), "FOO" -> Seq("b"), "Bar" -> Seq("baz")) - val response = new StreamedResponse(null, 200, "", null, srcHeaders, null, true) - val headers = response.headers - headers("foo") must_== Seq("a", "b", "b") - headers("BAR") must_== Seq("baz") - } - "get a single header" in { val ahcResponse: AHCResponse = mock[AHCResponse] val ahcHeaders = new DefaultHttpHeaders(true) diff --git a/play-ahc-ws-standalone/src/test/scala/play/libs/ws/ahc/AhcWSRequestSpec.scala b/play-ahc-ws-standalone/src/test/scala/play/libs/ws/ahc/AhcWSRequestSpec.scala index fa27a44d..4aad13fe 100644 --- a/play-ahc-ws-standalone/src/test/scala/play/libs/ws/ahc/AhcWSRequestSpec.scala +++ b/play-ahc-ws-standalone/src/test/scala/play/libs/ws/ahc/AhcWSRequestSpec.scala @@ -5,20 +5,15 @@ package play.libs.ws.ahc import com.typesafe.config.ConfigFactory - -import java.time.Duration -import java.util.Collections - import org.specs2.mutable._ -import play.libs.oauth.OAuth import play.libs.ws._ import play.shaded.ahc.io.netty.handler.codec.http.HttpHeaderNames -import play.shaded.ahc.org.asynchttpclient.Request -import play.shaded.ahc.org.asynchttpclient.RequestBuilderBase -import play.shaded.ahc.org.asynchttpclient.SignatureCalculator +import play.shaded.ahc.org.asynchttpclient.{Request, RequestBuilderBase, SignatureCalculator} -import scala.jdk.CollectionConverters._ +import java.time.Duration +import java.util.Collections import scala.collection.mutable +import scala.jdk.CollectionConverters._ import scala.jdk.OptionConverters._ class AhcWSRequestSpec extends Specification with DefaultBodyReadables with DefaultBodyWritables { @@ -129,50 +124,6 @@ class AhcWSRequestSpec extends Specification with DefaultBodyReadables with Defa List(new play.shaded.ahc.org.asynchttpclient.Param("param1", "value1")) ) } - - "have form params when content-type application/x-www-form-urlencoded and signed" in { - import scala.jdk.CollectionConverters._ - val client = StandaloneAhcWSClient.create( - AhcWSClientConfigFactory.forConfig(ConfigFactory.load(), this.getClass.getClassLoader), /*materializer*/ null - ) - val consumerKey = new OAuth.ConsumerKey("key", "secret") - val token = new OAuth.RequestToken("token", "secret") - val calc = new OAuth.OAuthCalculator(consumerKey, token) - val req = new StandaloneAhcWSRequest(client, "http://playframework.com/", null) - .setContentType("application/x-www-form-urlencoded") // set content type by hand - .setBody(body("param1=value1")) - .sign(calc) - .asInstanceOf[StandaloneAhcWSRequest] - .buildRequest() - // Note we use getFormParams instead of getByteData here. - req.getFormParams.asScala must containTheSameElementsAs( - List(new play.shaded.ahc.org.asynchttpclient.Param("param1", "value1")) - ) - } - - "remove a user defined content length header if we are parsing body explicitly when signed" in { - import scala.jdk.CollectionConverters._ - val client = StandaloneAhcWSClient.create( - AhcWSClientConfigFactory.forConfig(ConfigFactory.load(), this.getClass.getClassLoader), /*materializer*/ null - ) - val consumerKey = new OAuth.ConsumerKey("key", "secret") - val token = new OAuth.RequestToken("token", "secret") - val calc = new OAuth.OAuthCalculator(consumerKey, token) - val req = new StandaloneAhcWSRequest(client, "http://playframework.com/", null) - .setContentType("application/x-www-form-urlencoded") // set content type by hand - .setBody(body("param1=value1")) - .addHeader("Content-Length", "9001") // add a meaningless content length here... - .sign(calc) - .asInstanceOf[StandaloneAhcWSRequest] - .buildRequest() - - val headers = req.getHeaders - req.getFormParams.asScala must containTheSameElementsAs( - List(new play.shaded.ahc.org.asynchttpclient.Param("param1", "value1")) - ) - headers.get("Content-Length") must beNull // no content length! - } - } "Use a custom signature calculator" in { @@ -194,22 +145,24 @@ class AhcWSRequestSpec extends Specification with DefaultBodyReadables with Defa "setRequestTimeout(java.time.Duration)" should { "support setting a request timeout to a duration" in { - requestWithTimeout(Duration.ofSeconds(1)) must beEqualTo(1000) + requestWithTimeout(Duration.ofSeconds(1)) must beEqualTo(Duration.ofSeconds(1)) } "support setting a request timeout duration to infinite using -1" in { - requestWithTimeout(Duration.ofMillis(-1)) must beEqualTo(-1) + requestWithTimeout(Duration.ofMillis(-1)) must beEqualTo(Duration.ofMillis(-1)) } "support setting a request timeout duration to infinite using any negative duration" in { - requestWithTimeout(Duration.ofMillis(-2)) must beEqualTo(-1) - requestWithTimeout(Duration.ofMillis(-15)) must beEqualTo(-1) - requestWithTimeout(Duration.ofSeconds(-1)) must beEqualTo(-1) - requestWithTimeout(Duration.ofMillis(java.lang.Integer.MIN_VALUE)) must beEqualTo(-1) + requestWithTimeout(Duration.ofMillis(-2)) must beEqualTo(Duration.ofMillis(-1)) + requestWithTimeout(Duration.ofMillis(-15)) must beEqualTo(Duration.ofMillis(-1)) + requestWithTimeout(Duration.ofSeconds(-1)) must beEqualTo(Duration.ofMillis(-1)) + requestWithTimeout(Duration.ofMillis(java.lang.Integer.MIN_VALUE)) must beEqualTo(Duration.ofMillis(-1)) } "support setting a request timeout duration to Long.MAX_VALUE as infinite" in { - requestWithTimeout(Duration.ofMillis(java.lang.Long.MAX_VALUE)) must beEqualTo(-1) + requestWithTimeout(Duration.ofMillis(java.lang.Long.MAX_VALUE)) must beEqualTo( + Duration.ofMillis(java.lang.Long.MAX_VALUE) + ) } "not support setting a request timeout to null" in { diff --git a/play-ws-standalone/src/main/java/play/libs/ws/StandaloneWSRequest.java b/play-ws-standalone/src/main/java/play/libs/ws/StandaloneWSRequest.java index 7e06af07..8e22c70f 100644 --- a/play-ws-standalone/src/main/java/play/libs/ws/StandaloneWSRequest.java +++ b/play-ws-standalone/src/main/java/play/libs/ws/StandaloneWSRequest.java @@ -122,15 +122,6 @@ public interface StandaloneWSRequest { */ CompletionStage execute(); - /** - * Executes this request and streams the response body. - * - * Use {@code response.bodyAsSource()} with this method. - * - * @return a promise to the response - */ - CompletionStage stream(); - //------------------------------------------------------------------------- // Setters //------------------------------------------------------------------------- diff --git a/play-ws-standalone/src/main/scala/play/api/libs/ws/StandaloneWSRequest.scala b/play-ws-standalone/src/main/scala/play/api/libs/ws/StandaloneWSRequest.scala index 2f835956..e4826b8c 100644 --- a/play-ws-standalone/src/main/scala/play/api/libs/ws/StandaloneWSRequest.scala +++ b/play-ws-standalone/src/main/scala/play/api/libs/ws/StandaloneWSRequest.scala @@ -5,7 +5,6 @@ package play.api.libs.ws import java.net.URI - import scala.concurrent.Future import scala.concurrent.duration.Duration @@ -275,9 +274,4 @@ trait StandaloneWSRequest { */ def execute(): Future[Response] - /** - * Execute this request and stream the response body. - */ - def stream(): Future[Response] - } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 65eaa815..470b2a60 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,7 +1,7 @@ /* * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. */ -import sbt._ +import sbt.* object Dependencies { @@ -40,11 +40,7 @@ object Dependencies { val cachecontrol = Seq("org.playframework" %% "cachecontrol" % "3.1.0-M2") - val asyncHttpClient = Seq( - ("org.asynchttpclient" % "async-http-client" % "2.12.4") // 2.12.x comes with outdated netty-reactive-streams, so we ... - .exclude("com.typesafe.netty", "netty-reactive-streams"), // ... exclude it and pull in ... - "com.typesafe.netty" % "netty-reactive-streams" % "2.0.14", // ... a newer version ourselves (ahc v3 will drop that dependency) - ) + val asyncHttpClient = Seq("org.asynchttpclient" % "async-http-client" % "3.0.2") val pekkoVersion = "1.0.3"