I hate to ask - I really do but this one has got me for the moment..
I'm trying to compose some actions (in Play Framework & scala) with my main guide being this vid. However it was made a few years back so some of the functionality has since been deprecated and therefore I have had to find work-arounds as I go. Currently I am trying to output two asynchronous actions within some HTML markup.
I successfully outputted one action with this controller:
package controllers
import akka.actor.ActorSystem
import javax.inject._
import play.api.mvc._
import services.ServiceClient
import scala.concurrent.ExecutionContext
#Singleton
class AsyncController #Inject() (sc: ServiceClient)(actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
def index = Action.async { request =>
val asy1 = sc.makeServiceCall("async1")
for {
async1Message <- asy1
} yield {
Ok(views.html.async1.async1(async1Message))
}
}
}
In case you are wondering the sc.makeServiceCall refers to this file:
class ServiceClient #Inject() (ws: WSClient) {
def makeServiceCall(serviceName: String): Future[String] = {
ws.url(s"http://localhost:9000/mock/$serviceName").get().map(_.body)
}
}
So I followed the video in its' guidance to compose two asynchronous actions with some HTML. And this is where it gets difficult/interesting/upsetting:
package controllers
import javax.inject.Inject
import akka.actor.ActorSystem
import play.api.mvc._
import scala.concurrent.{ExecutionContext}
import Ui.Pagelet
class AsyncHomeController #Inject() (as1: AsyncController)(as2: Async2Controller)(actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
def index = Action.async { request =>
val asy1 = as1.index(request)
val asy2 = as2.index(request)
for {
async1Result <- asy1
async2Result <- asy2
async1Body <- Pagelet.readBody(async1Result)
async2Body <- Pagelet.readBody(async2Result)
} yield {
Ok(views.html.home2(async1Body, async2Body))
}
}
}
So Async2Controller is very similar to AsyncController and Pagelet.readBody refers to this:
package Ui
import play.api.libs.iteratee.Iteratee
import play.api.mvc.{Codec, Result}
import play.twirl.api.Html
import scala.concurrent._
object Pagelet {
def readBody(result: Result)(implicit codec: Codec): Future[Html] = {
result.body.run(Iteratee.consume()).map(bytes => Html(new String(bytes, codec.charset)))
}
}
And this is wherein the error lies - which is:
value run is not a member of play.api.http.HttpEntity
I cannot find documentation on whether it needs to be injected or any indication that it has since been deprecated. If someone has got an answer to this or a work-around please divulge. Many thanks
The Iteratee lib is deprecated and was replaced by akka-stream. You need to change the implementation of readBody:
def readBody(result: Result)(implicit mat: Materializer, ec: ExecutionContext, codec: Codec): Future[Html] = {
result.body.consumeData.map(byteString => Html(codec.decode(byteString))
}
You also need to change the dependencies of the controller to get a Materializer:
class AsyncHomeController #Inject() (as1: AsyncController, as2: Async2Controller)(actorSystem: ActorSystem)(implicit exec: ExecutionContext, mat: Materializer)
Edit: code updated
Related
I have an existing Scala play application which has a REST API that calls another external REST API. I want to mock the external Web service returning fake JSON data for internal tests. Based on example from: https://www.playframework.com/documentation/2.6.x/ScalaTestingWebServiceClients
I followed example exactly as in Documentation and I'm getting compiler errors due to deprecated class Action.
import play.core.server.Server
import play.api.routing.sird._
import play.api.mvc._
import play.api.libs.json._
import play.api.test._
import scala.concurrent.Await
import scala.concurrent.duration._
import org.specs2.mutable.Specification
import product.services.market.common.GitHubClient
class GitHubClientSpec extends Specification {
import scala.concurrent.ExecutionContext.Implicits.global
"GitHubClient" should {
"get all repositories" in {
Server.withRouter() {
case GET(p"/repositories") => Action {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result must_== Seq("octocat/Hello-World")
}
}
}
}
}
object Action in package mvc is deprecated: Inject an ActionBuilder
(e.g. DefaultActionBuilder) or extend
BaseController/AbstractController/InjectedController
And this is the primary example from latest official docs which in fact contains a compile time error, given this example doesn't work how should be the proper way to easily mock an external API using Scala Play?
You may change your example to:
Server.withRouterFromComponents() { cs => {
case GET(p"/repositories") => cs.defaultActionBuilder {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
}
} { implicit port =>
WsTestClient.withClient { client =>
val result = Await.result(
new GitHubClient(client, "").repositories(), 10.seconds)
result should be(Seq("octocat/Hello-World"))
}
}
To be honest, I'm not 100% sure if this is the nicest way. However I have submitted a PR to the play framework so you might watch that space for comments from the makers.
If you're using standalone version of play-ws you can use this library https://github.com/f100ded/play-fake-ws-standalone like this
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import org.f100ded.play.fakews._
import org.scalatest._
import play.api.libs.ws.JsonBodyWritables._
import scala.concurrent.duration.Duration
import scala.concurrent._
import scala.language.reflectiveCalls
/**
* Tests MyApi HTTP client implementation
*/
class MyApiClientSpec extends AsyncFlatSpec with BeforeAndAfterAll with Matchers {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
behavior of "MyApiClient"
it should "put access token to Authorization header" in {
val accessToken = "fake_access_token"
val ws = StandaloneFakeWSClient {
case request # GET(url"http://host/v1/foo/$id") =>
// this is here just to demonstrate how you can use URL extractor
id shouldBe "1"
// verify access token
request.headers should contain ("Authorization" -> Seq(s"Bearer $accessToken"))
Ok(FakeAnswers.foo)
}
val api = new MyApiClient(ws, baseUrl = "http://host/", accessToken = accessToken)
api.getFoo(1).map(_ => succeed)
}
// ... more tests
override def afterAll(): Unit = {
Await.result(system.terminate(), Duration.Inf)
}
}
Using Scala play version 2.5 and trying to follow the guidelines for unit testing controllers as per documentation at: https://www.playframework.com/documentation/2.5.x/ScalaTestingWithScalaTest
there's no example for unit testing an async controller.
I'm trying to create a unit test for my controller which has an async action method, I ended up mocking some objects
class ProductController #Inject()(
action: ProductAction,
handler: ProductResourceHandler)(implicit ec: ExecutionContext)
extends Controller {
/**
* Fetch a list of products
*/
def index: Action[AnyContent] = {
action.async { implicit request =>
handler.find.map { list =>
Ok(Json.toJson(list))
}
}
}
// ...
}
My unit test:
import scala.concurrent.Future
import org.scalatestplus.play._
import play.api.mvc._
import play.api.test._
import play.api.test.Helpers._
import org.scalatest.mockito.MockitoSugar
import product.ProductAction
import product.ProductController
import product.services.maps.GeolocationService
import product.ProductResourceHandler
import play.api.libs.concurrent.Execution.Implicits._
import scala.io.Source
import play.api.libs.json.Json
import product.model.OfferList
import product.model.OfferDetail
import org.mockito.Mockito._
class ProductControllerSpec extends PlaySpec with Results with MockitoSugar {
private val productList = Json.parse(Source.fromFile("conf/app/sample_products.json").getLines.mkString).as[ProductList]
"Example Page#index" should {
"should be valid" in {
val action = new ProductAction()
val handler = mock[ProductResourceHandler]
when(handler.find) thenReturn Future.successful(productList)
val controller = new ProductController(action, handler)
val result: Future[Result] = controller.index().apply(FakeRequest())
val bodyText: String = contentAsString(result)
bodyText != null mustBe true
}
}
}
up till now it's working but I'm wondering if this follows the best practices or guidelines for this type of test. Is this the right way to unit test an async controller in Scala play framework?
Some of my suggestions are
use contentAsJson instead of contentAsString and inspect the returned json.
use route to directly invoke the controller and test response (for eg route(app, FakeRequest..)
use status method to check if the returned status is HTTP OK (status code 200)
val Some(result) = route(app, FakeRequest(GET,
controllers.routes. ProductController.index.path()))
status(result) must be (OK)
val json = contentAsJson(result)
// inspect json fields like if you have to check if the json
// has string field called id you can do (json \ "id").as[String] must be ("<id value>")
According to Play documentation:
Play actions are asynchronous by default.
This means even if you are not using Action.async { Future { myAnonymousFunction } } but just Action { myAnonymousFunction }, internally the result of myAnonymousFunction will be enclosed in a Future.
For instance, say you have
class HelloWorld extends Controller {
def index = Action { request => Ok("") }
}
then
(new HelloWorld).index().apply(FakeRequest())
still has type
Future[Result]
This leads me to believe that your unit test is indeed appropriate way of testing controllers, that is, Play's documentation is implicitly covering also the case of Action.async.
I have a play-scala application using SqLite with slick. My tables are defined like this:
#Singleton
class DataSets #Inject()(protected val dbConfigProvider: DatabaseConfigProvider, keys: PublicKeys) extends DataSetsComponent
with HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
val DataSets = TableQuery[DataSetsTable]
def all = db.run(DataSets.sortBy { _.id }.result)
...
}
My controllers get access via DI:
#Singleton
class DataSetsController #Inject() (dataSets: DataSets, env: play.Environment) extends Controller {
...
How do I get a database handle in an Actor?
class TrainActor #Inject() (dataSets: DataSets) extends Actor {
...
of course does not work as Guice does not find the DataSets class.
Edit: to clarify: I do not want to use the actor for database access in the controller (via ask), but to start some resource intensive computations from the controller after a request and store them in the db afterwards (async).
I now found a way which integrates with DI, closely following the official documentation. Because for the need of an ActorContext, InjectedActorSupport can only be inherited by Actors. This means I had to create an actor which does nothing than instantiate and start new "worker" Actors. Maybe there is an easier way, but this works correctly.
TrainActor.scala:
package actors
import javax.inject.Inject
import akka.actor._
import com.google.inject.assistedinject.Assisted
import models.{DataSet, Model, PublicKey}
import play.api.Logger
import tables.DataSets
import scala.concurrent.ExecutionContext.Implicits.global
object TrainActor {
case object Start
case class LoadData(d: DataSet, k: PublicKey)
trait Factory {
def apply(model: Model): Actor
}
}
class TrainActor #Inject() (val dataSets: DataSets, #Assisted val model: Model) extends Actor {
import TrainActor._
def receive = {
case Start =>
dataSets.findWithKey(model.id.get)
...
TrainActorStarter.scala:
package actors
import javax.inject.Inject
import akka.actor.{Actor, ActorRef}
import models.Model
import play.api.libs.concurrent.InjectedActorSupport
object TrainActorStarter {
case class StartTraining(model: Model)
}
/**
* https://www.playframework.com/documentation/2.5.x/ScalaAkka#Dependency-injecting-actors
* #param childFactory
*/
class TrainActorStarter #Inject() (childFactory: TrainActor.Factory) extends Actor with InjectedActorSupport {
import TrainActorStarter._
def receive = {
case StartTraining(model: Model) =>
val trainer: ActorRef = injectedChild(childFactory(model), s"train-model-model-${model.id.get}")
trainer ! TrainActor.Start
}
}
ActorModule.scala:
package actors
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
class ActorModule extends AbstractModule with AkkaGuiceSupport {
def configure(): Unit = {
bindActor[TrainActorStarter]("train-actor-starter")
bindActorFactory[TrainActor, TrainActor.Factory]
}
}
And finally in the controller:
package controllers
import javax.inject._
import actors.{TrainActorStarter, TrainCallbackActor}
import akka.actor.{ActorRef, ActorSystem, _}
import akka.stream.Materializer
...
#Singleton
class ModelsController #Inject() (implicit system: ActorSystem, materializer: Materializer, ..., #Named("train-actor-starter") trainActorStarter: ActorRef) extends Controller with InjectedActorSupport {
def startTraining(model: Model): Unit = {
if(model.id.isEmpty) return
trainActorStarter ! TrainActorStarter.StartTraining(model)
}
You can inject dependencies into an actor:
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
class MyModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[TrainActor]("injected-train-actor")
}
}
After that just inject actor into controller:
class MyController #Inject()(#Named("injected-train-actor") trainActor: ActorRef) {
def endpointTest = Action.async {
for {
items <- (trainActor ? FetchAll).mapTo[Seq[DataSetsTableRow]]
} yield Ok(Json.toJson(items))
}
}
Instead of having
#Singleton
class DataSets
one can declare it as a simple scala object that can act as the DataSetsDAO
object DataSets
and then in the actor just use DataSets.dbOperation just bear in mind that the result type of that will be a Future, so just schedule a message to self in the actor on the onComplete to avoid any side effects.
I am trying to replicate the basic example proposed in the Integrating with Akka, Play 2.4 for Scala doc. But I have difficulties in placing the final pieces together...
I have defined the actor (see paragraph Writing actors) at app/actors/HelloActor.scala with the following code:
package actors
import akka.actor._
object HelloActor {
def props = Props[HelloActor]
case class SayHello(name: String)
}
class HelloActor extends Actor {
import HelloActor._
def receive = {
case SayHello(name: String) =>
sender() ! "Hello, " + name
}
}
Then (see Creating and using actors) I suppose I should create a controller at app/controllers/Hello.scala with something like:
package controllers
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
#Singleton
class Hello #Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
...
}
The question: where and how I utilize the code in the following paragraph Asking things of actors to have a working solution? I have tried to add it to the above Hello.scala controller but without success.
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
implicit val timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message =>
Ok(message)
}
}
Found the solution, I had some problems with defining the implicit timeout, this is the working controller:
package controllers
import play.api.mvc._
import akka.actor._
import javax.inject._
import actors.HelloActor
import actors.HelloActor.SayHello
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import akka.pattern.ask
import akka.util.Timeout
#Singleton
class Hello #Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
implicit val timeout: Timeout = 5.seconds
def sayHello(name: String) = Action.async {
(helloActor ? SayHello(name)).mapTo[String].map { message ⇒
Ok(message)
}
}
}
Plus I added the following route in app/conf/routes:
# Actor test
GET /hello/:name controllers.Hello.sayHello(name)
How can I log all requests coming in to a Play 2 application, in a similar way to how Apache logs (i.e. including the URL and client IP)?
I wanted to do the same thing with with Play 2.5.4. It took me a little while to put the pieces together, so I thought I'd share my steps in the hope that it'll save time for someone else:
Make sure you have an access logger. There's an example of how to configure one at https://www.playframework.com/documentation/2.5.x/SettingsLogger
but you might want to play with the settings; I put configured my logger with <file>${application.home:-.}/logs/access.log</file>, and <immediateFlush>true</immediateFlush>.
Create a new RequestHandler in your root package, extending the default handler:
import javax.inject.Inject
import play.api.http._
import play.api.routing._
import play.api.mvc.RequestHeader
import play.api.Logger
/**
* Implemented to get custom, apache-style logging of requests without dumping the full netty wire.
*/
class RequestHandler #Inject() (router: Router, errorHandler: HttpErrorHandler,
configuration: HttpConfiguration, filters: HttpFilters) extends DefaultHttpRequestHandler(
router, errorHandler, configuration, filters) {
override def routeRequest(request: RequestHeader) = {
Logger("access").info(s"Request from ${request.remoteAddress}: ${request}")
super.routeRequest(request)
}
}
I was coming from Play 2.3, so I was originally planning to use GlobalSettings until I found this guide: https://www.playframework.com/documentation/2.5.x/GlobalSettings
Apologies to anyone who wants answers specific to Play 2.0, but seeing as my own 2.5-focused searches led me here, I figure this answer won't do much harm.
That's what http filters are for. Here are some detailed examples: http://www.playframework.com/documentation/2.1.1/ScalaHttpFilters
In Play 2.5.x, I used the following
import javax.inject.Inject
import akka.stream.Materializer
import play.api.Logger
import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}
import java.util.Calendar
import java.text.SimpleDateFormat
class ApacheLoggingFilter #Inject() (implicit val mat: Materializer, ec: ExecutionContext) extends Filter {
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
nextFilter(requestHeader).map { result =>
val responseSize = result.body.contentLength.getOrElse("-")
Logger("access").info(s"""${requestHeader.remoteAddress} - - [${serverTime}] "${requestHeader}" ${result.header.status} ${responseSize}""")
result
}
}
private def serverTime = {
val calendar = Calendar.getInstance()
val dateFormat = new SimpleDateFormat(
"dd/MMM/yyyy:HH:mm:ss Z")
dateFormat.setTimeZone(calendar.getTimeZone)
dateFormat.format(calendar.getTime())
}
}
Make sure you configure this Filter correctly - https://www.playframework.com/documentation/2.5.x/ScalaHttpFilters#Using-filters
Create your own Action is far more powerful and flexible.
object MyAction {
def apply[A](bodyParser: BodyParser[A])(block: Request[A] => Result): Action[A] = Action(bodyParser) {
request =>
// TODO : authentication, cache logics here
// time it
val start = ...
// process
val r = block(request)
val end = ...
// log remote address, user agent, time, etc.
r
}
// simply override to use MyAction
def apply(block: Request[AnyContent] => Result): Action[AnyContent] = this.apply(BodyParsers.parse.anyContent)(block)
// simply override to use MyAction
def apply(block: => Result): Action[AnyContent] = this.apply(_ => block)
}
in order to use it in controller, simply replace Action with MyAction
def index = MyAction {
implicit request =>
// nothing to be changed here
}