Spray框架类似Node.JS中的Express,是Scala语言平台上的REST框架,Spray并非单纯的HTTP REST框架,它还支持JSON、缓存和路由等功能。Spray可以部署在Tomcat等服务器中,也可以单独部署,其内部有spray-can服务容器,能够实现微服务架构。
项目开始我们需要设置库包,在Eclipse等工具中配置SBT,下面是本案例SBT的配置:
organization := "org.smartjava"
version := "0.1"
scalaVersion := "2.11.2"
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8")
libraryDependencies ++= {
val akkaV = "2.3.6"
val sprayV = "1.3.2"
Seq(
"io.spray" %% "spray-can" % sprayV withSources() withJavadoc(),
"io.spray" %% "spray-routing" % sprayV withSources() withJavadoc(),
"io.spray" %% "spray-json" % "1.3.1",
"io.spray" %% "spray-testkit" % sprayV % "test",
"com.typesafe.akka" %% "akka-actor" % akkaV,
"com.typesafe.akka" %% "akka-testkit" % akkaV % "test",
"org.specs2" %% "specs2-core" % "2.3.11" % "test",
"org.scalaz" %% "scalaz-core" % "7.1.0"
)
}
将这些库包导入到IDE以后,我们准备就绪。
下面我们创建一个启动器用来启动Spray服务器 ,取名为Boot,继承Scala的App trait:
package org.smartjava;
import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
object Boot extends App {
//创建一个名为smartjava的Actor系统
implicit val system = ActorSystem("smartjava")
val service = system.actorOf(Props[SJServiceActor], "sj-rest-service")
// IO需要implicit ActorSystem, ? 需要implicit timeout
// 绑定 HTTP到指定服务.
implicit val timeout = Timeout(5.seconds)
IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080)
}
这里我们发送一个HTTP.Bind()消息来注册一个监听器,如果绑定成功,这个端口一旦有请求进来,我们的服务将接受到消息
下面看看我们将接受到的消息发给一个Actor:
package org.smartjava
import akka.actor.Actor
import spray.routing._
import spray.http._
import MediaTypes._
import spray.httpx.SprayJsonSupport._
import MyJsonProtocol._
// 处理路由简单的actor
class SJServiceActor extends Actor with HttpService {
// required as implicit value for the HttpService
// included from SJService
def actorRefFactory = context
// 我们不自己创建接受函数,而是使用
//HttpService的runRoute基于已经提供的路由来创建
def receive = runRoute(aSimpleRoute ~ anotherRoute)
// 一些路由案例
val aSimpleRoute = {...}
val anotherRoute = {...}
这里我们使用了runRoute函数,这是由HttpService提供的,创建一个接受函数来处理进来的消息。
最后一步我们需要创建路由处理代码,下面展示如何基于请求类型创建响应JSON,我们使用Spray支持的标准JSON:
package org.smartjava
import spray.json.DefaultJsonProtocol
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val personFormat = jsonFormat3(Person)
}
case class Person(name: String, fistName: String, age: Long)
Spray通过这种方式将响应对象转为JSON,看看路由代码是如何获得响应对象的:
// handles the api path, we could also define these in separate files
// this path respons to get queries, and make a selection on the
// media-type.
val aSimpleRoute = {
path("path1") {
get {
// 获得content-header值 Get the value of the content-header. Spray
// provides multiple ways to do this.
headerValue({
case x@HttpHeaders.`Content-Type`(value) => Some(value)
case default => None
}) {
// 包含内容类型的header被传递,我们来匹配判断一下
header => header match {
// 如果我们有这个内容类型,我们创建一个响应
case ContentType(MediaType("application/vnd.type.a"), _) => {
respondWithMediaType(`application/json`) {
complete {
Person("Bob", "Type A", System.currentTimeMillis());
}
}
}
// 如果有其他内容类型 返回不同类型
case ContentType(MediaType("application/vnd.type.b"), _) => {
respondWithMediaType(`application/json`) {
complete {
Person("Bob", "Type B", System.currentTimeMillis());
}
}
}
// 如果内容类型不匹配,返回错误编码
case default => {
complete {
HttpResponse(406);
}
}
}
}
}
}
}
// 处理其他路由, we could also define these in separate files
// This is just a simple route to explain the concept
val anotherRoute = {
path("path2") {
get {
// respond with text/html.
respondWithMediaType(`text/html`) {
complete {
// respond with a set of HTML elements
<html>
<body>
<h1>Path 2</h1>
</body>
</html>
}
}
}
}
}
我们使用Chrome Advanced Rest Client.来测试这个REST,http://localhost:8080/path1
当然还有一个开源类似Rails的Spray Route扩展