刚学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文件
- 把应用部署到服务器
搭起一个Server
创建一个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
返回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数据了
我们不能让Server干太多事
上面的代码基本没业务逻辑,业务逻辑我们应该让某个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