Akka Streams split Stream for Error handling - scala

I'm using akka http and streams to fulfill API requests.
When the request is invalid I want to return a 400 and if it's valid, i want to proceed with the computation and return the result afterwards.
The Problem I'm facing is, that the Payload I'm receiving from the POST request is a Source and I cannot convert it into 2 Streams (one for valid and one for invalid input data) and complete the request correct.
path("alarms")(
post(entity(asSourceOf[String]) { message =>
val flow = message.via(Flow[String].map((it) =>
Try(if valid(it) then it else throw Exception("Wrong input"))
))
complete(repository.create(flow).run) // <-- here I only want to pass all events that are valid. For the other events complete(HttpResponse(NotFound, entity = "Invalid input")) should be used
})
)
/// The signature of the repository.create looks like that
def create(message: Source[String, NotUsed]): RunnableGraph[Future[Done]]

You may use the akka-http handleExceptions directive, sth like this:
val exceptionHandler = ExceptionHandler {
case ex: RuntimeException =>
complete(HttpResponse(NotFound, entity = "Invalid input"))
}
path("alarms")(
handleExceptions(exceptionHandler) {
post(entity(asSourceOf[String]) { message =>
val flow = message.via(Flow[String].map((it) =>
Try(if valid(it) then it else throw new RuntimeException("Invalid input"))
))
complete(repository.create(flow).run)
})
}
)
Doc:
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/execution-directives/handleExceptions.html
https://doc.akka.io/docs/akka-http/current/routing-dsl/exception-handling.html
There is also handleRejections directive for even more control - see https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/execution-directives/handleRejections.html

Related

How to do some cleanup after the client closes the connection

I'm creating a proxy API using akka that does some preparations before forwarding the request to the actual API. For one of the endpoints, the response is streaming json data and the client may close the connection at any time. Akka seems to handle this automatically, but the issue is I need to do some cleanup after the client closes the connection.
path("query") {
post {
decodeRequest {
entity(as[Query]) { query =>
// proxy does some preparations
val json: String = query.prepared.toJson.toString()
// proxy sends request to actual server
val request = HttpRequest(
method = HttpMethods.POST,
uri = serverUrl + "/query",
entity = HttpEntity(ContentTypes.`application/json`, json)
)
val responseFuture = Http().singleRequest(request)
val response: HttpResponse = Await.result(responseFuture, PROXY_TIMEOUT)
// proxy forwards server's response to user
complete(response)
}
}
}
}
I've tried doing something like
responseFuture.onComplete(_ => doCleanup())
But that doesn't work because responseFuture completes immediately even though the server continues to send data until the client closes the connection. complete(response) also returns immediately.
So I'm wondering how I can make a call to doCleanup() only after the client has closed the connection.
Edit: The cleanup I need to do is because the proxy creates some data streams that are meant to be temporary and only persist until the last message is sent by the server. Once that happens these streams need to be deleted.
You can do it with minimal changes to you code like that:
val responseFuture = Http().singleRequest(request)
val response: HttpResponse = try {
Await.result(responseFuture, PROXY_TIMEOUT)
} finally {
doCleanup()
}
complete(response)
or you can do it without blocking:
val responseFuture = Http().singleRequest(request)
val cleaned = responseFuture.andThen{case _ => doCleanUp()}
complete(cleaned) //it's possible to complete response with Future

ScalaTest asserting multiple futures using AsyncFunSuiteLike

