Select Git revision
ExamplesController.scala
Will Billingsley authored
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")
}
}