Using side effects in Akka Streams to implement commands received from a websocket - websocket

I want to be able to click a button on a website, have it represent a command, issue that command to my program via a websocket, have my program process that command (which will produce a side effect), and then return the results of that command to the website to be rendered.
The websocket would be responsible for updating state changes applied by different actors that are within the users view.
Example: Changing AI instructions via the website. This modifies some values, which would get reported back to the website. Other users might change other AI instructions, or the AI would react to current conditions changing position, requiring the client to update the screen.
I was thinking I could have an actor responsible for updating the client with changed information, and just have the receiving stream update the state with the changes?
Is this the right library to use? Is there a better method to achieve what I want?

You can use akka-streams and akka-http for this just fine. An example when using an actor as a handler:
package test
import akka.actor.{Actor, ActorRef, ActorSystem, Props, Stash, Status}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage}
import akka.http.scaladsl.server.Directives._
import akka.stream.scaladsl.{Flow, Sink, Source, SourceQueueWithComplete}
import akka.stream.{ActorMaterializer, OverflowStrategy, QueueOfferResult}
import akka.pattern.pipe
import scala.concurrent.{ExecutionContext, Future}
import scala.io.StdIn
object Test extends App {
implicit val actorSystem = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit def executionContext: ExecutionContext = actorSystem.dispatcher
val routes =
path("talk") {
get {
val handler = actorSystem.actorOf(Props[Handler])
val flow = Flow.fromSinkAndSource(
Flow[Message]
.filter(_.isText)
.mapAsync(4) {
case TextMessage.Strict(text) => Future.successful(text)
case TextMessage.Streamed(textStream) => textStream.runReduce(_ + _)
}
.to(Sink.actorRefWithAck[String](handler, Handler.Started, Handler.Ack, Handler.Completed)),
Source.queue[String](16, OverflowStrategy.backpressure)
.map(TextMessage.Strict)
.mapMaterializedValue { queue =>
handler ! Handler.OutputQueue(queue)
queue
}
)
handleWebSocketMessages(flow)
}
}
val bindingFuture = Http().bindAndHandle(routes, "localhost", 8080)
println("Started the server, press enter to shutdown")
StdIn.readLine()
bindingFuture
.flatMap(_.unbind())
.onComplete(_ => actorSystem.terminate())
}
object Handler {
case object Started
case object Completed
case object Ack
case class OutputQueue(queue: SourceQueueWithComplete[String])
}
class Handler extends Actor with Stash {
import context.dispatcher
override def receive: Receive = initialReceive
def initialReceive: Receive = {
case Handler.Started =>
println("Client has connected, waiting for queue")
context.become(waitQueue)
sender() ! Handler.Ack
case Handler.OutputQueue(queue) =>
println("Queue received, waiting for client")
context.become(waitClient(queue))
}
def waitQueue: Receive = {
case Handler.OutputQueue(queue) =>
println("Queue received, starting")
context.become(running(queue))
unstashAll()
case _ =>
stash()
}
def waitClient(queue: SourceQueueWithComplete[String]): Receive = {
case Handler.Started =>
println("Client has connected, starting")
context.become(running(queue))
sender() ! Handler.Ack
unstashAll()
case _ =>
stash()
}
case class ResultWithSender(originalSender: ActorRef, result: QueueOfferResult)
def running(queue: SourceQueueWithComplete[String]): Receive = {
case s: String =>
// do whatever you want here with the received message
println(s"Received text: $s")
val originalSender = sender()
queue
.offer("some response to the client")
.map(ResultWithSender(originalSender, _))
.pipeTo(self)
case ResultWithSender(originalSender, result) =>
result match {
case QueueOfferResult.Enqueued => // okay
originalSender ! Handler.Ack
case QueueOfferResult.Dropped => // due to the OverflowStrategy.backpressure this should not happen
println("Could not send the response to the client")
originalSender ! Handler.Ack
case QueueOfferResult.Failure(e) =>
println(s"Could not send the response to the client: $e")
context.stop(self)
case QueueOfferResult.QueueClosed =>
println("Outgoing connection to the client has closed")
context.stop(self)
}
case Handler.Completed =>
println("Client has disconnected")
queue.complete()
context.stop(self)
case Status.Failure(e) =>
println(s"Client connection has failed: $e")
e.printStackTrace()
queue.fail(new RuntimeException("Upstream has failed", e))
context.stop(self)
}
}
There are lots of places here which could be tweaked, but the basic idea remains the same. Alternatively, you could implement the Flow[Message, Message, _] required by the handleWebSocketMessages() method by using GraphStage. Everything used above is also described in detail in akka-streams documentation.

Related

Flutter how to test Either<Failure,List<Object>>

