Spray.io尝试
使用Spray.io搭建Rest服务
Spray 是一个开源的 REST/HTTP 工具包和底层网络 IO 包,基于 Scala 和 Akka 构建。轻量级、异步、非堵塞、基于 actor 模式、模块化和可测试是 spray 的特点。
你可以通过git@osc上面获取代码 http://git.oschina.net/for-1988/Simples/tree/master/SpraySimple
工程环境
- IDE: IntelliJ IDEA 13.1.1 (安装Scala、SBT插件)
- Spray 1.2, akka 2.2.3 , Scala 2.10.3
SBT配置
build.sbt
name := "SpraySimple"
version := "1.0"
scalaVersion := "2.10.3"
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8")
resolvers ++= Seq(
"Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/",
"Spray repository" at "http://repo.spray.io/"
)
libraryDependencies ++= {
val akkaV = "2.2.3"
val sprayV = "1.2.0"
Seq(
//"org.java-websocket" % "Java-WebSocket" % "1.3.1",
"org.json4s" %% "json4s-native" % "3.2.4",
"io.spray" %% "spray-json" % "1.2.5",
"io.spray" % "spray-can" % sprayV,
"io.spray" % "spray-routing" % sprayV,
"com.typesafe.akka" %% "akka-actor" % akkaV,
"com.typesafe.akka" %% "akka-testkit" % akkaV % "test",
"io.spray" % "spray-testkit" % sprayV % "test",
"org.scalatest" %% "scalatest" % "2.0" % "test",
"junit" % "junit" % "4.11" % "test",
"org.specs2" %% "specs2" % "2.2.3" % "test"
)
}
plugins.sbt
logLevel := Level.Warn
addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.1")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.2")
Spray 启动web服务
Spray的Spray-Can模块来提供web服务,它本身就提供了服务器功能,这样就无需其他服务器。启动Http监听只需要下面一句代码
IO(Http) ! Http.Bind(httpServer, Configuration.host, port = Configuration.portHttp)
直接借助Scala的main方法就可以启动。Spray是基于Akka构建的,所以启动需要基于Akka的ActorSystem。httpServer对象就是一个Akka的Actor。
完整的启动类:
object Server extends App with Routes {
implicit lazy val system = ActorSystem("server-system")
lazy val index = system.actorOf(Props[IndexActor], "index")
implicit lazy val routes = {
new IndexService(index)(system).route
}
IO(Http) ! Http.Bind(httpServer, Configuration.host, port = Configuration.portHttp)
}
object Configuration {
import com.typesafe.config.ConfigFactory
private val config = ConfigFactory.load
config.checkValid(ConfigFactory.defaultReference)
val host = config.getString("http.server.host")
val portHttp = config.getInt("http.server.ports.http")
val portTcp = config.getInt("http.server.ports.tcp")
val portWs = config.getInt("http.server.ports.ws")
}
Spray的路由
Spray的spray-routing模块提供路由功能,上面可以看到启动类Server继承了Routes,这个Trait是我们自己写来处理路由部分的代码,我们先看下它的实现代码
trait Routes extends RouteConcatenation with StaticRoute with AbstractAkkaSystem {
val httpServer = system.actorOf(Props(classOf[HttpServer], allRoutes))
implicit def routes: Route
lazy val allRoutes = logRequest(showReq _) {
routes ~ staticRoute
}
private def showReq(req: HttpRequest) = LogEntry(req.uri, InfoLevel)
}
trait StaticRoute extends Directives {
this: AbstractAkkaSystem =>
lazy val staticRoute =
path("favicon.ico") {
getFromResource("favicon.ico")
} ~
pathPrefix("markers") {
getFromResourceDirectory("markers/")
} ~
pathPrefix("css") {
getFromResourceDirectory("css/")
} ~
pathEndOrSingleSlash {
getFromResource("index.html")
} ~ complete(NotFound)
}
AbstractAkkaSystem:
trait AbstractAkkaSystem {
implicit def system: ActorSystem
}
这里我们初始化了HttpServer对象,并定义了隐式参数routes,还有对静态资源的路由配置。在Server中,我们对routes进行了实现,加入了IndexService的实现类。在这个类中,我们实现了一个很简单的Rest功能。
HttpServer对象
class HttpServer(route: Route) extends Actor with HttpService with ActorLogging {
implicit def actorRefFactory = context
override def receive = runRoute(route)
}
HttpServer对象实现了spray.routing.HttpService 和akka.actor.Actor,
implicit def actorRefFactory = context //实现HttpService中的隐式方法 actorRefFactory
override def receive = runRoute(route) // 实现Actor的receive方法,交给HttpService中的runRoute处理
路由映射及返回资源处理
class IndexActor extends Actor with ActorLogging {
override def receive = {
case None =>
}
}
class IndexService(index: ActorRef)(implicit system: ActorSystem) extends Directives with Json4sSupport {
implicit def json4sFormats: Formats = DefaultFormats
lazy val route =
pathPrefix("page") {
val dir = "page/"
pathEndOrSingleSlash {
getFromResource(dir + "index.html")
} ~
getFromResourceDirectory(dir)
} ~
path("echo" / Segment) {
message => get {
complete {
s"${message}"
}
}
} ~
path("person") {
get {
complete {
val person = new Person("Feng Jiang", 26)
person
}
}
}
}