Here is the situation
// validateRoute act like a directive which validate a route before proceeding further
override def validateRoute(route: Route)(implicit ec: ExecutionContext): Route = {
extractRequest { request =>
decodeRequest {
entity(as[String]) { content =>
(headerValueByName("header1") & headerValueByName("header2")) {
case (header1, header2) => {
// dom some
// validate() returns a Future[Either[Error, Boolean]]
validate().map {
result => {
result match {
case Right(_) => route
case Left(ex) => complete(StatusCodes.Unauthorized -> ex.getMessage)
}
}
}
} // I get a error here saying It expect route whereas it is Future[Route]
}
}
}
}
}
I get the above mentioned error, also I can not change the return type of validate () , is there a way to fix this issue. I need a way to return route instead of Future[Route]
If you've a handleRejections directive registered, you can use the onSuccess directive on Future:
onSuccess(validate()) {
case Right(_) => route
case Left(ex) => complete(StatusCodes.Unauthorized -> ex.getMessage)
}
Else you can use onComplete directive and you'll have to match Success and Failure
If you HAVE to return route instead of Future[Route], you can try using
Await.result(validate(), Duration(2, TimeUnit.SECONDS)) //substitute your choice of duration
this will block till validate returns.
So the full solution will look like,
// validateRoute act like a directive which validate a route before proceeding further
override def validateRoute(route: Route)(implicit ec: ExecutionContext): Route = {
extractRequest { request =>
decodeRequest {
entity(as[String]) { content =>
(headerValueByName("header1") & headerValueByName("header2")) {
case (header1, header2) => {
// dom some
// validate() returns a Future[Either[Error, Boolean]]
Await.result(validate(),Duration(2, TimeUnit.SECONDS) ) match
{
case Right(_) => route
case Left(ex) => complete(StatusCodes.Unauthorized -> ex.getMessage)
}
}
}
}
}
}
}
Related
I have some API Rest with CRUD operations.
Each entity is identified by using UUID.
For example for Create it is similar to this:
private val createProduct = post {
path("product" / Segment) { productUUID =>
Try(UUID.fromString(productUUID)) match {
case Failure(_) => // Bad request
case Success(validUuid) =>
entity(as[ProductData]) { productData =>
onComplete(addProduct(validUuid, productData)) {
case Success(_) => complete(StatusCodes.OK)
case Failure(ex) => // some code
}
}
}
}
}
Operations Read(GET), Update(PUT) and Delete(DELETE) are similar to POST:
first step is get the uuid
for PUT get the payload using entity(as[ProductData]) (not needed for GET and DELETE)
invoke some method that returns Future[Something]
What I would like to do is remove boilerplate like:
getting the UUID, validating it, returning Bad Request if it's not valid
create some directive/function for handling the future: if there's an exception just return 500 Internal Server Error, but in case of Success continue for processing the value (I think using provide directive).
I found this example (https://fmsbeekmans.com/blog/akka-http-2-creating-custom-directives.html):
def simplifiedOnComplete[T](future: Future[T])(timeout: FiniteDuration): Directive1[T] = {
Try(Await.result(future, Duration.Inf)) match {
case Success(result) => provide(result)
case Failure(error) => failWith(error)
}
}
I said, ok, there's a try in this example! Maybe I can change it for working with UUID instead of Future:
def getUUID[T]: Directive1[T] = {
path(Segment) { maybeuuid =>
Try(UUID.fromString(maybeuuid)) match {
case Success(result) => provide(result) // compilation error here
case Failure(error) => failWith(error)
}
}
}
The code does not compile with the error:
Type mismatch. Required: Route, found: Directive1[UUID]
I guess the problem is I've added path ...
How can I create a Directive to extract the valid uuid and return Bad Request if it's not valid?
And, is it possible to encapsulate in a custom directive the code that handles de future?
For example the routes defined at the top would be something like:
private val createProduct = post {
path ("product") {
extractUUID { validUUID =>
entity(as[ProductData]) { productData =>
futureSuccess(addProduct(validUUID, productData)) { successValue =>
// code here, for example: complete(OK)
}
}
}
}
}
You were almost there - Your code is :
def getUUID[T]: Directive1[T] = {
path(Segment) { maybeuuid =>
Try(UUID.fromString(maybeuuid)) match {
case Success(result) => provide(result) // compilation error here
case Failure(error) => failWith(error)
}
}
}
You don't need generic T since you can only have a UUID back from UUID.fromString
path(Segment) gives you a Directive. So you want to useflatMap to get a Directive back (provide returns a Directive)
So it would be something like
def getUUID: Directive1[UUID] = {
path(Segment).flatMap { maybeuuid =>
Try(UUID.fromString(maybeuuid)) match {
case Success(result) => provide(result)
case Failure(error) => failWith(error)
}
}
}
And, is it possible to encapsulate in a custom directive the code that
handles de future?
Yes, it is the same as the above. onComplete returns a Directive so you will have to flatMap.
To return BadRequest, look up the rejection handlers in the akka-http doc.
Is there a way to ignore the following map/flatmap's without failed?
This is what I have:
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
serverService.findByIdAndUserId(serverId, user.id.get)
.flatMap{s =>
if (s.isEmpty) {
Future.failed(new DeleteFailedException)
// Can I return a `NotFound("...")` here instead of failed?
} else {
Future.successful(s.get)
}
}
.map{s =>
serverService.delete(s)
}.map{_ =>
Ok(Json.toJson(Map("success" -> "true")))
}
}
When I would return a NotFound("...") in the flatMap the following map would still be executed. Is there a way to ignore the following map/flatmap's?
Think so should be fine (I assumed that findByIdAndUserId returns Future[Option[_]], not an Option[_] as you answered in comment). In my approach I also removed usage of get and unnecessary map
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
request.user.flatMap(_.id).fold {
Future.successfull(NotFound("no user"))
} {userId =>
serverService.findByIdAndUserId(serverId, userId).map {
case None =>
NotFound("no server")
case Some(s) =>
serverService.delete(s)
Ok(Json.toJson(Map("success" -> "true")))
}
}
}
Instead of doing a Future.failed with an exception. you can return an Either. The good thing about either is that you can pattern match on it and then construct the appropriate http response.
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
serverService.findByIdAndUserId(serverId, user.id.get)
.flatMap{s =>
if (s.isEmpty) {
Left(new DeleteFailedException)
} else {
Right(s.get)
}
}
.map{s => s match {
case Left(notFound) =>
// construct the Json for failure
case Right(s) =>
serverService.delete(s)
// construct json for success.
}}
}
in my scala playframework application I want to return the via Post submitted and then with slick stored object back to frontend as json
I tried this:
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
})
}
but I get this error:
what could be my problem?
NEW ERROR
Try with something along these lines:
def createClient = Action.async { implicit request =>
request.body.asJson match {
case None => // do something that returns a Future[Result] ~ such as NotFound or
case Some(js) =>
js.validate[ClientModel] match {
case client: JsSuccess[ClientModel] =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
case e: JsError => // do something that returns a Future[Result] ~ such as InternalServerError
}
}
}
the .async, as the name suggests require a Future type.
You have 2 options:
Remove the .async, this will make your def synchronous (deprecated)
Leave the .async but return a Future result
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Future(Ok(Json.toJson(clients)))
}
})
}
But you still need to add the case of validation error:
case JsSuccess(client) =>
{
clientDTO.createClient(client).map
{
clients => Future(Ok(Json.toJson(clients)))
}
}
case _ => Future(BadRequest(""))
This should work and, in all the cases apart JsSuccess, the function will return a future BadRequest response.
A better solution is to change the _ with JsError:
case e: JsError =>
{
Println(e)
Future(BadRequest(.....))
}
This will also print the error.
You can read more here: https://www.playframework.com/documentation/2.6.x/ScalaJson
(Using validation chapter)
More about future in scala: https://docs.scala-lang.org/overviews/core/futures.html
i m trying to convert my spray routes in akka http.
It s really complicated for a novice but i almost do everything.
I m bloked by the authentication.
Indeed, i have a route with a get param token=????
How can i check this token with akka?
My route is :
val route : Route = {
path("appActive") {
get {
parameters('date_end.as[Long]) {
date_end =>
onSuccess(requestHandler ? AppActiveGetList(AppActiveRequest(date_end, null, 0))) {
case response: Answer =>
complete(StatusCodes.OK, response.result)
case _ =>
complete(StatusCodes.InternalServerError, "Error on the page")
}
}
}
}
}
My authenticate function at the moment is (with spray) :
trait TokenValidator {
def validateTokenApp(): ContextAuthenticator[InfoApp] = {
ctx =>
val access_token = ctx.request.uri.query.get("access_token")
if (access_token.isDefined) {
doAuthApp(access_token.get)
} else {
Future(Left(AuthenticationFailedRejection(AuthenticationFailedRejection.CredentialsMissing, List())))
}
}
}
I didn t find an example i can use easily.
Could you help me please?
Looks like Akka-HTTP authentication directives are stricter in what they expect than Spray's.
If you want to keep your doAuthApp unchanged, you'll need to define your own custom directive - along the lines of Akka-HTTP's own authenticateOrRejectWithChallenge.
def doAuthApp[T](token: String): Future[AuthenticationResult[T]] = ???
def authenticated[T](authenticator: String => Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
parameter('access_token.?).flatMap {
case Some(token) =>
onSuccess(authenticator(token)).flatMap {
case Right(s) => provide(s)
case Left(challenge) =>
reject(AuthenticationFailedRejection(CredentialsRejected, challenge)): Directive1[T]
}
case None =>
reject(AuthenticationFailedRejection(CredentialsMissing, HttpChallenges.oAuth2("my_realm"))): Directive1[T]
}
And then wire in the route somewhere like
val route : Route = {
path("appActive") {
(get & authenticated(doAuthApp)){ authResult =>
parameters('date_end.as[Long]) {
date_end =>
...
}
}
}
}
I need to write simple web service with akka-http and reactivemongo.
Function to save data looks like this
def saveRoute(route: Route):Future[WriteResult] = {
collection.insert(route)
}
a code that calls this function looks like this
val userRoutes = {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
Database.saveRoute(route)
}
}
}
}
I need to return result with inserted ID of Route and do this without making the thread to wait.
if try
Database.saveRoute(route).onComplete{
case Success(r) => complete(r.toString)
case Failure(e) => complete(e.getMessage)
}
It cannot compile, because it doesn't return value.
I know how to make it in dirty way, but really want to make in appropriate manner.
What should be done in this case?
Seems like I've found most efficient way to do this. It's built in onComplete directive
(path("routes" / "add") & post & entity(as[Route])) {
route =>
onComplete(routesController.addRoute(route)) {
case Success(result) => complete(StatusCodes.Created, "OK")
case Failure(ex) => complete(new ErrorResponse(StatusCodes.InternalServerError.intValue, ErrorResponse.ERROR, ex.getMessage))
}
}
Use onSuccess to handle the valid response when the future finishes and handleExceptions to handle when the future does not succeed.
val userRoutes = {
handleExceptions(mongoDbExceptionHandler) {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
onSuccess(Database.saveRoute(route)) { result =>
complete(result)
}
}
}
}
}
}
// Something like this for whatever the exceptions you expect are
val mongoDbExceptionHandler = ExceptionHandler {
case ex: MongoDbReadException => complete(HttpResponse(InternalServerError, "No database")))
}
onSuccess:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/directives/future-directives/onSuccess.html
handleExceptions:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/exception-handling.html
You can map over the future and then complete the request like below.
val future = Database.saveRoute(route)
val response = future.map(_.getId).recover(_.getMessage)
complete(response)
On a side note, for handling exceptions, it is a good practice to have a ExceptionHandler and wrap it with your route. You can find example here.
You have few option i will try to put the most commonly used ones for REST API based solutions:
OnSuccess use it when you want your expectations to be bubbled and handled by expectionHandler
concat(
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
},
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
)
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onSuccess.html
onComplete: When you want to manually handle the exception. Try Monad wrapped.
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html
How about this, replace:
Database.saveRoute(route)
with:
complete(Database.saveRoute(route).map(_.toString).recover(_.getMessage))
When you use RequestContext you should use something like this:
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.RouteResult.{Complete, Rejected}
...
val myRoute: Route = (path("my-path") & get) { req: RequestContext =>
val futureResp: Future[HttpResponse] = ???
futureResp.map(resp => RouteResult.Complete(resp))
}