/src/main/scala/com/experiments/calvin/WebsocketStreamsMain.scala

https://github.com/calvinlfer/akka-http-streaming-response-examples · Scala · 66 lines · 35 code · 7 blank · 24 comment · 0 complexity · 2fb4212d744edee7852fc9d4eacdc4f3 MD5 · raw file

  1. package com.experiments.calvin
  2. import akka.NotUsed
  3. import akka.actor.ActorSystem
  4. import akka.http.scaladsl.Http
  5. import akka.http.scaladsl.model.ws.{Message, TextMessage}
  6. import akka.http.scaladsl.server.Directives._
  7. import akka.stream.ActorMaterializer
  8. import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, MergeHub, Sink, Source}
  9. import scala.concurrent.Future
  10. /**
  11. * A much simpler WebSocket chat system using only Akka Streams with the help of MergeHubSource and BroadcastHub Sink
  12. * Credits: https://markatta.com/codemonkey/blog/2016/10/02/chat-with-akka-http-websockets/
  13. */
  14. object WebsocketStreamsMain extends App {
  15. implicit val actorSystem = ActorSystem(name = "example-actor-system")
  16. implicit val streamMaterializer = ActorMaterializer()
  17. implicit val executionContext = actorSystem.dispatcher
  18. val log = actorSystem.log
  19. /*
  20. many clients -> Merge Hub -> Broadcast Hub -> many clients
  21. Visually
  22. Akka Streams Flow
  23. ________________________________________________________________________________________________________________________________________________________________________________________
  24. c1 -------->\ | | /->----------- c1
  25. \ | | /
  26. c2 ----------->| Sink ========================(feeds data to)===========> MergeHub Source ->-->-->--> BroadcastHub Sink ======(feeds data to)===========> Source |->->------------ c2
  27. /| that comes from materializing the connected to that comes from materializing the | \
  28. / | MergeHub Source BroadcastHub Sink | \
  29. c3 -------->/ |________________________________________________________________________________________________________________________________________________________________________________________| \->---------- c3
  30. Runnable Flow (MergeHubSource -> BroadcastHubSink)
  31. Materializing a MergeHub Source yields a Sink that collects all the emitted elements and emits them in the MergeHub Source (the emitted elements that are collected in the Sink are coming from all WebSocket clients)
  32. Materializing a BroadcastHub Sink yields a Source that broadcasts all elements being collected by the MergeHub Sink (the elements that are emitted/broadcasted in the Source are going to all WebSocket clients)
  33. This example relies on a materializer
  34. */
  35. val (chatSink: Sink[String, NotUsed], chatSource: Source[String, NotUsed]) =
  36. MergeHub.source[String].toMat(BroadcastHub.sink[String])(Keep.both).run()
  37. private val userFlow: Flow[Message, Message, NotUsed] =
  38. Flow[Message].mapAsync(1) {
  39. case TextMessage.Strict(text) => Future.successful(text)
  40. case streamed: TextMessage.Streamed => streamed.textStream.runFold("") {
  41. (acc, next) => acc ++ next
  42. }
  43. }
  44. .via(Flow.fromSinkAndSource(chatSink, chatSource))
  45. .map[Message](string => TextMessage.Strict(string))
  46. def wsChatStreamsOnlyRoute =
  47. path("ws-chat-streams-only") {
  48. handleWebSocketMessages(userFlow)
  49. }
  50. val bindingFuture = Http().bindAndHandle(wsChatStreamsOnlyRoute, "localhost", 9000)
  51. bindingFuture
  52. .map(_.localAddress)
  53. .map(addr => s"Bound to $addr")
  54. .foreach(log.info)
  55. }