withRequestTimeout is ignored in Playframework - scala

when I'm unable to set the timeout when I use the HTTP client from the playframework. This is my code:
val request: Future[WSResponse] = WS
.url(url)
.withAuth(user, password, WSAuthScheme.BASIC)
//5 minute timeout in milliseconds
.withRequestTimeout(300000)
.put("")
This won't give me an error but the request will time out directly after 2 minutes. Is there something else which has to be set so that the timeout is used?
Update: I use the 2.4.8 version of playframework. It looks like this version has the following bug: https://github.com/playframework/playframework/issues/4846
However, the suggested hotfix isn't working for me eigher.
val request: Future[WSResponse] = WS
.url(url)
.asInstanceOf[NingWSRequest]
.copy(requestTimeout = Some(-1))
.withAuth(user, password, WSAuthScheme.BASIC)
.withRequestTimeout(-1)
.put("")
Both will give me a timeout after 2 minutes.

I can't guarantee this will work for you, but it seems to work for me and you could potentially adapt it:
val httpClient = Try(WS.underlying.asInstanceOf[AsyncHttpClient])
.getOrElse(throw new NotImplementedError("Unsupported HTTP client"))
val putBuilder = httpClient.preparePut(url)
putBuilder.setRequestTimeout(-1).addHeader(user, "Basic " + password)
val promise = Promise[Response]()
httpClient.executeRequest(putBuilder.build(),
new AsyncCompletionHandler[Response] {
def onCompleted(response: Response): Response = {
promise.success(response)
response
}
def onThrowable(t: Throwable): Unit = {
promise.failure(t)
super.onThrowable(t)
}
}
)
Good luck.

Related

Scala integration tests for Caliban GraphQL subscriptions

We have a Caliban GraphQL application, using it with Play framework. It is well covered with integration tests for queries and mutations, now we're about to add some integration tests for subscriptions and wondering how to do it correctly.
For queries/mutations testing we're using usual FakeRequest, sending it to our router that extends Caliban's PlayRouter, it works very good. Is there any similar way to test websockets/subscriptions?
There is very short amount of information in the Internet about websocket testing in Play and no information at all about GraphQL subscription testing.
Will be grateful for any ideas!
Ok, I managed it. There are couple of rules to follow:
Use websocket header "WebSocket-Protocol" -> "graphql-ws"
After connection is established, send GraphQLWSRequest of type "connection_init"
After receiving response "connection_ack", send GraphQLWSRequest of type "start" with subscription query as payload
After those steps server is listening and you can send your mutation queries.
Some draft example:
import caliban.client.GraphQLRequest
import caliban.client.ws.GraphQLWSRequest
import io.circe.syntax.EncoderOps
import play.api.libs.json.{JsValue, Json}
import play.api.test.Helpers.{POST, contentAsJson, contentAsString, contentType, route, status, _}
import org.awaitility.Awaitility
def getWS(subscriptionQuery: String, postQuery: String): JsValue = {
lazy val port = Helpers.testServerPort
val initRequest = prepareWSRequest("connection_init")
val startRequest = prepareWSRequest("start", Some(GraphQLRequest(subscriptionQuery, Map())))
Helpers.running(TestServer(port, app)) {
val headers = new java.util.HashMap[String, String]()
headers.put("WebSocket-Protocol", "graphql-ws")
val queue = new ArrayBlockingQueue[String](1)
lazy val ws = new WebSocketClient(new URI(s"ws://localhost:$port/ws/graphql"), headers) {
override def onOpen(handshakedata: ServerHandshake): Unit =
logger.info("Websocket connection established")
override def onClose(code: Port, reason: String, remote: Boolean): Unit =
logger.info(s"Websocket connection closed, reason: $reason")
override def onError(ex: Exception): Unit =
logger.error("Error handling websocket connection", ex)
override def onMessage(message: String): Unit = {
val ttp = (Json.parse(message) \ "type").as[JsString].value
if (ttp != "connection_ack" && ttp != "ka") queue.put(message)
}
}
ws.connectBlocking()
Future(ws.send(initRequest))
.flatMap(_ => Future(ws.send(startRequest)))
.flatMap(_ => post(query = postQuery)) // post is my local method, it sends usual FakeRequest
Awaitility.await().until(() => queue.peek() != null)
Json.parse(queue.take())
}
def prepareWSRequest(ttp: String, payload: Option[GraphQLRequest] = None) =
GraphQLWSRequest(ttp, None, payload).asJson.noSpaces
}

