-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
JavaWSSpec.scala
286 lines (231 loc) · 10.9 KB
/
JavaWSSpec.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.it.libs
import java.io.File
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.util
import java.util.concurrent.CompletionStage
import java.util.concurrent.TimeUnit
import akka.NotUsed
import akka.stream.javadsl
import akka.stream.scaladsl.FileIO
import akka.stream.scaladsl.Sink
import akka.stream.scaladsl.Source
import akka.util.ByteString
import org.specs2.concurrent.ExecutionEnv
import org.specs2.concurrent.FutureAwait
import play.api.http.Port
import play.api.libs.oauth.ConsumerKey
import play.api.libs.oauth.RequestToken
import play.api.libs.streams.Accumulator
import play.api.mvc.BodyParser
import play.api.mvc.Result
import play.api.mvc.Results
import play.api.mvc.Results.Ok
import play.api.test.PlaySpecification
import play.core.server.Server
import play.it.tools.HttpBinApplication
import play.it.AkkaHttpIntegrationSpecification
import play.it.NettyIntegrationSpecification
import play.it.ServerIntegrationSpecification
import play.libs.ws.WSBodyReadables
import play.libs.ws.WSBodyWritables
import play.libs.ws.WSRequest
import play.libs.ws.WSResponse
import play.mvc.Http
import scala.concurrent.Future
class NettyJavaWSSpec(val ee: ExecutionEnv) extends JavaWSSpec with NettyIntegrationSpecification
class AkkaHttpJavaWSSpec(val ee: ExecutionEnv) extends JavaWSSpec with AkkaHttpIntegrationSpecification
trait JavaWSSpec
extends PlaySpecification
with ServerIntegrationSpecification
with FutureAwait
with WSBodyReadables
with WSBodyWritables {
def ee: ExecutionEnv
implicit val ec = ee.executionContext
import play.libs.ws.WSSignatureCalculator
"Web service client" title
sequential
"WSClient@java" should {
"make GET Requests" in withServer { ws =>
val request: WSRequest = ws.url("/get")
val futureResponse: CompletionStage[WSResponse] = request.get()
val future = futureResponse.toCompletableFuture
val rep: WSResponse = future.get(10, TimeUnit.SECONDS)
(rep.getStatus().aka("status") must_== 200).and(rep.asJson().path("origin").textValue must not beNull)
}
"make DELETE Requests" in withServer { ws =>
val request: WSRequest = ws.url("/delete")
val futureResponse: CompletionStage[WSResponse] = request.execute("DELETE")
val future = futureResponse.toCompletableFuture
val rep: WSResponse = future.get(10, TimeUnit.SECONDS)
(rep.getStatus.aka("status") must_== 200).and(rep.asJson().path("origin").textValue must not beNull)
}
"use queryString in url" in withServer { ws =>
val rep = ws.url("/get?foo=bar").get().toCompletableFuture.get(10, TimeUnit.SECONDS)
(rep.getStatus.aka("status") must_== 200).and(rep.asJson().path("args").path("foo").textValue() must_== "bar")
}
"use user:password in url" in Server.withApplication(app) { implicit port =>
withClient { ws =>
val rep = ws
.url(s"http://user:password@localhost:$port/basic-auth/user/password")
.get()
.toCompletableFuture
.get(10, TimeUnit.SECONDS)
(rep.getStatus.aka("status") must_== 200).and(rep.asJson().path("authenticated").booleanValue() must beTrue)
}
}
"reject invalid query string" in withServer { ws =>
import java.net.MalformedURLException
ws.url("/get?=&foo").aka("invalid request") must throwA[RuntimeException].like {
case e: RuntimeException =>
e.getCause must beAnInstanceOf[MalformedURLException]
}
}
"reject invalid user password string" in withServer { ws =>
import java.net.MalformedURLException
ws.url("http://@localhost/get").aka("invalid request") must throwA[RuntimeException].like {
case e: RuntimeException =>
e.getCause must beAnInstanceOf[MalformedURLException]
}
}
"consider query string in JSON conversion" in withServer { ws =>
val empty = ws.url("/get?foo").get.toCompletableFuture.get(10, TimeUnit.SECONDS)
val bar = ws.url("/get?foo=bar").get.toCompletableFuture.get(10, TimeUnit.SECONDS)
(empty.asJson.path("args").path("foo").textValue() must_== "")
.and(bar.asJson.path("args").path("foo").textValue() must_== "bar")
}
"get a streamed response" in withResult(Results.Ok.chunked(Source(List("a", "b", "c")))) { ws =>
val res = ws.url("/get").stream().toCompletableFuture.get()
val materializedData = await(res.getBodyAsSource().runWith(foldingSink, app.materializer))
materializedData.decodeString("utf-8").aka("streamed response") must_== "abc"
}
"streaming a request body" in withEchoServer { ws =>
val source = Source(List("a", "b", "c").map(ByteString.apply)).asJava
val res = ws.url("/post").setMethod("POST").setBody(source).execute()
val body = res.toCompletableFuture.get().getBody
body must_== "abc"
}
"streaming a request body with manual content length" in withHeaderCheck { ws =>
val source = akka.stream.javadsl.Source.single(ByteString("abc"))
val res = ws.url("/post").setMethod("POST").addHeader(CONTENT_LENGTH, "3").setBody(source).execute()
val body = res.toCompletableFuture.get().getBody
body must_== s"Content-Length: 3; Transfer-Encoding: -1"
}
"sending a simple multipart form body" in withServer { ws =>
val source: Source[_ >: Http.MultipartFormData.Part[javadsl.Source[ByteString, _]], _] = Source
.single(new Http.MultipartFormData.DataPart("hello", "world"))
val res = ws.url("/post").post(source.asJava)
val body = res.toCompletableFuture.get().asJson()
body.path("form").path("hello").textValue() must_== "world"
}
"sending a multipart form body" in withServer { ws =>
val file = new File(this.getClass.getResource("/testassets/bar.txt").toURI).toPath
val dp = new Http.MultipartFormData.DataPart("hello", "world")
val fp = new Http.MultipartFormData.FilePart("upload", "bar.txt", "text/plain", FileIO.fromPath(file).asJava)
val source = akka.stream.javadsl.Source.from(util.Arrays.asList(dp, fp))
val res = ws.url("/post").post(source)
val body = res.toCompletableFuture.get().asJson()
body.path("form").path("hello").textValue() must_== "world"
body.path("file").textValue() must_== "This is a test asset."
}
"sending a multipart form body with escaped 'name' and 'filename' params" in withEchoServer { ws =>
val file = new File(this.getClass.getResource("/testassets/bar.txt").toURI).toPath
val dp = new Http.MultipartFormData.DataPart("f\ni\re\"l\nd1", "world")
val fp = new Http.MultipartFormData.FilePart(
"f\"i\rl\nef\"ie\nld\r1",
"f\rir\"s\ntf\ril\"e\n.txt",
"text/plain",
FileIO.fromPath(file).asJava
)
val source = akka.stream.javadsl.Source.from(util.Arrays.asList(dp, fp))
val res = ws.url("/post").post(source)
val body = res.toCompletableFuture.get().getBody()
body must contain("Content-Disposition: form-data; name=\"f%0Ai%0De%22l%0Ad1\"")
body must contain(
"Content-Disposition: form-data; name=\"f%22i%0Dl%0Aef%22ie%0Ald%0D1\"; filename=\"f%0Dir%22s%0Atf%0Dil%22e%0A.txt\""
)
}
"send a multipart request body via multipartBody()" in withServer { ws =>
val file = new File(this.getClass.getResource("/testassets/bar.txt").toURI)
val dp = new Http.MultipartFormData.DataPart("hello", "world")
val fp =
new Http.MultipartFormData.FilePart("upload", "bar.txt", "text/plain", FileIO.fromPath(file.toPath).asJava)
val source = akka.stream.javadsl.Source.from(util.Arrays.asList(dp, fp))
val res = ws.url("/post").setBody(multipartBody(source)).setMethod("POST").execute()
val body = res.toCompletableFuture.get().asJson()
body.path("form").path("hello").textValue() must_== "world"
body.path("file").textValue() must_== "This is a test asset."
}
"not throw an exception while signing requests" in withServer { ws =>
val key = "12234"
val secret = "asbcdef"
val token = "token"
val tokenSecret = "tokenSecret"
(ConsumerKey(key, secret), RequestToken(token, tokenSecret))
val calc: WSSignatureCalculator = new CustomSigner
ws.url("/").sign(calc).aka("signed request") must not(throwA[Exception])
}
}
def app = HttpBinApplication.app
val foldingSink = Sink.fold[ByteString, ByteString](ByteString.empty)((state, bs) => state ++ bs)
val isoString = {
// Converts the String "Hello €" to the ISO Counterparty
val sourceCharset = StandardCharsets.UTF_8
val buffer = ByteBuffer.wrap("Hello €".getBytes(sourceCharset))
val data = sourceCharset.decode(buffer)
val targetCharset = Charset.forName("Windows-1252")
new String(targetCharset.encode(data).array(), targetCharset)
}
class CustomSigner extends WSSignatureCalculator with play.shaded.ahc.org.asynchttpclient.SignatureCalculator {
def calculateAndAddSignature(
request: play.shaded.ahc.org.asynchttpclient.Request,
requestBuilder: play.shaded.ahc.org.asynchttpclient.RequestBuilderBase[_]
) = {
// do nothing
}
}
def withServer[T](block: play.libs.ws.WSClient => T) = {
Server.withApplication(app) { implicit port => withClient(block) }
}
def withEchoServer[T](block: play.libs.ws.WSClient => T) = {
def echo = BodyParser { req =>
Accumulator.source[ByteString].mapFuture { source => Future.successful(source).map(Right.apply) }
}
Server.withRouterFromComponents()(components => {
case _ =>
components.defaultActionBuilder(echo) { req => Ok.chunked(req.body) }
}) { implicit port => withClient(block) }
}
def withResult[T](result: Result)(block: play.libs.ws.WSClient => T) = {
Server.withRouterFromComponents() { components =>
{
case _ => components.defaultActionBuilder(result)
}
} { implicit port => withClient(block) }
}
def withClient[T](block: play.libs.ws.WSClient => T)(implicit port: Port): T = {
val wsClient = play.test.WSTestClient.newClient(port.value)
try {
block(wsClient)
} finally {
wsClient.close()
}
}
def withHeaderCheck[T](block: play.libs.ws.WSClient => T) = {
Server.withRouterFromComponents() { components =>
{
case _ =>
components.defaultActionBuilder { req =>
val contentLength = req.headers.get(CONTENT_LENGTH)
val transferEncoding = req.headers.get(TRANSFER_ENCODING)
Ok(s"Content-Length: ${contentLength.getOrElse(-1)}; Transfer-Encoding: ${transferEncoding.getOrElse(-1)}")
}
}
} { implicit port => withClient(block) }
}
}