Search code examples
scalasprayspray-jsonspray-dsl

Produce an error with scala and spray


I try to create simple CRUD application with scala and spray-routing. I have the following route:

override def receive = runRoute {
    path("entities" / LongNumber) { id =>
      get {
        produce(instanceOf[MyEntity]) {
          func => ctx => heavyLookupById(id, func)
        }
    }
}

I wrote this according to the official documentation http://spray.io/documentation/1.2.2/spray-routing/marshalling-directives/produce/

MyEntity is the following (doesn't matter really):

case class MyEntity(text: String, id: Option[Long])

And I have the following json-support object

object MyJsonSupport extends DefaultJsonProtocol with SprayJsonSupport {
    implicit val format = jsonFormat2(MyEntity)
}

The "heavyLookupById" function contains some heavy blocking computations (suppose database queries or http requests) and I have to deal with scala future because of it:

def heavyLookupById(id: Long, func: MyEntity => Unit) = {
   // ... heavy computations
   future onSuccess { case x => func(x) }
}

But what should I do if my future fails? I want to respond with something like bad request (400) or not found (404) HTTP errors, but how to do it? If I don't invoke "func" inside the "heavyLookupById" - request just hanging - I believe it will fail by default server timeout (1 minute or so).


Solution

  • You have the RequestContext (ctx) so you can call reject, failWith or any other methods available on the RequestContext.

      val route = path("entities" / LongNumber) { id =>
        get {
          produce(instanceOf[MyEntity]) {
            func => ctx => heavyLookupById(id, func, ctx)
          }
        }
      }
    
      def heavyLookupById(id: Long, func: MyEntity => Unit, ctx: RequestContext) = {
        // ... heavy computations
        val future = Future.successful(MyEntity("Hello", Some(1)))
        future.onComplete {
          case Success(value) => func(value)
          case Failure(ex) => ctx.failWith(ex)
        }
    
      }
    

    Personally prefer handleWith rather than produce, I find it a bit easier to read.

    Also in case of failure spray will just return a 500 that you can customize with exceptionHandlers.

      def heavyLookupById(id: Long) = {
        // ... heavy computations
        Future.successful(MyEntity("Hello", Some(1)))
      }
    
    
      val route = path("entities" / LongNumber) { id =>
        get {
          handleWith(heavyLookupById)
        }
      }