scala的akka框架有一个极简的http service组件,是把原来spray框架集成到akka里面修改而成,本文简单介绍了akka http 的使用,在一些简单web service项目或者将web作为一个简单模块的后台项目中非常适合。
sbt配置
name := "MockServer"
version := "0.1"
scalaVersion := "2.12.3"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % "10.0.10",
"com.typesafe.akka" %% "akka-stream" % "2.5.4",
"com.typesafe.akka" %% "akka-actor" % "2.5.4",
"com.typesafe.akka" %% "akka-http-spray-json" % "10.0.10",
"ch.megard" %% "akka-http-cors" % "0.2.2"
)
code
httpserver
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.Done
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.model.headers.HttpOriginRange
import ch.megard.akka.http.cors.scaladsl.CorsDirectives.cors
import ch.megard.akka.http.cors.scaladsl.settings.CorsSettings
import spray.json.DefaultJsonProtocol._
import scala.io.StdIn
import scala.collection.mutable
object MockServer
{
// cors setting for other origin access
val settings = CorsSettings.defaultSettings.copy(
allowedOrigins = HttpOriginRange.* // * refers to all
)
// model
final case class User(name: String, age: Int, addr: String)
implicit val itemFormat = jsonFormat3(User) // jsonFormatX refers to X number arguments
final case class UserGroup(items: List[User])
implicit val orderFormat = jsonFormat1(UserGroup)
// object variable
private val userGroup = mutable.ListBuffer[User]()
def main(args: Array[String]): Unit =
{
implicit val system = ActorSystem("mock_system")
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
// define routes, notice there are many ways
val route: Route =
(path("hello") & get & cors(settings))
{
complete("hello akka")
} ~
(path("list_all") & cors(settings))
{
// simple get
get
{
// add elem to userGroup, can use this.userGroup += User("jack", 18, "NewYork")
userGroup.clear()
userGroup += User("jack", 18, "NewYork")
userGroup += User("mike", 21, "paris")
val user_group = UserGroup(this.userGroup.toList)
complete(user_group)
}
} ~
get
{
// get by params using akka http path matcher
(pathPrefix("user" / IntNumber ) & cors(settings))
{
age =>
{
val user = User("lucy", age.toInt, "tokyo")
complete(user)
}
}
} ~
post {
(path("create_user") & cors(settings))
{
entity(as[User])
{
user =>
{
println(user)
// do sth to process
userGroup += user
println(userGroup)
complete("done")
}
}
}
}
// bind to ip and port and start server
val bindingFuture = Http().bindAndHandle(route, "localhost", 7070)
println(s"Server online at http://localhost:7070/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
// while (1) // or use a dead loop
// stop server
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
其中,
- get和post的内容需要写在route里面
- 如果想要传入传出自定义的数据结构,需要用json序列化和反序列化
- 为了支持跨域访问,加入了cors的setting
- 如果要支持https,请参考官网文档
httpclient
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import scala.concurrent.Future
import scala.util.{Failure, Success}
object WebClient extends App
{
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val host_url = "http://www.easy-mock.com/mock/59ed7cef591f361bb0d95ad8" // this is a mock service
val get_api = "user"
val post_api = "login"
val post_username_param = "mike"
val post_password_param = "ethhvhe35"
// url can be rest or non-rest
def getReq(url: String): Unit =
{
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = url, method = HttpMethods.GET))
responseFuture.onComplete {
case Success(res) => {
println(res)
Unmarshal(res.entity).to[String].map { json_str =>
Right {
// here we can deserialize json string to object or list of object
// sth like val res_obj = Json.deserialize([Model])(json_str)
// or val res_obj = Json.deserialize(Seq[Model])(json_str)
println("get result: ", json_str)
}
}
}
case Failure(error) => println(error.getMessage)
}
}
def postReq(url: String): Unit =
{
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = url, method = HttpMethods.POST))
responseFuture.onComplete {
case Success(res) => {
println(res)
Unmarshal(res.entity).to[String].map { json_str =>
Right {
// here we can deserialize json string to object or list of object
// sth like val res_obj = Json.deserialize([Model])(json_str)
// or val res_obj = Json.deserialize(Seq[Model])(json_str)
println("post result: ", json_str)
}
}
}
case Failure(error) => println(error.getMessage)
}
}
// test get
getReq(host_url + "/" + get_api)
// test post
postReq(s"$host_url/$post_api?username=$post_username_param&password=$post_password_param")
}
其中,
- 可以将entity解析成json字符串,然后将json字符串绑定到自定义的class获得数据
- Future传出到外面就失效了,必须带context
- 也可以写成actor的形式,请参考官网文档
download
github: akka_http_demo