it seems to have the same value with matcher but still can't pass the test probably cuz of the memory address stuff. can anyone let me know how i can test the result when a list is in Either<< Right >>
test('get board list from remote data source', () async {
when(mockBoardRemoteDataSource.getBoards())
.thenAnswer((_) async => tBoardModels);
final result = await repository.getBoards();
verify(mockBoardRemoteDataSource.getBoards());
expect(result, equals(Right(toBoards)));
// Either<Failure, List<BoardInfo>> result;
// (new) Right<dynamic, List<BoardInfo>> Right(List<BoardInfo> _r)
});
//console result
Expected: Right<dynamic, List<BoardInfo>>:<Right([_$_BoardInfo(1, name1, address1), _$_BoardInfo(2, name2, address2)])>
Actual: Right<Failure, List<BoardInfo>>:<Right([_$_BoardInfo(1, name1, address1), _$_BoardInfo(2, name2, address2)])>
package:test_api expect
package:flutter_test/src/widget_tester.dart 455:16 expect
test\features\nurban_honey\data\repositories\board_repository_impl_test.dart 58:9 main.<fn>.<fn>.<fn>
//BoardInfo Implementation
import 'package:equatable/equatable.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'board_info.freezed.dart';
#freezed
class BoardInfo extends Equatable with _$BoardInfo {
BoardInfo._();
factory BoardInfo(int id, String name, String address) = _BoardInfo;
#override
List<Object?> get props => [id, name, address];
}
Thanks to Jay, his answer helps me to made my resolution.
This is how i made the test act:
// Act
var results = (await repository.fetch()).fold(
(failure) => failure,
(response) => response,
);
Then I made a type expectation, matching to the failure type declared on my use case:
In the case of the successful execution
// Assert
expect(results, isA<ResultType>());
In the case of the failure expectation
// Assert
expect(results, isA<FailureType>());
The entire test case looks like the following
test('On successful execution, should returns a SuccessResultType', () async {
// Arrange
repository = MyUseCaseRepository();
// Act
var results = (await repository.fetch()).fold(
(failure) => failure,
(response) => response,
);
// Assert
expect(results, isA<SuccessResultType>());
});
I hope this approach can help anybody!
I've made the test pass by doing this:
result.fold(
(l) => null,
(resultR) => Right(toBoards)
.fold((l) => null, (matcherR) => expect(resultR, matcherR)));
is there better way to do it?

What is the correct way to start a MassTransit bus when using the CreateRequestClient method?

I'm using the following code to send a request/response message between two different processes.
This is the process that "sends" the request:
// configure host
var hostUri = new Uri(configuration["RabbitMQ:Host"]);
services.AddSingleton(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(hostUri, h =>
{
h.Username(configuration["RabbitMQ:Username"]);
h.Password(configuration["RabbitMQ:Password"]);
});
}));
// add request client
services.AddScoped(provider => provider.GetRequiredService<IBus>().CreateRequestClient<QueryUserInRole, QueryUserInRoleResult>(new Uri(hostUri, "focus-authorization"), TimeSpan.FromSeconds(5)));
// add dependencies
services.AddSingleton<IPublishEndpoint>(provider => provider.GetRequiredService<IBusControl>());
services.AddSingleton<ISendEndpointProvider>(provider => provider.GetRequiredService<IBusControl>());
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());
// add the service class so that the runtime can automatically handle the start and stop of our bus
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, BusService>();
Here's the implementation of the BusService:
public class BusService : Microsoft.Extensions.Hosting.IHostedService
{
private readonly IBusControl _busControl;
public BusService(IBusControl busControl)
{
_busControl = busControl;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return _busControl.StartAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _busControl.StopAsync(cancellationToken);
}
}
The problem is that when the CreateRequestClient code runs, the bus has not started up yet. Thus the response is never returned from the consumer.
If I replace the host configuration with the following code, I get the desired behavior:
var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(hostUri, h =>
{
h.Username(configuration["RabbitMQ:Username"]);
h.Password(configuration["RabbitMQ:Password"]);
});
});
bus.Start();
services.AddSingleton(bus);
For some reason, the BusService(IHostedService) executes AFTER the AddScoped delegates.
My question is: what is the correct way to start up the bus before using the CreateRequestClient method? Or is the latter approach to starting up the bus sufficient?

Akka HTTP WebSocket client equivalent of this node.js

I have some user documentation that expresses how to use a websocket with this node snippet:
var socket = io(“HOST:PORT”);
socket.on('request-server', function() {
socket.emit('server-type', 'red')
});
What would the equivalent client code be in Akka HTTP?
I have derived the following from the example in the Akka documentation. It isn't quite what I'd like to write, because
I think I need to connect and wait for the request-server event before sending any events & I don't know how to do that
I don't know how to format the TextMessages in the Source to be equivalent to `socket.emit('server-type', 'red').
It only prints "closed"
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
val incoming: Sink[Message, Future[Done]] = Sink.foreach[Message] {
case message: TextMessage.Strict => println(message.text)
case z => println(z)
}
val outgoing = Source(List(TextMessage("'server-type': 'red'")))
val webSocketFlow = Http().webSocketClientFlow(
WebSocketRequest("ws://localhost:3000/socket.io"))
val (upgradeResponse, closed) =
outgoing
.viaMat(webSocketFlow)(Keep.right)
.toMat(incoming)(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}")
}
}
connected.onComplete(println)
closed.foreach(_ => println("closed"))
What is the Akka client equivalent to the given socket.io code?
Your connection is getting closed immediately after sending message "outgoing".
Check out Half-Closed Websockets here http://doc.akka.io/docs/akka-http/10.0.0/scala/http/client-side/websocket-support.html#half-closed-websockets