I've been trying to perform a test that uses a mock Http server to respond and a function that returns a Future[String] or an Exception if the Http server response isn't 200.
I'm trying to achieve a test without using Awaits, but instead AsyncFunSuiteLike.
However the following test seems impossible to resolve without doing it synchronously:
test("Error responses") {
Future.sequence {
NanoHTTPD.Response.Status.values().toList.filter(status => status.getRequestStatus >= 400).map {
status => {
httpService.setStatusCode(status)
val responseBody = s"Request failed with status $status"
httpService.setResponseContent(responseBody)
val errorMessage = s"Error response (${status.getRequestStatus}) from http service: $responseBody"
recoverToExceptionIf[ServiceException] {
myObject.httpCall("123456")
}.map {
ex => assert(ex.getMessage === errorMessage)
}
}
}
}.map(assertions => assert(assertions.forall(_ == Succeeded)))
}
Basically the problem is that when the Futures are tested, the NanoHTTPD is set to the last valued set in the map, so all ex.getMessage are the same. If I run those status codes one by one I do get the desired results, but, is there a way to perform all this in one single Async test?
From the looks of it, NanoHTTPD is stateful, so you have a race between the .set... calls and the .httpCall.
If you can spin up a new httpService within each Future, then you should be able to parallelize the tests (unless the state in question would be shared across instances, in which case you're likely out of luck).
So you'd have something like (replace Status with the type of status in your code and HTTPService with the type of httpService):
// following code composed on the fly and not run through the compiler...
def spinUpHTTPService(status: Status, body: String): Future[HTTPService] = {
// insert the code outside of the test which creates httpService
httpService.setStatusCode(status)
httpService.setResponseContent(body)
httpService
}
test("Error responses") {
Future.sequence(
NanoHTTPD.Response.Status.values().toList.filter(status => status.getRequestStatus >= 400).map { status =>
spinUpHTTPService(status, s"Request failed with status $status")
.flatMap { httpService =>
val errorMessage = s"Error response (${status.getRequestStatus}) from http service: $responseBody"
recoverToExceptionIf[ServiceException] {
myObject.httpCall("123456")
} map {
ex => assert(ex.getMessage === errorMessage)
}
} // Future.flatMap
} // List.map
).map { assertions => assertions.forAll(_ == Succeeded) }
}

What's the simplest way to use SSE with Redis pub/sub and Akka Streams?

I'd like to stream a chunked server sent event for the following scenario:
Subscribe to a Redis key, and if the key changes, stream the new value with Akka Streams. It should only stream if there are new values.
As I understand it, I need a Source. I guess that is the subscription to a channel:
redis.subscriber.subscribe("My Channel") {
case message # PubSubMessage.Message(channel, messageBytes) => println(
message.readAs[String]()
)
case PubSubMessage.Subscribe(channel, subscribedChannelsCount) => println(
s"Successfully subscribed to $channel"
)
}
In my route I need to create a Source from this, but honestly I don't know how to get going:
val route: Route =
path("stream") {
get {
complete {
val source: Source[ServerSentEvent, NotUsed] =
Source
.asSubscriber(??) // or fromPublisher???
.map(_ => {
??
})
.map(toServerSentEvent)
.keepAlive(1.second, () => ServerSentEvent.heartbeat)
.log("stream")
}
}
One approach is to use Source.actorRef and BroadcastHub.sink:
val (sseActor, sseSource) =
Source.actorRef[String](10, akka.stream.OverflowStrategy.dropTail)
.map(toServerSentEvent) // converts a String to a ServerSentEvent
.keepAlive(1.second, () => ServerSentEvent.heartbeat)
.toMat(BroadcastHub.sink[ServerSentEvent])(Keep.both)
.run()
Subscribe the materialized ActorRef to your message channel: messages sent to this actor are emitted downstream. If there is no downstream demand, the messages are buffered up to a certain number (in this example, the buffer size is 10) with the specified overflow strategy. Note that there is no backpressure with this approach.
redis.subscriber.subscribe("My Channel") {
case message # PubSubMessage.Message(channel, messageBytes) =>
val strMsg = message.readAs[String]
println(strMsg)
sseActor ! strMsg
case ...
}
Also note that the above example uses a Source.actorRef[String]; adjust the type and the example as you see fit (for example, it could be Source.actorRef[PubSubMessage.Message]).
And you can use the materialized Source in your path:
path("stream") {
get {
complete(sseSource)
}
}
An alternative approach could be to create a Source as queue and offer the element to the queue as received in the subscriber callback
val queue =
Source
.queue[String](10, OverflowStrategy.dropHead) // drops the oldest element from the buffer to make space for the new element.
.map(toServerSentEvent) // converts a String to a ServerSentEvent
.keepAlive(1.second, () => ServerSentEvent.heartbeat)
.to(Sink.ignore)
.run()
and in the subscriber
redis.subscriber.subscribe("My Channel") {
case message # PubSubMessage.Message(channel, messageBytes) =>
val strMsg = message.readAs[String]
println(strMsg)
queue.offer(strMsg)
case ...
}

Consuming a service using WS in Play

I was hoping someone can briefly go over the various ways of consuming a service (this one just returns a string, normally it would be JSON but I just want to understand the concepts here).
My service:
def ping = Action {
Ok("pong")
}
Now in my Play (2.3.x) application, I want to call my client and display the response.
When working with Futures, I want to display the value.
I am a bit confused, what are all the ways I could call this method i.e. there are some ways I see that use Success/Failure,
val futureResponse: Future[String] = WS.url(url + "/ping").get().map { response =>
response.body
}
var resp = ""
futureResponse.onComplete {
case Success(str) => {
Logger.trace(s"future success $str")
resp = str
}
case Failure(ex) => {
Logger.trace(s"future failed")
resp = ex.toString
}
}
Ok(resp)
I can see the trace in STDOUT for success/failure, but my controller action just returns "" to my browser.
I understand that this is because it returns a FUTURE and my action finishes before the future returns.
How can I force it to wait?
What options do I have with error handling?
If you really want to block until feature is completed look at the Future.ready() and Future.result() methods. But you shouldn't.
The point about Future is that you can tell it how to use the result once it arrived, and then go on, no blocks required.
Future can be the result of an Action, in this case framework takes care of it:
def index = Action.async {
WS.url(url + "/ping").get()
.map(response => Ok("Got result: " + response.body))
}
Look at the documentation, it describes the topic very well.
As for the error-handling, you can use Future.recover() method. You should tell it what to return in case of error and it gives you new Future that you should return from your action.
def index = Action.async {
WS.url(url + "/ping").get()
.map(response => Ok("Got result: " + response.body))
.recover{ case e: Exception => InternalServerError(e.getMessage) }
}
So the basic way you consume service is to get result Future, transform it in the way you want by using monadic methods(the methods that return new transformed Future, like map, recover, etc..) and return it as a result of an Action.
You may want to look at Play 2.2 -Scala - How to chain Futures in Controller Action and Dealing with failed futures questions.

Play framework 2.0: Store values in Http.Context

I'm trying to implement "request based" sessions in scalaquery in the play framework. I create a session using scalaquery and attempt to store it in the current http context, as follows:
def withTransaction[A](bp: BodyParser[A])(f: Request[A] => Result): Action[A] = {
Action(bp) {
request =>
val context = Http.Context.current()
val session = createSession()
session.conn.setAutoCommit(false)
context.args.put("scalaquery.session", session)
try {
val result = f(request)
session.conn.commit()
result
}
catch {
case t: Throwable =>
session.conn.rollback()
throw t
}
finally {
session.close()
context.args.remove("scalaquery.session")
}
}
}
then i wrap my actions in my controllers like:
withTransaction(parse.anyContent) {
Action {
//code that produces a result here
}
}
However, it crashes in the following line saying:
val context = Http.Context.current()
[RuntimeException: There is no HTTP Context available from here.]
So, why is the context not available? This code is called directly by the framework, so shouldn't the context be set by the time this code executes? Or am i using the wrong way for accessing the context?
EDIT: The "session" is of type org.scalaquery.session.Session. The reason why i want to set it in the HttpContext is so that the wrapped actions can access it in an "http scoped" fashion, i.e. That each request stores their session separately, yet all services that need a session can find it in a public scope that is separated per request.
I think the problem is you're using the Java API with the Scala controller. Http.Context is only set if you're using the Java controller. Have you considered using the Scala Session API?
Also, another question is, why do you need to store the session in the context? I see you just remove it at the end anyway. If what you need is for the sub-actions to be able to access the session, you could just pass it in the function.
I'm just going to assume session is of type Session
def withTransaction[A](bp: BodyParser[A])(f: Session => Request[A] => Result): Action[A] = {
Action(bp) {
request =>
val session = createSession()
session.conn.setAutoCommit(false)
try {
val result = f(session)(request)
session.conn.commit()
result
}
catch {
case t: Throwable =>
session.conn.rollback()
throw t
}
finally {
session.close()
}
}
}
and your sub-actions would be
withTransaction(parse.anyContent) { session => request =>
//code that produces a result here
}
you don't need to wrap this in Action anymore since it's already wrapped by withTransaction