How to validate Complete in AKKA-HTTP - json

I want to validate the result of the complete function of my API, the Response generates a null when it can not transform the String to a Json type, I do not want it to generate null but say: "not found", this is my route:
val route = pathPrefix("auth") {
path("signIn") {
pathEndOrSingleSlash {
post {
entity(as[LoginPassword]) { loginPassword =>
val a = signIn(loginPassword.login, loginPassword.password).map(_.asJson)
if(signIn(loginPassword.login, loginPassword.password).map(_.asJson) == null){
complete(states.map(_.asJson))
}else {
def getObject : Option[Any] = Option(signIn(loginPassword.login, loginPassword.password).map(_.asJson))
val ahh = signIn(loginPassword.login, loginPassword.password).map(_.asJson)
if(getObject.isEmpty || getObject == null){ ///////NOT FOUND
complete("Not Found")
}else {
complete(signIn(loginPassword.login, loginPassword.password).map(_.asJson)
}
//complete(signIn(loginPassword.login, loginPassword.password).map(_.asJson))
}
}
}
}
}
this does not work, since it always enters the else within the condition, responding a Json when it gets the value in BD and null when not.
Function Sign
def signIn(login: String, password: String): Future[Option[TokenEntity]] = {
db.run(users.filter(u => u.username === login).result).flatMap { users =>
users.find(user => Bcrypt.validate(password, user.password)) match {
case Some(user) => db.run(tokens.filter(_.userId === user.id).result.headOption).flatMap {
case Some(token) => Future.successful(Some(token))
case None => createToken(user).map(token => Some(token))
}
case None => Future.successful(None)
}
}
}
The Json library I use is: Json
Some help? Thanks.

I stopped to think things through and how to solve what I needed and first to validate that it was a valid response was to analyze the response of signIn:
Assign the response to a val
val token = signIn(loginPassword.login, loginPassword.password)
token is of type: Future[Option[TokenEntity]] and I need to work with: Option[TokenEntity] and validate if it is a correct answer:
val response = token.map(_ match {
case Some(token) => prepareHttpResponse(StatusCodes.OK, token.asJson.toString)
case None => prepareHttpResponse(StatusCodes.Unauthorized, "{reason: \"not found\"")
})
if it is valid I respond in format Json the Token but I respond with: not found
finally was:
val route = pathPrefix("auth") {
path("signIn") {
post {
entity(as[LoginPassword]) { loginPassword =>
val token = signIn(loginPassword.login, loginPassword.password)
val response = token.map(_ match {
case Some(token) => prepareHttpResponse(StatusCodes.OK, token.asJson.toString)
case None => prepareHttpResponse(StatusCodes.Unauthorized, "{reason: \"not found\"")
})
complete(response)
}
}
}

Related

Handle exception for Scala Async and Future variable, error accessing variable name outside try block

#scala.throws[scala.Exception]
def processQuery(searchQuery : scala.Predef.String) : scala.concurrent.Future[io.circe.Json] = { /* compiled code */ }
How do I declare the searchResult variable at line 3 so that it can be initailized inside the try block and can be processed if it's successful after and outside the try block. Or, is there any other way to handle the exception? The file containing processQuery function is not editable to me, it's read-only.
def index = Action.async{ implicit request =>
val query = request.body.asText.get
var searchResult : scala.concurrent.Future[io.circe.Json] = Future[io.circe.Json] //line 3
var jsonVal = ""
try {
searchResult = search.processQuery(query)
} catch {
case e :Throwable => jsonVal = e.getMessage
}
searchResult onSuccess ({
case result => jsonVal = result.toString()
})
searchResult.map{ result =>
Ok(Json.parse(jsonVal))
}
}
if declared in the way it has been declared it's showing compilation error
Would using the recover method help you? I also suggest to avoid var and use a more functional approach if possible. In my world (and play Json library), I would hope to get to something like:
def index = Action.async { implicit request =>
processQuery(request.body.asText.get).map { json =>
Ok(Json.obj("success" -> true, "result" -> json))
}.recover {
case e: Throwable => Ok(Json.obj("success" -> false, "message" -> e.getMessage))
}
}
I guess it may be necessary to put the code in another whole try catch:
try {
processQuery....
...
} catch {
...
}
I have here a way to validate on the incoming JSON and fold on the result of the validation:
def returnToNormalPowerPlant(id: Int) = Action.async(parse.tolerantJson) { request =>
request.body.validate[ReturnToNormalCommand].fold(
errors => {
Future.successful{
BadRequest(
Json.obj("status" -> "error", "message" -> JsError.toJson(errors))
)
}
},
returnToNormalCommand => {
actorFor(id) flatMap {
case None =>
Future.successful {
NotFound(s"HTTP 404 :: PowerPlant with ID $id not found")
}
case Some(actorRef) =>
sendCommand(actorRef, id, returnToNormalCommand)
}
}
)
}

Do one webservice request only from play framework

I'm new to the play framework generally and how to use it with Scala. I want to build a proxy for big Json objects. I achieved so far that the json is stored in a cache and if it is not there, requested from a webservice.
However when two requests are coming in, targeting the same end point (webservice and path are identicall) only one call should be performed and the other request should wait for the result of the first call. At the moment it is performing a call to the service with every request.
This is my controller:
#Singleton
class CmsProxyController #Inject()(val cmsService: CmsProxyService) extends Controller {
implicit def ec : ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
def header(path: String) = Action.async { context =>
cmsService.head(path) map { title =>
Ok(Json.obj("title" -> title))
}
}
def teaser(path: String) = Action.async { context =>
cmsService.teaser(path) map { res =>
Ok(res).as(ContentTypes.JSON)
}
}
}
This is the service:
trait CmsProxyService {
def head(path: String): Future[String]
def teaser(path: String): Future[String]
}
#Singleton
class DefaultCmsProxyService #Inject()(cache: CacheApi, cmsCaller: CmsCaller) extends CmsProxyService {
private val BASE = "http://foo.com"
private val CMS = "bar/rest/"
private val log = Logger("application")
override def head(path: String) = {
query(url(path), "$.payload[0].title")
}
override def teaser(path: String) = {
query(url(path), "$.payload[0].content.teaserText")
}
private def url(path: String) = s"${BASE}/${CMS}/${path}"
private def query(url: String, jsonPath: String): Future[String] = {
val key = s"${url}?${jsonPath}"
val payload = findInCache(key)
if (payload.isDefined) {
log.debug("found payload in cache")
Future.successful(payload.get)
} else {
val queried = parse(fetch(url)) map { json =>
JSONPath.query(jsonPath, json).as[String]
}
queried.onComplete(value => saveInCache(key, value.get))
queried
}
}
private def parse(fetched: Future[String]): Future[JsValue] = {
fetched map { jsonString =>
Json.parse(jsonString)
}
}
//retrieve the requested value from the cache or from ws
private def fetch(url: String): Future[String] = {
val body = findInCache(url)
if (body.isDefined) {
log.debug("found body in cache")
Future.successful(body.get)
} else {
cmsCaller.call(url)
}
}
private def findInCache(key: String): Option[String] = cache.get(key)
private def saveInCache(key: String, value: String, duration: FiniteDuration = 5.minutes) = cache.set(key, value, 5.minutes)
}
And finally the call to the webservice:
trait CmsCaller {
def call(url: String): Future[String]
}
#Singleton
class DefaultCmsCaller #Inject()(wsClient: WSClient) extends CmsCaller {
import scala.concurrent.ExecutionContext.Implicits.global
//keep those futures which are currently requested
private val calls: Map[String, Future[String]] = TrieMap()
private val log = Logger("application")
override def call(url: String): Future[String] = {
if(calls.contains(url)) {
Future.successful("ok")
}else {
val f = doCall(url)
calls put(url, f)
f
}
}
//do the final call
private def doCall(url: String): Future[String] = {
val request = ws(url)
val response = request.get()
val mapped = mapResponse(response)
mapped.onComplete(_ => cmsCalls.remove(url))
mapped
}
private def ws(url: String): WSRequest = wsClient.url(url)
//currently executed with every request
private def mapResponse(f: Future[WSResponse]): Future[String] = {
f.onComplete(_ => log.debug("call completed"))
f map {res =>
val status = res.status
log.debug(s"ws called, response status: ${status}")
if (status == 200) {
res.body
} else {
""
}
}
}
}
My question is: How can only one call to the webservice beeing executed? Even if there are several requests to the same target. I don't want to block it, the other request (not sure if I use the right word here) shall just be informed that there is already a webservice call on the way.
The request to head and teaser, see controller, shall perform only one call to the webservice.
Simple answer using Scala lazy keyword
def requestPayload(): String = ??? //do something
#Singleton
class SimpleCache #Inject() () {
lazy val result: Future[String] = requestPayload()
}
//Usage
#Singleton
class SomeController #Inject() (simpleCache: SimpleCache) {
def action = Action { req =>
simpleCache.result.map { result =>
Ok("success")
}
}
}
First request will trigger the rest call and all the other requests will use the cached result. Use map and flatMap to chain the requests.
Complicated answer using Actors
Use Actor to queue requests and Cache the result of the first successful request json result. All the other requests will read the result of the first request.
case class Request(value: String)
class RequestManager extends Actor {
var mayBeResult: Option[String] = None
var reqs = List.empty[(ActorRef, Request)]
def receive = {
case req: Request =>
context become firstReq
self ! req
}
def firstReq = {
case req: Request =>
process(req).onSuccess { value =>
mayBeResult = Some(value)
context become done
self ! "clear_pending_reqs"
}
context become processing
}
def processing = {
case req: Request =>
//queue requests
reqs = reqs ++ List(sender -> req)
}
def done = {
case "clear_pending_reqs" =>
reqs.foreach { case (sender, _) =>
//send value to the sender
sender ! value.
}
}
}
handle the case where the first request fails. In the above code block if the first request fails then actor will never go to the done state.
I solved my problem with a synchronization of the cache in the service. I'm not sure if this an elegant solution, but it works for me.
trait SyncCmsProxyService {
def head(path: String): String
def teaser(path: String): String
}
#Singleton
class DefaultSyncCmsProxyService #Inject()(implicit cache: CacheApi, wsClient: WSClient) extends SyncCmsProxyService with UrlBuilder with CacheAccessor{
private val log = Logger("application")
override def head(path: String) = {
log.debug("looking for head ...")
query(url(path), "$.payload[0].title")
}
override def teaser(path: String) = {
log.debug("looking for teaser ...")
query(url(path), "$.payload[0].content.teaserText")
}
private def query(url: String, jsonPath: String) = {
val key = s"${url}?${jsonPath}"
val payload = findInCache(key)
if (payload.isDefined) {
payload.get
}else{
val json = Json.parse(body(url))
val queried = JSONPath.query(jsonPath, json).as[String]
saveInCache(key, queried)
}
}
private def body(url: String) = {
cache.synchronized {
val body = findInCache(url)
if (body.isDefined) {
log.debug("found body in cache")
body.get
} else {
saveInCache(url, doCall(url))
}
}
}
private def doCall(url : String): String = {
import scala.concurrent.ExecutionContext.Implicits.global
log.debug("calling...")
val req = wsClient.url(url).get()
val f = req map { res =>
val status = res.status
log.debug(s"endpoint called! response status: ${status}")
if (status == 200) {
res.body
} else {
""
}
}
Await.result(f, 15.seconds)
}
}
Note that I omitted the traits UrlBuilder and CacheAccessor here because they are trivial.