Can Websocket and normal get route be same in Akka Http?

I do have a scenario where I will want my websocket route and get route paths to be the same. Is it possible in Akka Http?
Consider the below mentioned code:
def flow: Flow[Message, Message, Any] =
Flow.fromSinkAndSource(Sink.ignore,
Source.single(TextMessage.Strict("Hello from websocket")))
val route =
path("hello") {
get {
complete(HttpEntity(ContentTypes.`application/json`,"Simple hello"))
}
} ~ path("hello") {
handleWebSocketMessages(flow)
}
If, through a websocket client, I access ws://localhost:8080/hello, I get an websocket error. But a normal curl request gives the result of Simple hello. Is it possible to somehow achieve both actions on same route.
Something along the lines of the below should do
val route = path("hello") {
optionalHeaderValueByType[UpgradeToWebSocket](()) {
case Some(upgrade) => complete(upgrade.handleMessages(flow))
case None => get {
complete("Simple hello")
}
}
}

Connection between js and akka-http websockets fails 95% of the time

I'm trying to setup a basic connection between an akka-http websocket server and simple javascript.
1 out of roughly 20 connections succeeds, the rest fails. I have no idea why the setup of the connection is so unreliable.
Application.scala:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import services.WebService
import scala.concurrent.Await
import scala.concurrent.duration._
import java.util.concurrent.TimeoutException
object Application extends App {
implicit val system = ActorSystem("api")
implicit val materializer = ActorMaterializer()
import system.dispatcher
val config = system.settings.config
val interface = config.getString("app.interface")
val port = config.getInt("app.port")
val service = new WebService
val binding = Http().bindAndHandle(service.route, interface, port)
try {
Await.result(binding, 1 second)
println(s"server online at http://$interface:$port/")
} catch {
case exc: TimeoutException =>
println("Server took to long to startup, shutting down")
system.shutdown()
}
}
WebService.scala:
import actors.{PublisherActor, SubscriberActor}
import akka.actor.{Props, ActorSystem}
import akka.http.scaladsl.model.ws.{Message, TextMessage}
import akka.http.scaladsl.server.Directives
import akka.stream.Materializer
import akka.stream.scaladsl.{Source, Flow}
import scala.concurrent.duration._
class WebService(implicit fm: Materializer, system: ActorSystem) extends Directives {
import system.dispatcher
system.scheduler.schedule(15 second, 15 second) {
println("Timer message!")
}
def route =
get {
pathSingleSlash {
getFromResource("web/index.html")
} ~
path("helloworld") {
handleWebsocketMessages(websocketActorFlow)
}
}
def websocketActorFlow: Flow[Message, Message, Unit] =
Flow[Message].collect({
case TextMessage.Strict(msg) =>
println(msg)
TextMessage.Strict(msg.reverse)
})
}
client side:
<input type="text" id="inputMessage"/><br/>
<input type="button" value="Send!" onClick="sendMessage()"/><br/>
<span id="response"></span>
<script type="application/javascript">
var connection;
function sendMessage() {
connection.send(document.getElementById("inputMessage").value);
}
document.addEventListener("DOMContentLoaded", function (event) {
connection = new WebSocket("ws://localhost:8080/helloworld");
connection.onopen = function (event) {
connection.send("connection established");
};
connection.onmessage = function (event) {
console.log(event.data);
document.getElementById("response").innerHTML = event.data;
}
});
</script>
if the connection to the server fails I get a timeout message after 5 seconds which says the following:
[DEBUG] [07/23/2015 07:59:54.517] [api-akka.actor.default-dispatcher-27] [akka://api/user/$a/flow-76-3-publisherSource-prefixAndTail] Cancelling akka.stream.impl.MultiStreamOutputProcessor$SubstreamOutput#a54778 (after: 5000 ms)
No matter if the connection fails or succeeds, I always get the following log message:
[DEBUG] [07/23/2015 07:59:23.849] [api-akka.actor.default-dispatcher-4] [akka://api/system/IO-TCP/selectors/$a/0] New connection accepted
Look at that error message carefully... it is coming from a source I would not have expected, some "MultiStreamOutputProcessor" when I only expect to handle a single stream.
That tells me - along with the webSocketActorFlow - that maybe you are getting messages and they aren't being caught by the flow, and so you're ending up with substreams you never expected.
So instead of it "only working some of the time," maybe it is "working most of the time but unable to handle all of the input as you have demanded in the flow, and you are left with un-selectable streams that have to die first.
See if you can either a) make sure you get a grip on the streams so you don't end up with stragglers, b) bandaid adjust timeouts, and c) detect such occurences and cancel processing the downstream
https://groups.google.com/forum/#!topic/akka-user/x-tARRaJ0LQ
akka {
stream {
materializer {
subscription-timeout {
timeout=30s
}
}
}
}
http://grokbase.com/t/gg/akka-user/1561gr0jgt/debug-message-cancelling-akka-stream-impl-multistreamoutputprocessor-after-5000-ms

Resources