From b519b10a85b808c6c565ad0caecb5199e1f6b42a Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 17:13:01 -0300 Subject: [PATCH 01/14] Remove duplicated logic/setup from tests Additionally, by using AkkaServerProvider, we are able to remove the need to run sequentially. --- .../api/libs/ws/ahc/AhcWSClientSpec.scala | 14 ++------ .../libs/ws/ahc/AhcWSRequestFilterSpec.scala | 32 ++++++++----------- .../ws/ahc/StandaloneWSClientSupport.scala | 21 ++++++++++++ .../api/libs/ws/ahc/cache/CachingSpec.scala | 7 ++-- .../play/libs/ws/ahc/AhcWSClientSpec.scala | 28 +++++++--------- .../libs/ws/ahc/AhcWSRequestFilterSpec.scala | 25 +++------------ 6 files changed, 58 insertions(+), 69 deletions(-) create mode 100644 integration-tests/src/test/scala/play/api/libs/ws/ahc/StandaloneWSClientSupport.scala 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 22a16fe1..0325e3e3 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 @@ -3,10 +3,10 @@ */ package play.api.libs.ws.ahc +import akka.http.scaladsl.server.Route import akka.stream.scaladsl.Sink import akka.util.ByteString import org.specs2.concurrent.{ ExecutionEnv, FutureAwait } -import org.specs2.execute.Result import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification import play.AkkaServerProvider @@ -16,20 +16,12 @@ import scala.concurrent._ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specification with AkkaServerProvider + with StandaloneWSClientSupport with FutureMatchers with FutureAwait with DefaultBodyReadables { - def withClient(config: AhcWSClientConfig = AhcWSClientConfigFactory.forConfig())(block: StandaloneAhcWSClient => Result): Result = { - val client = StandaloneAhcWSClient(config) - try { - block(client) - } finally { - client.close() - } - } - - override val routes = { + override val routes: Route = { import akka.http.scaladsl.server.Directives._ get { complete("

Say hello to akka-http

") 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 4f99b7a6..fd2e9c06 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 @@ -10,17 +10,16 @@ import akka.http.scaladsl.server.Route import org.specs2.concurrent.ExecutionEnv import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification -import org.specs2.specification.AfterAll import play.AkkaServerProvider import play.api.libs.ws._ import scala.collection.mutable -class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Specification with AkkaServerProvider with AfterAll with FutureMatchers { - import DefaultBodyReadables._ - - // Create the standalone WS client - val client = StandaloneAhcWSClient() +class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Specification + with AkkaServerProvider + with StandaloneWSClientSupport + with FutureMatchers + with DefaultBodyReadables { override val routes: Route = { import akka.http.scaladsl.server.Directives._ @@ -39,11 +38,6 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp } } - override def afterAll: Unit = { - super.afterAll() - client.close() - } - "with request filters" should { class CallbackRequestFilter(callList: mutable.Buffer[Int], value: Int) extends WSRequestFilter { @@ -59,7 +53,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp } } - "execute with adhoc request filter" in { + "work 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"))) }).get().map { response => @@ -67,7 +61,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp }.await(retries = 0, timeout = defaultTimeout) } - "stream with adhoc request filter" in { + "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 => @@ -75,7 +69,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp }.await(retries = 0, timeout = defaultTimeout) } - "execute with one request filter" in { + "work 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)) @@ -85,7 +79,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp .await(retries = 0, timeout = defaultTimeout) } - "stream with one request filter" in { + "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)) @@ -95,7 +89,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp .await(retries = 0, timeout = defaultTimeout) } - "execute with three request filters" in { + "work with three request filter" in withClient() { client => val callList = scala.collection.mutable.ArrayBuffer[Int]() client.url(s"http://localhost:$testServerPort") .withRequestFilter(new CallbackRequestFilter(callList, 1)) @@ -107,7 +101,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp .await(retries = 0, timeout = defaultTimeout) } - "stream with three request filters" in { + "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)) @@ -119,7 +113,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp .await(retries = 0, timeout = defaultTimeout) } - "allow filters to modify the executing request" in { + "should allow filters to modify the request" in withClient () { client => val appendedHeader = "X-Request-Id" val appendedHeaderValue = "someid" client.url(s"http://localhost:$testServerPort") @@ -130,7 +124,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp .await(retries = 0, timeout = defaultTimeout) } - "allow filters to modify the streaming request" in { + "allow filters to modify the streaming request" in withClient() { client => val appendedHeader = "X-Request-Id" val appendedHeaderValue = "someid" client.url(s"http://localhost:$testServerPort") diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/StandaloneWSClientSupport.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/StandaloneWSClientSupport.scala new file mode 100644 index 00000000..84c614f0 --- /dev/null +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/StandaloneWSClientSupport.scala @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2009-2017 Lightbend Inc. + */ +package play.api.libs.ws.ahc + +import akka.stream.Materializer +import org.specs2.execute.Result + +trait StandaloneWSClientSupport { + + def materializer: Materializer + + def withClient(config: AhcWSClientConfig = AhcWSClientConfigFactory.forConfig())(block: StandaloneAhcWSClient => Result): Result = { + val client = StandaloneAhcWSClient(config)(materializer) + try { + block(client) + } finally { + client.close() + } + } +} diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala index 41ded567..533b291b 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala @@ -17,10 +17,13 @@ import play.AkkaServerProvider import play.api.libs.ws.ahc._ import play.shaded.ahc.org.asynchttpclient._ -import scala.collection.mutable import scala.concurrent.Future -class CachingSpec(implicit val executionEnv: ExecutionEnv) extends Specification with AkkaServerProvider with AfterAll with FutureMatchers with Mockito { +class CachingSpec(implicit val executionEnv: ExecutionEnv) extends Specification + with AkkaServerProvider + with AfterAll + with FutureMatchers + with Mockito { val asyncHttpClient: AsyncHttpClient = { val config = AhcWSClientConfigFactory.forClientConfig() 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 31386e6b..de06e5f9 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 @@ -5,37 +5,36 @@ package play.libs.ws.ahc import akka.actor.ActorSystem import akka.http.scaladsl.Http +import akka.http.scaladsl.server.Route import akka.stream.ActorMaterializer -import akka.stream.javadsl.{ Sink, Source } +import akka.stream.javadsl.Sink import akka.util.ByteString import com.typesafe.config.ConfigFactory import org.specs2.concurrent.ExecutionEnv import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification import org.specs2.specification.AfterAll +import play.AkkaServerProvider import play.libs.ws._ import scala.compat.java8.FutureConverters._ import scala.concurrent.Future import scala.concurrent.duration._ -class AhcWSClientSpec(implicit executionEnv: ExecutionEnv) extends Specification with AfterAll with FutureMatchers with XMLBodyWritables with XMLBodyReadables { - val testServerPort = 49134 - - sequential - - // Create Akka system for thread and streaming management - implicit val system = ActorSystem() - implicit val materializer = ActorMaterializer() +class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specification + with AkkaServerProvider + with AfterAll + with FutureMatchers + with XMLBodyWritables + with XMLBodyReadables { // Create the standalone WS client with no cache val client = StandaloneAhcWSClient.create( AhcWSClientConfigFactory.forConfig(ConfigFactory.load, this.getClass.getClassLoader), - null, materializer ) - private val route = { + override val routes: Route = { import akka.http.scaladsl.server.Directives._ get { complete("

Say hello to akka-http

") @@ -47,14 +46,9 @@ class AhcWSClientSpec(implicit executionEnv: ExecutionEnv) extends Specification } } - private val futureServer = { - Http().bindAndHandle(route, "localhost", testServerPort) - } - override def afterAll = { - futureServer.foreach(_.unbind) client.close() - system.terminate() + super.afterAll() } "play.libs.ws.ahc.StandaloneAhcWSClient" should { 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 22f215a9..e4be8443 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 @@ -3,37 +3,27 @@ */ package play.libs.ws.ahc -import akka.actor.ActorSystem -import akka.http.scaladsl.Http import akka.http.scaladsl.model.{ ContentTypes, HttpEntity } import akka.http.scaladsl.model.headers.RawHeader -import akka.stream.ActorMaterializer import com.typesafe.config.ConfigFactory import org.specs2.concurrent.ExecutionEnv import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification import org.specs2.specification.AfterAll +import play.AkkaServerProvider import scala.concurrent.duration._ import scala.compat.java8.FutureConverters -class AhcWSRequestFilterSpec(implicit executionEnv: ExecutionEnv) extends Specification with AfterAll with FutureMatchers { - val testServerPort = 49134 - - sequential - - // Create Akka system for thread and streaming management - implicit val system = ActorSystem() - implicit val materializer = ActorMaterializer() +class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Specification with AkkaServerProvider with AfterAll with FutureMatchers { // Create the standalone WS client with no cache private val client = StandaloneAhcWSClient.create( AhcWSClientConfigFactory.forConfig(ConfigFactory.load, this.getClass.getClassLoader), - null, materializer ) - private val route = { + override val routes = { import akka.http.scaladsl.server.Directives._ headerValueByName("X-Request-Id") { value => respondWithHeader(RawHeader("X-Request-Id", value)) { @@ -46,14 +36,9 @@ class AhcWSRequestFilterSpec(implicit executionEnv: ExecutionEnv) extends Specif } } - private val futureServer = { - Http().bindAndHandle(route, "localhost", testServerPort) - } - - override def afterAll: Unit = { - futureServer.foreach(_.unbind) + override def afterAll = { client.close() - system.terminate() + super.afterAll() } "setRequestFilter" should { From 5d53d298d7bd0b1ad3b1227b36a4e28bfc86115d Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 17:13:56 -0300 Subject: [PATCH 02/14] Add tests to curl-logger request filter --- .../ws/ahc/AhcCurlRequestLoggerSpec.scala | 127 ++++++++++++++++++ .../libs/ws/ahc/AhcCurlRequestLogger.scala | 10 +- 2 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala new file mode 100644 index 00000000..810b9a9d --- /dev/null +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009-2017 Lightbend Inc. + */ +package play.api.libs.ws.ahc + +import akka.http.scaladsl.server.Route +import org.specs2.concurrent.{ ExecutionEnv, FutureAwait } +import org.specs2.mutable.Specification +import play.AkkaServerProvider +import play.api.libs.ws.{ DefaultBodyWritables, EmptyBody, WSAuthScheme } +import uk.org.lidalia.slf4jext.Level +import uk.org.lidalia.slf4jtest.{ TestLogger, TestLoggerFactory } + +import scala.collection.JavaConverters._ + +class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends Specification + with AkkaServerProvider + with StandaloneWSClientSupport + with FutureAwait + with DefaultBodyWritables { + + override def routes: Route = { + import akka.http.scaladsl.server.Directives._ + get { + complete("

Say hello to akka-http

") + } ~ + post { + entity(as[String]) { echo => + complete(echo) + } + } + } + + // Level.OFF because we don't want to pollute the test output + def createTestLogger: TestLogger = new TestLoggerFactory(Level.OFF).getLogger("test-logger") + + "Logging request as curl" should { + + "be verbose" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("--verbose") + } + + "add all headers" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .addHttpHeaders("My-Header" -> "My-Header-Value") + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + val messages = testLogger.getLoggingEvents.asScala.map(_.getMessage) + + messages must containMatch("My-Header") + messages must containMatch("My-Header-Value") + } + + "add method" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("--request GET") + } + + "add authorization header" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .withAuth("username", "password", WSAuthScheme.BASIC) + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("""--header "Authorization: Basic""") + } + + "handle body" in { + + "add when in memory" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .withBody("the-body") + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("the-body") + } + + "do nothing for empty bodies" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .withBody(EmptyBody) + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must not containMatch ("--data") + } + } + } +} diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala index bd1a81ae..8b61f3d3 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala @@ -51,11 +51,10 @@ trait CurlFormat { //authentication request.auth match { - case Some((userName, password, WSAuthScheme.BASIC)) => { + case Some((userName, password, WSAuthScheme.BASIC)) => val encodedPassword = Base64.getUrlEncoder.encodeToString(s"$userName:$password".getBytes(StandardCharsets.US_ASCII)) b.append(s""" --header 'Authorization: Basic ${quote(encodedPassword)}'""") b.append(" \\\n") - } case _ => Unit } @@ -102,8 +101,13 @@ trait CurlFormat { Option(HttpUtils.parseCharset(ct)).getOrElse { StandardCharsets.UTF_8 }.name() - }.getOrElse(HttpUtils.parseCharset("UTF-8").name()) + }.getOrElse(StandardCharsets.UTF_8.name()) } def quote(unsafe: String): String = unsafe.replace("'", "'\\''") } + +/** + * Java API. + */ +object JavaCurlFormat extends CurlFormat \ No newline at end of file From 33f8d1bda748ba6ccaeb34d6ebf17e07afa32149 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 17:16:25 -0300 Subject: [PATCH 03/14] Add structured auth information to Java API --- .../libs/ws/ahc/StandaloneAhcWSRequest.java | 53 +++++-------------- .../play/libs/ws/StandaloneWSRequest.java | 37 ++++++++++--- .../main/java/play/libs/ws/WSAuthInfo.java | 36 +++++++++++++ 3 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 play-ws-standalone/src/main/java/play/libs/ws/WSAuthInfo.java 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 fc0c457e..69c8b686 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 @@ -51,9 +51,7 @@ public class StandaloneAhcWSRequest implements StandaloneWSRequest { private final List cookies = new ArrayList<>(); - private String username; - private String password; - private WSAuthScheme scheme; + private WSAuthInfo auth; private WSSignatureCalculator calculator; private final StandaloneAhcWSClient client; @@ -165,8 +163,6 @@ public StandaloneAhcWSRequest setCookies(List cookies) { @Override public StandaloneAhcWSRequest setAuth(String userInfo) { - this.scheme = WSAuthScheme.BASIC; - if (userInfo.equals("")) { throw new RuntimeException(new MalformedURLException("userInfo should not be empty")); } @@ -174,32 +170,23 @@ public StandaloneAhcWSRequest setAuth(String userInfo) { int split = userInfo.indexOf(":"); if (split == 0) { // We only have a password without user - this.username = ""; - this.password = userInfo.substring(1); + this.auth = new WSAuthInfo("", userInfo.substring(1), WSAuthScheme.BASIC); } else if (split == -1) { // We only have a username without password - this.username = userInfo; - this.password = ""; + this.auth = new WSAuthInfo(userInfo, "", WSAuthScheme.BASIC); } else { - this.username = userInfo.substring(0, split); - this.password = userInfo.substring(split + 1); + this.auth = new WSAuthInfo( + userInfo.substring(0, split), + userInfo.substring(split + 1), + WSAuthScheme.BASIC + ); } return this; } @Override - public StandaloneAhcWSRequest setAuth(String username, String password) { - this.username = username; - this.password = password; - this.scheme = WSAuthScheme.BASIC; - return this; - } - - @Override - public StandaloneAhcWSRequest setAuth(String username, String password, WSAuthScheme scheme) { - this.username = username; - this.password = password; - this.scheme = scheme; + public StandaloneAhcWSRequest setAuth(WSAuthInfo auth) { + this.auth = auth; return this; } @@ -301,18 +288,8 @@ public Map> getQueryParameters() { } @Override - public String getUsername() { - return this.username; - } - - @Override - public String getPassword() { - return this.password; - } - - @Override - public WSAuthScheme getScheme() { - return this.scheme; + public Optional getAuth() { + return Optional.ofNullable(this.auth); } @Override @@ -486,9 +463,7 @@ Request buildRequest() { builder.setVirtualHost(this.virtualHost); } - if (this.username != null && this.password != null && this.scheme != null) { - builder.setRealm(auth(this.username, this.password, this.scheme)); - } + this.getAuth().ifPresent(auth -> builder.setRealm(auth(auth.getUsername(), auth.getPassword(), auth.getScheme()))); if (this.calculator != null) { if (this.calculator instanceof OAuth.OAuthCalculator) { @@ -532,7 +507,7 @@ private void addValueTo(Map> map, String name, String value Realm auth(String username, String password, WSAuthScheme scheme) { Realm.AuthScheme authScheme = Realm.AuthScheme.valueOf(scheme.name()); - Boolean usePreemptiveAuth = !(this.scheme != null && this.scheme == WSAuthScheme.DIGEST); + Boolean usePreemptiveAuth = scheme != WSAuthScheme.DIGEST; return (new Realm.Builder(username, password)) .setScheme(authScheme) .setUsePreemptiveAuth(usePreemptiveAuth) 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 36548faf..69a0d0df 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 @@ -5,7 +5,6 @@ import java.time.Duration; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -243,7 +242,9 @@ public interface StandaloneWSRequest { * @param password the basic auth password * @return the modified WSRequest. */ - StandaloneWSRequest setAuth(String username, String password); + default StandaloneWSRequest setAuth(String username, String password) { + return setAuth(new WSAuthInfo(username, password, WSAuthScheme.BASIC)); + } /** * Sets the authentication header for the current request. @@ -253,7 +254,18 @@ public interface StandaloneWSRequest { * @param scheme authentication scheme * @return the modified WSRequest. */ - StandaloneWSRequest setAuth(String username, String password, WSAuthScheme scheme); + default StandaloneWSRequest setAuth(String username, String password, WSAuthScheme scheme) { + return setAuth(new WSAuthInfo(username, password, scheme)); + } + + /** + * Sets the authentication header for the current request. + * + * @param authInfo the authentication information. + * + * @return the modified request. + */ + StandaloneWSRequest setAuth(WSAuthInfo authInfo); /** * Sets an (OAuth) signature calculator. @@ -380,17 +392,30 @@ default List getCookies() { /** * @return the auth username, null if not an authenticated request. */ - String getUsername(); + default String getUsername() { + return getAuth().map(WSAuthInfo::getUsername).orElse(null); + } /** * @return the auth password, null if not an authenticated request */ - String getPassword(); + default String getPassword() { + return getAuth().map(WSAuthInfo::getPassword).orElse(null); + } /** * @return the auth scheme, null if not an authenticated request. */ - WSAuthScheme getScheme(); + default WSAuthScheme getScheme() { + return getAuth().map(WSAuthInfo::getScheme).orElse(null); + } + + /** + * @return get auth information if present. + * + * @see WSAuthInfo + */ + Optional getAuth(); /** * @return the signature calculator (example: OAuth), null if none is set. diff --git a/play-ws-standalone/src/main/java/play/libs/ws/WSAuthInfo.java b/play-ws-standalone/src/main/java/play/libs/ws/WSAuthInfo.java new file mode 100644 index 00000000..85b17fe9 --- /dev/null +++ b/play-ws-standalone/src/main/java/play/libs/ws/WSAuthInfo.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009-2017 Lightbend Inc. + */ +package play.libs.ws; + +/** + * Holds information for request authentication. + * + * @see WSAuthScheme + * @see StandaloneWSRequest#setAuth(String) + * @see RFC 7235 - Hypertext Transfer Protocol (HTTP/1.1): Authentication + */ +public class WSAuthInfo { + + private final String username; + private final String password; + private final WSAuthScheme scheme; + + public WSAuthInfo(String username, String password, WSAuthScheme scheme) { + this.username = username; + this.password = password; + this.scheme = scheme; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public WSAuthScheme getScheme() { + return scheme; + } +} From ebe034bec21ef8451caff7797431701828ed6934 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 17:18:37 -0300 Subject: [PATCH 04/14] Java version of curl-logger request filter This required some changes in StandaloneWSRequest interface so that we can access more information to create the request filter. The good thing is that we bring Scala and Java APIs closer. --- .../ws/ahc/AhcCurlRequestLoggerSpec.scala | 136 ++++++++++++++++++ .../ws/ahc/StandaloneWSClientSupport.scala | 24 ++++ .../libs/ws/ahc/AhcCurlRequestLogger.java | 102 +++++++++++++ .../libs/ws/ahc/StandaloneAhcWSRequest.java | 17 +-- .../main/java/play/libs/ws/BodyWritable.java | 2 - .../play/libs/ws/InMemoryBodyWritable.java | 4 +- .../play/libs/ws/StandaloneWSRequest.java | 5 + 7 files changed, 279 insertions(+), 11 deletions(-) create mode 100644 integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala create mode 100644 integration-tests/src/test/java/play/libs/ws/ahc/StandaloneWSClientSupport.scala create mode 100644 play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java diff --git a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala new file mode 100644 index 00000000..0f121bc5 --- /dev/null +++ b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2009-2017 Lightbend Inc. + */ +package play.libs.ws.ahc + +import akka.http.scaladsl.server.Route +import org.specs2.concurrent.{ ExecutionEnv, FutureAwait } +import org.specs2.mutable.Specification + +import play.AkkaServerProvider +import play.libs.ws.{ DefaultBodyWritables, WSAuthScheme, WSAuthInfo } + +import uk.org.lidalia.slf4jext.Level +import uk.org.lidalia.slf4jtest.{ TestLogger, TestLoggerFactory } + +import scala.collection.JavaConverters._ +import scala.compat.java8.FutureConverters._ + +class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends Specification + with AkkaServerProvider + with StandaloneWSClientSupport + with FutureAwait + with DefaultBodyWritables { + + override def routes: Route = { + import akka.http.scaladsl.server.Directives._ + get { + complete("

Say hello to akka-http

") + } ~ + post { + entity(as[String]) { echo => + complete(echo) + } + } + } + + // Level.OFF because we don't want to pollute the test output + def createTestLogger: TestLogger = new TestLoggerFactory(Level.OFF).getLogger("test-logger") + + "Logging request as curl" should { + + "be verbose" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("--verbose") + } + + "add all headers" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .addHeader("My-Header", "My-Header-Value") + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + val messages = testLogger.getLoggingEvents.asScala.map(_.getMessage) + + messages must containMatch("My-Header") + messages must containMatch("My-Header-Value") + } + + "add method" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("--request GET") + } + + "add authorization header" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .setAuth(new WSAuthInfo("username", "password", WSAuthScheme.BASIC)) + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("""--header "Authorization: Basic""") + } + + "handle body" in { + + "add when in memory" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .setBody(body("the-body")) + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("the-body") + } + + "do nothing for empty bodies" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + // no body setBody, so body is "empty" + client.url(s"http://localhost:$testServerPort/") + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.asScala.map(_.getMessage) must not containMatch ("--data") + } + } + } +} diff --git a/integration-tests/src/test/java/play/libs/ws/ahc/StandaloneWSClientSupport.scala b/integration-tests/src/test/java/play/libs/ws/ahc/StandaloneWSClientSupport.scala new file mode 100644 index 00000000..a52d1db4 --- /dev/null +++ b/integration-tests/src/test/java/play/libs/ws/ahc/StandaloneWSClientSupport.scala @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009-2017 Lightbend Inc. + */ +package play.libs.ws.ahc + +import akka.stream.Materializer +import org.specs2.execute.Result +import play.api.libs.ws.ahc.{ AhcConfigBuilder, AhcWSClientConfig, AhcWSClientConfigFactory => ScalaAhcWSClientConfigFactory } +import play.shaded.ahc.org.asynchttpclient.DefaultAsyncHttpClient + +trait StandaloneWSClientSupport { + + def materializer: Materializer + + def withClient(config: AhcWSClientConfig = ScalaAhcWSClientConfigFactory.forConfig())(block: StandaloneAhcWSClient => Result): Result = { + val asyncHttpClient = new DefaultAsyncHttpClient(new AhcConfigBuilder(config).build()) + val client = new StandaloneAhcWSClient(asyncHttpClient, materializer) + try { + block(client) + } finally { + client.close() + } + } +} diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java new file mode 100644 index 00000000..aba9c8f7 --- /dev/null +++ b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2009-2017 Lightbend Inc. + */ +package play.libs.ws.ahc; + +import play.libs.ws.*; +import play.shaded.ahc.org.asynchttpclient.Request; +import play.shaded.ahc.org.asynchttpclient.proxy.ProxyServer; +import play.shaded.ahc.org.asynchttpclient.util.HttpUtils; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Optional; + +public class AhcCurlRequestLogger implements WSRequestFilter { + + private final org.slf4j.Logger logger; + + public AhcCurlRequestLogger(org.slf4j.Logger logger) { + this.logger = logger; + } + + public AhcCurlRequestLogger() { + this(org.slf4j.LoggerFactory.getLogger("play.libs.ws.ahc.AhcCurlRequestLogger")); + } + + @Override + public WSRequestExecutor apply(WSRequestExecutor requestExecutor) { + return request -> { + logger.info(toCurl((StandaloneAhcWSRequest)request)); + return requestExecutor.apply(request); + }; + } + + private String toCurl(StandaloneAhcWSRequest request) { + final StringBuilder b = new StringBuilder("curl \\\n"); + + // verbose, since it's a fair bet this is for debugging + b.append(" --verbose") + .append(" \\\n"); + + // method + b.append(" --request ").append(request.getMethod()) + .append(" \\\n"); + + //authentication + request.getAuth().ifPresent(auth -> { + String encodedPasswd = Base64.getUrlEncoder().encodeToString((auth.getUsername() + ":" + auth.getPassword()).getBytes(StandardCharsets.US_ASCII)); + b.append(" --header \"Authorization: Basic ").append(quote(encodedPasswd)) + .append(" \\\n"); + }); + + // headers + request.getHeaders().forEach((name, values) -> + values.forEach(v -> + b.append(" --header '").append(quote(name)).append(": ").append(quote(v)) + .append(" \\\n") + ) + ); + + // body + request.getBody().ifPresent(requestBody -> { + if (requestBody instanceof InMemoryBodyWritable) { + InMemoryBodyWritable inMemoryBody = (InMemoryBodyWritable) requestBody; + + String charset = findCharset(request); + String bodyString = inMemoryBody.body().get().decodeString(charset); + + b.append(" --data '").append(quote(bodyString)).append("'") + .append(" \\\n"); + } else { + throw new UnsupportedOperationException("Unsupported body type " + requestBody.getClass()); + } + }); + + // pull out some underlying values from the request. This creates a new Request + // but should be harmless. + Request ahcRequest = request.buildRequest(); + ProxyServer proxyServer = ahcRequest.getProxyServer(); + if (proxyServer != null) { + b.append(" --proxy ").append(proxyServer.getHost()).append(":").append(proxyServer.getPort()) + .append(" \\\n"); + } + + // url + b.append(" '").append(quote(ahcRequest.getUrl())).append("'"); + return b.toString(); + } + + private String findCharset(StandaloneAhcWSRequest request) { + return Optional.ofNullable(request.getContentType()) + .map(contentType -> + Optional.ofNullable(HttpUtils.parseCharset(contentType)) + .orElse(StandardCharsets.UTF_8).name() + ).orElse(StandardCharsets.UTF_8.name()); + } + + private String quote(String unsafe) { + return unsafe.replace("'", "'\\''"); + } + +} 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 69c8b686..06490494 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 @@ -20,8 +20,6 @@ import play.shaded.ahc.org.asynchttpclient.util.HttpUtils; import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -267,6 +265,11 @@ public List getCookies() { return new ArrayList<>(cookies); } + @Override + public Optional getBody() { + return Optional.ofNullable(this.bodyWritable); + } + @Override public Map> getHeaders() { return new HashMap<>(this.headers); @@ -382,8 +385,8 @@ private WSRequestExecutor foldRight(WSRequestExecutor executor, Iterator { // Detect and maybe add content type String contentType = possiblyModifiedHeaders.get(CONTENT_TYPE); if (contentType == null) { @@ -447,7 +448,7 @@ Request buildRequest() { } else { throw new IllegalStateException("Unknown body writable: " + bodyWritable); } - } + }); builder.setHeaders(possiblyModifiedHeaders); diff --git a/play-ws-standalone/src/main/java/play/libs/ws/BodyWritable.java b/play-ws-standalone/src/main/java/play/libs/ws/BodyWritable.java index 68e64f7d..7dd5b3bb 100644 --- a/play-ws-standalone/src/main/java/play/libs/ws/BodyWritable.java +++ b/play-ws-standalone/src/main/java/play/libs/ws/BodyWritable.java @@ -3,8 +3,6 @@ */ package play.libs.ws; -import java.util.function.Supplier; - /** * Writes out a {@code WSBody} * diff --git a/play-ws-standalone/src/main/java/play/libs/ws/InMemoryBodyWritable.java b/play-ws-standalone/src/main/java/play/libs/ws/InMemoryBodyWritable.java index dedd8de0..255746b2 100644 --- a/play-ws-standalone/src/main/java/play/libs/ws/InMemoryBodyWritable.java +++ b/play-ws-standalone/src/main/java/play/libs/ws/InMemoryBodyWritable.java @@ -7,7 +7,9 @@ import akka.util.ByteString; /** - * A body writable that takes a bytestring with InMemoryBody. + * A body writable that takes a ByteString with InMemoryBody. + * + * @see ByteString */ public class InMemoryBodyWritable implements BodyWritable { private final InMemoryBody body; 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 69a0d0df..8dfa4341 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 @@ -360,6 +360,11 @@ default List getCookies() { throw new UnsupportedOperationException(); } + /** + * @return the body of the request. + */ + Optional getBody(); + /** * @return the headers (a copy to prevent side-effects). This has not passed through an internal request builder and so will not be signed. */ From 8e9e9b83d0b8dde3bcf897b0a0e78335825911bd Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 17:18:52 -0300 Subject: [PATCH 05/14] Remove unused cache stub --- .../play/api/libs/ws/ahc/cache/CachingSpec.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala index 533b291b..7bb1a746 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/cache/CachingSpec.scala @@ -63,15 +63,3 @@ class CachingSpec(implicit val executionEnv: ExecutionEnv) extends Specification } } } - -class StubHttpCache(underlying: mutable.HashMap[EffectiveURIKey, ResponseEntry] = new mutable.HashMap()) extends Cache { - - override def remove(key: EffectiveURIKey): Future[Unit] = Future.successful(underlying.remove(key)) - - override def put(key: EffectiveURIKey, entry: ResponseEntry): Future[Unit] = Future.successful(underlying.put(key, entry)) - - override def get(key: EffectiveURIKey): Future[Option[ResponseEntry]] = Future.successful(underlying.get(key)) - - override def close(): Unit = {} - -} From 970a7130f8b94f7458bdfd2647a5ba570491cf00 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 17:48:38 -0300 Subject: [PATCH 06/14] Add test to check complete curl command --- .../ws/ahc/AhcCurlRequestLoggerSpec.scala | 27 +++++++++++++++++++ .../ws/ahc/AhcCurlRequestLoggerSpec.scala | 26 ++++++++++++++++++ .../libs/ws/ahc/AhcCurlRequestLogger.java | 4 +-- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index 0f121bc5..3ca00fb0 100644 --- a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -132,5 +132,32 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends testLogger.getLoggingEvents.asScala.map(_.getMessage) must not containMatch ("--data") } } + + "print complete curl command" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .setBody(body("the-body")) + .addHeader("My-Header", "My-Header-Value") + .setAuth(new WSAuthInfo("username", "password", WSAuthScheme.BASIC)) + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.get(0).getMessage must beEqualTo( + s""" + |curl \\ + | --verbose \\ + | --request GET \\ + | --header "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \\ + | --header 'My-Header: My-Header-Value' \\ + | --header 'Content-Type: text/plain' \\ + | --data 'the-body' \\ + | 'http://localhost:$testServerPort/' + """.stripMargin.trim) + } } } diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index 810b9a9d..78224dd9 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -123,5 +123,31 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends testLogger.getLoggingEvents.asScala.map(_.getMessage) must not containMatch ("--data") } } + + "print complete curl command" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .withBody("the-body") + .addHttpHeaders("My-Header" -> "My-Header-Value") + .withAuth("username", "password", WSAuthScheme.BASIC) + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + testLogger.getLoggingEvents.get(0).getMessage must beEqualTo( + s""" + |curl \\ + | --verbose \\ + | --request GET \\ + | --header "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \\ + | --header 'My-Header: My-Header-Value' \\ + | --header 'Content-Type: text/plain' \\ + | --data 'the-body' \\ + | 'http://localhost:$testServerPort/' + """.stripMargin.trim) + } } } diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java index aba9c8f7..d4e4577d 100644 --- a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java +++ b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java @@ -46,14 +46,14 @@ private String toCurl(StandaloneAhcWSRequest request) { //authentication request.getAuth().ifPresent(auth -> { String encodedPasswd = Base64.getUrlEncoder().encodeToString((auth.getUsername() + ":" + auth.getPassword()).getBytes(StandardCharsets.US_ASCII)); - b.append(" --header \"Authorization: Basic ").append(quote(encodedPasswd)) + b.append(" --header \"Authorization: Basic ").append(quote(encodedPasswd)).append("\"") .append(" \\\n"); }); // headers request.getHeaders().forEach((name, values) -> values.forEach(v -> - b.append(" --header '").append(quote(name)).append(": ").append(quote(v)) + b.append(" --header '").append(quote(name)).append(": ").append(quote(v)).append("'") .append(" \\\n") ) ); From 5cbd7410cb237ee5d5e60313c6c78cf83bdbd9ac Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 17:48:53 -0300 Subject: [PATCH 07/14] Add docs and remove unused code --- .../main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java | 5 +++++ .../play/api/libs/ws/ahc/AhcCurlRequestLogger.scala | 9 +++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java index d4e4577d..42c144f7 100644 --- a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java +++ b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java @@ -12,6 +12,11 @@ import java.util.Base64; import java.util.Optional; +/** + * Logs {@link StandaloneWSRequest} and pulls information into Curl format to an SLF4J logger. + * + * @see https://curl.haxx.se/ + */ public class AhcCurlRequestLogger implements WSRequestFilter { private final org.slf4j.Logger logger; diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala index 8b61f3d3..f5b4b52b 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala @@ -11,9 +11,11 @@ import play.api.libs.ws._ import play.shaded.ahc.org.asynchttpclient.util.HttpUtils import play.api.libs.ws.EmptyBody /** - * Logs WSRequest and pulls information into Curl format to an SLF4J logger. + * Logs [[StandaloneWSRequest]] and pulls information into Curl format to an SLF4J logger. * * @param logger an SLF4J logger + * + * @see https://curl.haxx.se/ */ class AhcCurlRequestLogger(logger: org.slf4j.Logger) extends WSRequestFilter with CurlFormat { def apply(executor: WSRequestExecutor): WSRequestExecutor = { @@ -106,8 +108,3 @@ trait CurlFormat { def quote(unsafe: String): String = unsafe.replace("'", "'\\''") } - -/** - * Java API. - */ -object JavaCurlFormat extends CurlFormat \ No newline at end of file From 31c702c2b4916faee6434d6284c9af77a73d447a Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Tue, 25 Jul 2017 22:34:24 -0300 Subject: [PATCH 08/14] Java API does not set content-type when setting body --- .../test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index 3ca00fb0..6a2a27d3 100644 --- a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -154,7 +154,6 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends | --request GET \\ | --header "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \\ | --header 'My-Header: My-Header-Value' \\ - | --header 'Content-Type: text/plain' \\ | --data 'the-body' \\ | 'http://localhost:$testServerPort/' """.stripMargin.trim) From 3367285976aa2b6ec2d4fe2743bd8b038e3fe7ce Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Wed, 26 Jul 2017 10:26:08 -0300 Subject: [PATCH 09/14] Configure mima filters --- build.sbt | 16 +++++++++++++++- .../api/libs/ws/ahc/AhcCurlRequestLogger.scala | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 1add2950..9ce7623d 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,21 @@ lazy val mimaSettings = mimaDefaultSettings ++ Seq( mimaBinaryIssueFilters ++= Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSResponse.getBodyAsSource"), ProblemFilters.exclude[MissingClassProblem]("play.api.libs.ws.package$"), - ProblemFilters.exclude[MissingClassProblem]("play.api.libs.ws.package") + ProblemFilters.exclude[MissingClassProblem]("play.api.libs.ws.package"), + + // Implemented as a default method at the interface + ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSResponse.getBodyAsSource"), + // Added to have better parity between Java and Scala APIs + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSRequest.getBody"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSRequest.getAuth"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSRequest.getMethod"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSRequest.setAuth"), + + // Now have a default implementation at the interface + ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.getPassword"), + ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.getUsername"), + ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.setAuth"), + ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.getScheme") ) ) diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala index f5b4b52b..71d9b70f 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala @@ -11,7 +11,7 @@ import play.api.libs.ws._ import play.shaded.ahc.org.asynchttpclient.util.HttpUtils import play.api.libs.ws.EmptyBody /** - * Logs [[StandaloneWSRequest]] and pulls information into Curl format to an SLF4J logger. + * Logs StandaloneWSRequest and pulls information into Curl format to an SLF4J logger. * * @param logger an SLF4J logger * From f8201f0d380e172757d2bbc9d3056f617a323d0b Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Thu, 27 Jul 2017 11:54:44 -0300 Subject: [PATCH 10/14] Use withClient where possible --- .../play/libs/ws/ahc/AhcWSClientSpec.scala | 32 +++++-------------- .../libs/ws/ahc/AhcWSRequestFilterSpec.scala | 28 +++++----------- 2 files changed, 16 insertions(+), 44 deletions(-) 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 de06e5f9..e08b77cf 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 @@ -3,17 +3,12 @@ */ package play.libs.ws.ahc -import akka.actor.ActorSystem -import akka.http.scaladsl.Http import akka.http.scaladsl.server.Route -import akka.stream.ActorMaterializer import akka.stream.javadsl.Sink import akka.util.ByteString -import com.typesafe.config.ConfigFactory import org.specs2.concurrent.ExecutionEnv import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification -import org.specs2.specification.AfterAll import play.AkkaServerProvider import play.libs.ws._ @@ -22,17 +17,11 @@ import scala.concurrent.Future import scala.concurrent.duration._ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specification - with AkkaServerProvider - with AfterAll - with FutureMatchers - with XMLBodyWritables - with XMLBodyReadables { - - // Create the standalone WS client with no cache - val client = StandaloneAhcWSClient.create( - AhcWSClientConfigFactory.forConfig(ConfigFactory.load, this.getClass.getClassLoader), - materializer - ) + with AkkaServerProvider + with StandaloneWSClientSupport + with FutureMatchers + with XMLBodyWritables + with XMLBodyReadables { override val routes: Route = { import akka.http.scaladsl.server.Directives._ @@ -46,14 +35,9 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specifica } } - override def afterAll = { - client.close() - super.afterAll() - } - "play.libs.ws.ahc.StandaloneAhcWSClient" should { - "get successfully" in { + "get successfully" in withClient() { client => def someOtherMethod(string: String) = { new InMemoryBodyWritable(akka.util.ByteString.fromString(string), "text/plain") } @@ -62,7 +46,7 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specifica ).await(retries = 0, timeout = 5.seconds) } - "source successfully" in { + "source successfully" in withClient() { client => val future = toScala(client.url(s"http://localhost:$testServerPort").stream()) val result: Future[ByteString] = future.flatMap { response: StandaloneWSResponse => toScala(response.getBodyAsSource.runWith(Sink.head(), materializer)) @@ -71,7 +55,7 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specifica result must be_==(expected).await(retries = 0, timeout = 5.seconds) } - "round trip XML successfully" in { + "round trip XML successfully" in withClient() { client => val document = XML.fromString(""" | | hello 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 e4be8443..4d7661ce 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 @@ -5,25 +5,18 @@ package play.libs.ws.ahc import akka.http.scaladsl.model.{ ContentTypes, HttpEntity } import akka.http.scaladsl.model.headers.RawHeader -import com.typesafe.config.ConfigFactory +import akka.http.scaladsl.server.Route import org.specs2.concurrent.ExecutionEnv import org.specs2.matcher.FutureMatchers import org.specs2.mutable.Specification -import org.specs2.specification.AfterAll import play.AkkaServerProvider import scala.concurrent.duration._ import scala.compat.java8.FutureConverters -class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Specification with AkkaServerProvider with AfterAll with FutureMatchers { +class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Specification with AkkaServerProvider with StandaloneWSClientSupport with FutureMatchers { - // Create the standalone WS client with no cache - private val client = StandaloneAhcWSClient.create( - AhcWSClientConfigFactory.forConfig(ConfigFactory.load, this.getClass.getClassLoader), - materializer - ) - - override val routes = { + override val routes: Route = { import akka.http.scaladsl.server.Directives._ headerValueByName("X-Request-Id") { value => respondWithHeader(RawHeader("X-Request-Id", value)) { @@ -36,14 +29,9 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp } } - override def afterAll = { - client.close() - super.afterAll() - } - "setRequestFilter" should { - "execute with one request filter" in { + "work with one request filter" in withClient() { client => import scala.collection.JavaConverters._ val callList = new java.util.ArrayList[Integer]() val responseFuture = FutureConverters.toScala(client.url(s"http://localhost:$testServerPort") @@ -54,7 +42,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp }.await(retries = 0, timeout = 5.seconds) } - "stream with one request filter" in { + "stream with one request filter" in withClient() { client => import scala.collection.JavaConverters._ val callList = new java.util.ArrayList[Integer]() val responseFuture = FutureConverters.toScala(client.url(s"http://localhost:$testServerPort") @@ -65,7 +53,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp }.await(retries = 0, timeout = 5.seconds) } - "execute with three request filter" in { + "work with three request filter" in withClient() { client => import scala.collection.JavaConverters._ val callList = new java.util.ArrayList[Integer]() val responseFuture = FutureConverters.toScala(client.url(s"http://localhost:$testServerPort") @@ -78,7 +66,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp }.await(retries = 0, timeout = 5.seconds) } - "stream with three request filters" in { + "stream with three request filters" in withClient() { client => import scala.collection.JavaConverters._ val callList = new java.util.ArrayList[Integer]() val responseFuture = FutureConverters.toScala(client.url(s"http://localhost:$testServerPort") @@ -91,7 +79,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp }.await(retries = 0, timeout = 5.seconds) } - "allow filters to modify the executing request" in { + "should allow filters to modify the request" in withClient() { client => val appendedHeader = "X-Request-Id" val appendedHeaderValue = "someid" val responseFuture = FutureConverters.toScala(client.url(s"http://localhost:$testServerPort") From 594ed30a86cdff76076f603fe557f5be9b034db9 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Mon, 15 Jan 2018 18:22:29 -0200 Subject: [PATCH 11/14] Fixes after rebase --- .../play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala | 9 +++++---- .../api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala | 8 ++++---- .../test/scala/play/libs/ws/ahc/AhcWSClientSpec.scala | 10 +++++----- .../play/libs/ws/ahc/AhcWSRequestFilterSpec.scala | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index 6a2a27d3..b9669222 100644 --- a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -17,10 +17,10 @@ import scala.collection.JavaConverters._ import scala.compat.java8.FutureConverters._ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends Specification - with AkkaServerProvider - with StandaloneWSClientSupport - with FutureAwait - with DefaultBodyWritables { + with AkkaServerProvider + with StandaloneWSClientSupport + with FutureAwait + with DefaultBodyWritables { override def routes: Route = { import akka.http.scaladsl.server.Directives._ @@ -154,6 +154,7 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends | --request GET \\ | --header "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \\ | --header 'My-Header: My-Header-Value' \\ + | --header 'Content-Type: text/plain' \\ | --data 'the-body' \\ | 'http://localhost:$testServerPort/' """.stripMargin.trim) diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index 78224dd9..ca78f64b 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -14,10 +14,10 @@ import uk.org.lidalia.slf4jtest.{ TestLogger, TestLoggerFactory } import scala.collection.JavaConverters._ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends Specification - with AkkaServerProvider - with StandaloneWSClientSupport - with FutureAwait - with DefaultBodyWritables { + with AkkaServerProvider + with StandaloneWSClientSupport + with FutureAwait + with DefaultBodyWritables { override def routes: Route = { import akka.http.scaladsl.server.Directives._ 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 e08b77cf..1463f5d6 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 @@ -17,11 +17,11 @@ import scala.concurrent.Future import scala.concurrent.duration._ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv) extends Specification - with AkkaServerProvider - with StandaloneWSClientSupport - with FutureMatchers - with XMLBodyWritables - with XMLBodyReadables { + with AkkaServerProvider + with StandaloneWSClientSupport + with FutureMatchers + with XMLBodyWritables + with XMLBodyReadables { override val routes: Route = { import akka.http.scaladsl.server.Directives._ 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 4d7661ce..cf6fc43b 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 @@ -91,7 +91,7 @@ class AhcWSRequestFilterSpec(implicit val executionEnv: ExecutionEnv) extends Sp }.await(retries = 0, timeout = 5.seconds) } - "allow filters to modify the streaming request" in { + "allow filters to modify the streaming request" in withClient() { client => val appendedHeader = "X-Request-Id" val appendedHeaderValue = "someid" val responseFuture = FutureConverters.toScala(client.url(s"http://localhost:$testServerPort") From 790e6b839152b16eb25e7f4297e1b0d3703b91f5 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 9 Mar 2018 19:19:37 -0300 Subject: [PATCH 12/14] Fix tests --- .../test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala | 4 ++-- .../scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala | 4 ++-- .../src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index b9669222..71fbd7fb 100644 --- a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -97,7 +97,7 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends .toScala .awaitFor(defaultTimeout) - testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("""--header "Authorization: Basic""") + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("""--header 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ='""") } "handle body" in { @@ -152,7 +152,7 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends |curl \\ | --verbose \\ | --request GET \\ - | --header "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \\ + | --header 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=' \\ | --header 'My-Header: My-Header-Value' \\ | --header 'Content-Type: text/plain' \\ | --data 'the-body' \\ diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index ca78f64b..cf7f592f 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -90,7 +90,7 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends .get() .awaitFor(defaultTimeout) - testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("""--header "Authorization: Basic""") + testLogger.getLoggingEvents.asScala.map(_.getMessage) must containMatch("""--header 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ='""") } "handle body" in { @@ -142,7 +142,7 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends |curl \\ | --verbose \\ | --request GET \\ - | --header "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \\ + | --header 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=' \\ | --header 'My-Header: My-Header-Value' \\ | --header 'Content-Type: text/plain' \\ | --data 'the-body' \\ diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java index 42c144f7..6a3d7994 100644 --- a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java +++ b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java @@ -51,7 +51,7 @@ private String toCurl(StandaloneAhcWSRequest request) { //authentication request.getAuth().ifPresent(auth -> { String encodedPasswd = Base64.getUrlEncoder().encodeToString((auth.getUsername() + ":" + auth.getPassword()).getBytes(StandardCharsets.US_ASCII)); - b.append(" --header \"Authorization: Basic ").append(quote(encodedPasswd)).append("\"") + b.append(" --header 'Authorization: Basic ").append(quote(encodedPasswd)).append("'") .append(" \\\n"); }); From dde98f1134c05adf1d4b727eb49568a4f382e3b9 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 23 Mar 2018 16:27:22 -0300 Subject: [PATCH 13/14] Add cookies to curl logger --- .../ws/ahc/AhcCurlRequestLoggerSpec.scala | 24 +++++++++++++++---- .../ws/ahc/AhcCurlRequestLoggerSpec.scala | 21 +++++++++++++--- .../libs/ws/ahc/AhcCurlRequestLogger.java | 8 ++++++- .../libs/ws/ahc/AhcCurlRequestLogger.scala | 6 +++++ .../play/libs/ws/ahc/AhcWSRequestSpec.scala | 2 +- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index 71fbd7fb..a95d17ae 100644 --- a/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/java/play/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -6,10 +6,8 @@ package play.libs.ws.ahc import akka.http.scaladsl.server.Route import org.specs2.concurrent.{ ExecutionEnv, FutureAwait } import org.specs2.mutable.Specification - import play.AkkaServerProvider -import play.libs.ws.{ DefaultBodyWritables, WSAuthScheme, WSAuthInfo } - +import play.libs.ws.{ DefaultBodyWritables, DefaultWSCookie, WSAuthInfo, WSAuthScheme } import uk.org.lidalia.slf4jext.Level import uk.org.lidalia.slf4jtest.{ TestLogger, TestLoggerFactory } @@ -67,8 +65,24 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends val messages = testLogger.getLoggingEvents.asScala.map(_.getMessage) - messages must containMatch("My-Header") - messages must containMatch("My-Header-Value") + messages must containMatch("--header 'My-Header: My-Header-Value'") + } + + "add all cookies" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = new AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .addCookie(new DefaultWSCookie("cookie1", "value1", "localhost", "path", 10L, true, true)) + .setRequestFilter(curlRequestLogger) + .get() + .toScala + .awaitFor(defaultTimeout) + + val messages = testLogger.getLoggingEvents.asScala.map(_.getMessage) + + messages must containMatch("""--cookie 'cookie1=value1'""") } "add method" in withClient() { client => diff --git a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala index cf7f592f..d095645b 100644 --- a/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala +++ b/integration-tests/src/test/scala/play/api/libs/ws/ahc/AhcCurlRequestLoggerSpec.scala @@ -7,7 +7,7 @@ import akka.http.scaladsl.server.Route import org.specs2.concurrent.{ ExecutionEnv, FutureAwait } import org.specs2.mutable.Specification import play.AkkaServerProvider -import play.api.libs.ws.{ DefaultBodyWritables, EmptyBody, WSAuthScheme } +import play.api.libs.ws.{ DefaultBodyWritables, DefaultWSCookie, EmptyBody, WSAuthScheme } import uk.org.lidalia.slf4jext.Level import uk.org.lidalia.slf4jtest.{ TestLogger, TestLoggerFactory } @@ -62,8 +62,23 @@ class AhcCurlRequestLoggerSpec(implicit val executionEnv: ExecutionEnv) extends val messages = testLogger.getLoggingEvents.asScala.map(_.getMessage) - messages must containMatch("My-Header") - messages must containMatch("My-Header-Value") + messages must containMatch("--header 'My-Header: My-Header-Value'") + } + + "add all cookies" in withClient() { client => + + val testLogger = createTestLogger + val curlRequestLogger = AhcCurlRequestLogger(testLogger) + + client.url(s"http://localhost:$testServerPort/") + .addCookies(DefaultWSCookie("cookie1", "value1")) + .withRequestFilter(curlRequestLogger) + .get() + .awaitFor(defaultTimeout) + + val messages = testLogger.getLoggingEvents.asScala.map(_.getMessage) + + messages must containMatch("--cookie 'cookie1=value1'") } "add method" in withClient() { client => diff --git a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java index 6a3d7994..bcb74672 100644 --- a/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java +++ b/play-ahc-ws-standalone/src/main/java/play/libs/ws/ahc/AhcCurlRequestLogger.java @@ -26,7 +26,7 @@ public AhcCurlRequestLogger(org.slf4j.Logger logger) { } public AhcCurlRequestLogger() { - this(org.slf4j.LoggerFactory.getLogger("play.libs.ws.ahc.AhcCurlRequestLogger")); + this(org.slf4j.LoggerFactory.getLogger(AhcCurlRequestLogger.class)); } @Override @@ -63,6 +63,12 @@ private String toCurl(StandaloneAhcWSRequest request) { ) ); + // cookies + request.getCookies().forEach(cookie -> + b.append(" --cookie '").append(cookie.getName()).append("=").append(cookie.getValue()).append("'") + .append(" \\\n") + ); + // body request.getBody().ifPresent(requestBody -> { if (requestBody instanceof InMemoryBodyWritable) { diff --git a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala index 71d9b70f..8d0bd926 100644 --- a/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala +++ b/play-ahc-ws-standalone/src/main/scala/play/api/libs/ws/ahc/AhcCurlRequestLogger.scala @@ -69,6 +69,12 @@ trait CurlFormat { } } + // cookies + request.cookies.foreach { cookie => + b.append(s""" --cookie '${cookie.name}=${cookie.value}'""") + b.append(" \\\n") + } + // body (note that this has only been checked for text, not binary) request.body match { case (InMemoryBody(byteString)) => 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 f743020e..b167fcee 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 @@ -41,7 +41,7 @@ class AhcWSRequestSpec extends Specification with Mockito with DefaultBodyReadab "get method" in { val client = mock[StandaloneAhcWSClient] val req = new StandaloneAhcWSRequest(client, "http://playframework.com/", null) - .setMethod("POST") + .setMethod("POST") req.getMethod must be_==("POST") } From 9f5054c93bd271a75d887534c469bfcddd5c0547 Mon Sep 17 00:00:00 2001 From: Marcos Pereira Date: Fri, 23 Mar 2018 18:16:04 -0300 Subject: [PATCH 14/14] Move all mima filters to the same place --- build.sbt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 9ce7623d..4ef55170 100644 --- a/build.sbt +++ b/build.sbt @@ -42,6 +42,7 @@ lazy val mimaSettings = mimaDefaultSettings ++ Seq( // Implemented as a default method at the interface ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSResponse.getBodyAsSource"), + // Added to have better parity between Java and Scala APIs ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSRequest.getBody"), ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSRequest.getAuth"), @@ -52,7 +53,11 @@ lazy val mimaSettings = mimaDefaultSettings ++ Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.getPassword"), ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.getUsername"), ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.setAuth"), - ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.getScheme") + ProblemFilters.exclude[DirectMissingMethodProblem]("play.libs.ws.ahc.StandaloneAhcWSRequest.getScheme"), + + // Add getUri method + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSResponse.getUri"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.libs.ws.StandaloneWSResponse.uri") ) ) @@ -283,10 +288,6 @@ lazy val `play-ws-standalone` = project .in(file("play-ws-standalone")) .settings(commonSettings ++ Seq( libraryDependencies ++= standaloneApiWSDependencies, - mimaBinaryIssueFilters ++= Seq( - ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSResponse.getUri"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.libs.ws.StandaloneWSResponse.uri") - ), mimaPreviousArtifacts := mimaPreviousArtifactFor(scalaVersion.value, "com.typesafe.play" %% "play-ws-standalone" % "1.0.0")) ) .disablePlugins(sbtassembly.AssemblyPlugin) @@ -318,9 +319,6 @@ lazy val `play-ahc-ws-standalone` = project Tests.Argument(TestFrameworks.JUnit, "-a", "-v")), libraryDependencies ++= standaloneAhcWSDependencies, mimaPreviousArtifacts := mimaPreviousArtifactFor(scalaVersion.value, "com.typesafe.play" %% "play-ahc-ws-standalone" % "1.0.0"), - mimaBinaryIssueFilters ++= Seq( - ProblemFilters.exclude[DirectMissingMethodProblem]( - "play.libs.ws.ahc.StandaloneAhcWSResponse.getBodyAsSource")), // This will not work if you do a publishLocal, because that uses ivy... pomPostProcess := { (node: xml.Node) => addShadedDeps(List(