Rejection In The Akka HTTP: Let’s handle errors scenarios more properly

Reading Time: 4 minutes

Hello folks, in this blog we will see Rejection in the Akka HTTP and how it helps in handling errors scenarios in our Application.

Rejection concept in Akka HTTP helps to deal with errors scenarios more properly. When a filtering directive, like the get directive. It does not allow the request to pass through its inner route . As the filter condition is not satisfy, a rejection concept invoked. The ~ (Tilde) operator is also called chaining operator. As it is used to connect two or more paths. The Request allows to flow through the routing structure. And try to find another route that can complete it. If a request does not find any route, it will generates Rejection.

By default, Response for rejected requests was generated by “handleNotFound ” method of “ RejectionHandler” .

trait RejectionHandler extends (immutable.Seq[Rejection] ⇒Option[Route])

We will understand this in detail through example –

val route: Route =
 path("hello") {
   get {
     complete(HttpResponse(entity = "Hello world"))
   }	} ~
   path("ping") {
     get {
       complete(HttpResponse(entity = "PONG!"))  }
 }

If we access with path “hello”. Then we will get a response as “Hello world”, while we will get response “PONG!” for the route “ping” .

But you may have a question that what if we hit any other route. So the answer is, we’ll get a response as : “The requested resource could not be found.”

If we want this response in a more descriptive way. We can customize the Rejectionhandler. The easiest way to construct a RejectionHandler is with “ RejectionHandler.newBuilder() ” that Akka HTTP provides.

Refer to following code snippet-

implicit def rejectionHandler = RejectionHandler.newBuilder()
 .handleNotFound {
 complete(HttpResponse(NotFound,   entity = HttpEntity(ContentTypes.`application/json`, Json.stringify(
     Json.toJson(
       ErrorResponse(
         NotFound.intValue, "NotFound", "The requested resource could not be found.")
     )
   )
 )))
}

we’ll get a response as follow:

{
   "code": 404,
   "message": "NotFound",
   "reason": "The requested resource could not be found."
}

where, ErrorResponse is case class –

case class ErrorResponse(code: Int, message: String, reason: String)

There are Three helper methods in RejectionHandler-

1.handleNotFound(Route) :

As described in the above example “Resource Not Found” is special. As it is represented with an empty rejection set. The handleNotFound helper lets you specify the “recovery route” for this case.

2.handle(PartialFunction[Rejection, Route]) :

Handles provided the type of rejection with given partial function. This partial function produces route which is run when certain rejection occurs.

3.handleAll[T <: Rejection: ClassTag](f: immutable.Seq[T] => Route) :

Handles all rejections of a certain type at the same time. This is useful for cases where you need access to more than the first rejection of a certain type. e. g. for producing the error message to an unsupported request method.

There are many Predefined Rejections that are provided by Akka Http. They are MethodRejection, AuthorizationFailedRejection, MissingCookieRejection, MissingQueryParamRejection.

We invoked these handle calls through implicit definition using “newBuilder()” to build a new RejectionHandler. And tell this handler how to handle particular Rejection.

1.MethodRejection :

This rejection occurs when a user uses an unsupported method to access request. In the following example, a supported method is “get” . And it will generate rejection when try to access with post/ put any other method.

val route: Route =
 path("hello") {
   get {
     complete(HttpResponse(OK,   entity = HttpEntity(ContentTypes.`application/json`, Json.stringify(
       Json.toJson(
         ProperResponse(
           OK.intValue,"Hello world")))
     )))}
 }
implicit def rejectionHandler = RejectionHandler.newBuilder()

 .handleAll[MethodRejection] { methodRejections =>
 val names = methodRejections.map(_.supported.name)
 complete(HttpResponse(MethodNotAllowed,   entity = HttpEntity(ContentTypes.`application/json`, Json.stringify(
   Json.toJson(
     ErrorResponse(
       MethodNotAllowed.intValue, "Method Rejection", s"Method not supported! Supported for : ${names mkString "or"}!"))
 )
 )))
}

For unsupported method, the output will be :

{
   "code": 405,
   "message": "Method Rejection",
   "reason": "Method not supported! Supported for : GET!"
}

2.MissingQueryParamRejection :

MissingQueryParamRejection arises when the user didn’t mention required parameters in Request.

implicit def rejectionHandler =
 RejectionHandler.newBuilder()
   .handle {
     case MissingQueryParamRejection(param) =>
       complete(HttpResponse(BadRequest,   entity = HttpEntity(ContentTypes.`application/json`, Json.stringify(
         Json.toJson(
           ErrorResponse(
 BadRequest.intValue, "MissingQueryParamRejection", s"Request has missing required query parameter for = '$param")))
       )))
   }
val route: Route =
 path("paint") {
 parameters('color, 'bgColor) {
   (color, bgColor) =>
     complete(HttpResponse(NotFound,   entity = HttpEntity(ContentTypes.`application/json`, Json.stringify(
       Json.toJson(
         ProperResponse(
           OK.intValue, s"You mention color is $color and background color is $bgColor")))
     )))}
}

In the given example : if we hit URL, localhost:8080/paint?color = red .

Then Response would be :

Request has missing required query parameter for = ‘bgColor’ .

And if we hit URL, “ localhost:8080/paint?bgColor = red “. Then Response will be : Request has missing required query parameter for = ‘color’ .

When we mention both parameters in the URL localhost:8080/paint?bgColor=red&color=blue . Then get the proper result as :

{
   "code": 200,
   "message": "You mention color is blue and background color is red"
}

3.AuthorizationFailedRejection:

This Rejection created by the “authorize” directive. This signals that the request was rejected because the user is not authorized.

Let’s see an example :

val route: Route =
path("login") {
   parameters('username, 'password) {
     (username, password) =>
       if (username.equals("knoldus") && password.equals("pune")) {
         complete(HttpResponse(NotFound,entity = HttpEntity(ContentTypes.`application/json`, Json.stringify(
           Json.toJson(
             ProperResponse(
               OK.intValue, "Login Successful")))
         )))}
        else {
         reject(AuthorizationFailedRejection)
       }  } }

implicit def rejectionHandler =
 RejectionHandler.newBuilder()
.handle { case AuthorizationFailedRejection =>
 complete(HttpResponse(BadRequest,   entity = HttpEntity(ContentTypes.`application/json`, Json.stringify(
   Json.toJson(
     ErrorResponse(
       BadRequest.intValue, "AuthorizationFailedRejection", "You have entered invalid credentials")
   )
 )
 )))
}

If we hit the path with right credential values. Such as  “ localhost:8080/login?username = knoldus&password = pune ”.

We will get a response as  –

{
   "code": 200,
   "message": "Login Successful"
}

When we try to access with wrong values, like “ localhost:8080/login?username = software&password = abcd ”.

We will get a response as –

{
   "code": 400,
   "message": "AuthorizationFailedRejection",
   "reason": "You have entered invalid credentials"
}

In conclusion, this is how RejectionHandler can be customized to handle errors in code. You can check out here for entire code.

Thanks for reading ! Happy Coding .

Reference:

https://blog.knoldus.com/rejection-handling-in-akka-http/

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章