{"id":13485854,"url":"https://github.com/playframework/play-ws","last_synced_at":"2025-05-14T14:08:13.881Z","repository":{"id":14274930,"uuid":"75882803","full_name":"playframework/play-ws","owner":"playframework","description":"Standalone Play WS, an async HTTP client with fluent API","archived":false,"fork":false,"pushed_at":"2025-05-07T07:34:06.000Z","size":2430,"stargazers_count":226,"open_issues_count":76,"forks_count":88,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-05-08T02:40:07.639Z","etag":null,"topics":["asynchronous","asynchttpclient","http-client","java","playframework","reactive","reactive-streams","scala"],"latest_commit_sha":null,"homepage":"https://www.playframework.com/documentation/latest/JavaWS","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/playframework.png","metadata":{"funding":{"github":["playframework"],"open_collective":"playframework"},"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-12-07T23:00:14.000Z","updated_at":"2025-05-07T07:34:10.000Z","dependencies_parsed_at":"2023-10-03T10:28:32.633Z","dependency_job_id":"339e3a7a-f5fa-4786-8ff3-986e6d4175bb","html_url":"https://github.com/playframework/play-ws","commit_stats":{"total_commits":772,"total_committers":54,"mean_commits":"14.296296296296296","dds":0.8095854922279793,"last_synced_commit":"3dd91a1e4050aa96debb83ebcdf638670f17bb95"},"previous_names":[],"tags_count":104,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playframework%2Fplay-ws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playframework%2Fplay-ws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playframework%2Fplay-ws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playframework%2Fplay-ws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/playframework","download_url":"https://codeload.github.com/playframework/play-ws/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254029224,"owners_count":22002321,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","asynchttpclient","http-client","java","playframework","reactive","reactive-streams","scala"],"created_at":"2024-07-31T18:00:32.730Z","updated_at":"2025-05-14T14:08:13.862Z","avatar_url":"https://github.com/playframework.png","language":"Scala","readme":"# Play WS Standalone\n\n[![Twitter Follow](https://img.shields.io/twitter/follow/playframework?label=follow\u0026style=flat\u0026logo=twitter\u0026color=brightgreen)](https://twitter.com/playframework)\n[![Discord](https://img.shields.io/discord/931647755942776882?logo=discord\u0026logoColor=white)](https://discord.gg/g5s2vtZ4Fa)\n[![GitHub Discussions](https://img.shields.io/github/discussions/playframework/playframework?\u0026logo=github\u0026color=brightgreen)](https://github.com/playframework/playframework/discussions)\n[![StackOverflow](https://img.shields.io/static/v1?label=stackoverflow\u0026logo=stackoverflow\u0026logoColor=fe7a16\u0026color=brightgreen\u0026message=playframework)](https://stackoverflow.com/tags/playframework)\n[![YouTube](https://img.shields.io/youtube/channel/views/UCRp6QDm5SDjbIuisUpxV9cg?label=watch\u0026logo=youtube\u0026style=flat\u0026color=brightgreen\u0026logoColor=ff0000)](https://www.youtube.com/channel/UCRp6QDm5SDjbIuisUpxV9cg)\n[![Twitch Status](https://img.shields.io/twitch/status/playframework?logo=twitch\u0026logoColor=white\u0026color=brightgreen\u0026label=live%20stream)](https://www.twitch.tv/playframework)\n[![OpenCollective](https://img.shields.io/opencollective/all/playframework?label=financial%20contributors\u0026logo=open-collective)](https://opencollective.com/playframework)\n\n[![Build Status](https://github.com/playframework/play-ws/actions/workflows/build-test.yml/badge.svg)](https://github.com/playframework/play-ws/actions/workflows/build-test.yml)\n[![Maven](https://img.shields.io/maven-central/v/org.playframework/play-ws-standalone_2.13.svg?logo=apache-maven)](https://mvnrepository.com/artifact/org.playframework/play-ws-standalone_2.13)\n[![Javadocs](https://javadoc.io/badge/org.playframework/play-ws-standalone_2.13.svg)](https://javadoc.io/doc/org.playframework/play-ws-standalone_2.13)\n[![Repository size](https://img.shields.io/github/repo-size/playframework/play-ws.svg?logo=git)](https://github.com/playframework/play-ws)\n[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat\u0026logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)\n[![Mergify Status](https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/playframework/play-ws\u0026style=flat)](https://mergify.com)\n\nPlay WS is a powerful HTTP Client library, originally developed by the Play team for use with Play Framework. It uses AsyncHttpClient for HTTP client functionality and has no Play dependencies.\n\nWe've provided some documentation here on how to use Play WS in your app (without Play). For more information on how to use Play WS in Play, please refer to the Play documentation.\n\n## Getting Started\n\nTo get started, you can add `play-ahc-ws-standalone` as a dependency in SBT:\n\n```scala\nlibraryDependencies += \"org.playframework\" %% \"play-ahc-ws-standalone\" % \"LATEST_VERSION\"\n\n// Before version 3.0.0:\nlibraryDependencies += \"com.typesafe.play\" %% \"play-ahc-ws-standalone\" % \"LATEST_VERSION\"\n```\n\nWhere you replace `LATEST_VERSION` with the version shown in this image: [![Latest released version](https://img.shields.io/maven-central/v/org.playframework/play-ws-standalone_2.13.svg)](http://mvnrepository.com/artifact/org.playframework/play-ws-standalone_2.13).\n\nThis adds the standalone version of Play WS, backed by [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client).  This library contains both the Scala and Java APIs, under `play.api.libs.ws` and `play.libs.ws`.\n\nTo add XML and JSON support using Play-JSON or Scala XML, add the following:\n\n```scala\nlibraryDependencies += \"org.playframework\" %% \"play-ws-standalone-xml\" % playWsStandaloneVersion\nlibraryDependencies += \"org.playframework\" %% \"play-ws-standalone-json\" % playWsStandaloneVersion\n\n// Before version 3.0.0:\nlibraryDependencies += \"com.typesafe.play\" %% \"play-ws-standalone-xml\" % playWsStandaloneVersion\nlibraryDependencies += \"com.typesafe.play\" %% \"play-ws-standalone-json\" % playWsStandaloneVersion\n```\n\n## Shading\n\nPlay WS uses shaded versions of AsyncHttpClient and OAuth Signpost, repackaged under the `play.shaded.ahc` and `play.shaded.oauth` package names, respectively.  Shading AsyncHttpClient means that the version of Netty used behind AsyncHttpClient is completely independent of the application and Play as a whole.\n\nSpecifically, shading AsyncHttpClient means that there are no version conflicts introduced between Netty 4.0 and Netty 4.1 using Play WS.\n\n\u003e **NOTE**: If you are developing play-ws and publishing `shaded-asynchttpclient` and `shaded-oauth` using `sbt publishLocal`, you need to be aware that updating `~/.ivy2/local` does not overwrite `~/.ivy2/cache` and so you will not see your updated shaded code until you remove it from cache.  See http://eed3si9n.com/field-test for more details.  This bug has been filed as https://github.com/sbt/sbt/issues/2687.\n\n### Shaded AHC Defaults \n\nBecause Play WS shades AsyncHttpClient, the default settings are also shaded and so do not adhere to the AHC documentation.  This means that the settings in `ahc-default.properties` and the AsyncHttpClient system properties are prepended with `play.shaded.ahc`, for example the `usePooledMemory` setting in the shaded version of AsyncHttpClient is defined like this:\n\n```properties\nplay.shaded.ahc.org.asynchttpclient.usePooledMemory=true\n```\n\n### Typed Bodies\n\nThe type system in Play-WS has changed so that the request body and the response body can use richer types.\n\nYou can define your own BodyWritable or BodyReadable, but if you want to use the default out of the box settings, you can import the type mappings with the DefaultBodyReadables / DefaultBodyWritables.\n\n#### Scala\n\n```scala\nimport play.api.libs.ws.DefaultBodyReadables._\nimport play.api.libs.ws.DefaultBodyWritables._\n```\n\nMore likely you will want the XML and JSON support:\n\n```scala\nimport play.api.libs.ws.XMLBodyReadables._\nimport play.api.libs.ws.XMLBodyWritables._\n```\n\nor\n\n```scala\nimport play.api.libs.ws.JsonBodyReadables._\nimport play.api.libs.ws.JsonBodyWritables._\n```\n\nTo use a BodyReadable in a response, you must type the response explicitly:\n\n```scala\nimport scala.concurrent.{ ExecutionContext, Future }\n\nimport play.api.libs.ws.StandaloneWSClient\nimport play.api.libs.ws.XMLBodyReadables._ // required\n\ndef handleXml(ws: StandaloneWSClient)(\n  implicit ec: ExecutionContext): Future[scala.xml.Elem] =\n  ws.url(\"...\").get().map { response =\u003e\n    response.body[scala.xml.Elem]\n  }\n```\n\nor using Play-JSON:\n\n```scala\nimport scala.concurrent.{ ExecutionContext, Future }\n\nimport play.api.libs.json.JsValue\nimport play.api.libs.ws.StandaloneWSClient\n\nimport play.api.libs.ws.JsonBodyReadables._ // required\n\ndef handleJsonResp(ws: StandaloneWSClient)(\n  implicit ec: ExecutionContext): Future[JsValue] =\n  ws.url(\"...\").get().map { response =\u003e\n    response.body[JsValue]\n  }\n```\n\nNote that there is a special case: when you are streaming the response, then you should get the body as a Source:\n\n```scala\nimport scala.concurrent.ExecutionContext\nimport org.apache.pekko.util.ByteString\nimport org.apache.pekko.stream.scaladsl.Source\nimport play.api.libs.ws.StandaloneWSClient\n\ndef useWSStream(ws: StandaloneWSClient)(implicit ec: ExecutionContext) =\n  ws.url(\"...\").stream().map { response =\u003e\n     val source: Source[ByteString, _] = response.bodyAsSource\n     val _ = source // do something with source\n  }\n```\n\nTo POST, you should pass in a type which has an implicit class mapping of BodyWritable:\n\n```scala\nimport scala.concurrent.ExecutionContext\nimport play.api.libs.ws.DefaultBodyWritables._ // required\n\ndef postExampleString(ws: play.api.libs.ws.StandaloneWSClient)(\n  implicit ec: ExecutionContext) = {\n  val stringData = \"Hello world\"\n  ws.url(\"...\").post(stringData).map { response =\u003e /* do something */ }\n}\n```\n\nYou can also define your own custom BodyReadable: \n\n```scala\nimport play.api.libs.ws.BodyReadable\nimport play.api.libs.ws.ahc.StandaloneAhcWSResponse\n\ncase class Foo(body: String)\n\nimplicit val fooBodyReadable = BodyReadable[Foo] { response =\u003e\n  import play.shaded.ahc.org.asynchttpclient.{ Response =\u003e AHCResponse }\n  val ahcResponse = response.asInstanceOf[StandaloneAhcWSResponse].underlying[AHCResponse]\n  Foo(ahcResponse.getResponseBody)\n}\n```\n\nor custom BodyWritable:\n\n```scala\nimport org.apache.pekko.util.ByteString\nimport play.api.libs.ws.{ BodyWritable, InMemoryBody }\n\nimplicit val writeableOf_Foo: BodyWritable[Foo] = {\n  // https://tools.ietf.org/html/rfc6838#section-3.2\n  BodyWritable(foo =\u003e InMemoryBody(ByteString.fromString(foo.toString)), \"application/vnd.company.category+foo\")\n}\n```\n\n#### Java\n\nTo use the default type mappings in Java, you should use the following:\n\n```java\nimport play.libs.ws.DefaultBodyReadables;\nimport play.libs.ws.DefaultBodyWritables;\n```\n\nfollowed by:\n\n```java\npublic class MyClient implements DefaultBodyWritables, DefaultBodyReadables {    \n    public CompletionStage\u003cString\u003e doStuff() {\n      return client.url(\"http://example.com\").post(body(\"hello world\")).thenApply(response -\u003e\n        response.body(string())\n      );\n    }\n}\n```\n\nNote that there is a special case: when you are using a stream, then you should get the body as a Source:\n\n```java\n\nclass MyClass {\n    public CompletionStage\u003cSource\u003cByteString, NotUsed\u003e\u003e readResponseAsStream() {\n        return ws.url(url).stream().thenApply(response -\u003e\n            response.bodyAsSource()\n        );\n    }\n}\n```\n\nYou can also post a Source:\n\n```java\nclass MyClass {\n    public CompletionStage\u003cString\u003e doStuff() {\n        Source\u003cByteString, NotUsed\u003e source = fromSource();\n        return ws.url(url).post(body(source)).thenApply(response -\u003e\n            response.body()\n        );\n    }\n}\n```\n\nYou can define a custom `BodyReadable`:\n\n```java\nimport play.libs.ws.ahc.*;\nimport play.shaded.ahc.org.asynchttpclient.Response;\n\nclass FooReadable implements BodyReadable\u003cStandaloneWSResponse, Foo\u003e {\n    public Foo apply(StandaloneWSResponse response) {\n        Response ahcResponse = (Response) response.getUnderlying();\n        return Foo.serialize(ahcResponse.getResponseBody(StandardCharsets.UTF_8));\n    }\n}\n```\n\nYou can also define your own custom `BodyWritable`:\n\n```java\npublic class MyClient {\n    private BodyWritable\u003cString\u003e someOtherMethod(String string) {\n        org.apache.pekko.util.ByteString byteString = org.apache.pekko.util.ByteString.fromString(string);\n      return new DefaultBodyWritables.InMemoryBodyWritable(byteString, \"text/plain\");\n    }\n}\n```\n\n## Instantiating a standalone client\n\nThe standalone client needs [Pekko](https://pekko.apache.org/) to handle streaming data internally:\n\n### Scala\n\nIn Scala, the way to call out to a web service and close down the client:\n\n```scala\npackage playwsclient\n\nimport org.apache.pekko.actor.ActorSystem\nimport org.apache.pekko.stream._\nimport play.api.libs.ws._\nimport play.api.libs.ws.ahc._\n\nimport scala.concurrent.Future\n\nobject ScalaClient {\n  import DefaultBodyReadables._\n  import scala.concurrent.ExecutionContext.Implicits._\n\n  def main(args: Array[String]): Unit = {\n    // Create Pekko system for thread and streaming management\n    implicit val system = ActorSystem()\n    system.registerOnTermination {\n      System.exit(0)\n    }\n\n    implicit val materializer = SystemMaterializer(system).materializer\n\n    // Create the standalone WS client\n    // no argument defaults to a AhcWSClientConfig created from\n    // \"AhcWSClientConfigFactory.forConfig(ConfigFactory.load, this.getClass.getClassLoader)\"\n    val wsClient = StandaloneAhcWSClient()\n\n    call(wsClient)\n      .andThen { case _ =\u003e wsClient.close() }\n      .andThen { case _ =\u003e system.terminate() }\n  }\n\n  def call(wsClient: StandaloneWSClient): Future[Unit] = {\n    wsClient.url(\"http://www.google.com\").get().map { response =\u003e\n      val statusText: String = response.statusText\n      val body = response.body[String]\n      println(s\"Got a response $statusText: $body\")\n    }\n  }\n}\n```\n\nYou can also create the standalone client directly from an AsyncHttpClient instance:\n\n```scala\nobject ScalaClient {\n  def main(args: Array[String]): Unit = {\n    // Use \n    import play.shaded.ahc.org.asynchttpclient._\n    val asyncHttpClientConfig = new DefaultAsyncHttpClientConfig.Builder()\n      .setMaxRequestRetry(0)\n      .setShutdownQuietPeriod(0)\n      .setShutdownTimeout(0).build\n    val asyncHttpClient = new DefaultAsyncHttpClient(asyncHttpClientConfig)\n    val wsClient = new StandaloneAhcWSClient(asyncHttpClient)\n    /// ...\n  }\n}\n```\n\nThis is useful when there is an AsyncHttpClient configuration option that is not available in the WS config layer.\n\n### Java\n\nIn Java the API is much the same:\n\n```java\npackage playwsclient;\n\nimport org.apache.pekko.actor.ActorSystem;\nimport org.apache.pekko.stream.*;\nimport com.typesafe.config.ConfigFactory;\n\nimport play.libs.ws.*;\nimport play.libs.ws.ahc.*;\n\npublic class JavaClient implements DefaultBodyReadables {\n    private final StandaloneAhcWSClient client;\n    private final ActorSystem system;\n\n    public static void main(String[] args) {\n        // Set up Pekko materializer to handle streaming\n        final String name = \"wsclient\";\n        ActorSystem system = ActorSystem.create(name);\n        system.registerOnTermination(() -\u003e System.exit(0));\n        Materializer materializer = SystemMaterializer.get(system).materializer();\n\n        // Create the WS client from the `application.conf` file, the current classloader and materializer.\n        StandaloneAhcWSClient ws = StandaloneAhcWSClient.create(\n                AhcWSClientConfigFactory.forConfig(ConfigFactory.load(), system.getClass().getClassLoader()),\n                materializer\n        );\n\n        JavaClient javaClient = new JavaClient(system, ws);\n        javaClient.run();\n    }\n\n    JavaClient(ActorSystem system, StandaloneAhcWSClient client) {\n        this.system = system;\n        this.client = client;\n    }\n\n    public void run() {\n        client.url(\"http://www.google.com\").get()\n                .whenComplete((response, throwable) -\u003e {\n                    String statusText = response.getStatusText();\n                    String body = response.getBody(string());\n                    System.out.println(\"Got a response \" + statusText);\n                })\n                .thenRun(() -\u003e {\n                    try {\n                        client.close();\n                    } catch (Exception e) {\n                        e.printStackTrace();\n                    }\n                })\n                .thenRun(system::terminate);\n    }\n}\n```\n\nLikewise, you can provide the AsyncHttpClient client explicitly from configuration:\n\n```java\npublic class JavaClient implements DefaultBodyReadables {\n     public static void main(String[] args) { \n        // ...\n        // Set up AsyncHttpClient directly from config\n        AsyncHttpClientConfig asyncHttpClientConfig =\n            new DefaultAsyncHttpClientConfig.Builder()\n                .setMaxRequestRetry(0)\n                .setShutdownQuietPeriod(0)\n                .setShutdownTimeout(0)\n                .build();\n        AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(asyncHttpClientConfig);\n    \n        // Set up WSClient instance directly from asynchttpclient.\n        WSClient client = new AhcWSClient(asyncHttpClient, materializer);\n        // ...\n    }\n}\n```\n\n## Caching\n\nPlay WS implements [HTTP Caching](https://tools.ietf.org/html/rfc7234) through CachingAsyncHttpClient, AhcHTTPCache and [CacheControl](https://github.com/playframework/cachecontrol), a minimal HTTP cache management library in Scala.\n\nTo 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:\n\n```scala\nimport scala.concurrent.Future\nimport java.util.concurrent.TimeUnit\nimport com.github.benmanes.caffeine.cache.{ Caffeine, Ticker }\n\nimport play.api.libs.ws.ahc.StandaloneAhcWSClient\nimport play.api.libs.ws.ahc.cache.{\n  AhcHttpCache, Cache, EffectiveURIKey, ResponseEntry\n}\n\nclass CaffeineHttpCache extends Cache {\n  val underlying = Caffeine.newBuilder()\n    .ticker(Ticker.systemTicker())\n    .expireAfterWrite(365, TimeUnit.DAYS)\n    .build[EffectiveURIKey, ResponseEntry]()\n\n  def remove(key: EffectiveURIKey) =\n    Future.successful(Option(underlying.invalidate(key)))\n\n  def put(key: EffectiveURIKey, entry: ResponseEntry) =\n    Future.successful(underlying.put(key, entry))\n\n  def get(key: EffectiveURIKey) =\n    Future.successful(Option(underlying getIfPresent key ))\n\n  def close(): Unit = underlying.cleanUp()\n}\n\ndef withCache(implicit m: org.apache.pekko.stream.Materializer): StandaloneAhcWSClient = {\n  implicit def ec = m.executionContext\n\n  val cache = new CaffeineHttpCache()\n  StandaloneAhcWSClient(httpCache = Some(new AhcHttpCache(cache)))\n}\n```\n\nThere are a number of guides that help with putting together Cache-Control headers:\n\n* [Mozilla's Guide to HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)\n* [Mark Nottingham's Guide to Caching](https://www.mnot.net/cache_docs/)\n* [HTTP Caching](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching)\n* [REST Easy: HTTP Cache](http://odino.org/rest-better-http-cache/)\n\n## Releasing a new version\n\nSee https://github.com/playframework/.github/blob/main/RELEASING.md\n\n## License\n\nPlay WS is licensed under the Apache license, version 2. See the LICENSE file for more information.\n","funding_links":["https://github.com/sponsors/playframework","https://opencollective.com/playframework"],"categories":["Projects","网络编程","Solutions"],"sub_categories":["HTTP Clients","Spring Cloud框架"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplayframework%2Fplay-ws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplayframework%2Fplay-ws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplayframework%2Fplay-ws/lists"}