Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generically wrap a Rejection with Akka-Http

I would like to use the Akka Http routing system, along with its rejection system but need to nest the response Json for a rejection within a generic Json message block.

I have this working in a very non-generic manner creating a RejectionHandler then adding cases for all possible rejections and handling them all with the specific response code and message.

example:

    // Wraps string into control block format
def WrappingBlock(msg: String) = ???

val myRejectionHandler = RejectionHandler
.newBuilder()
.handle{case MalformedRequestContentRejection(msg, detail) =>
          complete(BadRequest, WrappingBlock(msg)) }
...     // Further lines for all other possible rejections
...     // along with their response codes and messages.
...     // It would be nice if this was just generic code 
...     // rather than specific to every rejection type.
.result()


val routes = handleRejections(myRejectionHandler){ 
    ...
}

However, what I would like is the response code that Akka HTTP provides by default and also the pretty print message that is provided, just nested within a Json control wrapper without a line for every possible rejection type. This seems like it should be possible but I have not been able to complete it.

like image 459
AlexC Avatar asked Jan 05 '16 20:01

AlexC


1 Answers

I think it's possible to do what you want using a combination of handleRejections explicitly with mapResponse. First, consider this simple route definition:

(get & path("foo")){
  complete((StatusCodes.OK, HttpEntity(ContentTypes.`application/json`, """{"foo": "bar"}""" )))
}

If I get a matching request I will respond using json and my caller is happy because they can parse the response as json. But if you try and call this endpoint with a POST request, you will get a response as follows:

HTTP 405 Method Not Allowed

Date: Wed, 06 Jan 2016 13:19:27 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 47
Allow: GET
Server: akka-http/2.3.12

HTTP method not allowed, supported methods: GET

So here we get a plain text response which is not desirable. We can solve that problem universally by adding a couple of directives to the very top of my routing tree like so:

mapResponse(wrapToJson){
  handleRejections(RejectionHandler.default){
    (get & path("foo")){
      complete((StatusCodes.OK, HttpEntity(ContentTypes.`application/json`, """{"foo": "bar"}""" )))
    }
  }
}

With wrapToJson being defined as:

def wrapToJson(resp:HttpResponse):HttpResponse = {

  //If we get a text/plain response entity, remap it otherwise do nothing
  val newResp = resp.entity match{
    case HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)` , content ) => 
      val jsonResp = s"""{"error": "${content.utf8String}"}"""
      resp.copy(entity = HttpEntity(ContentTypes.`application/json`, jsonResp))
    case other =>
      resp
  }

  newResp
}

This is a very basic example, and you'd probable have a better way to generating the json, but this just serves to show how you can fix the plan text responses from the default rejection handler. Now, you must nest the default rejection handler under the mapResponse explicitly because the automatic handling gets added outside the top level of whatever tree you define and thus mapResponse would not see the rejection cases. You still get the default handling though via RejectionHandler.default.

Hopefully this is close to what you were after.

like image 92
cmbaxter Avatar answered Oct 21 '22 09:10

cmbaxter