Play scala - confusing about the result type of Action.async

I'm little bit confusing about the expected result of Action.async. Here the use case : from the frontend, I receive a JSON to validate (a Foo), I send this data calling an another web service and I extract and validate the received JSON (Bar case class) which I want to validate too. The problem is when I return a result, I have the following error :
type mismatch;
found : Object
required: scala.concurrent.Future[play.api.mvc.Result]
Here my code :
case class Foo(id : String)
case class Bar(id : String)
def create() = {
Action.async(parse.json) { request =>
val sessionTokenOpt : Option[String] = request.headers.get("sessionToken")
val sessionToken : String = "Bearer " + (sessionTokenOpt match {
case None => throw new NoSessionTokenFound
case Some(session) => session
})
val user = ""
val structureId : Option[String] = request.headers.get("structureId")
if (sessionToken.isEmpty) {
Future.successful(BadRequest("no token"))
} else {
val url = config.getString("createURL").getOrElse("")
request.body.validate[Foo].map {
f =>
Logger.debug("sessionToken = " + sessionToken)
Logger.debug(f.toString)
val data = Json.toJson(f)
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-type","application/json"),("Authorization",(sessionToken)))
Logger.debug("url = " + url)
Logger.debug(complexHolder.headers.toString)
Logger.debug((Json.prettyPrint(data)))
val futureResponse = complexHolder.put(data)
futureResponse.map { response =>
if(response.status == 200) {
response.json.validate[Bar].map {
b =>
Future.successful(Ok(Json.toJson(b)))
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
} else {
Logger.debug("status from apex " + response.status)
Future.successful(BadRequest("alo"))
}
}
Await.result(futureResponse,5.seconds)
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
}
}
}
What is wrong in my function ?
Firstly, this is doing nothing:
futureResponse.map { response =>
if(response.status == 200) {
response.json.validate[Bar].map {
b =>
Future.successful(Ok(Json.toJson(b)))
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
} else {
Logger.debug("status from apex " + response.status)
Future.successful(BadRequest("alo"))
}
}
Because you're not capturing or assigning the result of it to anything. It's equivalent to doing this:
val foo = "foo"
foo + " bar"
println(foo)
The foo + " bar" statement there is pointless, it achieves nothing.
Now to debug type inference problems, what you need to do is assign results to things, and annotate with the types you're expecting. So, assign the result of the map to something first:
val newFuture = futureResponse.map {
...
}
Now, what is the type of newFuture? The answer is actually Future[Future[Result]], because you're using map, and then returning a future from inside that. If you want to return a future inside your map function, then you have to use flatMap instead, this flattens the Future[Future[Result]] to Future[Result]. But actually in your case, you don't need that you can use map, and just get rid of all those Future.successful calls, because you're not actually doing anything in that map function that needs to return a future.
And then get rid of that await as others have said - using await means blocking, which negates the point of using futures in the first place.
Anyway, this should compile:
def create() = {
Action.async(parse.json) { request =>
val sessionTokenOpt : Option[String] = request.headers.get("sessionToken")
val sessionToken : String = "Bearer " + (sessionTokenOpt match {
case None => throw new NoSessionTokenFound
case Some(session) => session
})
val user = ""
val structureId : Option[String] = request.headers.get("structureId")
if (sessionToken.isEmpty) {
Future.successful(BadRequest("no token"))
} else {
val url = config.getString("createURL").getOrElse("")
request.body.validate[Foo].map {
f =>
Logger.debug("sessionToken = " + sessionToken)
Logger.debug(f.toString)
val data = Json.toJson(f)
val holder = WS.url(url)
val complexHolder =
holder.withHeaders(("Content-type","application/json"),("Authorization",(sessionToken)))
Logger.debug("url = " + url)
Logger.debug(complexHolder.headers.toString)
Logger.debug((Json.prettyPrint(data)))
val futureResponse = complexHolder.put(data)
futureResponse.map { response =>
if(response.status == 200) {
response.json.validate[Bar].map {
b =>
Ok(Json.toJson(b))
}.recoverTotal { e : JsError =>
BadRequest("The JSON in the body is not valid.")
}
} else {
Logger.debug("status from apex " + response.status)
BadRequest("alo")
}
}
}.recoverTotal { e : JsError =>
Future.successful(BadRequest("The JSON in the body is not valid."))
}
}
}
}
Do not Await.result(futureResponse, 5 seconds). Just return the futureResponse as is. The Action.async can deal with it (in fact, it wants to deal with it, it requires you to return a Future).
Note that in your various other codepaths (else, recoverTotal) you are already doing that.
If you use Action.async you don't need to await for result. So try to return future as is, without Await.result

ReactiveMongo: How to use projection

I'm trying to filter out the password field when querying a document from MongoDB with ReactiveMongo:
val projection = Json.obj("password" -> 0)
def find(selector: JsValue, projection: Option[JsValue]) = {
val query = collection.genericQueryBuilder.query(selector)
projection.map(query.projection(_))
query.cursor[JsValue].collect[Vector](perPage).transform(
success => success,
failure => failure match {
case e: LastError => DaoException(e.message, Some(DATABASE_ERROR))
}
)
}
The code above has no effect... I also get the password field. If I try the following from the mongo client, then it works and password is not returned:
db.users.find( { username: 'j3d' }, { password:0} )
Am I missing something?
Here is the solution:
def find(selector: JsValue, projection: Option[JsValue]) = {
var query = collection.genericQueryBuilder.query(selector)
projection.map(query = query.projection(_))
query.cursor[JsValue].collect[Vector](perPage).transform(
success => success,
failure => failure match {
case e: LastError => DaoException(e.message, Some(DATABASE_ERROR))
}
)
}
or alternatively:
def find(selector: JsValue, projection: Option[JsValue]) = {
val query = collection.genericQueryBuilder
.query(selector)
.projection(projection.getOrElse(Json.obj())
query.cursor[JsValue].collect[Vector](perPage).transform(
success => success,
failure => failure match {
case e: LastError => DaoException(e.message, Some(DATABASE_ERROR))
}
)
}
I hope that helps.

Scala Play 2: action composition and BodyParser

I'm using action composition for authentication and to avoid passing common parameters in each action. My question is how can I combine it the BodyParser parse.json, like in the method below?
def setUser() = Action(parse.json) {
implicit request =>
val id = (request.body \ "id").as[String]
val username = (request.body \ "username").as[String]
val previousURI = request.session.get("previousURI") match {
case Some(x) => x
case None => "/"
}
Ok(previousURI).withSession(
"id" -> id,
"username" -> username
)
}
The controller that uses the above method uses the 'Auth' trait:
case class User(id: Option[String], username: Option[String])
case class Context(user: User, request: Request[AnyContent])
extends WrappedRequest(request)
trait Auth {
def CheckLogin(f: Context => Result) = {
Action { request =>
request.session.get("username").map { token =>
// username? yes continue...
val id = request.session.get("id")
val username = request.session.get("username")
f(Context(new User(id, username), request))
}.getOrElse {
// no redirect to login
Results.Redirect(routes.Application.login).withSession(
"previousURI" -> request.uri
)
}
}
}
}
If I try:
def myMethod() = CheckLogin(parse.json) { ...
I get:
type mismatch; found : play.api.mvc.BodyParser[play.api.libs.json.JsValue] required: controllers.Context => play.api.mvc.Result
thanks!
Answering my own question, still not sure if this is the most elegant but it works:
def CheckLogin(bp: BodyParser[AnyContent] = parse.anyContent)(f: Context => Result) = {
Action(bp) { request =>
// ...
CheckLogin now has a BodyParser that defaults to parse.anyContent so it can accept other BodyParsers as well like parse.json