Search code examples
scalaasynchronousplayframeworkfutureexecutioncontext

Async Controller with PlayFramework 2.6


I am using a Future.successful with the Action.async in playframework 2.6 with scala:

Code

package controllers

import com.google.inject.Inject
import play.api.Configuration
import play.api.libs.ws.{WSClient}
import play.api.libs.oauth.{ConsumerKey, RequestToken, OAuthCalculator}
import play.api.mvc.{AbstractController, ControllerComponents}
import play.api.libs.concurrent.CustomExecutionContext

import scala.concurrent.{Future, ExecutionContext}
import akka.actor.ActorSystem

trait MyExecutionContext extends ExecutionContext

class MyExecutionContextImpl @Inject()(system: ActorSystem)
  extends CustomExecutionContext(system, "my.executor") with MyExecutionContext



class Application @Inject() (config: Configuration,
                             ws: WSClient,
                             myExecutionContext: MyExecutionContext,
                             cc: ControllerComponents ) extends AbstractController(cc) {
  def credentials : Option[(ConsumerKey, RequestToken)] = for {
    apiKey <- config.getOptional[String]("twitter.apiKey")
    apiSecret <- config.getOptional[String]("twitter.apiSecret")
    token <- config.getOptional[String]("twitter.token")
    tokenSecret <- config.getOptional[String]("twitter.tokenSecret")
  } yield (
    ConsumerKey(apiKey.toString, apiSecret.toString),
    RequestToken(token.toString, tokenSecret.toString)
  )


  def tweets = Action.async {

    implicit val executor =  scala.concurrent.ExecutionContext.global
    credentials.map {
      case (consumerKey, requestToken) => {

        Future.successful {
          ws
            .url("https://stream.twitter.com/1.1/statuses/filter.json")
            .sign(OAuthCalculator(consumerKey, requestToken))
            .withQueryString("track" -> "reactive")
            .get()
            .map(response => {
              Ok(response.body)
            })
        }
      }
    } getOrElse {
      Future.successful {
        InternalServerError("Twitter credentials are missing")
      }
    }
  }
}

I have injected an execution context into the future as suggested here:

implicit val executor =  scala.concurrent.ExecutionContext.global

To make the controller async, I used the blog post here

Error

Compiling 1 Scala source to /Users/localuser/Do/play-twitter-example/target/scala-2.12/classes ...
[error] /Users/localuser/Do/play-twitter-example/app/controllers/Application.scala:37:23: overloaded method value async with alternatives:
[error]   [A](bodyParser: play.api.mvc.BodyParser[A])(block: play.api.mvc.Request[A] => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[A] <and>
[error]   (block: play.api.mvc.Request[play.api.mvc.AnyContent] => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[play.api.mvc.AnyContent] <and>
[error]   (block: => scala.concurrent.Future[play.api.mvc.Result])play.api.mvc.Action[play.api.mvc.AnyContent]
[error]  cannot be applied to (scala.concurrent.Future[Object])

Solution

  • The method ws....get() already returns a Future so you don't need to wrap it inside a Future.successful. You can simply write

    def tweets = Action.async {
    
      implicit val executor =  scala.concurrent.ExecutionContext.global
      credentials.map {
        case (consumerKey, requestToken) => {
    
          ws
            .url("https://stream.twitter.com/1.1/statuses/filter.json")
            .sign(OAuthCalculator(consumerKey, requestToken))
            .withQueryString("track" -> "reactive")
            .get()
            .map(response => {
              Ok(response.body)
            })
        }
      } getOrElse {
        Future.successful {
          InternalServerError("Twitter credentials are missing")
        }
      }
    }