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!!
Related
I have a WebSocket server I need to send and receive messages to it over the established WebSocket connection as Akka client-side WebSocket does not provide us the conventional websocket.send() feature as in java
so I found this solution How to use Akka-HTTP client websocket send message
which works great but I am a beginner in Akka stream I am trying to achieve the following scenario
example in my case WebSocket server is running at ws://0.0.0.0:8188
first, I will send a message to the server for initiating the sessionID
request# 1
{
"janus" : "create",
"transaction" : "<random alphanumeric string>"
}
the server will respond with the session id
response #1
{
"janus": "success",
"session_id": 2630959283560140,
"transaction": "asqeasd4as3d4asdasddas",
"data": {
"id": 4574061985075210
}
}
then based on id 4574061985075210 I will send another message and receive further info
request # 02 {
"janus": "attach","session_id":${sessionId},"plugin":"janus.plugin.echotest","transaction":"asqeasd4as3d4asdasddas"
}
response # 02 {
}
----
so far I can display the response of request #1 but I don't know how to initiate request #2 with sessionID I got from request #1 and display its response
here is my code
def main(args: Array[String]): Unit = {
val url = "ws://0.0.0.0:8188"
val req = WebSocketRequest(url, Nil, Option("janus-protocol"))
implicit val materializer = ActorMaterializer()
import system.dispatcher
val webSocketFlow = Http().webSocketClientFlow(req)
val messageSource: Source[Message, ActorRef] =
Source.actorRef[TextMessage.Strict](bufferSize = 10, OverflowStrategy.fail)
val messageSink: Sink[Message, NotUsed] =
Flow[Message]
.map{message =>
println(s"Received text message: [$message]")
val strJson = message.toString
val jsonResponse = strJson.parseJson
val jsonObj = jsonResponse.asJsObject
val janus = jsonObj.fields("janus").convertTo[String]
val data = jsonObj.fields("data").asJsObject
val sessionID = data.fields("id")
// i need to take this SessionId and send to the websocket established connection and receive its response
}
.to(Sink.ignore)
val ((ws, upgradeResponse), closed) =
messageSource
.viaMat(webSocketFlow)(Keep.both)
.toMat(messageSink)(Keep.both)
.run()
val connected = upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}
val source =
"""{ "janus": "create", "transaction":"d1403sa54a5s3d4as3das"}"""
val jsonAst = source.parseJson
ws ! TextMessage.Strict(jsonAst.toString())
}
any help would be appreciated Thanks in advance
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.
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.
in Scala, I have an akka http client class with some local binding:
class AkkaConPoolingHttpClient(
override val timeout: Option[FiniteDuration] = None,
val localBinding: Option[InetSocketAddress] = None,
val userAgentHeader: Option[String] = None)(
implicit val config: HttpClient.Config,
val system: ActorSystem,
val materializer: Materializer)
extends AkkaHttpClient {
protected val http = Http()
override def dispatch(request: HttpRequest): Future[HttpResponse] = {
val effectivePort = request.uri.effectivePort
val connection =
http.outgoingConnection(
request.uri.authority.host.address(),
port = effectivePort,
localAddress = localBinding)
val preparedRequest = userAgentHeader match {
case Some(userAgent) => fixUri(request.withHeaders(request.headers ++ Seq(headers.`User-Agent`(userAgent))))
case None => fixUri(request)
}
Source.single(preparedRequest) via connection runWith Sink.head
}
object AkkaConPoolingHttpClient {
private def fixUri(request: HttpRequest): HttpRequest =
request.withUri(request.uri.toRelative)
}
and I'm trying to see if it reuses the connections and it seems it doesn't:
val connectionCount = new AtomicInteger()
val testServerFuture = Http().bind("127.0.0.1", 0).to {
Sink.foreach { incomingConnection =>
connectionCount.incrementAndGet()
incomingConnection.flow.join(Flow[HttpRequest].map(_ => HttpResponse())).run()
}
}.run()
val testServerPort = Await.result(testServerFuture, defaultExpectTimeout)
.localAddress.getPort
val address = "127.0.0.1"
val addr = Some(new InetSocketAddress(address, 0))
val client = new AkkaConPoolingHttpClient(localBinding = addr)
// Send some requests concurrently
val requests = List(
Get(s"http://127.0.0.1:$testServerPort/1"),
Get(s"http://127.0.0.1:$testServerPort/2"),
Get(s"http://127.0.0.1:$testServerPort/3"))
val responses = Await.result(
Future.sequence(requests.map(client.sendRequest)),
defaultExpectTimeout)
// Send some more requests -- the connections from before should be reused
Thread.sleep(500)
val responses2 = Await.result(
Future.sequence(requests.map(client.sendRequest)),
defaultExpectTimeout)
// Usually this is "3", occasionally "4".
connectionCount.get() must beLessThanOrEqualTo(4)
Unfortunately, the test fails, connectionCount.get() has 6 connections. Why isn't it reuse the connections? what's wrong with this code?
I also tried with:
val effectivePort = request.uri.effectivePort
val clientSettings = ClientConnectionSettings(system).withSocketOptions(SO.ReuseAddress(true) :: Nil)
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
Http().outgoingConnection(
request.uri.authority.host.address(),
port = effectivePort,
localAddress = localBinding,
settings = clientSettings
)
..................
Source.single(preparedRequest)
.via(connectionFlow)
.runWith(Sink.head)
But I still have 6 connections in my test...
Problem
The problem is rooted in the fact that you are creation a new connection for each request. The client code is actually quite clear:
Source.single(preparedRequest) via connection runWith Sink.head
Each request is being sent through a newly instantiated connection. This is due to a general design flaw where you are getting the address from the request:
val connection =
http.outgoingConnection(
request.uri.authority.host.address(), //address comes from request
port = effectivePort,
localAddress = localBinding)
It would be more efficient to establish the address once (ensuring a single Connection), and then each Request would just need the path.
Solution
To use a single connection you'll have to create a single Flow and send all of your requests through that, as described here.
When using spray's pipelining to make an HTTP request like this:
val urlpipeline = sendReceive ~> unmarshal[String]
urlpipeline { Get(url) }
is there a way to specify a timeout for the request and the number of times it should retry for that specific request?
All the documentation I've found only references doing in a config (and even then I can't seem to get it to work).
thx
With the configuration file
I use Spray 1.2.0 in an Akka system. Inside my actor, I import the existing Akka system so I can use the default Akka configuration file.
implicit val system = context.system
import context.dispatcher
val pipeline: HttpRequest => Future[HttpResponse] = sendReceive
Now you can change the configuration in application.conf.
spray.can.host-connector {
max-connections = 10
max-retries = 3
max-redirects = 0
pipelining = off
idle-timeout = 30 s
client = ${spray.can.client}
}
In code
It is possible to change the settings in code using the HostConnectorSetup, but you have to define all parameters. (Based on the spray usage example.)
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)
I believe that the easiest way to override the default timeout in code is to use the implicit futureTimeout argument to the sendReceive method. The data type of that parameter is akka.util.Timeout. So, if you wanted a 120 second timeout instead of the default 60 seconds, you can do this...
implicit val timeout = Timeout(120 seconds)
val urlpipeline = sendReceive ~> unmarshal[String]
urlpipeline { Get(url) }
However, I don't see any parameter that would allow your client code to change the maximum number of retries in a similar fashion.