Skip to content

Commit

Permalink
Merge pull request #431 from marcospereira/backports/2.1.x/prepare-2.1.2
Browse files Browse the repository at this point in the history
[2.1.x]: Backports and play-json update
  • Loading branch information
marcospereira authored Dec 12, 2019
2 parents 8a1dfdd + 2d34dd6 commit 735b382
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 78 deletions.
105 changes: 80 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,69 @@ import play.api.libs.ws.JsonBodyWritables._
To use a BodyReadable in a response, you must type the response explicitly:

```scala
val responseBody: Future[scala.xml.Elem] = ws.url(...).get().map { response =>
response.body[scala.xml.Elem]
}
import scala.concurrent.{ ExecutionContext, Future }

import play.api.libs.ws.StandaloneWSClient
import play.api.libs.ws.XMLBodyReadables._ // required

def handleXml(ws: StandaloneWSClient)(
implicit ec: ExecutionContext): Future[scala.xml.Elem] =
ws.url("...").get().map { response =>
response.body[scala.xml.Elem]
}
```

or using Play-JSON:

```scala
val jsonBody: Future[JsValue] = ws.url(...).get().map { response =>
response.body[JsValue]
}
import scala.concurrent.{ ExecutionContext, Future }

import play.api.libs.json.JsValue
import play.api.libs.ws.StandaloneWSClient

import play.api.libs.ws.JsonBodyReadables._ // required

def handleJsonResp(ws: StandaloneWSClient)(
implicit ec: ExecutionContext): Future[JsValue] =
ws.url("...").get().map { response =>
response.body[JsValue]
}
```

Note that there is a special case: when you are streaming the response, then you should get the body as a Source:

```scala
ws.url(...).stream().map { response =>
val source: Source[ByteString, NotUsed] = response.bodyAsSource
}
import scala.concurrent.ExecutionContext
import akka.util.ByteString
import akka.stream.scaladsl.Source
import play.api.libs.ws.StandaloneWSClient

def useWSStream(ws: StandaloneWSClient)(implicit ec: ExecutionContext) =
ws.url("...").stream().map { response =>
val source: Source[ByteString, _] = response.bodyAsSource
val _ = source // do something with source
}
```

To POST, you should pass in a type which has an implicit class mapping of BodyWritable:

```scala
val stringData = "Hello world"
ws.url(...).post(stringData).map { response => ... }
import scala.concurrent.ExecutionContext
import play.api.libs.ws.DefaultBodyWritables._ // required

def postExampleString(ws: play.api.libs.ws.StandaloneWSClient)(
implicit ec: ExecutionContext) = {
val stringData = "Hello world"
ws.url("...").post(stringData).map { response => /* do something */ }
}
```

You can also define your own custom BodyReadable:

```scala
import play.api.libs.ws.BodyReadable
import play.api.libs.ws.ahc.StandaloneAhcWSResponse

case class Foo(body: String)

implicit val fooBodyReadable = BodyReadable[Foo] { response =>
Expand All @@ -114,9 +146,12 @@ implicit val fooBodyReadable = BodyReadable[Foo] { response =>
or custom BodyWritable:

```scala
import akka.util.ByteString
import play.api.libs.ws.{ BodyWritable, InMemoryBody }

implicit val writeableOf_Foo: BodyWritable[Foo] = {
// https://tools.ietf.org/html/rfc6838#section-3.2
BodyWritable(foo => InMemoryBody(ByteString.fromString(foo.serialize)), application/vnd.company.category+foo)
BodyWritable(foo => InMemoryBody(ByteString.fromString(foo.toString)), "application/vnd.company.category+foo")
}
```

Expand Down Expand Up @@ -234,10 +269,10 @@ object ScalaClient {
}

def call(wsClient: StandaloneWSClient): Future[Unit] = {
wsClient.url("http://www.google.com").get().map { response
wsClient.url("http://www.google.com").get().map { response =>
val statusText: String = response.statusText
val body = response.body[String]
println(s"Got a response $statusText")
println(s"Got a response $statusText: $body")
}
}
}
Expand Down Expand Up @@ -299,19 +334,39 @@ Play WS implements [HTTP Caching](https://tools.ietf.org/html/rfc7234) through C
To create a standalone AHC client that uses caching, pass in an instance of AhcHttpCache with a cache adapter to the underlying implementation. For example, to use Caffeine as the underlying cache, you could use the following:

```scala
import scala.concurrent.Future
import java.util.concurrent.TimeUnit
import com.github.benmanes.caffeine.cache.{ Caffeine, Ticker }

