刚学Scala,其实已经蓄谋已久,看了好些资料,奈何scala太多东西,而且网上资料并不算多,官方英文资料啃得也是很难受,主要是Scala不熟悉,有时候看不懂代码。对于刚学一门语言的新手来说,写一个小Demo可以帮助理解和记忆,这里就以Scala比较流行的框架Akka Http来写一个简单的Web。
Akka Http并不是专门用来搭建Web项目,但是用来搭几个简单的接口和页面,用它也是足够的并且不会太麻烦。Akka Htpp基于Akka Actor,所以需要对Actor模型有一点了解(我也是粗略了解而已),但只要知道Actor模型的工作机制就好了。这个demo用sbt构建,所以对sbt或者maven之类的有一点了解,当然还需要Scala一点点语法基础。
目标:
- 首先完成一个hello接口,访问就返回一个文本hello
- 在上面基础上完成一个JSON接口
- 完成有业务逻辑的JSON接口
- Web服务支持静态资源的读取,可以访问html文件
- 把应用部署到服务器
创建一个sbt项目,然后在build.sbt里加上下面的依赖
val akkaHttpV = "10.1.1" val akkaV = "2.5.12" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % akkaV, "com.typesafe.akka" %% "akka-stream" % akkaV, "com.typesafe.akka" %% "akka-http" % akkaHttpV, "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpV, ) 创建一个Server Object,为了方便,直接继承HttpApp,复杂方式可以看官网例子,在重写的routes方法里定义hello接口的Route,最后写上main方法来启动Server
object Server extends HttpApp{ override protected def routes: Route = { path(""){ get{ complete("hello") } } } def main(args: Array[String]): Unit = { Server.startServer("localhost",8080) } } 这时候访问下8080端口就可以看到输出hello了。这真的很简单了,只是官网把复杂的例子放在前面,真的要学下别人框架的Get Started,越简单越好才是。
返回JSON其实可以直接用jackson,这里按照官方意见,用spray json来做,话说spray和akka http有很大姻缘呢:)
scala里面的case class真的是好东西,但是在序列化的时候就比较麻烦了。首先创建一个trait和我们的返回实体HelloDTO
case class HelloDTO(str:String,name:String) trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol{ implicit val helloDTOFormat = jsonFormat2(HelloDTO) } 按照官网的说法,继承SprayJsonSupport后这个trait就拥有了(反)序列化一般对象的功能,例如Array,String,Int。而我们需要做的就是为HelloDTO定义Format方法,jsonFormat2就是告诉spray json(据说它不用反射) HelloDTO有两个参数。
接着还需要把JsonSupport应用到需要(反)序列化的地方,我们直接应用给Server,另外提供一个接口返回HelloDTO,用~来lian’qi
object Server extends HttpApp with JsonSupport { override protected def routes: Route = { path(""){ get{ complete("hello") } }~ path("helloDTO"){ get{ complete(HelloDTO("hello","martin")) } } } def main(args: Array[String]): Unit = { Server.startServer("localhost",8080) } } 此时,访问/helloDTO就可以看到返回的JSON数据了
上面的代码基本没业务逻辑,业务逻辑我们应该让某个Object来专门负责做。这时候轮到Akka Actor登场了
首先先创建一个HelloActor,接收参数然后返回HelloDTO
class HelloActor extends Actor{ override def receive: Receive = { case YourName(name) => sender ! HelloDTO("hello",name) } } 然后把HelloActor注册到我们自己的actorSystem里面。然后再开一个route接收客户端传来的name参数,封装好给HelloActor处理,最后Server代码如下
...其他import import akka.pattern.ask import scala.concurrent.duration._ object Server extends HttpApp with JsonSupport { implicit val actorSystem = ActorSystem("simple-web") implicit val helloActor = actorSystem.actorOf(Props[HelloActor]) implicit val timeout = Timeout(5.seconds) override protected def routes: Route = { path(""){ get{ complete("hello") } }~ path("helloDTO"){ get{ complete(HelloDTO("hello","martin")) } }~ (get & path("helloActor")){ parameter("name"){name=> onSuccess(helloActor ? YourName(name)){ case helloDTO:HelloDTO=>complete{helloDTO} } } } } def main(args: Array[String]): Unit = { //这里注册我们的system给Server Server.startServer("localhost",8080,actorSystem) } } 测试curl localhost:8080/helloActor?name='Tony' 就可以看到返回的JSON里面name是Tony了
上面代码虽然可以跑,但是akka http不会自动帮我们关掉actorSystem,详情看官网
作为一个简单的Server,静态资源的支持也是很有必要的,毕竟写个小项目没有页面没什么卵用。
首先我们让src/main/resources/static作为静态资源的根目录,然后开一个static作为前缀的路由来处理静态资源
...其他路由 ~ (get & pathPrefix("static")){ getFromResourceDirectory("static") } 这样我们的Server就可以支持静态资源了,包括static下面的js目录里面的js文件等
用Akka Http写个小工具后,应该怎么部署到服务器,由于上面的Demo完全是为了Demo而写,所以有些地方不适合直接部署,首先Server需要一直跑,不能接收回车就退出去,然后就是Server退出时顺便把actorSystem也关掉。这里只要重写Server继承HttpApp的两个方法
override protected def waitForShutdownSignal(system: ActorSystem)(implicit ec: ExecutionContext): Future[Done] = Future.never override protected def postServerShutdown(attempt: Try[Done], system: ActorSystem): Unit = { actorSystem.terminate() super.postServerShutdown(attempt, system) } 另外Server绑定的地址不能是localhost,我就因为这个卡了好久,在生产环境中,机器可能会有多个地址,需要绑定到0.0.0.0
Server.startServer("0.0.0.0",8080,actorSystem) 打包部署用sbt-native-packager比较好,不然每次都要跑sbt run很慢。首先在/project/plugins.sbt文件上引用插件
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.5") 在build.sbt里开启插件
enablePlugins(JavaAppPackaging) 然后执行sbt stage,在/target/universal/stage/bin目录下可以找到打包后的文件,直接运行就可以了
项目地址:Github