Spray client Request timeout

I am new to scala. I am trying to timeout api request. I am using spray to make API request. I have spray client to get response from some other server. In my application.conf i've specified timeout of request in spray.can something like:
spray.can {
server {
idle-timeout = ${idle-timeout}
request-timeout = ${request-timeout}
request-chunk-aggregation-limit = 20m
parsing {
max-content-length = 21m
}
}
client {
idle-timeout = ${idle-timeout}
request-timeout = ${request-timeout}
response-chunk-aggregation-limit = 20m
}
}
Now, I want to override this request-timeout in one my api. I have written api something like:
def completeService(jwttoken: String, completeRequest: String): Future[HttpResponse] = {
val pipeline: HttpRequest => Future[HttpResponse] = sendReceive ~> unmarshal[HttpResponse]
val response: Future[HttpResponse] = pipeline(Post(some-remote-url, completeRequest)
~> addHeader("FROM", jwttoken))
response
}
So, how can I put request-timeout here in this method? By overriding application.conf
I tried
implicit val timeoutVal: Timeout = Timeout(scala.concurrent.duration.Duration(100, MILLISECONDS).asInstanceOf[FiniteDuration])
and I got this:
test 2020-03-06 08:48:12.147 GMT [WARN] a.k.i.AskPatternInstrumentation - Timeout triggered for ask pattern to actor [IO-HTTP] at [pipelining.scala:38]
test 2020-03-06 08:48:12.573 GMT [INFO] akka.actor.DeadLetterActorRef - Message [spray.http.HttpResponse] from Actor[akka://test-app/user/IO-HTTP/host-connector-1/1#-1105043312] to Actor[akka://test-app/deadLetters] was not deliver
I think you can add an implicit parameter requestTimeout. The data type of that parameter is akka.util.Timeout. You can checkout this link for more details or this link
val _pipeline: Future[SendReceive] =
for (
Http.HostConnectorInfo(connector, _) <-
IO(Http) ? Http.HostConnectorSetup("www.spray.io", port = 80, settings = Some(new HostConnectorSettings(maxConnections = 3, maxRetries = 3, maxRedirects = 0, pipelining = false, idleTimeout = 5 seconds, connectionSettings = ClientConnectionSettings(...))))
) yield sendReceive(connector)
Let me know if it helps!!

How to save a websocket client's connection and send it later with akka-streams and akka-http

I'm trying to follow this part of the akka-http documentation where it talks about handling web socket messages asynchronously
What I am trying to do is this:
Receive a websocket request for a client
Serve a payment invoice back to the client
Run a background process that has the client's websocket connection saved, and when the client pays their invoice, send the data they queried about in return ("World") in this case.
Here is the code I have so far
def hello: Route = {
val amt = 1000
val helloRoute: Route = pathPrefix(Constants.apiVersion) {
path("hello") {
val source: Source[Message, SourceQueueWithComplete[Message]] = {
Source.queue(1, OverflowStrategy.backpressure)
}
val paymentRequest = createPaymentRequest(1000, extractUpgradeToWebSocket)
Directives.handleWebSocketMessages(
paymentFlow(paymentRequest)
)
}
}
helloRoute
}
private def createPaymentRequest(amt: Long, wsUpgrade: Directive1[UpgradeToWebSocket]) = {
val httpResponse: Directive1[HttpResponse] = wsUpgrade.map { ws =>
val sink: Sink[Message, NotUsed] = Sink.cancelled()
val source: Source[Message, NotUsed] = Source.single(TextMessage("World"))
val x: HttpResponse = ws.handleMessagesWithSinkSource(sink, source)
x
}
httpResponse.map { resp =>
//here is where I want to send a websocket message back to the client
//that is the HttpResponse above, how do I complete this?
Directives.complete(resp)
}
}
What I can't seem to figure out is how to get access to a RequestContext or a UpgradeToWebSocket outside of the container type Directive? And when I map on httpResponse the map is not executing.

How to make htttp request with akka stream for 10K request

I build server with akka-http and akka-stream but it lost some request in 2K+ request. What I miss something or my understand for akka is wrong.
this is my code
implicit val actorRef = ActorSystem("system", testConf)
implicit val materializer = ActorMaterializer(ActorMaterializerSettings(actorRef).withInputBuffer(initialSize = 4, maxSize = 16))
implicit val requestTimeout = Timeout(8 seconds)
def response(req: HttpRequest): Future[Response] = {
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
Http().outgoingConnection(host = "url").async
for {
res <- Source.single(req.copy(uri = s"${req.uri.path}?${req.uri.rawQueryString.get}")).via(connectionFlow).runWith(Sink.head)
data <- res.entity.toStrict(5 second)
} yield (data.getData().decodeString("UTF-8"), res.status.intValue())
}
Thank you.
Most likely there were either timeout or server-side errors in first part of your for-comprehension therefore you got only successful responses in res.
I recommend to create flow/graph that processes your requests with withSupervisionStrategy in your materializer so you can see what exactly went wrong. Details of implementation depend on your business logic.

Decode a streaming GZIP response in Scala Dispatch?

Receiving a Gzipped response from an API, but Dispatch 0.9.5 doesn't appear to have any methods to decode the response. Any ideas?
Here's my current implementation, the println only prints out string representations of bytes.
Http(
host("stream.gnip.com")
.secure
.addHeader("Accept-Encoding", "gzip")
/ gnipUrl
> as.stream.Lines(println))()
Tried to look at implementing my own handler, but not sure where to begin. Here's the relevant file for Lines: https://github.com/dispatch/reboot/blob/master/core/src/main/scala/as/stream/lines.scala
Thanks!
Simply abandoned Dispatch and used Java APIs directly. Disappointing, but it got the job done.
val GNIP_URL = isDev match {
case true => "https://url/apath/track/dev.json"
case false => "https://url/path/track/prod.json"
}
val GNIP_CHARSET = "UTF-8"
override def preStart() = {
log.info("[tracker] Starting new Twitter PowerTrack connection to %s" format GNIP_URL)
val connection = getConnection(GNIP_URL, GNIP_USER, GNIP_PASSWORD)
val inputStream = connection.getInputStream()
val reader = new BufferedReader(new InputStreamReader(new StreamingGZIPInputStream(inputStream), GNIP_CHARSET))
var line = reader.readLine()
while(line != null){
println(line)
line = reader.readLine()
}
}
private def getConnection(urlString: String, user: String, password: String): HttpURLConnection = {
val url = new URL(urlString)
val connection = url.openConnection().asInstanceOf[HttpURLConnection]
connection.setReadTimeout(1000 * 60 * 60)
connection.setConnectTimeout(1000 * 10)
connection.setRequestProperty("Authorization", createAuthHeader(user, password));
connection.setRequestProperty("Accept-Encoding", "gzip")
connection
}
private def createAuthHeader(username: String, password: String) = {
val encoder = new BASE64Encoder()
val authToken = username+":"+password
"Basic "+encoder.encode(authToken.getBytes())
}
Used GNIP's example: https://github.com/gnip/support/blob/master/Premium%20Stream%20Connection/Java/StreamingConnection.java
This isn't so much a solution as a workaround, but I ended up resorting to bypassing the Future-based stuff and doing:
val stream = Http(req OK as.Response(_.getResponseBodyAsStream)).apply
val result =
JsonParser.parse(
new java.io.InputStreamReader(
new java.util.zip.GZIPInputStream(stream)))
I'm using JsonParser here because in my case the data I'm receiving happens to be JSON; substitute with something else in your use case, if needed.
My solution just defined a response parser and also adopted json4s parser:
object GzipJson extends (Response => JValue) {
def apply(r: Response) = {
if(r.getHeader("content-encoding")!=null && r.getHeader("content-encoding").equals("gzip")){
(parse(new GZIPInputStream(r.getResponseBodyAsStream), true))
}else
(dispatch.as.String andThen (s => parse(StringInput(s), true)))(r)
}
}
So that I can use it to extract Gzip Json response as the following code:
import GzipJson._
Http(req OK GzipJson).apply
Try >> instead of >
See https://github.com/dispatch/dispatch/blob/master/core/src/main/scala/handlers.scala#L58