import play.api.libs.ws.ahc.StandaloneAhcWSClient
import play.api.libs.ws.ahc.cache.{
AhcHttpCache, Cache, EffectiveURIKey, ResponseEntry
}

class CaffeineHttpCache extends Cache {
val underlying = Caffeine.newBuilder()
.ticker(Ticker.systemTicker())
.expireAfterWrite(365, TimeUnit.DAYS)
.build[EffectiveURIKey, ResponseEntry]()

override def remove(key: EffectiveURIKey): Unit = underlying.invalidate(key)
override def put(key: EffectiveURIKey, entry: ResponseEntry): Unit = underlying.put(key, entry)
override def get(key: EffectiveURIKey): ResponseEntry = underlying.getIfPresent(key)
override def close(): Unit = underlying.cleanUp()
val underlying = Caffeine.newBuilder()
.ticker(Ticker.systemTicker())
.expireAfterWrite(365, TimeUnit.DAYS)
.build[EffectiveURIKey, ResponseEntry]()

def remove(key: EffectiveURIKey) =
Future.successful(Option(underlying.invalidate(key)))

def put(key: EffectiveURIKey, entry: ResponseEntry) =
Future.successful(underlying.put(key, entry))

def get(key: EffectiveURIKey) =
Future.successful(Option(underlying getIfPresent key ))

def close(): Unit = underlying.cleanUp()
}

