構建一個簡單的 Spray 應用
帶你輕鬆入坑spray
前段時間使用 scala 構建了一個略微複雜的業務系統, web 框架選用了 spray, 參考了這個文檔, 就順帶翻譯了, 當做一個簡明的入坑教程, 多少為scala的普及做點貢獻。
正文
你可能已經從別的地方聽說過spray了, 也有可能你在 解析json的時候用到了spray。 其實Spray可以讓你以極簡的方式實現一個 rest 服務, 上手一個新框架最難的就是去了解其中的一些概念, 我用一個簡單的應用帶你去搞定spray,學會一些最常用的功能。
Spray的文檔中對於 依賴部分和 imports 哪些類可能講的不多, 我這裡全都引用好, 免得你還得去找。
寫一個項目的 build.sbt 文件, 加上以下的依賴
val akka = "2.3.9" val spray = "1.3.2" resolvers += Resolver.url("TypeSafe Ivy releases", url("http://dl.bintray.com/typesafe/ivy-releases/"))(Resolver.ivyStylePatterns) libraryDependencies ++= Seq( // -- Logging -- "ch.qos.logback" % "logback-classic" % "1.1.2", "com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2", // -- Akka -- "com.typesafe.akka" %% "akka-testkit" % akka % "test", "com.typesafe.akka" %% "akka-actor" % akka, "com.typesafe.akka" %% "akka-slf4j" % akka, // -- Spray -- "io.spray" %% "spray-routing" % spray, "io.spray" %% "spray-client" % spray, "io.spray" %% "spray-testkit" % spray % "test", // -- json -- "io.spray" %% "spray-json" % "1.3.1", // -- config -- "com.typesafe" % "config" % "1.2.1", // -- testing -- "org.scalatest" %% "scalatest" % "2.2.1" % "test" )創建一個乾淨的空殼服務
spray 使用 akka actors 來接收服務調用, 一種設置路由的方式是創建 scala 的 traits, 如下:
import akka.actor.Actor import spray.routing.HttpService class SampleServiceActor extends Actor with SampleRoute { def actorRefFactory = context def receive = runRoute(route) } trait SampleRoute extends HttpService { val route = { get { complete("I exist!") } } }
你可以看到, 我們創建了一個定義路由的 trait, 並且吧路由傳入到 runRoute里, 你不一定要使用這種方式, 但是這樣能更好的組織你的路由。
到現在, 我們有了一個完備的service, 讓我們運行起來玩一玩, 我們需要一個main class 來綁定埠
import akka.actor. import akka.io.IO import spray.can.Http object Main extends App { implicit val system = ActorSystem("simple-service") val service = system.actorOf(Props[SampleServiceActor], "simple-service") IO(Http) ! Http.Bind(service, host, port = port) }
我們的main 函數裡面構造了一個 Akka ActorSystem, 並且把我們的 SampleServiceActor 跑在 系統里,
這個時候你就可以 在 IntelliJ 里啟動main 函數了, 啟動後, 可以去瀏覽器訪問 http://localhost:8080/ 裡面看一下有什麼東西。
現在我們有了一個基本的殼子, 我們來干點有趣的。
增加一個 paths/routes
我們現在直接在根路徑下添加一個 stuff 的路由。
val route = { get { complete("I exist!") } ~ get { path("stuff") { complete("That s my stuff!") } } }
path 指令允許我們定義一個路由, 符號可以把多個路由串起來(這是spray裡面常用的做法), http://localhost:8080/stuff 按理應該展示 , 然而卻顯示了 .
其實這裡的問題是, spray 會依次往下匹配, 先匹配上了 , 所以就解析到這個路徑了, 我們可以重構一下
val route = { get { path("stuff") { complete("That s my stuff!") } } ~ get { complete("I exist!") } }
這樣就可以了
發送一個 POST 請求
你可能注意到了, get 這個關鍵字代表著一種動作, 當然我們可以使用其他的動詞(post/put/delete/etc), 我們發送一個 post請求,
val route = { get { path("stuff") { complete("That s my stuff!") } } ~ post { path("stuff") { complete("stuff posted!") } } ~ get { complete("I exist!") } }
我們現在可以 post一個消息, 並且從響應中看到這條消息, 但是還有點小問題, 我們在 get 和post 中使用了兩次 相同的path,
spray 有很靈活的 DSL, 你可以隨意調整 path 的位置, 我們可以很輕鬆去掉重複的代碼, 只需要把 path 放在外面就可以了,裡面包含 get 和post 方法。
val route = { path("stuff") { get { complete("That s my stuff!") } ~ post { complete("stuff posted!") } } ~ get { complete("I exist!") } }
這樣是不是好一點了,
讀取和返回 json 數據
如果我們需要處理一下數據, 我們就需要用到 json 對象了,
我們這裡就用一些簡單的json 數據, 我這裡用 spray json 來做序列化。
import spray.json.DefaultJsonProtocol case class Stuff(id: Int, data: String) object Stuff extends DefaultJsonProtocol { implicit val stuffFormat = jsonFormat2(Stuff.apply) }
我們來返回一些測試數據
trait SampleRoute extends HttpService { import spray.httpx.SprayJsonSupport._ import Stuff._ import spray.http.MediaTypes val route = { path("stuff") { respondWithMediaType(MediaTypes.`application/json`) { get { complete(Stuff(1, "my stuff")) } ~ post { complete("stuff posted!") } } } ~ get { complete("I exist!") } } }
這裡 complete 方法處理 Stuff 的case class 類, 並且自動 把數據序列化為了 json。 注意這裡的 respondWithMediaType 方法, 如果期望獲取 XML 類型的數據, 你可以使用這個方法來構造路由。
現在我們返回了數據, 我們稍微改一下, 讓我們的介面可以讀取 Stuff 類型的數據, 我們在 id 上面加100, 並且返回。
post { entity(as[Stuff]) { stuff => complete(Stuff(stuff.id + 100, stuff.data + " posted")) } }
這裡我們可以看到, 我們使用 entity(as[Stuff]) 加了一層嵌套, 這個指令會解析json數據到一個 對象裡面, 並且把這個對象當參數傳到你的代碼塊中。
多層路由
你可能需要處理 類似 junk/mine 和 junk/yours 這種的路由, 直覺可能寫成 path(「junk/mine」), 但是這樣不行, 當你需要複雜一點的路由的時候, spray 提供了 pathPrefix 和 pathEnd 指令
pathPrefix("junk") { pathPrefix("mine") { pathEnd { get { complete("MINE!") } } } ~ pathPrefix("yours") { pathEnd { get { complete("YOURS!") } } } }
這裡可以嵌套很多層, 所以是極其靈活的, 你可以構建很複雜的路由, 當然我們這裡只舉一些簡單的例子。
獲取 query 參數
有時候,我們需要獲取query 參數, 有必填的參數, 也有和選填的參數, 這個時候我們可以使用 parameters 指令。
path("params") { get { parameters( req, opt.?) { (req, opt) => complete(s"Req: $req, Opt: $opt") } } }
注意這裡的 符合, 這代表前面的這個參數是選填的, 否則是必填的。 這裡你可以加很多參數,都作為變數 傳到後面的代碼塊中, 如果你的 請求是
那麼返回的應該是
Req: hi, Opt: Some(bye)
這裡的 選填的參數是 scala Option 類型的,
這樣, 如果你的請求一個參數都沒加(http://localhost:8080/params), 那麼匹配的就是根路徑, 因為這裡有一個必填的參數,你不帶,就匹配不到這裡的路徑。
Request is missing required query parameter req 獲取 headers
類似於獲取query 參數, 我們這裡使用 headerValueByName 指令來獲取headers
path("headers") { get { headerValueByName("ct-remote-user") { userId => complete(userId) } } }
這裡也是 Option 類型,
返回一個 futures
你的服務可能會調用 一些響應式的框架, 比如 , 或者你想直接返回 scala 的future 類型, 恭喜你, complete 也是支持的。
path("reactive") { get { complete(future { "I m reactive!" }) } }
這樣你的代碼就不需要 Await.result! 了, 是不是很爽
打包部署
我們現在有了一個完整的應用,我們來部署上線, 我們使用 sbt 來構建發布,
lazy val root = (project in file(".")).enablePlugins(JavaAppPackaging)
我們使用了 sbt-native-packager 插件, 需要 在 project/plugins.sbt 文件中加一下
這樣, 你運行 sbt dist 命令就可以在 target/universal 目錄下發現一個 zip 目標文件, 你就可以用來發布了。
※2017 斯坦福創業課:如何打造產品
※緊貼底部的頁腳
※針對數千萬睡眠疾病潛在患者,兆觀科技推出獲得醫療器械認證的智能檢測設備
※離散/整數組合優化概述及其在AI的應用
※Android增量代碼測試覆蓋率工具
TAG:推酷 |
※使用 Redis 和 Python 構建一個共享單車的應用程序
※使用 Python 和 Pygame 模塊構建一個遊戲框架
※用Pytorch構建一個自動解碼器
※如何快速構建一個 Spring Boot 工程?
※用 Python 構建一個極小的區塊鏈
※使用Python構建的七大應用程序
※使用 Scrapy 構建一個網路爬蟲
※我們是如何使用 Electron 構建 Linux 桌面應用程序的
※一文詳解如何使用Python和Keras構建屬於你的AlphaZero AI
※一文詳解如何使用Python和Keras構建屬於你的「AlphaZero AI」
※用 OpenStack Designate 構建一個 DNS 即服務(DNSaaS)
※使用 Ansible 在樹莓派上構建一個基於 Linux 的高性能計算系統
※.net core下簡單構建高可用服務集群
※如何使用 Docker Compose 來構建一套開發環境
※前端每周清單:Slack Webpack構建優化,CSS 命名規範與用戶追蹤
※使用 Nginx的image_filter 模塊來構建動態縮略圖伺服器
※electron-toolKit:構建和啟動Electron應用的工具包
※使用 Angular 5.0和Spring Boot 2.0 構建一個基本的 CRUD 應用
※如何使用Spring Boot構建微服務
※想學習區塊鏈?那就用 Python 構建一個