Spray是一个基于akka的高性能的Rest服务框架,基本设计原理遵循全无阻塞的Actor模型。Spray中有不同的库帮助工程师处理服务器、routing、json等不同问题。
通常Spray用于高并发、注重性能的Rest服务上,前端配以其他前端框架调用Rest请求,不大会需要写前端页面。为何要和Twirl配合开发完整的web应用呢?常见适用的场景有:
- Rest服务需要几个简单的管理页面;
- 或者是pretty化的简单状态监控页面;
- 装逼
这边文章大体介绍一下从spray搭建RestService到集成Twirl页面的全过程。
##Spray Spray是一个基于akka的提供http服务的轻量级库。Spray有意不设计成完整的框架,而为不同的问题提供工具库。关于Spray的文章还是很多的,简单说一下搭建一个Rest服务的过程。
- 做个实现HttpService接口的Actor或Service
class DemoServiceActor extends Actor with DemoService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing,
// timeout handling or alternative handler registration
def receive = runRoute(demoRoute)
}
2.在Actor或Service中实现route
trait DemoService extends HttpService {
// we use the enclosing ActorContext's or ActorSystem's dispatcher for our Futures and Scheduler
implicit def executionContext = actorRefFactory.dispatcher
val demoRoute = {
get {
pathSingleSlash {
complete(index)
} ~
path("ping") {
complete("PONG!")
} ~
- 然后用spray-can来启动
object Boot extends App {
implicit val system = ActorSystem("on-spray-can")
val service = system.actorOf(Props[DemoServiceActor], "demo-service")
IO(Http) ! Http.Bind(service, "localhost", port = 8080)
}
##Spray routing Spray routing是开发者定制化最多的部分,其中又有很多预定义的directives,这里把常用的介绍一下:
get
post
匹配method为get/post的请求path
pathPrefix
pathSuffix
匹配path满足条件的请求parameters
接受传入参数complete
reject
redirect
以不同方式结束请求getFromFile
getFromDirectory
getFromResource
load静态文件和资源,比如js和images什么的
举个栗子:
path("subscribe" / "uids") {
get {
parameters('subId) {
subId => completeWithJSON(StatusCodes.OK, subId)
}
}~
post {
formFields('subId, 'key, 'projectId ? "0") {
(subId, key, projectId) =>
complete {
completeWithJSON(StatusCodes.Accepted)
}
}
}
其中~表示chain,就是前一个route没有处理的话就传给下一个。
##集成Twirl模板
-
安装和集成
-
这个加在plugin.sbt中:
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.1.1")
-
build.sbt中
lazy val root = (project in file(".")).enablePlugins(SbtTwirl)
-
大坑!在Service类(或Directive)类中一定要引入
import spray.httpx.PlayTwirlSupport._
-
如果要用MediaType处理的话,引入
import MediaTypes._
-
-
渲染页面
val routes: Route = { path("index") { get { complete { html.index.render(r.tasks) } } }
-
模板引入 模板文件一定要放在src/main/twirl下,文件名为xxx.scala.html. 比如index.scala.html就会被编译成上面的html.index对象。 我使用的Eclipse没有插件解析twirl页面文件,必须用sbt命令才行。
-
模板编程 Twirl页面可以被看成是一个scala方法,方法的最顶部是入参的说明,比如:
@(taskList: Map[String, String])
主要的语法看这里,和大部分的模板语言差距并不大。 唯一要注意的是import,在import时在package最前面要添加_root_.@import _root_.scala.collection.mutable.Map
##和Actor的交互 像我们当前的项目,有时候Spray需要和akka的其他Actor交互。举个栗子,A Actor在执行一个长任务,用户询问RestService关于A的执行情况。这时候大概有两种做法。
- A定期向RestService更新自己的状态,用户询问RestService时立即返回;
- 请求来了以后RestService询问A,等待A回消息,然后返回给用户。
两种方案都是不错的,具体讲一下第二种的写法。
- 在AActor中
class AActor extends Actor {
import AActor._
def receive: Actor.Receive = {
case Status =>
sender ! Result("blah")
}
}
object AActor{
case class Status()
case class Result(res:String)
}
- 在RestService(或directive)中
path("index") {
get {
complete {
(AActor ? Status ).mapTo[Result].map(
r =>
html.index.render(r.res)
)
}
}