Skip to content

Commit

Permalink
Java version of curl request filter (#178)
Browse files Browse the repository at this point in the history
* Remove duplicated logic/setup from tests

Additionally, by using AkkaServerProvider, we are able to
remove the need to run sequentially.

* Add tests to curl-logger request filter

* Add structured auth information to Java API

* 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.

* Remove unused cache stub

* Add test to check complete curl command

* Add docs and remove unused code

* Java API does not set content-type when setting body

* Configure mima filters

* Use withClient where possible

* Fixes after rebase

* Fix tests

* Add cookies to curl logger

* Move all mima filters to the same place
  • Loading branch information
marcospereira authored Jun 14, 2018
1 parent da16f21 commit 7d186f5
Show file tree
Hide file tree
Showing 17 changed files with 676 additions and 184 deletions.
28 changes: 20 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,26 @@ 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"),

// Add getUri method
ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSResponse.getUri"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.libs.ws.StandaloneWSResponse.uri")
)
)

Expand Down Expand Up @@ -269,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)
Expand Down Expand Up @@ -304,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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright (C) 2009-2017 Lightbend Inc. <https://www.lightbend.com>
*/
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, DefaultWSCookie, WSAuthInfo, WSAuthScheme }
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("<h1>Say hello to akka-http</h1>")
} ~
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("--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 =>

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 dXNlcm5hbWU6cGFzc3dvcmQ='""")
}

"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")
}
}

"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)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2009-2017 Lightbend Inc. <https://www.lightbend.com>
*/
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()
}
}
}
Loading

0 comments on commit 7d186f5

Please sign in to comment.