Frage

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?

War es hilfreich?

Lösung

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) }
  }
}

Andere Tipps

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
    }
  }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top