I have this piece of code in the PLAY framework
def ws: WebSocket = WebSocket.acceptOrResult[JsValue, JsValue] { rh =>
implicit val req = Request(rh, AnyContentAsEmpty)
silhouette.SecuredRequestHandler { securedRequest =>
Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
}.map {
case HandlerResult(_, Some(_)) => wsFutureFlow(rh).map { flow => Right(flow) }
case HandlerResult(r, None) => Left(r)
}
// wsFutureFlow(rh).map { flow =>
// Right(flow)
// }.recover {
// case e: Exception =>
// logger.error("Cannot create websocket", e)
// val jsError = Json.obj("error" -> "Cannot create websocket")
// val result = InternalServerError(jsError)
// Left(result)
// }
}
private def wsFutureFlow(request: RequestHeader): Future[Flow[JsValue, JsValue, NotUsed]] = {
// Use guice assisted injection to instantiate and configure the child actor.
implicit val timeout = Timeout(1.second) // the first run in dev can take a while :-(
val future: Future[Any] = userParentActor ? UserParentActor.Create(request.id.toString)
val futureFlow: Future[Flow[JsValue, JsValue, NotUsed]] = future.mapTo[Flow[JsValue, JsValue, NotUsed]]
futureFlow
}
I'm a beginner to scala and essentially what I am trying to do is authenticate the request to the ws endpoint. If it's authenticated, then I give it a Flow[JsValue, JsValue, None] to act as the WebSocket connection otherwise I need to return a Result. The issue I am having is I can't quite figure out how to design the futures correctly. For context, here is the authenticated endpoint example in the documentation https://www.silhouette.rocks/docs/endpoints.
The line that doesn't compile is the below:
case HandlerResult(_, Some(_)) => wsFutureFlow(rh).map { flow => Right(flow) }
The function I'm passing to WebSocket.acceptOrResult[JsValue, JsValue] needs to return a Future[Either[Result, Flow[In, Out, _]]]. As you can see in the line that doesn't compile I'm trying to Right() the Flow, but it's not quite right. The commented section does compile though. Here is the compile error
[error] /home/julian/IdeaProjects/crypto-bloomberg-app/app-admin/src/main/scala/admin/controllers/HomeController.scala:32:62: type mismatch;
[error] found : scala.concurrent.Future[scala.util.Right[Nothing,akka.stream.scaladsl.Flow[play.api.libs.json.JsValue,play.api.libs.json.JsValue,akka.NotUsed]]]
[error] required: Either[play.api.mvc.Result,akka.stream.scaladsl.Flow[play.api.libs.json.JsValue, play.api.libs.json.JsValue, _]]
[error] case HandlerResult(_, Some(_)) => wsFutureFlow(rh).map { flow => Right(flow) }
[error] ^
[error] one error found
[error] (app-admin/compile:compileIncremental) Compilation failed
[error] Total time: 4 s, completed 21-Mar-2018 4:29:04 PM
Thanks
Use flatMap instead of map:
def ws: WebSocket = WebSocket.acceptOrResult[JsValue, JsValue] { rh =>
implicit val req = Request(rh, AnyContentAsEmpty)
silhouette.SecuredRequestHandler { securedRequest =>
Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
} flatMap {
case HandlerResult(_, Some(_)) => wsFutureFlow(rh).map(Right(_))
case HandlerResult(r, None) => Future.successful(Left(r))
}
}
Related
def partitionDocumentStorage(successMessage: SuccessMessage) =
successMessage.message.request match {
case InlineDocumentRequest(_) => 0
case ImportDocumentRequest(_) => 1
case RegisterDocumentRequest(_) => 2
case _ => 3}
val documentStorageSplitter = builder.add(Partition[SuccessMessage](4, partitionDocumentStorage))
val inlineDocumentRequest = builder.add(Flow[SuccessMessage].map {
case successMessage => successMessage.message.request match {
case inlineDoc#InlineDocumentRequest(_) => (inlineDoc, successMessage)
}
})
val inlineDocSource = Source.fromGraph(GraphDSL.create() { implicit builder =>
val to = documentStorageSplitter.out(0) ~>
inlineDocumentRequest ~>
inlineDocumentPartitionTransform
SourceShape(to.outlet)
})
...
I use Partition[T] to split the flow, it compiles well but when i start the service, i have
[error] java.lang.IllegalArgumentException: [Partition.out0] is already connected
[error] at akka.stream.scaladsl.GraphDSL$Builder.addEdge(Graph.scala:1565)
[error] at akka.stream.scaladsl.GraphDSL$Implicits$CombinerBase.$tilde$greater(Graph.scala:1722)
[error] at akka.stream.scaladsl.GraphDSL$Implicits$CombinerBase.$tilde$greater$(Graph.scala:1721)
Someone has the same issue ?
Given a scala http client being parsed through json4s, I had the following working code to transform a Future of a HttpResponse into a Future of a case class:
def getJobIds(): Future[Seq[Job]] = {
...
var req = HttpRequest(
method = HttpMethods.GET,
uri = uri
)
val res: Future[HttpResponse] = Http().singleRequest(req)
res.flatMap {
case response#HttpResponse(StatusCodes.OK, _, _, _) => {
val buffer = ByteString("")
response.entity.dataBytes
.runFold(buffer)(_ ++ _)
.map(_.utf8String)
.map(
(body: String) => {
parse(body).as[Seq[Job]]
}
)
}
case _: Any => throw new Throwable("Error!")
}
}
Now I wanted to extract the logic to transform the HttpResponse to a case classes using generics in order to not repeat myself for other calls:
def getJobIds(): Future[Seq[Job]] = {
...
val res: Future[HttpResponse] = Http().singleRequest(req)
transformResponse[Seq[Job]](res)
}
private def transformResponse[T](r: Future[HttpResponse]): Future[T] = {
r.flatMap {
case response#HttpResponse(StatusCodes.OK, _, _, _) => {
val buffer = ByteString("")
response.entity.dataBytes
.runFold(buffer)(_ ++ _)
.map(_.utf8String)
.map(parse(_).extract[T])
}
case _: Any => throw new Throwable("Error!")
}
}
Yet this now throws:
Metronome.scala:46:32: No Manifest available for T
for the line
.map(parse(_).extract[T])
How do I make the code work with the use of generics?
The issues comes from json4s extract method.
If you look it up you'll find that its defintion is:
def extract[A](implicit formats: Formats, mf: scala.reflect.Manifest[A]): A =
Extraction.extract(jv)(formats, mf)
For some reason, the implicit Manifest look-up works for the concrete type, yet for the generic case you'll have to define the implicit manifest for your T type:
private def transformResponse[T](r: Future[HttpResponse])
(implicit ev: scala.reflect.Manifest[T]): Future[T] = {
r.flatMap {
case response#HttpResponse(StatusCodes.OK, _, _, _) => {
val buffer = ByteString("")
response.entity.dataBytes
.runFold(buffer)(_ ++ _)
.map(_.utf8String)
.map(parse(_).extract[T])
}
case _: Any => throw new Throwable("Error!")
}
Now your code will compile and run.
Following code when written using generic give a compilation error.
Without Generic
def getData(id: String) = Action.async {
val items = getItems(id)
sendResult(items)
}
private def sendResult(result: Future[Any]) = {
result.map {
items => {
try {
val itemStr = items.asInstanceOf[String]
Ok(itemStr)
} catch {
case t: ClassCastException => InternalServerError(s"Casting Exception while processing output $t")
}
}
}.recover {
case t:TimeoutException => InternalServerError("Api Timed out")
case t: Throwable => InternalServerError(s"Exception in the api $t")
}
}
With Generic
def getData(id: String) = Action.async {
val items = getItems(id)
sendResult[String](items)
}
private def sendResult[T](result: Future[Any]) = {
result.map {
items => {
try {
val itemStr = items.asInstanceOf[T]
Ok(itemStr)
} catch {
case t: ClassCastException => InternalServerError(s"Casting Exception while processing output $t")
}
}
}.recover {
case t:TimeoutException => InternalServerError("Api Timed out")
case t: Throwable => InternalServerError(s"Exception in the api $t")
}
}
The code is part of play app's contorller method. First one works fine. Second one gives following compilation error
Cannot write an instance of T to HTTP response. Try to define a
Writeable[T] [error] Ok(itemStr) [error]
Using Any with a generic function doesn't make much sense.
private def sendResult[T](result: Future[Any])
// Should better be
private def sendResult[T](result: Future[T])
// ... also remove the unsafe cast
Then this T needs to be provided an instance of Writeable, so it can be written a Array[Byte] over network.
// Either ...
private def sendResult[T: Writeable](result: Future[T])
// ... or ...
private def sendResult[T](result: Future[T])(implicit w: Writeable[T])
The Ok(...) is calling the method apply[C](content: C)(implicit writeable: Writeable[C]): Result on Status class. You need to have an implicit value for Writeable[T] in scope.
As a side note, rather than using Future[Any], you may as well use Future[T] and remove the cast. Also you can use Writes for JSON serialization.
private def sendResult[T](result: Future[T])(implicit writeable: Writes[T]) = {
result.map {
items => {
Ok(Json.toJson(items))
}
}.recover {
case t:TimeoutException => InternalServerError("Api Timed out")
case t: Throwable => InternalServerError(s"Exception in the api $t")
}
}
I'm trying to craft a ScalaInterceptor that looks for an X-Forwarded-Proto header, so basically if its in production or behind a proxy then Play! auto redirects to SSL.
I've run into issues with getting this code to compile, and I'm also not sure whether this will work with the SecureSocial plugin. There are specific reasons why we aren't setting SSL=true in SecureSocial.conf that I won't go into here.
Here's what I have in my Global.scala
def WithHttpsRedirect[A](action: Action[A]): Action[A] = {
Action(action.parser) { request =>
val result = action(request)
request.headers.get("X-Forwarded-Proto").collect {
case "https" =>
result
case "http" =>
val url = "https://"+request.host+request.uri
Redirect(url)
} getOrElse {
result
}
}
}
override def onRouteRequest(request: RequestHeader): Option[Handler] = {
super.onRouteRequest(request).map { handler =>
handler match {
case a: Action[_] => WithHttpsRedirect(a)
case _ => handler
}
}
}
I'm getting a compiler error after the getOrElse:
[error] found : scala.concurrent.Future[play.api.mvc.SimpleResult]
[error] required: play.api.mvc.Result
[error] result
[error] ^
Your help is greatly appreciated!
Replace:
Action(action.parser) { request =>
with:
Action.async(action.parser) { request =>
You made need to also replace:
Redirect(url)
with:
Future.successful(Redirect(url))
Changed my method of attack, and instead implemented a filter instead of overriding onRouteRequest:
In Global.scala:
object Global extends WithFilters(HttpsFilter) with GlobalSettings
then HttpsFilter.scala:
import play.api.mvc.Results._
import play.api.mvc.{SimpleResult, RequestHeader, Filter}
import scala.concurrent._
import ExecutionContext.Implicits.global
object HttpsFilter extends Filter {
def apply(next: (RequestHeader) => Future[SimpleResult])(request: RequestHeader): Future[SimpleResult] = {
request.headers.get("X-Forwarded-Proto").collect {
case "https" =>
next(request)
case "http" =>
val url = "https://"+request.host+request.uri
Future{ Redirect(url) }
} getOrElse {
next(request)
}
}
}
I have a Future[T] and I want to map the result, on both success and failure.
Eg, something like
val future = ... // Future[T]
val mapped = future.mapAll {
case Success(a) => "OK"
case Failure(e) => "KO"
}
If I use map or flatmap, it will only map successes futures. If I use recover, it will only map failed futures. onComplete executes a callback but does not return a modified future. Transform will work, but takes 2 functions rather than a partial function, so is a bit uglier.
I know I could make a new Promise, and complete that with onComplete or onSuccess/onFailure, but I was hoping there was something I was missing that would allow me to do the above with a single PF.
Edit 2017-09-18: As of Scala 2.12, there is a transform method that takes a Try[T] => Try[S]. So you can write
val future = ... // Future[T]
val mapped = future.transform {
case Success(_) => Success("OK")
case Failure(_) => Success("KO")
}
For 2.11.x, the below still applies:
AFAIK, you can't do this directly with a single PF. And transform transforms Throwable => Throwable, so that won't help you either. The closest you can get out of the box:
val mapped: Future[String] = future.map(_ => "OK").recover{case _ => "KO"}
That said, implementing your mapAll is trivial:
implicit class RichFuture[T](f: Future[T]) {
def mapAll[U](pf: PartialFunction[Try[T], U]): Future[U] = {
val p = Promise[U]()
f.onComplete(r => p.complete(Try(pf(r))))
p.future
}
}
Since Scala 2.12 you can use transform to map both cases:
future.transform {
case Success(_) => Try("OK")
case Failure(_) => Try("KO")
}
You also have transformWith if you prefer to use a Future instead of a Try. Check the documentation for details.
In a first step, you could do something like:
import scala.util.{Try,Success,Failure}
val g = future.map( Success(_):Try[T] ).recover{
case t => Failure(t)
}.map {
case Success(s) => ...
case Failure(t) => ...
}
where T is the type of the future result. Then you can use an implicit conversion to add this structure the Future trait as a new method:
implicit class MyRichFuture[T]( fut: Future[T] ) {
def mapAll[U]( f: PartialFunction[Try[T],U] )( implicit ec: ExecutionContext ): Future[U] =
fut.map( Success(_):Try[T] ).recover{
case t => Failure(t)
}.map( f )
}
which implements the syntax your are looking for:
val future = Future{ 2 / 0 }
future.mapAll {
case Success(i) => i + 0.5
case Failure(_) => 0.0
}
Both map and flatMap variants:
implicit class FutureExtensions[T](f: Future[T]) {
def mapAll[Target](m: Try[T] => Target)(implicit ec: ExecutionContext): Future[Target] = {
val promise = Promise[Target]()
f.onComplete { r => promise success m(r) }(ec)
promise.future
}
def flatMapAll[Target](m: Try[T] => Future[Target])(implicit ec: ExecutionContext): Future[Target] = {
val promise = Promise[Target]()
f.onComplete { r => m(r).onComplete { z => promise complete z }(ec) }(ec)
promise.future
}
}