Spray+Twirl搭建web应用

原创
2016/07/29 16:16
阅读数 177

Spray是一个基于akka的高性能的Rest服务框架,基本设计原理遵循全无阻塞的Actor模型。Spray中有不同的库帮助工程师处理服务器、routing、json等不同问题。

通常Spray用于高并发、注重性能的Rest服务上,前端配以其他前端框架调用Rest请求,不大会需要写前端页面。为何要和Twirl配合开发完整的web应用呢?常见适用的场景有:

  • Rest服务需要几个简单的管理页面;
  • 或者是pretty化的简单状态监控页面;
  • 装逼

这边文章大体介绍一下从spray搭建RestService到集成Twirl页面的全过程。

##Spray Spray是一个基于akka的提供http服务的轻量级库。Spray有意不设计成完整的框架,而为不同的问题提供工具库。关于Spray的文章还是很多的,简单说一下搭建一个Rest服务的过程。

  1. 做个实现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!")
      } ~
  1. 然后用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模板

  1. 安装和集成

    • 这个加在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._

  2. 渲染页面

    val routes: Route = {
        path("index") {
          get {
            complete {
                 html.index.render(r.tasks)
            }
          }
      }
    
  3. 模板引入 模板文件一定要放在src/main/twirl下,文件名为xxx.scala.html. 比如index.scala.html就会被编译成上面的html.index对象。 我使用的Eclipse没有插件解析twirl页面文件,必须用sbt命令才行。

  4. 模板编程 Twirl页面可以被看成是一个scala方法,方法的最顶部是入参的说明,比如: @(taskList: Map[String, String]) 主要的语法看这里,和大部分的模板语言差距并不大。 唯一要注意的是import,在import时在package最前面要添加_root_. @import _root_.scala.collection.mutable.Map

##和Actor的交互 像我们当前的项目,有时候Spray需要和akka的其他Actor交互。举个栗子,A Actor在执行一个长任务,用户询问RestService关于A的执行情况。这时候大概有两种做法。

  1. A定期向RestService更新自己的状态,用户询问RestService时立即返回;
  2. 请求来了以后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)
          )
        }
      }
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部