面向函数编程

使用Scala和Spray实现REST

  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扩展

 

使用Scala的Akka HTTP,Akka Stream和Reactive Mongo建立REST服务

scala专题

Scala教程

Akka教程

四种Scala的Web框架

Go语言入门教程