【问题标题】:Spray: How to apply respondWithHeaders to all the routes instead of each oneSpray:如何将 respondWithHeaders 应用于所有路由而不是每条路由
【发布时间】:2014-04-11 08:39:59
【问题描述】:

我正在学习 Scala,但还不熟悉使用 Spray 框架构建一些 REST-API 应用程序并面临一个问题:我所有的 HTTP 响应都应该有特定的标头 (Access-Control-Allow-Origin)。所以我不知道如何将它设置为一次所有应用程序的响应,而不是每个响应。

我的路线是这样的:

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

如你所见,添加

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

去每条路都不方便...

有什么可爱的方法可以解决这个问题吗?比如说,用一些自定义扩展 HttpService 并使用它而不是基本的?

【问题讨论】:

  • 当然可以。您不喜欢扩展 HttpService 并对其进行自定义的解决方案?
  • @maks 对扩展 HttpService 有点不确定:我需要的功能只是响应自定义,而不是整个 HttpService... 所以我建议,必须有一种更短、更友好的方式来避免仅针对一些基本特征参数化扩展的基本组件。

标签: scala http-headers httpresponse spray


【解决方案1】:

那是一条大路 =)。实际上,spray 指令是完全可组合的,因此无需在此重复中,您可以将结构简化为如下所示:

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") { ... } ~
    ... 
  }  
}

其中 PathPrefix 检查路径是否以给定前缀开头,Path 与路由的其余部分简单匹配,还有 pathSuffix、pathEnd 等...

另外,为了简化大型结构,我发现用 Cake Pattern 组合 Spray 并制作所谓的 Handlers 来处理你的逻辑很有用,这个解决方案更灵活,更容易测试:

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

【讨论】:

  • 起初,我并不清楚这些指令是否非常可组合和灵活。谢谢!
【解决方案2】:

您可以简单地将主路由包裹在 respondWithHeader 指令周围。参见示例(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
    }
  }
}

【讨论】:

  • 不幸的是,无法让它工作...(使用 Spray 1.2)
  • 谢谢!你启发了我这样的事情:val route = { respondWithHeader(RawHeader("Access-Control-Allow-Origin", "*")) { usersRoute ~ SellersRoute ~ statisticsRoute } }
猜你喜欢
  • 2016-02-19
  • 2022-06-15
  • 2020-12-04
  • 2012-04-05
  • 2020-02-26
  • 2019-10-05
  • 2017-08-24
  • 1970-01-01
  • 2018-06-02
相关资源
最近更新 更多