I am using spray to serve an api. I am trying to create a directive to add a list of headers to all responses, including rejections and failures. I have tried the following but both only work for successful responses:
val impl1: Directive0 = respondWithSingletonHeaders(myHeaderList)
val impl2: Directive0 = mapRequestContext { ctx =>
ctx.withHttpResponseHeadersMapped { headers =>
myHeaderList ::: headers
}
}
Is there an equivalent construct that could work for ALL responses? I suppose I can define custom error handlers, but, correct me if I am wrong, they are expected to work by calling ctx.complete(...), since it a side effect, I think I will have to override every failure case. I haven't found a place where I can simply map HttpResponses to add the headers.
An exception will bubble up the route as an exception, until it reaches an exception handler. And you can't add headers to an exception. But you can re-use your directives in your exception handlers. You can avoid repetition by making an implicit ExceptionHandler at the runRoute level, use your directive to add headers for the general case (all exceptions) and delegate the actual exception-to-response mapping to an inner pattern match:
implicit def exceptionHandler: ExceptionHandler = ExceptionHandler {
case exception => impl1 {
exception match {
case e: IllegalArgumentException => ctx =>
ctx.complete(BadRequest)
...
case e: Exception => ctx =>
ctx.complete(InternalServerError)
}
}
}
Related
We have some legacy code in our codebase that is eventually going to be refactored to use Validated and Either from Cats library. This is because Validated does not use fail-fast mechanics. The unrefactored code uses fail-fast mechanics of Try monad.
Since the refactoring hasn't happened yet, I am doing a kludgy hack to get around the fact that the Try monad is fail-fast. I am having trouble implementing it however.
I basically have a list of type Try[T] that is guaranteed to all be Failures.
I am trying to aggregate all of the error messages of all the Failures into a single Failure.
Here is the function I am refactoring:
private def extractTry[T](xs: IndexedSeq[Try[T]]): Try[IndexedSeq[T]] = {
val failures = xs.collect { case Failure(ex) => Failure(ex) }
if (failures.size > 0) failures.head
else Success(xs.map(_.get))
}
Instead of failures.head in the second line of the method, I want to aggregate all the Failures.
So something like
if (failures.size > 0) failures.foldLeft(Failure(new IllegalArgumentException(""))){case (Failure(acc), Failure(e)) => Failure(new IllegalArgumentException(acc.getMessage + e.getMessage))}
The only thing I don't like about this implementation is that I would like each step of fold not to use IllegalArgumentException, but to use the new element's exception type. So the idea is to keep the exception type of the last element in failures, and not to use an arbitrary exception type.
We are planning to eventually use Either[Throwable, T] in place of Try and will probably run into the exact same problem there when we try to aggregate errors. We want to keep the exception type and not assign an arbitrary one like IllegalArgumentException. So this problem is going to have to be solved sooner or later, and I would prefer that it be sooner.
Does anyone have any suggestions? Any help would be appreciated.
Ideally, we would follow suggestion by #Luis. Until then consider perhaps something like so
sealed trait OverallResult[+T]
case class OverallError(accumulatedMessage: String, finalErrorCode: Int) extends OverallResult[Nothing]
case class OverallSuccess[T](xs: IndexedSeq[T]) extends OverallResult[T]
object OverallResult {
/**
* Aggregating over a chain of Failures, it will only keep the exception type of the last Failure.
* This is just a heuristic to decide on the error code. Depending on the exception type, we use
* a different error code. So NoSuchElementException is 404 and IllegalArgumentException is 400.
*/
def apply[T](xs: IndexedSeq[Try[T]]): OverallResult[T] = {
val failures = xs.collect { case Failure(ex) => ex }
if (failures.nonEmpty) {
val accMessage = failures.map(_.getMessage).mkString("[", ",", "]")
OverallError(accMessage, errorCode(failures.last))
}
else OverallSuccess(xs.map(_.get))
}
private def errorCode(ex: Throwable): Int = ex match {
case _: NoSuchElementException => 404
case _: IllegalArgumentException => 400
case e => throw new RuntimeException("Unexpected exception. Fix ASAP!", e)
}
}
OverallResult(Vector(Try(throw new NoSuchElementException("boom")), Try(throw new IllegalArgumentException("crash"))))
OverallResult(Vector(Try(42), Try(11)))
which outputs
res0: OverallResult[Nothing] = OverallError([boom,crash],400)
res1: OverallResult[Int] = OverallSuccess(Vector(42, 11))
Note explicit documentation of the heuristic mentioned in the comments:
/**
* Aggregating over a chain of Failures, it will only keep the exception type of the last Failure.
* This is just a heuristic to decide on the error code. Depending on the exception type, we use
* a different error code. So NoSuchElementException is 404 and IllegalArgumentException is 400.
*/
Error accumulation is simulated with
failures.map(_.getMessage).mkString("[", ",", "]")
and overall status code decided with
errorCode(failures.last)
Now clients of extractTry need to be refactored to pattern match on OverallResult ADT, and finalErrorCode instead of exceptions, but lower level codebase should remain unaffected.
Mario wrote up a response that I think deserves being the accepted answer because of its thoroughness. It was while reading his answer that I stumbled upon another solution that requires less code change, but still gets the job done.
The answer is to pattern match on the exception type, which seems very obvious in retrospect.
if (failures.size > 0) failures.foldLeft(Failure(new IllegalArgumentException(""))){case (Failure(acc), Failure(e)) =>
val message = acc.getMessage + e.getMessage
e match {
case ex: IllegalArgumentException => Failure(new IllegalArgumentException(message))
case ex: NoSuchElementException => Failure(new NoSuchElementException(message))
}
}
I'm using http4s & rho (mainly for Swagger integration)
My services are using this DAO object, that methods that can throw Exceptions (fail the Task)
case class BasicMatchDao() {
def readAll(): Task[List[BasicMatch]] = Task.fail(ActionNotImplemented("readAll"))
def read(id: String): Task[Option[BasicMatch]] = readQuery(id).option.transact(xa)
}
In my RhoService I can handle these like
private def exceptionToJson(t: Throwable):Json = Json.obj("error" -> t.getMessage.asJson)
val rhoService = new RhoService {
GET / path |>> { (request: Request) =>
Ok(dao.readAll.map(_.asJson)).handleWith {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
}
This way I make sure that whatever I return, it's always a Json
Since I don't want to pollute every RhoRoute with a similar errorhandling I want to do something which is possible with the default http4s.dsl, but I can't seem to get working with rho:
1. Create default error handler
e.g. add
...
Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)
...
private def errorHandler(): PartialFunction[Throwable, Task[Response]] = {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
This will fail because NotImplemented is not a Response (I can call .pure on these to make type checking work)
But then the code will compile, but I get this exception:
Cannot convert from fs2.Task[Product with Serializable]
to an Entity, because no EntityEncoder[fs2.Task[Product with
Serializable]] instance could be found.
Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)
2. Add errorhandler to each RhoRoute
After defining the rhoRoute I'd like to map over it and add the errorhandler to each route, so do something at the r that let's me add the 'handleWith' somewhere (below will not work)
new RhoService(rhoService.getRoutes.map(_.handleWith(errorHandler))
If I can't get this to work, I'll probably move back to the default dsl, but I really liked rho
So Part 1 is fixed for now. Defining the Task as Task[BaseResult] instead of Task[Response] will work
import org.http4s.rho.Result.BaseResult
val errorHandler: PartialFunction[Throwable, Task[BaseResult]] = {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
I'm looking into part 2 as well. All help is welcome :-)
In my Akka-http route I get a specific message back and I want to wrap its content as error message like:
val response:Future[T] = (actor ? command).mapTo[T]
response match {
case err : Future[InvalidRequest] =>
HttpResponse(408, entity = err.map(_.toJson).????)
case r : Future[T] => r.map(_.toJson)
}
case class InvalidRequest(error:String)
implicit val invalidRequestFormat = jsonFormat1(InvalidRequest)
but that doesn't work. How can I map it as text in json format?
I think I can provide a generic solution for what it is you are trying to do. You can start by creating a method that returns a Route as follows:
def service[T:ClassTag](actor:ActorRef, command:Any)
(implicit timeout:Timeout, _marshaller: ToResponseMarshaller[T]):Route = {
val fut = (actor ? command).mapTo[ServiceResponse]
onComplete(fut){
case util.Success(ir:InvalidRequest) =>
complete(StatusCodes.BadRequest, ir)
case util.Success(t:T) =>
complete(t)
case util.Failure(ex) =>
complete(StatusCodes.InternalServerError )
}
}
This method fires a request to a supplied actor, via ask, and gets the Future representing the result. It then uses the onComplete directive to apply special handling to the InvalidResponse case. It's important here that you have an implicit ToResponseMarshaller[T] in scope as you will need that for the success case.
Then, let's say you had the following classes and formatters defined:
trait ServiceResponse
case class Foo(id:Int) extends ServiceResponse
implicit val fooFormat = jsonFormat1(Foo)
case class InvalidRequest(error:String) extends ServiceResponse
implicit val invalidRequestFormat = jsonFormat1(InvalidRequest)
You could use your new service method within your routing tree as follows:
val routes:Route = {
path("api" / "foo"){
get{
service[Foo](fooActor, FooActor.DoFoo)
}
}
}
The problem with your example is that you were not waiting for the completion of the Future before building out the response. You were trying to match on the underlying type of the Future, which is eliminated by erasure at runtime, so is not a good idea to try and match against in that way. You instead need to wait until it's completed and then see the type that is behind the Future.
I have a controller that exposes a method as a route. In this method, I call a long running computation that returns a Future[SomeType].
I now have the following:
def compute(id: String) = Action.async { request =>
val result: Future[SomeType] = compute(id)
result.map(value => Ok(transform(value, id)))
}
So far this is just the happy path. What if compute(id) results in a Failure? How to handle that? I could wrap the whole thing in a Try block, but is there a better alternative? Any suggestions?
We usually use the following pattern:
def compute(id: String) = Action.async { request =>
val result: Future[SomeType] = compute(id)
result.map(value => Ok(transform(value, id)))
.recover { case ex =>
Logger.error("Something went wrong", ex)
InternalServerError
}
}
This way the HTTP response code will be 500 INTERNAL SERVER ERROR, so the caller will be informed. You may also want to add validation on the parameters of the request and return a 400 BAD REQUEST etc.
I need to query a RESTful service that always returns a JSON response. I need to contact it a few times, always with some more information that I learned from the previous request. I'm using Akka2, Scala, Jerkson and Spray-Can.
My current approach seems to work, but it looks ugly and requires nesting everything. I read that there should be some techniques available regarding chaining and such, but I couldn't figure out how to apply them to my current use-case.
Here is the code I'm talking about:
def discoverInitialConfig(hostname: String, bucket: String) = {
val poolResponse: Future[HttpResponse] =
HttpDialog(httpClient, hostname, 8091)
.send(HttpRequest(uri = "/pools"))
.end
poolResponse onComplete {
case Right(response) =>
log.debug("Received the following global pools config: {}", response)
val pool = parse[PoolsConfig](response.bodyAsString)
.pools
.find(_("name") == defaultPoolname)
.get
val selectedPoolResponse: Future[HttpResponse] =
HttpDialog(httpClient, hostname, 8091)
.send(HttpRequest(uri = pool("uri")))
.end
selectedPoolResponse onComplete {
case Right(response) =>
log.debug("Received the following pool config: {}", response)
println(response.bodyAsString)
case Left(failure) =>
log.error("Could not load the pool config! {}", failure)
}
case Left(failure) =>
log.error("Could not load the global pools config! {}", failure)
}
I think you can see the pattern. Contact the REST service, read it, on success parse it into a JSON case class, extract information out and then do the next call.
My structure here is only two-levels deep but I need to add a third level as well.
Is there a technique available to improve this for better readability or can I only stick with this? If you need any further information I'm happy to provide it. You can see the full code here: https://github.com/daschl/cachakka/blob/f969d1f56a4c90a929de9c7ed4e4a0cccea5ba70/src/main/scala/com/cachakka/cluster/actors/InitialConfigLoader.scala
Thanks,
Michael
HttpDialog seems to cover your use case exactly.
I think I found a reasonable shortcut by using the provided reply method from spray-can.
def discoverInitialConfig(hostname: String, bucket: String) = {
val poolResponse: Future[HttpResponse] =
HttpDialog(httpClient, hostname, 8091)
.send(HttpRequest(uri = "/pools"))
.reply(response => {
log.debug("Received the following global pools config: {}", response)
val selectedPool = parse[PoolsConfig](response.bodyAsString)
.pools.find(_("name") == defaultPoolname).get
HttpRequest(uri = selectedPool("uri"))
})
.reply(response => {
log.debug("Received the following pool config: {}", response)
println(response.bodyAsString)
HttpRequest(uri = "/")
})
.end
}
If this is the best available approach, I'll mark it as "answered" but I'm eager to get actual replies from people who know this stuff much better than me.