用Akka Http写一个web应用

刚学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

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值