def withCache(implicit m: akka.stream.Materializer): StandaloneAhcWSClient = {
implicit def ec = m.executionContext

val cache = new CaffeineHttpCache()
StandaloneAhcWSClient(httpCache = Some(new AhcHttpCache(cache)))
}
val cache = new CaffeineHttpCache()
val client = StandaloneAhcWSClient(httpCache = AhcHttpCache(cache))
```

There are a number of guides that help with putting together Cache-Control headers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import play.api.libs.ws.StandaloneWSRequest
* Compare your results before/after on your machine. Don't trust the ones in scaladoc.
*
* Sample benchmark results:
*
* {{{
* // not compilable
* > bench/jmh:run .*StandaloneAhcWSRequestBenchMapsBench
* [info] Benchmark (size) Mode Cnt Score Error Units
* [info] StandaloneAhcWSRequestBenchMapsBench.addHeaders 1 avgt 162.673 ns/op
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import akka.stream.Materializer
import akka.util.ByteString
import org.mockito.Mockito.{ times, verify, when }
import org.specs2.mock.Mockito
import org.specs2.mock.Mockito.mock

import org.specs2.mutable.Specification
import org.specs2.specification.AfterAll
import play.api.libs.json.{ JsString, JsValue, Json }
import play.api.libs.ws.{ JsonBodyReadables, JsonBodyWritables, StandaloneWSResponse }
import play.api.libs.ws.{ JsonBodyReadables, JsonBodyWritables }
import play.libs.ws.DefaultObjectMapper
import play.shaded.ahc.org.asynchttpclient.Response

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import play.shaded.oauth.oauth.signpost.exception.OAuthException
import play.shaded.ahc.org.asynchttpclient.oauth.OAuthSignatureCalculator
import play.shaded.ahc.org.asynchttpclient.{ Request, RequestBuilderBase, SignatureCalculator }
import play.api.libs.ws.WSSignatureCalculator
import play.shaded.ahc.org.asynchttpclient.oauth.{ ConsumerKey => AHCConsumerKey, RequestToken => AHCRequestToken }

import play.shaded.ahc.org.asynchttpclient.oauth.{
ConsumerKey => AHCConsumerKey,
RequestToken => AHCRequestToken
}

/**
* Library to access resources protected by OAuth 1.0a.
Expand Down Expand Up @@ -111,10 +115,24 @@ class OAuthCalculator(consumerKey: ConsumerKey, requestToken: RequestToken) exte
*
* Example:
* {{{
* import play.api.libs.oauth._
* val consumerKey: ConsumerKey = ConsumerKey(twitterConsumerKey, twitterConsumerSecret)
* val requestToken: RequestToken = RequestToken(accessTokenKey, accessTokenSecret)
* WS.url("http://example.com/protected").sign(OAuthCalculator(consumerKey, requestToken)).get()
* 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,19 @@ object StandaloneAhcWSClient {
* Typical usage:
*
* {{{
* import play.api.libs.ws.ahc.StandaloneAhcWSClient
*
* def example(someUrl: String)(implicit m: akka.stream.Materializer) = {
* implicit def ec = m.executionContext
*
* val client = StandaloneAhcWSClient()
* val request = client.url(someUrl).get()
*
* request.foreach { response =>
* doSomething(response)
* //doSomething(response)
* client.close()
* }
* }
* }}}
*
* @param config configuration settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ import play.shaded.ahc.org.asynchttpclient.HttpResponseBodyPart
* Note that this is only usable with a stream call, i.e.
*
* {{{
* class MyClass extends StreamedBodyReadable {
* ws.url("http://example.com").stream().map { response =>
* val source = response.body[Source[ByteString, NotUsed]]
* ...
* }
* import scala.concurrent.{ ExecutionContext, Future }
*
* import akka.util.ByteString
* import akka.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
* }
* }
* }}}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,31 @@ import scala.concurrent.Future
* Implementations can write adapters that map through to this trait, i.e.
*
* {{{
* import java.util.concurrent.TimeUnit
* import scala.concurrent.Future
*
* import com.github.benmanes.caffeine.cache.{ Caffeine, Ticker }
*
* import play.api.libs.ws.ahc.cache.{
* Cache, EffectiveURIKey, ResponseEntry
* }
*
* class CaffeineHttpCache extends Cache {
* val underlying = Caffeine.newBuilder()
* .ticker(Ticker.systemTicker())
* .expireAfterWrite(365, TimeUnit.DAYS)
* .build[EffectiveURIKey, ResponseEntry]()
*
* override def remove(key: EffectiveURIKey) = Future.successful(Option(underlying.invalidate(key))
* override def put(key: EffectiveURIKey, entry: ResponseEntry) = Future.successful(underlying.put(key, entry))
* override def get(key: EffectiveURIKey) = Future.successful(underlying.getIfPresent(key))
* override def close(): Unit = underlying.cleanUp()
* val underlying = Caffeine.newBuilder()
* .ticker(Ticker.systemTicker())
* .expireAfterWrite(365, TimeUnit.DAYS)
* .build[EffectiveURIKey, ResponseEntry]()
*
* def remove(key: EffectiveURIKey) =
* Future.successful(Option(underlying.invalidate(key)))
*
* def put(key: EffectiveURIKey, entry: ResponseEntry) =
* Future.successful(underlying.put(key, entry))
*
* def get(key: EffectiveURIKey) =
* Future.successful(Option(underlying getIfPresent key ))
*
* def close(): Unit = underlying.cleanUp()
* }
* }}}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@

package play.api.libs.ws.ahc

import scala.collection.JavaConverters._
import scala.concurrent.duration._

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.Materializer
import akka.util.ByteString

import org.specs2.execute.Result
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
import org.specs2.specification.AfterAll

import play.api.libs.oauth.{ ConsumerKey, RequestToken, 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, Param, Request => AHCRequest }

import scala.collection.JavaConverters._
import scala.concurrent.duration._

class AhcWSRequestSpec extends Specification with Mockito with AfterAll with DefaultBodyReadables with DefaultBodyWritables {

sequential
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ trait JsonBodyReadables {
* Converts a response body into Play JSON format:
*
* {{{
* val json = response.body[play.api.libs.json.JsValue]
* import play.api.libs.ws.StandaloneWSResponse
* import play.api.libs.ws.JsonBodyReadables._
*
* def json(r: StandaloneWSResponse) = r.body[play.api.libs.json.JsValue]
* }}}
*/
implicit val readableAsJson: BodyReadable[JsValue] = BodyReadable { response =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ trait XMLBodyReadables {
* Converts a response body into XML document:
*
* {{{
* val xml = response.body[scala.xml.Elem]
* import scala.xml.Elem
*
* import play.api.libs.ws.StandaloneWSResponse
* import play.api.libs.ws.XMLBodyReadables._
*
* def foo(resp: StandaloneWSResponse): Elem = resp.body[Elem]
* }}}
*/
implicit val readableAsXml: BodyReadable[Elem] = BodyReadable { response =>
Expand Down
Loading

0 comments on commit 735b382

Please sign in to comment.