Skip to content
Snippets Groups Projects
Select Git revision
  • 70d8eeb104252372da2f20457037f854fb0363be
  • master default protected
2 results

ExamplesController.scala

Blame
  • ExamplesController.scala 5.39 KiB
    package controllers
    
    import javax.inject._
    
    import akka.stream.{Materializer, OverflowStrategy, ThrottleMode}
    import akka.stream.scaladsl.{Flow, Keep, Sink, Source, SourceQueueWithComplete}
    import play.api.libs.EventSource
    
    import scala.concurrent.duration._
    import scala.util.Random
    import play.api.libs.json.{JsValue, Json}
    import play.api.mvc._
    
    import scala.collection.mutable
    import play.api.libs.concurrent.Futures._
    
    import scala.concurrent.ExecutionContext
    
    
    @Singleton
    class ExamplesController @Inject()(cc: ControllerComponents)(implicit ec: ExecutionContext, mat: Materializer) extends AbstractController(cc) {
    
      /**
        * A controller action that receives JSON as input.
        * The bodyParser parses the content for you, and leaves the parsed JSON in req.body
        *
        * @return
        */
      def receiveJsonPost():Action[JsValue] = Action(bodyParser = parse.json) { req =>
    
        val json:JsValue = req.body
    
        /*
         * If the JSON is { name: "Fred" }
         * then this gets the value that's in name
         */
        val name = (json \ "name").as[String]
    
        Ok(s"Hello $name")
      }
    
      /**
        * A controller that outputs some JSON
        * @return
        */
      def returnJson():Action[AnyContent] = Action { req =>
    
        Ok(Json.obj(
          "Hello" -> Seq("World", "Bob", "Alice"),
          "Goodbye" -> Json.obj(
            "Mr" -> "Chips"
          )
        ))
      }
    
      /**
        * A controller that takes an optional id in the query string.
        * If we get an ID, we return it. If not, we're just allocating a random ID and then
        * redirecting so we have an ID.
        * @param id
        * @return
        */
      def game(id:Option[String]) = Action { req =>
    
        def randomId = Random.alphanumeric.take(8).toString
    
        id match {
          case Some(s) => Ok(s"It was $s")
          case None => Redirect("/examples/call", Map("id" -> Seq(randomId)))
        }
    
      }
    
    
      // A list of connected sockets that we'll push the time out to every second
      var listeners = mutable.Set.empty[SourceQueueWithComplete[String]]
    
      // Just keeps putting out the time to every listening websocket every second
      val timeTick = Source.tick(1.second, 1.second, 1).runForeach { _ =>
        for { q <- listeners } q.offer(s"The time now is ${System.currentTimeMillis()}")
      }
    
      /**
        * A websocket written using a helper function that I have defined for you.
        *
        * In this case, whenever a websocket is connected, it starts sending random message
        *
        * @return
        */
      def websocketWithHelper():WebSocket = {
        WebSocketHelper.createWebSocket[String, String](
          { queue =>
            // We now have an asynchronous queue. What would we like to do with it
            listeners.add(queue)
          },
          { msg =>
            // What would we like to do with each message
            println(msg)
          },
          { queue =>
            // What would we like to do with the queue when the websocket is complete?
            listeners.remove(queue)
          }
        )
      }
    
      /**
        * An example of a WebSocket in Play. Play uses "Akka Streams" which is a fairly advanced way of doing streams of
        * asynchronous work. However, using this template, you should just
        * @return
        */
      def websocket() = WebSocket.accept[String, String] { request =>
    
        // Our source is going to be an asynchronous queue we can push messages to with 'out.offer(msg)'. This is going to
        // be the "source" of our flow. A complication is it doesn't create the queue immediately - this just says
        // *when* this source is connected to a flow, create a queue. So we don't have a queue yet.
        val outSource = Source.queue[String](50, OverflowStrategy.backpressure)
    
        // Because I want to refer to the queue in the sink, but I haven't actually got the queue yet, I'm just creating
        // a box to put our queue in. At the moment, it's empty
        var outOpt:Option[SourceQueueWithComplete[String]] = None
    
        // Create a "sink" that describes what we want to do with each message. Here, I'm just going to count the characters
        // and echo it straight back out on the "out" queue.
        val in = Sink.foreach[String] { message =>
    
          // If we have an out queue, send the message on it
          outOpt foreach { out =>
            out.offer(s"That message had ${message.length} characters in it")
          }
    
        }
    
        // This defines a "flow" -- something that has an input and an output.
        // "Coupled" means that if the input closes, the output will close
        // "Mat" means "materialised" -- ie, it'll give us the output queue that gets created and the Future that will
        // complete when the flow is done.
        Flow.fromSinkAndSourceCoupledMat(in, outSource) { case (done, out) =>
    
          // done is a future that will complete when the flow finishes
          // out is our "materialised" output queue. All we need do is put it in the box we made earlier
          out.offer("Connected!")
          outOpt = Some(out)
    
          done.foreach { _ =>
            outOpt = None
            println("Connection closed!")
          }
    
        }
    
      }
    
      /**
        * An example using EventSource (server sent events).
        * @return
        */
      def eventSource() = Action { request =>
        // The source "will be" a queue we can push to. But this doesn't create the queue yet
        val s = Source.queue[String](50, OverflowStrategy.backpressure)
    
        // Connect the source via the EventSource's flow.
        val stream = s.viaMat(EventSource.flow[String]) {
          // It calls us back when the queue has been created!
          case (queue, m) =>
            listeners.add(queue)
            m
        }
    
        Ok.chunked(stream).as("text/event-stream")
      }
    
    
    }