Rejection Handling in Akka HTTP
This thorough guide to rejection handling in Akka HTTP uses Scala to test out the various routing responses you can apply when data goes down the wrong road.
Join the DZone community and get the full member experience.
Join For FreeWhile working on one my projects, which uses Akka HTTP, for routing, I came across a situation where we had to use a custom rejection handler. It was a worthy thing to be shared with all and, hence, this article was born.
Rejections are used by Akka HTTP to help us handle error scenarios more aptly. When the filter directives do not let a request pass through their internal body because the filtering conditions are not met, a rejection occurs. This rejection flows through the chain of the routes to see if any route can handle it. If there is a route in the routing structure that can take it, it's good to go. Otherwise, it generates a rejection.
Let me illustrate this with an example:
val route =
path("hello") {
get {
complete(HttpEntity(ContentTypes.`application/json`,"Hello"))
}
} ~ path("name") {
get {
complete(HttpEntity(ContentTypes.`application/json`,"Akka Http"))
}
}
If we access the route /hello
, then we will successfully get the response Hello
. But what if we hit the route /random
? Then what?
Well, that will give us a response saying The requested resource could not be found.
This response was generated by handleNotFound
method of RejectionHandler provided by Akka Http.
There are two more such methods in RejectionHandler:
handle
: Handles certain rejections with the given partial function. The partial function produces the route that is executed when the defined rejection occurs.handleAll
: According to Akka HTTP's documentation, handleAll “Handles all rejections of a certain type at the same time. This is useful for cases where your need access to more than the first rejection of a certain type, e.g. for producing the error message to an unsupported request method.”
Above, you saw the message the route /random
produced was The requested resource could not be found.
But we may not want our program to display output just like this. We would want our output to be formatted. This is where we can customize rejections.
Consider this piece of code:
implicit def rejectionHandler =
RejectionHandler.newBuilder()
.handleNotFound {
val errorResponse = write(ErrorResponse(NotFound.intValue, "NotFound", "The requested resource could not be found."))
complete(HttpResponse(NotFound, entity = HttpEntity(ContentTypes.`application/json`, errorResponse)))
}
.result()
In this example, ErrorResponse is a case class like:case class ErrorResponse(code: Int, `type`: String, message: String)
.
We give an implicit definition using newBuilder()
to build a new RejectionHandler and tell it how to handle the notFound
cases. Now if we hit /random
, the output we will be:
{
"code": 404,
"type": "NotFound",
"message": "The requested resource could not be found."
}
That's much more descriptive.
There are many predefined rejections that are provided by Akka HTTP that you might find very useful. I will illustrate two of them here.
MissingQueryParamRejection: This is generated when the request does not have adequate required query parameters. Consider a route:
val route = path("check") {
parameters('color, 'bgColor) {
(color, bgColor) =>
val properResponse = write(ProperResponse(OK.intValue, s"Your preference is color $color with background color $bgColor."))
complete(HttpResponse(OK, entity = HttpEntity(ContentTypes.`application/json`, properResponse)))
}
}
Its rejection handler would look like:
implicit def rejectionHandler =
RejectionHandler.newBuilder()
.handle {
case MissingQueryParamRejection(param) =>
val errorResponse = write(ErrorResponse(BadRequest.intValue, "Missing Parameter", s"The required $param was not found."))
complete(HttpResponse(BadRequest, entity = HttpEntity(ContentTypes.`application/json`, errorResponse)))
}
.result()
So, if someone hits the route localhost:8080/check?color=red
, then result would be:
{
"code": 400,
"type": "Missing Parameter",
"message": "The required bgColor was not found."
}
One thing to note is that if no parameter is provided, then it will only highlight the first parameter as missing.
So if the route `localhost:8080/check` is hit, then the output would be:
{
"code": 400,
"type": "Missing Parameter",
"message": "The required color was not found."
}
AuthorizationFailedRejection: This rejection occurs when the user is not authorized to access a resource. Consider the piece of code:
val route = path("admin") {
parameters('username, 'password) {
(username, password) =>
if (username.equals("knoldus") && password.equals("knoldus")) {
val properResponse = write(ProperResponse(OK.intValue, "Welcome!!!"))
complete(HttpResponse(OK, entity = HttpEntity(ContentTypes.`application/json`, properResponse)))
} else {
reject(AuthorizationFailedRejection)
}
}
}
implicit def rejectionHandler =
RejectionHandler.newBuilder()
.handle { case AuthorizationFailedRejection =>
val errorResponse = write(ErrorResponse(BadRequest.intValue, "Authorization", "The authorization check failed for you. Access Denied."))
complete(HttpResponse(BadRequest, entity = HttpEntity(ContentTypes.`application/json`, errorResponse)))
}
Here you will notice that it is entirely possible to explicitly reject a route and then handle it the way you want to by using the Rejection Handler. This comes handy when you want to explicitly reject a route and the process it’s handling. So now, if you hit the route with the wrong values in parameters localhost:8080/admin?username=knol&password=dus
, we will get a response like this:
{
"code": 400,
"type": "Authorization",
"message": "The authorization check failed for you. Access Denied."
}
Again, looking decent.
Therefore, we have seen how the Rejection Handler can be aptly used. There are many predefined handlers, which you can browse through in the Akka HTTP documentation. You may find my entire code here. In the next blog post, I will talk about testing rejection handlers. Until then, happy coding!
Published at DZone with permission of Rishabh Verma, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Scaling Site Reliability Engineering (SRE) Teams the Right Way
-
How To Use Pandas and Matplotlib To Perform EDA In Python
-
Database Integration Tests With Spring Boot and Testcontainers
-
Part 3 of My OCP Journey: Practical Tips and Examples
Comments