UPDATED: Method is returning type ANY rather than type Future[string]. Require return of type String.
I'm making a http request using the play.ws library 2.6. This was previously done with a curl request but this only uses basic authentication.
Below is my code and I'm trying to return a json string from this function to be deserialised in another method.
import java.io.{File, InputStream}
import java.nio.file.Paths
import javax.inject._
import org.apache.commons.io.FileUtils
import play.api._
import play.api.http.HttpEntity
import play.api.libs.ws._
import play.api.mvc._
import play.api.Play.current
import scala.collection.mutable.ListBuffer
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import sys.process._
#Singleton
class BuildService #Inject() (
ws: WSClient,
ec: ExecutionContext,
config: Configuration) {
def bbApiRequest(requestUrl: String, timeout: FiniteDuration):
Future[String] = {
val request = ws
.url(requestUrl)
.withAuth(
"user",
"pw1234",
WSAuthScheme.BASIC)
.get()
Await.result(request, timeout)
val returner = request.map(_.json)
} // <-- line 72 in below error points here.
}
When run it produces the error:
[error] C:\my_path\app\services\BuildService.scala:72: type mismatch;
[error] found : Unit
[error] required: scala.concurrent.Future[String]
[error] }
[error] ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[info] Compiling 1 Scala source to C:\my_path\restapi\target\scala-
2.12\classes...
[error] C:\my_path\restapi\app\services\BuildService.scala:72: type
mismatch;
[error] found : Unit
[error] required: scala.concurrent.Future[String]
[error] }
[error] ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
I'm trying to get ideally:
A string return (case class and json method to unpack a string)
Synchronous request (if async I'll need to wait to complete to progress application)
Secure (allows use of tokens for verification)
Appreciate any help!
Here is the function I use:
// first work with Futures the Play Controller will support that!
def bbApiRequest(requestUrl: String): Future[String] = {
// call the webservice with basic authentication
wsClient.url(requestUrl)
.withAuth("tester", "pwd123", WSAuthScheme.BASIC)
.get()
.map(checkStatus) // function that evaluates the HTTP Status
.map(_.json) // get the json
.map(Json.prettyPrint) // get it as string
}
I would create a case class directly like:
.map(jsValue => handleJson(jsValue.validate[YourModel])) // or create a model of it (instead) instead of .map(Json.prettyPrint)
Edit
Here an example of checkStatus:
protected def checkStatus(resp: WSResponse): WSResponse = {
resp.status match {
case Status.OK => resp
case Status.NOT_FOUND => throw WebNotFoundException()
case Status.FORBIDDEN | Status.UNAUTHORIZED => throw WebAccessForbiddenException()
case Status.NOT_ACCEPTABLE => throw WebNotAcceptableException()
case _ => throw WebBadStatusException(resp.status + " - " + resp.statusText.toString)
}
}
The Exception are created by myself.
The authorization can also be inputs into the function or retrieved from environment variables within the method (much easier to manage).
Simply needed to use .body on the Await call, which converts the output to generic type string.
package utils
import javax.inject._
import play.api._
import play.api.http.HttpEntity
import play.api.libs.ws._
import play.api.mvc._
import play.api.Play.current
import scala.collection.mutable.ListBuffer
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import sys.process._
#Singleton
class HTTPRequest #Inject() (
ws: WSClient,
ec: ExecutionContext,
config: Configuration) {
def bbApiRequest(requestUrl: String, timeout: FiniteDuration) = {
val request = ws
.url(requestUrl)
.withAuth(
"user",
"PW123",
WSAuthScheme.BASIC)
.get()
Await.result(request, timeout).body
}
}
Related
Basically I trying to get data from database using akka http. if i pass (EmployeeRepo.findAll()) directly in api then it show all data but while using actor it shows cast error....
here problem for data fetching only please solve it
this is my EmployeeRepo-----------------------------------
package org.repo
import org.data.Employee
import org.db.DbConfig
import org.mongodb.scala.MongoCollection
import org.utils.JsonUtils
import org.mongodb.scala.bson.conversions.Bson
import org.mongodb.scala.model.Filters.equal
import org.mongodb.scala.model.FindOneAndUpdateOptions
import org.mongodb.scala.model.Updates.{combine, set}
import scala.concurrent.Future
object EmployeeRepo extends JsonUtils{
private val employeeDoc:MongoCollection[Employee]=DbConfig.employees
def createCollection()={
DbConfig.database.createCollection("employee").subscribe(
(result)=>println(s"$result"),
e=>println(e.getLocalizedMessage),
()=>println("complete")
)
}
def insertData(emp:Employee)={
employeeDoc.insertOne(emp).toFuture()
}
def findAll()= employeeDoc.find().toFuture()
def update(emp:Employee,id:String)=
employeeDoc
.findOneAndUpdate(equal("_id", id),
setBsonValue(emp),
FindOneAndUpdateOptions().upsert(true)).toFuture()
def delete(id:String)=
employeeDoc.deleteOne(equal("_id",id)).toFuture()
private def setBsonValue(emp:Employee)={
combine(
set("name",emp.name),
set("dateOfBirth",emp.dateOfBirth)
)
}
}
============EMployeeService------------
package org.service
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.UUID
import akka.NotUsed
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Source
import org.data.Employee
import org.domain.EmployeeRequest
import org.repo.EmployeeRepo
import scala.concurrent.Future
class EmployeeService {
implicit val system=ActorSystem("Employeee")
implicit val materializer=ActorMaterializer
import system.dispatcher
def saveEmployeeData= (employeeRequest:EmployeeRequest) => {
val employeeDoc:Employee=employeeMapperWithNewId(employeeRequest)
EmployeeRepo.insertData(employeeDoc)
}
def findAll={
EmployeeRepo.findAll()
}
def update(employeeRequest: EmployeeRequest,id:String)={
val employeeDoc=employeeMapperWithNewId(employeeRequest)
EmployeeRepo.update(emp = employeeDoc,id)
}
def delete(id:String)=EmployeeRepo.delete(id)
private def employeeMapperWithNewId(employee:EmployeeRequest)={
Employee(name=employee.name,dateOfBirth = LocalDate.parse(employee.dateOfBirth, DateTimeFormatter.ISO_DATE),
_id=UUID.randomUUID.toString)
}
}
-----------------EmployeeActor------
package org.actor
import akka.actor.{Actor, ActorLogging}
import org.actor.EmployeeActor.{Delete, Save, SearchAll, Update}
import org.data.Employee
import org.domain.EmployeeRequest
import org.service.EmployeeService
object EmployeeActor {
case class Save(emp: EmployeeRequest)
case object SearchAll
case class Update(emp: EmployeeRequest, id: String)
case class Delete(id: String)
}
class
EmployeeActor extends Actor with ActorLogging {
private val employeeService: EmployeeService = new EmployeeService()
override def receive: Receive = {
case Save(emp) =>
log.info(s"recevied msg saved with employee :$emp")
sender() ! employeeService.saveEmployeeData(emp)
case SearchAll =>
log.info("received msg find ALL")
sender() ! employeeService.findAll
case Update(emp, id) =>
log.info(s"received update msg for id $id and employee $emp")
sender() ! employeeService.update(emp, id)
case Delete(id) =>
log.info(s"received msg for deleting employee of id $id")
sender() ! employeeService.delete(id)
case _ =>
log.debug("Unhandled msg")
}
}
---------------EmployeeRoute----
package org
import akka.actor.{ActorSystem, Props}
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCodes}
import akka.stream.ActorMaterializer
import org.actor.EmployeeActor
import org.utils.{JsonUtils, TimeUtils}
import akka.http.scaladsl.server.Directives._
import akka.{NotUsed, util}
import akka.util.{ByteString, Timeout}
import org.data.Employee
import org.domain.EmployeeRequest
import akka.pattern.{Patterns, ask}
import akka.stream.scaladsl.Source
import org.actor.EmployeeActor.{Delete, Save, SearchAll, Update}
import org.service.EmployeeService
import scala.concurrent.duration._
import spray.json._
import scala.concurrent.{Await, Future}
class EmployeeRoute extends JsonUtils{
implicit val system=ActorSystem("Employee")
implicit val materializer=ActorMaterializer
import system.dispatcher
val actor=system.actorOf(Props[EmployeeActor],"employeeActor")
val employeeService=new EmployeeService()
implicit val timeOut=Timeout(5.seconds)
val getRoute={
pathPrefix("employee"){
(pathEndOrSingleSlash & get){
complete((actor ? SearchAll).mapTo[Seq[EmployeeRequest]])
}~
( path("update") & put & parameter("id".as[String])){id=>
entity(as[EmployeeRequest]){employee=>
complete((actor ? Update(employee,id)).map(_=>StatusCodes.OK))
}
}~
post{
entity(as[EmployeeRequest]) { employee =>
complete((actor ? Save(employee)).map(_ => StatusCodes.OK))
}
}~
delete{
(path(Segment) |parameter("id".as[String])){id=>
complete((actor ? Delete(id)).map(_=>StatusCodes.OK))
}
}
}
}
}
=================Error===============
[ERROR] [09/09/2020 19:46:48.551] [web-app-akka.actor.default-dispatcher-4] [akka.actor.ActorSystemImpl(web-app)] Error during processing of request: 'Cannot cast scala.concurrent.impl.Promise$Transformation to scala.collection.immutable.Seq'. Completing with 500 Internal Server Error response. To change default exception handling behavior, provide a custom ExceptionHandler.
java.lang.ClassCastException: Cannot cast scala.concurrent.impl.Promise$Transformation to scala.collection.immutable.Seq
at java.base/java.lang.Class.cast(Class.java:3734)
at scala.concurrent.Future.$anonfun$mapTo$1(Future.scala:464)
at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:430)
at scala.concurrent.ExecutionContext$parasitic$.execute(ExecutionContext.scala:164)
at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:392)
at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:299)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:249)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:242)
at akka.pattern.PromiseActorRef.$bang(AskSupport.scala:615)
at org.actor.EmployeeActor$$anonfun$receive$1.applyOrElse(EmployeeActor.scala:32)
at akka.actor.Actor.aroundReceive(Actor.scala:537)
at akka.actor.Actor.aroundReceive$(Actor.scala:535)
at org.actor.EmployeeActor.aroundReceive(EmployeeActor.scala:22)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:577)
at akka.actor.ActorCell.invoke(ActorCell.scala:547)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:270)
at akka.dispatch.Mailbox.run(Mailbox.scala:231)
at akka.dispatch.Mailbox.exec(Mailbox.scala:243)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
please help me out..
You analyzed almost everything. EmployeeRepo.findAll is your problem. You should not use Futures in akka actors. pipeTo should be used instead.
Please try to update EmployeeActor
case SearchAll =>
log.info("received msg find ALL")
employeeService.findAll.pipeTo(sender())
I've created a http server with akka http as follows:
import akka.actor.typed.{ActorRef, ActorSystem}
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Route
import com.sweetsoft.LoggerActor.Log
import akka.actor.typed.scaladsl.adapter._
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.model._
import com.sweetsoft._
import akka.http.scaladsl.server.Directives._
import akka.stream.typed.scaladsl.ActorMaterializer
import scala.concurrent.Future
object ProducerActor {
private val route: Option[ActorRef[ProducerMessage]] => Option[ActorRef[Log]] => Route
= store => logger =>
path("producer") {
post {
entity(as[ProducerMessage]) { msg =>
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
}
}
}
def create[A](store: Option[ActorRef[ProducerMessage]], logger: Option[ActorRef[Log]])
(implicit system: ActorSystem[A])
: Future[ServerBinding] = {
implicit val materializer = ActorMaterializer()
//Please log
Http()(system.toUntyped).bindAndHandle(route(store)(logger), getServerIp, getServerPort)
}
}
The compiler complains:
[error] /home/developer/scala/plugger/src/main/scala/com/sweetsoft/producer/ProducerActor.scala:35:56: type mismatch;
[error] found : akka.http.scaladsl.server.Route
[error] (which expands to) akka.http.scaladsl.server.RequestContext => scala.concurrent.Future[akka.http.scaladsl.server.RouteResult]
[error] required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]
[error] Http()(system.toUntyped).bindAndHandle(route(store)(logger), getServerIp, getServerPort)
Do I forget to import any libraries?
From the documentation:
Using Route.handlerFlow or Route.asyncHandler a Route can be lifted into a handler Flow or async handler function to be used with a bindAndHandleXXX call from the Core Server API.
Note: There is also an implicit conversion from Route to Flow[HttpRequest, HttpResponse, Unit] defined in the RouteResult companion, which relies on Route.handlerFlow.
Therefore, you have at least three options:
Call Route.handlerFlow:
...bindAndHandle(Route.handlerFlow(route(store)(logger)), ...)
Import the methods in the Route companion object and do the same as above, except now you can drop the explicit reference to the Route object:
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Route._
...bindAndHandle(handlerFlow(route(store)(logger)), ...)
Import akka.http.scaladsl.server.RouteResult._:
import akka.http.scaladsl.server.RouteResult._
...bindAndHandle(route(store)(logger), ...)
I have below code.
import enumeratum.{Enum, EnumEntry}
sealed abstract class AppEnvironment extends EnumEntry
object AppEnvironment extends Enum[AppEnvironment] {
case object Local extends AppEnvironment
case object Testing extends AppEnvironment
case object Production extends AppEnvironment
override val values: Vector[AppEnvironment] =
findValues.toVector
}
import java.net.InetAddress
import ciris.Secret
import eu.timepit.refined.types.net.UserPortNumber
import scala.concurrent.duration.Duration
final case class ApiConfig(
host: InetAddress,
port: UserPortNumber,
apiKey: Secret[ApiKey],
timeout: Duration
)
import java.net.InetAddress
import cats.Show
import cats.derived._
import cats.implicits._
import ciris.Secret
import ciris.cats._
import enumeratum.EnumEntry
import eu.timepit.refined.auto._
import eu.timepit.refined.cats._
import eu.timepit.refined.types.net.UserPortNumber
import eu.timepit.refined.types.string.NonEmptyString
import is.cir.example.domain.config.AppEnvironment.{Local, Production, Testing}
import scala.concurrent.duration._
final case class Config(
appName: NonEmptyString,
environment: AppEnvironment,
api: ApiConfig
)
object Config {
import cats.implicits._
implicit val showConfig: Show[Config] = {
implicit val showInetAddress: Show[InetAddress] =
Show.fromToString
implicit def showEnumEntry[E <: EnumEntry]: Show[E] =
Show.show(_.entryName)
semi.show
}
}
I have to add the scalac option -Ypartial-unification in build.sbt to resolve another issue after adding I get another exception which was not happening earlier(without the flag).
The error is -
[error] /Users/rajkumar.natarajan/Documents/Coding/OS/ciris-example/src/main/scala/is/cir/example/domain/config/Config.scala:38:10: type mismatch;
[error] found : cats.Show[shapeless.CNil]
[error] required: cats.Show[is.cir.example.domain.config.Config]
[error] semi.show
[error] ^
[error] one error found
How can I resolve this error?
You should call it specifying type parameter
semi.show[Config]
At least error message is different then
Error:(82, 14) diverging implicit expansion for type cats.derived.MkShow[is.cir.example.domain.config.Config]
starting with value tagRefType in object RefType
semi.show[Config]
You can try to play with your Show instances. In order to have a Show for "bigger" case class you should have Show for "smaller" ones.
implicitly[Show[Config]]
implicitly[Show[AppEnvironment]]
implicitly[Show[AppEnvironment.Local.type]]
//...
implicitly[Show[ApiConfig]]
implicitly[Show[InetAddress]]
//...
I'm using a Silhouette v4.0 library with play framework 2.5.
And have been trying to write test code using play specs2.
But, I get the following error with my test class as below.
Error Message
[error] could not find implicit value for parameter env: com.mohiva.play.silhouette.api.Environment[utils.auth.DefaultEnv]
.withAuthenticator[DefaultEnv](identity.loginInfo)
^
Here's the test class
package controllers
import com.google.inject.AbstractModule
import org.joda.time.DateTime
import org.specs2.specification.Scope
import org.specs2.matcher._
import org.specs2.mock._
import play.api.test._
import play.api.libs.json._
import play.api.libs.json.Json
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.mailer.{ MailerClient, Email }
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.inject.bind
import com.mohiva.play.silhouette.test._
import com.mohiva.play.silhouette.api._
import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
import com.mohiva.play.silhouette.api.util._
import com.mohiva.play.silhouette.impl.providers._
import net.codingwell.scalaguice.ScalaModule
import utils.auth.DefaultEnv
class TestControllerSpec extends PlaySpecification with Mockito {
"case" in new Context {
new WithApplication(application) {
val request = FakeRequest(POST, "/api/test")
.withAuthenticator[DefaultEnv](identity.loginInfo) // <-
val result = route(app, request).get
status(result) must be equalTo OK
}
}
trait Context extends Scope {
val identity = User(
loginInfo = LoginInfo(..)
..
)
implicit val env = FakeEnvironment[DefaultEnv](Seq(identity.loginInfo -> identity))
class FakeModule extends AbstractModule with ScalaModule {
def configure() = {
bind[Environment[DefaultEnv]].toInstance(env)
}
}
lazy val application = new GuiceApplicationBuilder()
.overrides(new FakeModule)
.build
}
}
There are some other test classes similar to this class are properly able to compile and execute.
It's kind of implicit problem with scope..
Therefore, I tried to import all the same as another test class which's able to compile properly. But, still unable to compile.
Missing some import?
As the compiler states, you're missing an implicit value. Use the following, which is modeled after one of Silhouette's specs:
class TestControllerSpec extends PlaySpecification with Mockito {
"the POST request" should {
"return an OK response" in new Context {
new WithApplication(application) {
val identity = User(LoginInfo(...))
implicit val env = FakeEnvironment[DefaultEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest(POST, "/api/test")
.withAuthenticator(identity.loginInfo)
val result = route(app, request).get
status(result) must be equalTo OK
}
}
}
trait Context extends Scope {
...
}
}
I am trying to create a controller that extracts a file on the fly from an archive and then then renders it as a static asset with the default Asset controller
import play.api.Play.current
// import controllers.Assets
import play.api.Logger
import myextract.Extract
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object MyAssets extends Controller {
// drop the version and serve the asset
def at(path: String, file: String): Action[AnyContent] = Action.async {
Logger.info("looking for file = %s" format file)
val ret = Future {
Extract.getFile(path, file) // extracts file from an archive
// throws exception if file cannot be extracted
}
ret.map {
v => Assets.at(path, file)
}
}
}
I get these errors at compile
./activator compile
[info] Loading project definition from /home/sylvain/yo-scala/project
[info] Set current project to play-slick-advanced (in build file:/home /sylvain/yo-scala/)
[info] Compiling 1 Scala source and 1 Java source to /home/sylvain/yo-scala/target/scala-2.11/classes...
[error] /home/sylvain/yo-scala/app/controllers/MyAssets.scala:14: overloaded method value async with alternatives:
[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[play.api.mvc.Action[play.api.mvc.AnyContent]])
[error] def at(path: String, file: String): Action[AnyContent] = Action.async {
[error]
[error] one error found
[error] (compile:compile) Compilation failed
You either have to make sure your action returns a Future:
import play.api.Play.current
// import controllers.Assets
import play.api.Logger
import myextract.Extract
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object MyAssets extends Controller {
// drop the version and serve the asset
def at(path: String, file: String): Action[AnyContent] = Action.async {
Logger.info("looking for file = %s" format file)
val ret = Future {
Extract.getFile(path, file) // extracts file from an archive
// throws exception if file cannot be extracted
}
ret.map {
v => scala.concurrent.Future {Assets.at(path, file)}
}
}
}
Or change it so it's not async anymore:
import play.api.Play.current
// import controllers.Assets
import play.api.Logger
import myextract.Extract
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object MyAssets extends Controller {
// drop the version and serve the asset
def at(path: String, file: String): Action[AnyContent] = Action {
Logger.info("looking for file = %s" format file)
val ret = Future {
Extract.getFile(path, file) // extracts file from an archive
// throws exception if file cannot be extracted
}
ret.map {
v => Assets.at(path, file)
}
}
}