diff --git a/documentation/manual/working/javaGuide/main/index.toc b/documentation/manual/working/javaGuide/main/index.toc index 8248f4bee48..8c581467f8e 100644 --- a/documentation/manual/working/javaGuide/main/index.toc +++ b/documentation/manual/working/javaGuide/main/index.toc @@ -8,7 +8,7 @@ xml:Working with XML upload:Handling file upload sql:Accessing an SQL database cache:Using the Cache -ws:Calling WebServices +ws:Calling REST APIs with Play WS akka:Integrating with Akka i18n:Internationalization with Messages dependencyinjection:Dependency Injection diff --git a/documentation/manual/working/javaGuide/main/ws/JavaWS.md b/documentation/manual/working/javaGuide/main/ws/JavaWS.md index b30244172cf..e0c59df578f 100644 --- a/documentation/manual/working/javaGuide/main/ws/JavaWS.md +++ b/documentation/manual/working/javaGuide/main/ws/JavaWS.md @@ -1,5 +1,5 @@ -# The Play WS API +# Calling REST APIs with Play WS Sometimes we would like to call other HTTP services from within a Play application. Play supports this via its [WS library](api/java/play/libs/ws/package-summary.html), which provides a way to make asynchronous HTTP calls. @@ -32,7 +32,7 @@ Using an HTTP cache means savings on repeated requests to backend REST services, Now any controller or component that wants to use WS will have to add the following imports and then declare a dependency on the [`WSClient`](api/java/play/libs/ws/WSClient.html) type to use dependency injection: -@[ws-controller](code/javaguide/ws/Application.java) +@[ws-controller](code/javaguide/ws/MyClient.java) > If you are calling out to an [unreliable network](https://queue.acm.org/detail.cfm?id=2655736) or doing any blocking work, including any kind of DNS work such as calling [`java.util.URL.equals()`](https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#equals-java.lang.Object-), then you should use a custom execution context as described in [[ThreadPools]]. You should size the pool to leave a safety margin large enough to account for futures, and consider using [`play.libs.concurrent.Futures.timeout`](api/java/play/libs/concurrent/Futures.html) and a [Failsafe Circuit Breaker](https://github.com/jhalterman/failsafe#circuit-breakers). @@ -78,9 +78,15 @@ For example, if you are sending plain text in a particular format, you may want @[ws-header-content-type](code/javaguide/ws/JavaWS.java) +### Request with cookie + +You can specify cookies for a request. + +@[ws-cookie](code/javaguide/ws/JavaWS.java) + ### Request with timeout -If you wish to specify a request timeout, you can use `setRequestTimeout` to set a value in milliseconds. A value of `-1` can be used to set an infinite timeout. +If you wish to specify a request timeout, you can use `setRequestTimeout` to set a value in milliseconds. A value of `Duration.ofMillis(Long.MAX_VALUE)` can be used to set an infinite timeout. @[ws-timeout](code/javaguide/ws/JavaWS.java) @@ -92,7 +98,7 @@ To post url-form-encoded data you can set the proper header and formatted data. ### Submitting JSON data -The easiest way to post JSON data is to use the [[JSON library|JavaJsonActions]]. +The easiest way to post JSON data is to use the [[JSON library|JavaJsonActions]] with the WSBodyWritables `body(JsonNode)` method. @[json-imports](code/javaguide/ws/JavaWS.java) @@ -100,13 +106,13 @@ The easiest way to post JSON data is to use the [[JSON library|JavaJsonActions]] ### Submitting multipart/form data -The easiest way to post multipart/form data is to use a `Source, ?>, ?>` +The easiest way to post multipart/form data is to use a `Source, ?>, ?>` using `multipartBody()` @[multipart-imports](code/javaguide/ws/JavaWS.java) @[ws-post-multipart](code/javaguide/ws/JavaWS.java) -To Upload a File you need to pass a `Http.MultipartFormData.FilePart, ?>` to the `Source`: +To upload a File you need to pass a `Http.MultipartFormData.FilePart, ?>` to the `Source`: @[ws-post-multipart2](code/javaguide/ws/JavaWS.java) @@ -132,14 +138,13 @@ Working with the [`WSResponse`](api/java/play/libs/ws/WSResponse.html) is done b ### Processing a response as JSON -You can process the response as a `JsonNode` by calling `response.asJson()`. +You can process the response as a `JsonNode` by calling `response.getBody(json())`, with the `json()` method provided by `WSBodyReadables`. @[ws-response-json](code/javaguide/ws/JavaWS.java) - ### Processing a response as XML -Similarly, you can process the response as XML by calling `response.asXml()`. +Similarly, you can process the response as XML by calling `response.getBody(xml())` with the `xml()` method provided by `WSBodyReadables`. @[ws-response-xml](code/javaguide/ws/JavaWS.java) @@ -147,7 +152,7 @@ Similarly, you can process the response as XML by calling `response.asXml()`. Calling `get()`, `post()` or `execute()` will cause the body of the response to be loaded into memory before the response is made available. When you are downloading a large, multi-gigabyte file, this may result in unwelcomed garbage collection or even out of memory errors. -`WS` lets you consume the response's body incrementally by using an Akka Streams `Sink`. The `stream()` method on `WSRequest` returns a `CompletionStage`. A `StreamedResponse` is a simple container holding together the response's headers and body. +You can consume the response's body incrementally by using an [Akka Streams](http://doc.akka.io/docs/akka/current/scala/stream/stream-flows-and-basics.html) `Sink`. The `stream()` method on `WSRequest` returns a `CompletionStage`, where the `WSResponse` contains a `bodyAsSource` method that provides a source of `ByteStream`. Any controller or component that wants to leverage the WS streaming functionality will have to add the following imports and dependencies: diff --git a/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/JavaWS.java b/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/JavaWS.java index d986cfb531b..1bff67c16d9 100644 --- a/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/JavaWS.java +++ b/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/JavaWS.java @@ -13,6 +13,8 @@ import play.libs.concurrent.Futures; import play.libs.ws.*; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.concurrent.*; // #ws-imports @@ -48,7 +50,7 @@ public class JavaWS { private static final String feedUrl = "http://localhost:3333/feed"; - public static class Controller0 extends MockJavaAction { + public static class Controller0 extends MockJavaAction implements WSBodyReadables, WSBodyWritables { private final WSClient ws; private final Materializer materializer; @@ -66,9 +68,9 @@ public void requestExamples() { // #ws-holder // #ws-complex-holder - WSRequest complexRequest = request.setHeader("headerKey", "headerValue") - .setRequestTimeout(1000) - .setQueryParameter("paramKey", "paramValue"); + WSRequest complexRequest = request.addHeader("headerKey", "headerValue") + .setRequestTimeout(Duration.of(1000, ChronoUnit.MILLIS)) + .addQueryParameter("paramKey", "paramValue"); // #ws-complex-holder // #ws-get @@ -85,27 +87,31 @@ public void requestExamples() { // #ws-follow-redirects // #ws-query-parameter - ws.url(url).setQueryParameter("paramKey", "paramValue"); + ws.url(url).addQueryParameter("paramKey", "paramValue"); // #ws-query-parameter // #ws-header - ws.url(url).setHeader("headerKey", "headerValue").get(); + ws.url(url).addHeader("headerKey", "headerValue").get(); // #ws-header + // #ws-cookie + ws.url(url).addCookies(new WSCookieBuilder().setName("headerKey").setValue("headerValue").build()).get(); + // #ws-cookie + String jsonString = "{\"key1\":\"value1\"}"; // #ws-header-content-type - ws.url(url).setHeader("Content-Type", "application/json").post(jsonString); + ws.url(url).addHeader("Content-Type", "application/json").post(body(jsonString)); // OR - ws.url(url).setContentType("application/json").post(jsonString); + ws.url(url).setContentType("application/json").post(body(jsonString)); // #ws-header-content-type // #ws-timeout - ws.url(url).setRequestTimeout(1000).get(); + ws.url(url).setRequestTimeout(Duration.of(1000, ChronoUnit.MILLIS)).get(); // #ws-timeout // #ws-post-form-data ws.url(url).setContentType("application/x-www-form-urlencoded") - .post("key1=value1&key2=value2"); + .post(body("key1=value1&key2=value2")); // #ws-post-form-data // #ws-post-json @@ -113,11 +119,11 @@ public void requestExamples() { .put("key1", "value1") .put("key2", "value2"); - ws.url(url).post(json); + ws.url(url).post(body(json)); // #ws-post-json // #ws-post-multipart - ws.url(url).post(Source.single(new DataPart("hello", "world"))); + ws.url(url).post(multipartBody(Source.single(new DataPart("hello", "world")))); // #ws-post-multipart // #ws-post-multipart2 @@ -125,7 +131,7 @@ public void requestExamples() { FilePart> fp = new FilePart<>("hello", "hello.txt", "text/plain", file); DataPart dp = new DataPart("key", "value"); - ws.url(url).post(Source.from(Arrays.asList(fp, dp))); + ws.url(url).post(multipartBody(Source.from(Arrays.asList(fp, dp)))); // #ws-post-multipart2 String value = IntStream.range(0,100).boxed(). @@ -134,7 +140,7 @@ public void requestExamples() { Stream largeSource = IntStream.range(0,10).boxed().map(i -> seedValue); Source largeImage = Source.from(largeSource.collect(Collectors.toList())); // #ws-stream-request - CompletionStage wsResponse = ws.url(url).setBody(largeImage).execute("PUT"); + CompletionStage wsResponse = ws.url(url).setBody(body(largeImage)).execute("PUT"); // #ws-stream-request } @@ -143,13 +149,15 @@ public void responseExamples() { String url = "http://example.com"; // #ws-response-json + // implements WSBodyReadables or use WSBodyReadables.instance.json() CompletionStage jsonPromise = ws.url(url).get() - .thenApply(WSResponse::asJson); + .thenApply(r -> r.getBody(json())); // #ws-response-json // #ws-response-xml + // implements WSBodyReadables or use WSBodyReadables.instance.xml() CompletionStage documentPromise = ws.url(url).get() - .thenApply(WSResponse::asXml); + .thenApply(r -> r.getBody(xml())); // #ws-response-xml } @@ -157,11 +165,11 @@ public void streamSimpleRequest() { String url = "http://example.com"; // #stream-count-bytes // Make the request - CompletionStage futureResponse = + CompletionStage futureResponse = ws.url(url).setMethod("GET").stream(); CompletionStage bytesReturned = futureResponse.thenCompose(res -> { - Source responseBody = res.getBody(); + Source responseBody = res.getBodyAsSource(); // Count the number of bytes returned Sink> bytesSum = @@ -179,11 +187,11 @@ public void streamFile() throws IOException, FileNotFoundException, InterruptedE OutputStream outputStream = java.nio.file.Files.newOutputStream(file.toPath()); // Make the request - CompletionStage futureResponse = + CompletionStage futureResponse = ws.url(url).setMethod("GET").stream(); CompletionStage downloadedFile = futureResponse.thenCompose(res -> { - Source responseBody = res.getBody(); + Source responseBody = res.getBodyAsSource(); // The sink that writes to the output stream Sink> outputWriter = @@ -208,21 +216,20 @@ public void streamResponse() { String url = "http://example.com"; //#stream-to-result // Make the request - CompletionStage futureResponse = ws.url(url).setMethod("GET").stream(); + CompletionStage futureResponse = ws.url(url).setMethod("GET").stream(); CompletionStage result = futureResponse.thenApply(response -> { - WSResponseHeaders responseHeaders = response.getHeaders(); - Source body = response.getBody(); + Source body = response.getBodyAsSource(); // Check that the response was successful - if (responseHeaders.getStatus() == 200) { + if (response.getStatus() == 200) { // Get the content type String contentType = - Optional.ofNullable(responseHeaders.getHeaders().get("Content-Type")) + Optional.ofNullable(response.getHeaders().get("Content-Type")) .map(contentTypes -> contentTypes.get(0)) .orElse("application/octet-stream"); // If there's a content length, send that, otherwise return the body chunked - Optional contentLength = Optional.ofNullable(responseHeaders.getHeaders() + Optional contentLength = Optional.ofNullable(response.getHeaders() .get("Content-Length")) .map(contentLengths -> contentLengths.get(0)); if (contentLength.isPresent()) { @@ -244,21 +251,21 @@ public void streamResponse() { public void streamPut() { String url = "http://example.com"; //#stream-put - CompletionStage futureResponse = - ws.url(url).setMethod("PUT").setBody("some body").stream(); + CompletionStage futureResponse = + ws.url(url).setMethod("PUT").setBody(body("some body")).stream(); //#stream-put } public void patternExamples() { String urlOne = "http://localhost:3333/one"; // #ws-composition - final CompletionStage responseThreePromise = ws.url(urlOne).get() + final CompletionStage responseThreePromise = ws.url(urlOne).get() .thenCompose(responseOne -> ws.url(responseOne.getBody()).get()) .thenCompose(responseTwo -> ws.url(responseTwo.getBody()).get()); // #ws-composition // #ws-recover - CompletionStage responsePromise = ws.url("http://example.com").get(); + CompletionStage responsePromise = ws.url("http://example.com").get(); responsePromise.handle((result, error) -> { if (error != null) { return ws.url("http://backup.example.com").get(); @@ -316,13 +323,13 @@ public Controller1(JavaHandlerComponents javaHandlerComponents, WSClient client) // #ws-action public CompletionStage index() { return ws.url(feedUrl).get().thenApply(response -> - ok("Feed title: " + response.asJson().findPath("title").asText()) + ok("Feed title: " + response.getBody(WSBodyReadables.instance.json()).findPath("title").asText()) ); } // #ws-action } - public static class Controller2 extends MockJavaAction { + public static class Controller2 extends MockJavaAction implements WSBodyWritables, WSBodyReadables { private final WSClient ws; @@ -335,13 +342,13 @@ public Controller2(JavaHandlerComponents javaHandlerComponents, WSClient ws) { // #composed-call public CompletionStage index() { return ws.url(feedUrl).get() - .thenCompose(response -> ws.url(response.asJson().findPath("commentsUrl").asText()).get()) - .thenApply(response -> ok("Number of comments: " + response.asJson().findPath("count").asInt())); + .thenCompose(response -> ws.url(response.getBody(json()).findPath("commentsUrl").asText()).get()) + .thenApply(response -> ok("Number of comments: " + response.getBody(json()).findPath("count").asInt())); } // #composed-call } - public static class Controller3 extends MockJavaAction { + public static class Controller3 extends MockJavaAction implements WSBodyWritables, WSBodyReadables { private final WSClient ws; private Logger logger; @@ -364,9 +371,13 @@ public CompletionStage index() { return executor.apply(request); }; - return ws.url(feedUrl).setRequestFilter(filter).get().thenApply(response -> - ok("Feed title: " + response.asJson().findPath("title").asText()) - ); + return ws.url(feedUrl) + .setRequestFilter(filter) + .get() + .thenApply((WSResponse r) -> { + String title = r.getBody(json()).findPath("title").asText(); + return ok("Feed title: " + title); + }); } // #ws-request-filter } diff --git a/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/Application.java b/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/MyClient.java similarity index 60% rename from documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/Application.java rename to documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/MyClient.java index 7070c6f5418..045d34b67d9 100644 --- a/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/Application.java +++ b/documentation/manual/working/javaGuide/main/ws/code/javaguide/ws/MyClient.java @@ -10,10 +10,13 @@ import play.libs.ws.*; import java.util.concurrent.CompletionStage; -public class Application extends Controller { - - @Inject WSClient ws; +public class MyClient implements WSBodyReadables, WSBodyWritables { + private final WSClient ws; + @Inject + public MyClient(WSClient ws) { + this.ws = ws; + } // ... } // #ws-controller diff --git a/documentation/manual/working/scalaGuide/main/http/code/ScalaBodyParsers.scala b/documentation/manual/working/scalaGuide/main/http/code/ScalaBodyParsers.scala index 4b39246fc47..d58d1290d52 100644 --- a/documentation/manual/working/scalaGuide/main/http/code/ScalaBodyParsers.scala +++ b/documentation/manual/working/scalaGuide/main/http/code/ScalaBodyParsers.scala @@ -26,7 +26,7 @@ import org.specs2.execute.AsResult "parse request as json" in { import scala.concurrent.ExecutionContext.Implicits.global //#access-json-body - def save = Action { request => + def save = Action { request: Request[AnyContent] => val body: AnyContent = request.body val jsonBody: Option[JsValue] = body.asJson @@ -43,7 +43,7 @@ import org.specs2.execute.AsResult "body parser json" in { //#body-parser-json - def save = Action(parse.json) { request => + def save = Action(parse.json) { request: Request[JsValue] => Ok("Got: " + (request.body \ "name").as[String]) } //#body-parser-json @@ -52,7 +52,7 @@ import org.specs2.execute.AsResult "body parser tolerantJson" in { //#body-parser-tolerantJson - def save = Action(parse.tolerantJson) { request => + def save = Action(parse.tolerantJson) { request: Request[JsValue] => Ok("Got: " + (request.body \ "name").as[String]) } //#body-parser-tolerantJson @@ -61,7 +61,7 @@ import org.specs2.execute.AsResult "body parser file" in { //#body-parser-file - def save = Action(parse.file(to = new File("/tmp/upload"))) { request => + def save = Action(parse.file(to = new File("/tmp/upload"))) { request: Request[File] => Ok("Saved the request content to " + request.body) } //#body-parser-file @@ -77,7 +77,7 @@ import org.specs2.execute.AsResult val text = "hello" //#body-parser-limit-text // Accept only 10KB of data. - def save = Action(parse.text(maxLength = 1024 * 10)) { request => + def save = Action(parse.text(maxLength = 1024 * 10)) { request: Request[String] => Ok("Got: " + text) } //#body-parser-limit-text @@ -115,7 +115,7 @@ import org.specs2.execute.AsResult def forward(request: WSRequest): BodyParser[WSResponse] = BodyParser { req => Accumulator.source[ByteString].mapFuture { source => request - .withBody(StreamedBody(source)) + .withBody(source) .execute() .map(Right.apply) } diff --git a/documentation/manual/working/scalaGuide/main/index.toc b/documentation/manual/working/scalaGuide/main/index.toc index a216ca56203..e26259c3461 100644 --- a/documentation/manual/working/scalaGuide/main/index.toc +++ b/documentation/manual/working/scalaGuide/main/index.toc @@ -9,7 +9,7 @@ xml:Working with XML upload:Handling file upload sql:Accessing an SQL database cache:Using the Cache -ws:Calling WebServices +ws:Calling REST APIs with Play WS akka:Integrating with Akka i18n:Internationalization with Messages dependencyinjection:Dependency injection diff --git a/documentation/manual/working/scalaGuide/main/ws/ScalaWS.md b/documentation/manual/working/scalaGuide/main/ws/ScalaWS.md index 882c9f3857f..6ca615a060a 100644 --- a/documentation/manual/working/scalaGuide/main/ws/ScalaWS.md +++ b/documentation/manual/working/scalaGuide/main/ws/ScalaWS.md @@ -1,5 +1,5 @@ -# The Play WS API +# Calling REST APIs with Play WS Sometimes we would like to call other HTTP services from within a Play application. Play supports this via its [WS library](api/scala/play/api/libs/ws/), which provides a way to make asynchronous HTTP calls through a WSClient instance. @@ -51,6 +51,8 @@ You end by calling a method corresponding to the HTTP method you want to use. T This returns a `Future[WSResponse]` where the [Response](api/scala/play/api/libs/ws/WSResponse.html) contains the data returned from the server. +WSRequest extends [`play.api.libs.ws.WSBodyWritables`](api/scala/play/api/libs/ws/WSBodyWritables.html), which contains type classes for converting body input into a `ByteString`. You can create your own type classes if you would like to post, patch or put a custom type into the request. + ### Request with authentication If you need to use HTTP authentication, you can specify it in the builder, using a username, password, and an `AuthScheme`. Valid case objects for the AuthScheme are `BASIC`, `DIGEST`, `KERBEROS`, `NTLM`, and `SPNEGO`. @@ -65,13 +67,13 @@ If an HTTP call results in a 302 or a 301 redirect, you can automatically follow ### Request with query parameters -Parameters can be specified as a series of key/value tuples. +Parameters can be specified as a series of key/value tuples. Use `addQueryStringParameters` to add parameters, and `withQueryStringParameters` to overwrite all query string parameters. @[query-string](code/ScalaWSSpec.scala) ### Request with additional headers -Headers can be specified as a series of key/value tuples. +Headers can be specified as a series of key/value tuples. Use `addHttpHeaders` to append additional headers, and `withHttpHeaders` to overwrite all headers. @[headers](code/ScalaWSSpec.scala) @@ -79,6 +81,12 @@ If you are sending plain text in a particular format, you may want to define the @[content-type](code/ScalaWSSpec.scala) +### Request with cookies + +Cookies can be added to the request by using `DefaultWSCookie` or by passing through [`play.api.mvc.Cookie`](api/scala/play/api/mvc/Cookie.html). + +@[cookie](code/ScalaWSSpec.scala) + ### Request with virtual host A virtual host can be specified as a string. @@ -158,17 +166,15 @@ Whenever an operation is done on a `Future`, an implicit execution context must @[scalaws-context-injected](code/ScalaWSSpec.scala) -If you are not using DI, you can still access the default Play execution context: - -@[scalaws-context](code/ScalaWSSpec.scala) - The examples also use the following case class for serialization/deserialization: @[scalaws-person](code/ScalaWSSpec.scala) +The WSResponse extends [`play.api.libs.ws.WSBodyReadables`](api/scala/play/api/libs/ws/WSBodyReadables.html) trait, which contains type classes for Play JSON and Scala XML conversion. You can also create your own custom type classes if you would like to convert the response to your own types, or use a different JSON or XML encoding. + ### Processing a response as JSON -You can process the response as a [JSON object](https://oss.sonatype.org/service/local/repositories/public/archive/com/typesafe/play/play-json_2.12/2.6.0-M1/play-json_2.12-2.6.0-M1-javadoc.jar/!/play/api/libs/json/JsValue.html) by calling `response.json`. +You can process the response as a [JSON object](https://oss.sonatype.org/service/local/repositories/public/archive/com/typesafe/play/play-json_2.12/2.6.0-M1/play-json_2.12-2.6.0-M1-javadoc.jar/!/play/api/libs/json/JsValue.html) by calling `response.body[JsValue]`. @[scalaws-process-json](code/ScalaWSSpec.scala) @@ -178,7 +184,7 @@ The JSON library has a [[useful feature|ScalaJsonCombinators]] that will map an ### Processing a response as XML -You can process the response as an [XML literal](http://www.scala-lang.org/api/current/index.html#scala.xml.NodeSeq) by calling `response.xml`. +You can process the response as an [XML literal](http://www.scala-lang.org/api/current/index.html#scala.xml.NodeSeq) by calling `response.body[Elem]`. @[scalaws-process-xml](code/ScalaWSSpec.scala) @@ -186,7 +192,7 @@ You can process the response as an [XML literal](http://www.scala-lang.org/api/c Calling `get()`, `post()` or `execute()` will cause the body of the response to be loaded into memory before the response is made available. When you are downloading a large, multi-gigabyte file, this may result in unwelcomed garbage collection or even out of memory errors. -`WS` lets you consume the response's body incrementally by using an Akka Streams `Sink`. The `stream()` method on `WSRequest` returns a `Future[StreamedResponse]`. A `StreamedResponse` is a simple container holding together the response's headers and body. +`WS` lets you consume the response's body incrementally by using an Akka Streams `Sink`. The `stream()` method on `WSRequest` returns a streaming `WSResponse` which contains a `bodyAsSource` method that returns a `Source[ByteString, _]` Here is a trivial example that uses a folding `Sink` to count the number of bytes returned by the response: diff --git a/documentation/manual/working/scalaGuide/main/ws/code/ScalaWSSpec.scala b/documentation/manual/working/scalaGuide/main/ws/code/ScalaWSSpec.scala index 28a9c3d5672..2be1f67bbf7 100644 --- a/documentation/manual/working/scalaGuide/main/ws/code/ScalaWSSpec.scala +++ b/documentation/manual/working/scalaGuide/main/ws/code/ScalaWSSpec.scala @@ -12,6 +12,7 @@ import org.junit.runner.RunWith import org.specs2.runner.JUnitRunner import org.specs2.specification.AfterAll import play.api.libs.concurrent.Futures +import play.api.libs.json.JsValue //#dependency import javax.inject.Inject @@ -101,9 +102,9 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { //#complex-holder val complexRequest: WSRequest = - request.withHeaders("Accept" -> "application/json") + request.addHttpHeaders("Accept" -> "application/json") + .addQueryStringParameters("search" -> "play") .withRequestTimeout(10000.millis) - .withQueryString("search" -> "play") //#complex-holder //#holder-get @@ -137,7 +138,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { "allow setting a query string" in withSimpleServer { ws => val response = //#query-string - ws.url(url).withQueryString("paramKey" -> "paramValue").get() + ws.url(url).addQueryStringParameters("paramKey" -> "paramValue").get() //#query-string await(response).status must_== 200 @@ -146,7 +147,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { "allow setting headers" in withSimpleServer { ws => val response = //#headers - ws.url(url).withHeaders("headerKey" -> "headerValue").get() + ws.url(url).addHttpHeaders("headerKey" -> "headerValue").get() //#headers await(response).status must_== 200 @@ -156,12 +157,23 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { val xmlString = "" val response = //#content-type - ws.url(url).withHeaders("Content-Type" -> "application/xml").post(xmlString) + ws.url(url) + .addHttpHeaders("Content-Type" -> "application/xml") + .post(xmlString) //#content-type await(response).status must_== 200 } + "allow setting the cookie" in withSimpleServer { ws => + val response = + //#cookie + ws.url(url).addCookies(DefaultWSCookie("cookieName", "cookieValue")).get() + //#cookie + + await(response).status must_== 200 + } + "allow setting the virtual host" in withSimpleServer { ws => val response = //#virtual-host @@ -239,7 +251,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { val futureResponse: Future[WSResponse] = ws.url(url).post(data) // #scalaws-post-json - await(futureResponse).json must_== data + await(futureResponse).body[JsValue] must_== data } "post with XML data" in withServer { @@ -271,7 +283,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { // #scalaws-process-json val futureResult: Future[String] = ws.url(url).get().map { response => - (response.json \ "person" \ "name").as[String] + (response.body[JsValue] \ "person" \ "name").as[String] } // #scalaws-process-json @@ -292,7 +304,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { implicit val personReads = Json.reads[Person] val futureResult: Future[JsResult[Person]] = ws.url(url).get().map { - response => (response.json \ "person").validate[Person] + response => (response.body[JsValue] \ "person").validate[Person] } // #scalaws-process-json-with-implicit @@ -317,7 +329,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { // #scalaws-process-xml val futureResult: Future[scala.xml.NodeSeq] = ws.url(url).get().map { response => - response.xml \ "message" + response.body[scala.xml.Elem] \ "message" } // #scalaws-process-xml await(futureResult).text must_== "Hello" @@ -329,13 +341,13 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { } { ws => //#stream-count-bytes // Make the request - val futureResponse: Future[StreamedResponse] = + val futureResponse: Future[WSResponse] = ws.url(url).withMethod("GET").stream() val bytesReturned: Future[Long] = futureResponse.flatMap { res => // Count the number of bytes returned - res.body.runWith(Sink.fold[Long, ByteString](0L){ (total, bytes) => + res.bodyAsSource.runWith(Sink.fold[Long, ByteString](0L){ (total, bytes) => total + bytes.length }) } @@ -351,7 +363,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { try { //#stream-to-file // Make the request - val futureResponse: Future[StreamedResponse] = + val futureResponse: Future[WSResponse] = ws.url(url).withMethod("GET").stream() val downloadedFile: Future[File] = futureResponse.flatMap { @@ -364,7 +376,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { } // materialize and run the stream - res.body.runWith(sink).andThen { + res.bodyAsSource.runWith(sink).andThen { case result => // Close the output stream whether there was an error or not outputStream.close() @@ -388,9 +400,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { def downloadFile = Action.async { // Make the request - ws.url(url).withMethod("GET").stream().map { - case StreamedResponse(response, body) => - + ws.url(url).withMethod("GET").stream().map { response => // Check that the response was successful if (response.status == 200) { @@ -401,9 +411,9 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { // If there's a content length, send that, otherwise return the body chunked response.headers.get("Content-Length") match { case Some(Seq(length)) => - Ok.sendEntity(HttpEntity.Streamed(body, Some(length.toLong), Some(contentType))) + Ok.sendEntity(HttpEntity.Streamed(response.bodyAsSource, Some(length.toLong), Some(contentType))) case _ => - Ok.chunked(body).as(contentType) + Ok.chunked(response.bodyAsSource).as(contentType) } } else { BadGateway @@ -424,13 +434,13 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { case other => Action { NotFound } } { ws => //#stream-put - val futureResponse: Future[StreamedResponse] = + val futureResponse: Future[WSResponse] = ws.url(url).withMethod("PUT").withBody("some body").stream() //#stream-put val bytesReturned: Future[Long] = futureResponse.flatMap { res => - res.body.runWith(Sink.fold[Long, ByteString](0L){ (total, bytes) => + res.bodyAsSource.runWith(Sink.fold[Long, ByteString](0L){ (total, bytes) => total + bytes.length }) } @@ -446,7 +456,7 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { def largeImageFromDB: Source[ByteString, _] = largeSource //#scalaws-stream-request val wsResponse: Future[WSResponse] = ws.url(url) - .withBody(StreamedBody(largeImageFromDB)).execute("PUT") + .withBody(largeImageFromDB).execute("PUT") //#scalaws-stream-request await(wsResponse).status must_== 200 } @@ -502,10 +512,9 @@ class ScalaWSSpec extends PlaySpecification with Results with AfterAll { "allow timeout across futures" in new WithServer() with Injecting { val url2 = url - //#ws-futures-timeout implicit val futures = inject[Futures] val ws = inject[WSClient] - + //#ws-futures-timeout // Adds withTimeout as type enrichment on Future[WSResponse] import play.api.libs.concurrent.Futures._