Question

I'm learning Scala that not yet familiar with using Spray framework to build some REST-API application and faced with the issue: all my HTTP responses should have specific header (Access-Control-Allow-Origin). So I can't find out how to set it to all application's responses one time, not to each one.

My route looks like this:

trait Statistics extends HttpService { self: StatisticModuleLike =>

  implicit def MM: MarshallerM[Future]

  lazy val statisticsRoute =
    path("statistics" / "applications" / Segment / Segment ) { (measure, period) =>
      get {
        respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
          complete {
            getAppCount(MeasureType.withName(measure), period.toInt)
          }
        }
      }
    } ~
    path("statistics" / "approvals" / Segment / Segment ) { (measure, period) =>
      get {
        respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
          complete {
            getApproval(MeasureType.withName(measure), period.toInt)
          }
        }
      }
    } ~
      path("statistics" / "amounts" / Segment / Segment ) { (measure, period) =>
        get {
          respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
            complete {
              getAmount(MeasureType.withName(measure), period.toInt)
            }
          }
        }
      } ~
      path("statistics" / "sellers" / "snooze") {
        get {
          respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
            complete {
              getSellerSnooze(MeasureType.withName("Month"), 100)
            }
          }
        }
      } ~
      path("statistics" / "sellers" / "snooze" / Segment / Segment ) { (measure, period) =>
        get {
          respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
            complete {
                getSellerSnooze(MeasureType.withName(measure), period.toInt)
            }
          }
        }
      } ~
      path("statistics" / "sellers" / "growing" / Segment / Segment ) { (measure, period) =>
        get {
          parameter('percent.as[Int] ? 0) { percent =>
            respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
              complete {
                getSellerDynamic(MeasureType.withName(measure), period.toInt, DynamicTrendType.withName("Growing"), percent)
              }
            }
          }
        }
      } ~
      path("statistics" / "sellers" / "falling" / Segment / Segment ) { (measure, period) =>
        get {
          parameters('percent.as[Int] ? 0, 'average.as[Int] ? 0) { (percent, average) =>
            respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
              complete {
                getSellerDynamic(MeasureType.withName(measure), period.toInt, DynamicTrendType.withName("Falling"), percent)
              }
            }
          }
        }
      }
}

As you can see, adding

respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*"))

to each path is inconvenient...

Is any cute way to solve this? Say, for example, to extend HttpService with some customization and use it instead of base one?

Was it helpful?

Solution

That's a big route =). Actually spray directives are perfectly composable, so no need in this duplication, you can reduce you structure to something like this:

respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
  (pathPrefix("statistics") & get) {
    pathPrefix("applications") { 
      path(Segment / Segment) { (measure, period) =>
        complete { getAppCount(MeasureType.withName(measure), period.toInt) }
      }
    } ~
    pathPrefix("applications") { ... } ~
    path("amounts") { ... } ~
    ... 
  }  
}

where PathPrefix checks that path starts with the given prefix, Path simple matches against the rest of the route, there is also pathSuffix, pathEnd, etc...

Also in order to simplify large structures, i've found useful to compose Spray with Cake Pattern and making so called Handlers, which would handle your logic, this solution is more flexible and much easier to test:

trait SprayRoute extends CounterHandler with ... {
  val service: Route = {
    respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) {
      (pathPrefix("statistics") & get) {
        pathPrefix("applications") { 
          path(Segment / Segment) { CounterHandler }
        } ~
        pathPrefix("applications") { ... } ~
        path("amounts") { ... } ~
        ... 
      }  
    }
  }
}

trait CounterHandler {
  def CounterHandler: (String, String) => Route = { (measure, period) =>
    complete { getAppCount(MeasureType.withName(measure), period.toInt) }
  }
}

OTHER TIPS

You simply can wrap your main route around the respondWithHeader directive. See the example (Spray 1.1.:

object HelloRouting extends SimpleRoutingApp with App{
  implicit val system = ActorSystem("test")
  import system.dispatcher
  startServer(interface= "localhost", port= 8082){
    lazy val api = pathPrefix("api"){
        path("hello"){
          get{
            complete( "Hello, World" )
          }
        }
    }
    respondWithHeader(RawHeader("X-My-Header", "My Header is awesome!")) {
        api
    }
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top