Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ generated.keystore
generated.truststore
*.log
.bsp/
.metals/
Comment thread
ewalsh marked this conversation as resolved.
Outdated
.vscode/

# Scala-IDE specific
.scala_dependencies
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ def postExampleString(ws: play.api.libs.ws.StandaloneWSClient)(
}
```

To perform a QUERY request ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)), which is safe and idempotent like GET but carries a request body:

```scala
import scala.concurrent.ExecutionContext
import play.api.libs.ws.DefaultBodyWritables._ // required

def queryExample(ws: play.api.libs.ws.StandaloneWSClient)(
implicit ec: ExecutionContext) = {
val queryBody = """{"search": "play framework"}"""
ws.url("...").query(queryBody).map { response => /* do something */ }
}
```

You can also define your own custom BodyReadable:

```scala
Expand Down Expand Up @@ -222,6 +235,18 @@ class MyClass {
}
```

To perform a QUERY request ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)), which is safe and idempotent like GET but carries a request body:

```java
public class MyClient implements DefaultBodyWritables, DefaultBodyReadables {
public CompletionStage<String> doQuery() {
return client.url("http://example.com").query(body("{\"search\": \"play framework\"}")).thenApply(response ->
response.body(string())
);
}
}
```

You can define a custom `BodyReadable`:

```java
Expand Down
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ lazy val mimaSettings = Seq(
ProblemFilters.exclude[FinalClassProblem]("play.libs.ws.DefaultObjectMapper"),
// instance is now a method, not "just" a field anymore, so it always returns the current mapper
ProblemFilters.exclude[MissingFieldProblem]("play.libs.ws.DefaultObjectMapper.instance"),
// Add HTTP QUERY method support (RFC 10008)
ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.libs.ws.StandaloneWSRequest.query"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("play.libs.ws.StandaloneWSRequest.query"),
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv)
Results.Ok("Say hello to play")
case POST(_) =>
Results.Ok(s"POST: ${request.body.asText.getOrElse("")}")
case _ if request.method == "QUERY" =>
Results.Ok(s"QUERY: ${request.body.asText.getOrElse("")}")
case _ =>
Results.NotFound
}
Expand Down Expand Up @@ -129,6 +131,16 @@ class AhcWSClientSpec(implicit val executionEnv: ExecutionEnv)
}
}

"perform a QUERY request with body" in {
withClient() { client =>
val result = Await.result(
client.url(s"http://localhost:$testServerPort/index").query("query body").map(res => res.body[String]),
defaultTimeout
)
result must beEqualTo("QUERY: query body")
Comment thread
ewalsh marked this conversation as resolved.
Outdated
Comment thread
ewalsh marked this conversation as resolved.
Outdated
}
}

