I am trying to compose a basic authentication with some other action:
def findByNameSecure(username: String) = Authenticated { _ =>
val cursor: Cursor[JsObject] = persons.
find(Json.obj("userdetails.username" -> username)).
cursor[JsObject](ReadPreference.primary)
val res = cursor.collect[List]().map { persons =>
Ok(Json.toJson(persons))
} .recover {
case _ => BadRequest(Json.parse("{'error': 'failed to read from db'}"))
}
Await.result(res, 10.seconds)
}
Route:
GET /secure/user/findbyname controllers.UserController.findByNameSecure(username: String)
This works as expected. What is disturbing is that I used Await.result which is blocking. How can I compose an async version of this kind of authentication?
I am using play 2.4.
AuthendicatedBuilder is child of ActionBuilder. So I supposed its async method should work as well.
Example of usage:
def findByNameSecure(username: String) = Authenticated.async { _ =>
Related
I am trying to play around with ZIO http using their simples hello world example. I have a Java-written service which does some logic, and it expecting a handler function, so it can call it when result is ready. How do I user it together with ZIO http ?
I want something like this:
object HelloWorld extends App {
def app(service: JavaService) = Http.collect[Request] {
case Method.GET -> Root / "text" => {
service.doSomeStuffWIthCallback((s:String) => Response.text(s))
}
}
override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
Server.start(8090, app(new JavaService)).exitCode
}
Basically I want to send ZIO HTTP response from the callback function, I am just not sure how to go about that. Thanks.
EDIT:
I coudn't get the right types from your code, so I decided to simplify the whole thing, and arrived to this:
val content: HttpData[Blocking, Throwable] = HttpData.fromStream {
ZStream.fromEffect(doSomeStuffWrapped)
}
def doSomeStuffWrapped = {
UIO.effectAsync[String] { cb =>
cb(
IO.succeed("TEST STRING")
)
}
}
However, the issue here is that types do not match, HttpData.fromStream requires ZStream of byte
Here is the link to my gist:
https://gist.github.com/pmkyl/a37ff8b49e013c4e2e6f8ab5ad83e258
You should wrap your Java service with callback in an effect using effectAsync:
def doSomeStuffWrapped(service: JavaService): Task[String] = {
IO.effectAsync[Throwable, String] { cb =>
service.doSomeStuffWithCallback((s: String) => {
// Success case
cb(IO.succeed(s))
// Optional error case?
// cb(IO.fail(someException))
})
}
}
def app(service: JavaService) = Http.collectM[Request] {
case Method.GET -> Root / "text" => {
doSomeStuffWrapped(service)
.fold(err => {
// Handle errors in some way
Response.text("An error occured")
}, successStr => {
Response.text(successStr)
})
}
}
You might want to see this article presenting different options for wrapping impure code in ZIO: https://medium.com/#ghostdogpr/wrapping-impure-code-with-zio-9265c219e2e
In ZIO-http v1.0.0.0-RC18 HttpData.fromStream can also take ZStream[R, E, String] as input with Http charset which defaults to CharsetUtil.UTF_8 however you can pass any charset to the HttpData.fromStream as its second argument. you can find the solution below
val stream: ZStream[Any, Nothing, String] = ZStream.fromEffect(doSomeStuffWrapped)
val content: HttpData[Any, Nothing] = HttpData.fromStream(stream)
def doSomeStuffWrapped = {
UIO.effectAsync[String] { cb =>
cb(
IO.succeed("TEST STRING"),
)
}
}
// Create HTTP route
val app = Http.collect[Request] {
case Method.GET -> !! / "health" => Response.ok
case Method.GET -> !! / "file" => Response(data = content)
}
// Run it like any simple app
override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
Server.start(8090, app.silent).exitCode
However in previous versions you could have done something like given below to make it work
val stream: ZStream[Any, Nothing, Byte] =
ZStream.fromEffect(doSomeStuffWrapped).mapChunks(_.map(x => Chunk.fromArray(x.getBytes(HTTP_CHARSET))).flatten)
val content: HttpData[Any, Nothing] = HttpData.fromStream(stream)
def doSomeStuffWrapped = {
UIO.effectAsync[String] { cb =>
cb(
IO.succeed("TEST STRING"),
)
}
}
// Create HTTP route
val app = Http.collect[Request] {
case Method.GET -> !! / "health" => Response.ok
case Method.GET -> !! / "file" => Response(data = content)
}
// Run it like any simple app
override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
Server.start(8090, app.silent).exitCode
Here is also another way of achieving the same result:
case class MyService(name: String) {
def imDone[R, E](s: String => Unit): Unit = s(name)
}
val s: MyService = MyService("test")
val app: Http[Any, Nothing, Request, UResponse] = Http.collectM[Request] { case Method.GET -> Root / "text" =>
ZIO.effectAsync[Any, Nothing, UResponse] { cb =>
s.imDone { b =>
cb(IO.succeed(Response.text(b)))
}
}
}
I'm a scala newbie trying to write a Rest Api using play framework. I have the following 3 data access methods
getDataDict: (dsType:String, name:String) => Future[Option[DatasetDictionary]]
getDatasetData: (DatasetDictionary) => Future[List[DatasetData]]
getMetadata: (DatasetDictionary) => Future[List[Metadata]]
I need to use these 3 methods to get the result of my async action method.
def index(dstype:String, name:String, metadata:Option[Boolean]) = Action.async{
/*
1. val result = getDataDict(type, name)
2. If result is Some(d) call getDatasetData
3.1 if metadata = Some(true)
call getMetadata function
return Ok((dict, result, metadata))
3.2 if metadata is None or Some(false)
return Ok(result)
4. If result is None
return BadRequest("Dataset not found")
*/
}
I got the steps 1 and 2 working as follows
def index1(dsType:String, dsName: String, metadata:Option[Boolean]) = Action.async {
getDataDict(dsType, dsName) flatMap {
case Some(x) => getDatasetData(x) map (x => Ok(Json.toJson(x)))
case None => Future.successful(BadRequest("Dataset not found"))
}
}
I'm stuck at how to get the metadata part working.
First of all, it is not very clear (d, result, x) what you really want to return. Hopefully I guessed it correctly:
def index(dstype:String, name:String, metadata:Option[Boolean]) = Action.async {
getDataDict(dstype, name) flatMap {
case Some(datasetDictionary) =>
getDatasetData(datasetDictionary) flatMap { datasetDataList =>
if (metadata == Some(true)) {
getMetadata(datasetDictionary) map { metadataList =>
Ok(Json.toJson((datasetDictionary, datasetDataList, metadataList)))
}
} else {
Future.successful(Ok(Json.toJson(datasetDataList)))
}
}
case None => Future.successful(BadRequest("Dataset not found"))
}
}
I'm new to asynchronous programming. I read this tutorial http://danielwestheide.com/blog/2013/01/09/the-neophytes-guide-to-scala-part-8-welcome-to-the-future.html and was surprised by how effortless I can incorporate Future into the program. However, when I was using Future with Routing, the return type is kind of wrong.
get {
optionalCookie("commToken") {
case Some(commCookie) =>
val response = (MTurkerProgressActor ? Register).mapTo[..].map({...})
val result = Await.result(response, 5 seconds)
setCookie(HttpCookie("commToken", content = result._2.mturker.get.commToken)) {
complete(result._1, result._2.mturker.get)
}
case None => // ...
}
}
I really don't want to use Await (what's the point of asynchronous if I just block the thread and wait for 5 seconds?). I tried to use for-comprehension or flatMap and place the setCookie and complete actions inside, but the return type is unacceptable to Spray. For-comprehension returns "Unit", and flatMap returns a Future.
Since I need to set up this cookie, I need the data inside. Is Await the solution? Or is there a smatter way?
You can use the onSuccess directive:
get {
optionalCookie("commToken") { cookie =>
//....
val response = (MTurkerProgressActor ? Register).mapTo[..].map({...})
onSuccess(response) {
case (result, mTurkerResponse) =>
setCookie(HttpCookie("commToken", content = mTurkerResponse.mturker.get.commToken)) {
complete(result, mturkerResponse.mturker.get)
}
}
}
There's also onFailure and onComplete (for which you have to match on Success and Failure) See http://spray.io/documentation/1.2.1/spray-routing/future-directives/onComplete/
Also, instead of using get directly it's much more idiomatic to use map (I assume the mturker is an Option or something similar):
case (result, mTurkerResponse) =>
mTurkerResponse.mturker.map { mt =>
setCookie(HttpCookie("commToken", content = mt.commToken)) {
complete(result, mt)
}
}
You can also make a custom directive using this code -
case class ExceptionRejection(ex: Throwable) extends Rejection
protected def futureDirective[T](x: Future[T],
exceptionHandler: (Throwable) => Rejection = ExceptionRejection(_)) =
new Directive1[T] {
override def happly(f: (::[T, HNil]) => Route): Route = { ctx =>
x
.map(t => f(t :: HNil)(ctx))
.onFailure { case ex: Exception =>
ctx.reject(exceptionHandler(ex))
}
}
}
Example usage -
protected def getLogin(account: Account) = futureDirective(
logins.findById(account.id)
)
getAccount(access_token) { account =>
getLogin(account) { login =>
// ...
}
}
I am working on a simple RESTful web service using Play Framework 2.1.5 and ReactiveMongo 0.9 using ReactiveMongo Play plugin. It has been a long time since I used Play Framework for the last time. I am trying to insert a document using:
def create = Action(parse.json) { request =>
Async {
val coll = db.collection[JSONCollection](...)
val obj = Json.obj(
"username" -> ...,
...
)
users.insert(obj).map { err => err match {
case e if !e.ok => InternalServerError(Json.obj("result" -> 0, "error" -> e.message))
case _ => Ok(Json.obj("result" -> 1))
}}
}
}
I have expected that once the query execution fails (e.g. due to the duplicate value in an index), I will handle it without any problem. But it is working differently - in case of failure a DatabaseException is thrown instead of satisfying the Promise[LastError] with an appropriate value. What am I missing please?
When an exception happens in a future any calls to map will be ignored and the exception will be passed along the chain of futures.
Explicitly handling the exceptions in a chain of Futures can be done with recover and recoverWith. You can read more about it in the overview of futures in the scala-lang docs:
http://docs.scala-lang.org/overviews/core/futures.html#exceptions
Try this code-
def insert(coll: BSONCollection, doc: BSONDocument): Future[Unit] = {
val p = Promise[Unit]
val f = coll.insert(doc)
f onComplete {
case Failure(e) => p failure (e)
case Success(lastError) => {
p success ({})
}
}
p.future
}
I hope this simplifies your need...
def create = Action (parse.json) { request =>
Async {
val coll = db.collection[JSONCollection](...)
val obj = Json.obj ("username" -> ...)
users.insert(obj).map {
case ins if ins.ok => OK (...)
case ins => InternalServerError (...)
} recover {
case dex: DatabaseException =>
log.error(..)
InternalServerEror(...)
case e: Throwable =>
log.error (..)
InternalServerError (...)
}
}
}
The below code does streaming back to client, in, what I gather is a more idiomatic way than using Java's IO Streams. It, however, has an issue: connection is kept open after stream is done.
def getImage() = Action { request =>
val imageUrl = "http://hereandthere.com/someimageurl.png"
Ok.stream({ content: Iteratee[Array[Byte], Unit] =>
WS.url(imageUrl).withHeaders("Accept"->"image/png").get { response => content }
return
}).withHeaders("Content-Type"->"image/png")
}
this is intended for streaming large (>1 mb) files from internal API to requester.
The question is, why does it keep the connection open? Is there something it expects from upstream server? I tested the upstream server using curl, and the connection does close - it just doesn't close when passed through this proxy.
The reason that the stream doesn't finish is because an EOF isn't sent to the iteratee that comes back from WS.get() call. Without this explicit EOF, the connection stays open - as it's in chunked mode, and potentially a long-running, comet-like connection.
Here's the fixed code:
Ok.stream({ content: Iteratee[Array[Byte], Unit] =>
WS.url(imageUrl)
.withHeaders("Accept"->"image/png")
.get { response => content }
.onRedeem { ii =>
ii.feed(Input.EOF)
}
}).withHeaders("Content-Type"->"image/png")
Here is a modified version for play 2.1.0. See https://groups.google.com/forum/#!msg/play-framework/HwoRR-nipCc/gUKs9NexCx4J
Thanks Anatoly G for sharing.
def proxy = Action {
val url = "..."
Async {
val iterateePromise = Promise[Iteratee[Array[Byte], Unit]]
val resultPromise = Promise[ChunkedResult[Array[Byte]]]
WS.url(url).get { responseHeaders =>
resultPromise.success {
new Status(responseHeaders.status).stream({ content: Iteratee[Array[Byte], Unit] =>
iterateePromise.success(content)
}).withHeaders(
"Content-Type" -> responseHeaders.headers.getOrElse("Content-Type", Seq("application/octet-stream")).head,
"Connection" -> "Close")
}
Iteratee.flatten(iterateePromise.future)
}.onComplete {
case Success(ii) => ii.feed(Input.EOF)
case Failure(t) => resultPromise.failure(t)
}
resultPromise.future
}
}
Update for play 2.2.x:
def proxy = Action.async {
val url = "http://localhost:9000"
def enumerator(chunks: Iteratee[Array[Byte], Unit] => _) = {
new Enumerator[Array[Byte]] {
def apply[C](i: Iteratee[Array[Byte], C]): Future[Iteratee[Array[Byte], C]] = {
val doneIteratee = Promise[Iteratee[Array[Byte], C]]()
chunks(i.map {
done =>
doneIteratee.success(Done[Array[Byte], C](done)).asInstanceOf[Unit]
})
doneIteratee.future
}
}
}
val iterateePromise = Promise[Iteratee[Array[Byte], Unit]]()
val resultPromise = Promise[SimpleResult]()
WS.url(url).get {
responseHeaders =>
resultPromise.success(new Status(responseHeaders.status).chunked(
enumerator({
content: Iteratee[Array[Byte], Unit] => iterateePromise.success(content)
}
)).withHeaders(
"Content-Type" -> responseHeaders.headers.getOrElse("Content-Type", Seq("application/octet-stream")).head,
"Connection" -> "Close"))
Iteratee.flatten(iterateePromise.future)
}.onComplete {
case Success(ii) => ii.feed(Input.EOF)
case Failure(t) => throw t
}
resultPromise.future
}
if anyone has a better solution, it interests me greatly!