"request a url as a Foo" in {
case class Foo(body: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,11 @@ public CompletionStage<? extends StandaloneWSResponse> put(BodyWritable body) {
return setMethod("PUT").setBody(body).execute();
}

@Override
public CompletionStage<? extends StandaloneWSResponse> query(BodyWritable body) {
return setMethod("QUERY").setBody(body).execute();
}

@Override
public CompletionStage<? extends StandaloneWSResponse> delete() {
return setMethod("DELETE").execute();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ case class StandaloneAhcWSRequest(
withBody(body).execute("PUT")
}

/**
*/
Comment thread
ewalsh marked this conversation as resolved.
Outdated
override def query[T: BodyWritable](body: T): Future[Response] = {
withBody(body).execute("QUERY")
}

/**
* Sets the body for this request.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -684,4 +684,41 @@ class AhcWSRequestSpec extends Specification with AfterAll with DefaultBodyReada
req.getHeaders.getAll(HttpHeaderNames.CONTENT_TYPE.toString()).asScala must_== Seq("text/plain; charset=US-ASCII")
}

"For QUERY requests" in {

"set the method to QUERY and attach the body" in withClient { client =>
val req: AHCRequest = client
.url("http://playframework.com/")
.withBody("HELLO WORLD")
.withMethod("QUERY")
.asInstanceOf[StandaloneAhcWSRequest]
.buildRequest()
req.getMethod must beEqualTo("QUERY")
(new String(req.getByteData, "UTF-8")) must be_==("HELLO WORLD")
Comment thread
ewalsh marked this conversation as resolved.
Outdated
}

"set content-type for the body" in withClient { client =>
val req: AHCRequest = client
.url("http://playframework.com/")
.withBody("HELLO WORLD")
.withMethod("QUERY")
.asInstanceOf[StandaloneAhcWSRequest]
.buildRequest()
req.getHeaders.get(HttpHeaderNames.CONTENT_TYPE.toString()) must beEqualTo("text/plain; charset=UTF-8")
Comment thread
ewalsh marked this conversation as resolved.
Outdated
}

"send binary data as body" in withClient { client =>
val binData = ByteString((0 to 127).map(_.toByte).toArray)
val req: AHCRequest = client
.url("http://playframework.com/")
.addHttpHeaders(HttpHeaderNames.CONTENT_TYPE.toString() -> "application/octet-stream")
.withBody(binData)
.withMethod("QUERY")
.asInstanceOf[StandaloneAhcWSRequest]
.buildRequest()
req.getMethod must beEqualTo("QUERY")
Comment thread
ewalsh marked this conversation as resolved.
Outdated
ByteString(req.getByteData) must_== binData
Comment thread
ewalsh marked this conversation as resolved.
Outdated
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,34 @@ class AhcWSRequestSpec extends Specification with DefaultBodyReadables with Defa
req.getRealm.isUsePreemptiveAuth must beFalse
}

"For QUERY requests" in {

"set method to QUERY and attach the body" in {
val client = StandaloneAhcWSClient.create(
AhcWSClientConfigFactory.forConfig(ConfigFactory.load(), this.getClass.getClassLoader), /*materializer*/ null
)
val req = new StandaloneAhcWSRequest(client, "http://playframework.com/", null)
.setBody(body("HELLO WORLD"))
.setMethod("QUERY")
.asInstanceOf[StandaloneAhcWSRequest]
.buildRequest()
req.getMethod must be_==("QUERY")
Comment thread
ewalsh marked this conversation as resolved.
Outdated
req.getStringData must be_==("HELLO WORLD")
}

"set content-type for the body" in {
val client = StandaloneAhcWSClient.create(
AhcWSClientConfigFactory.forConfig(ConfigFactory.load(), this.getClass.getClassLoader), /*materializer*/ null
)
val req = new StandaloneAhcWSRequest(client, "http://playframework.com/", null)
.setBody(body("HELLO WORLD"))
.setMethod("QUERY")
.asInstanceOf[StandaloneAhcWSRequest]
.buildRequest()
req.getHeaders.get(HttpHeaderNames.CONTENT_TYPE) must be_==("text/plain; charset=UTF-8")
}
}

"Set Realm.UsePreemptiveAuth to true when WSAuthScheme.DIGEST not being used" in {
val client = StandaloneAhcWSClient.create(
AhcWSClientConfigFactory.forConfig(ConfigFactory.load(), this.getClass.getClassLoader), /*materializer*/ null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ public interface StandaloneWSRequest {
*/
CompletionStage<? extends StandaloneWSResponse> put(BodyWritable body);

//-------------------------------------------------------------------------
// "QUERY"
//-------------------------------------------------------------------------

/**
* Perform a QUERY on the request asynchronously.
*
* @param body the BodyWritable
* @return a promise to the response
*/
CompletionStage<? extends StandaloneWSResponse> query(BodyWritable body);

//-------------------------------------------------------------------------
// Miscellaneous execution methods
//-------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ trait StandaloneWSRequest {
*/
def put[T: BodyWritable](body: T): Future[Response]

/**
* Performs a QUERY request.
*
* @param body the payload body submitted with this request
* @return a future with the response for the QUERY request
*/
def query[T: BodyWritable](body: T): Future[Response]

/**
* Perform a DELETE on the request asynchronously.
*/
Expand Down
Loading