1. 程式人生 > >Akka(34): Http:Unmarshalling,from Json

Akka(34): Http:Unmarshalling,from Json

define data mda 活性 clas akka buffer 模式 use

Unmarshalling是Akka-http內把網上可傳輸格式的數據轉變成程序高級結構話數據的過程,比如把Json數據轉換成某個自定義類型的實例。按具體流程來說就是先把Json轉換成可傳輸格式數據如:MessageEntity,HttpRequest,HttpReponse等,然後再轉換成程序高級結構數據如classXX實例。Unmarshalling對一個A類實例到B類實例的轉換是通過Unmarshaller[A,B]來實現的:

trait Unmarshaller[-A, B] extends akka.http.javadsl.unmarshalling.Unmarshaller[A, B] {...}
object Unmarshaller extends GenericUnmarshallers with PredefinedFromEntityUnmarshallers with PredefinedFromStringUnmarshallers { // format: OFF //#unmarshaller-creation /** * Creates an `Unmarshaller` from the given function. */ def apply[A, B](f: ExecutionContext ? A ? Future[B]): Unmarshaller[A, B]
= withMaterializer(ec => _ => f(ec)) ...}

從Unmarshaller的構建函數apply可以估計它的作用應該與函數A=>Future[B]很相似。A代表網上可傳輸類型如MessageEntity、HttpRequest,B代表某種程序高級數據類型。因為A到B的轉換是non-blocking的,所以可以立即返回Future類型結果。Akka-http按被轉換對象類型分類命名了下面這些類型別名:

type FromEntityUnmarshaller[T] = Unmarshaller[HttpEntity, T]
type FromMessageUnmarshaller[T] 
= Unmarshaller[HttpMessage, T] type FromResponseUnmarshaller[T] = Unmarshaller[HttpResponse, T] type FromRequestUnmarshaller[T] = Unmarshaller[HttpRequest, T] type FromByteStringUnmarshaller[T] = Unmarshaller[ByteString, T] type FromStringUnmarshaller[T] = Unmarshaller[String, T] type FromStrictFormFieldUnmarshaller[T] = Unmarshaller[StrictForm.Field, T]

Akka-http對以下類型提供了自動的Unmarshalling轉換:

PredefinedFromStringUnmarshallers
Byte
Short
Int
Long
Float
Double
Boolean
PredefinedFromEntityUnmarshallers
Array[Byte]
ByteString
Array[Char]
String
akka.http.scaladsl.model.FormData
GenericUnmarshallers
Unmarshaller[T, T] (identity unmarshaller)
Unmarshaller[Option[A], B], if an Unmarshaller[A, B] is available
Unmarshaller[A, Option[B]], if an Unmarshaller[A, B] is available

也就是說Akka-http提供了這些U類型的Unmarshaller[U,B]隱式實例。Akka-http也提供了工具類型Unmarshal:

object Unmarshal {
  def apply[T](value: T): Unmarshal[T] = new Unmarshal(value)
}

class Unmarshal[A](val value: A) {
  /**
   * Unmarshals the value to the given Type using the in-scope Unmarshaller.
   *
   * Uses the default materializer [[ExecutionContext]] if no implicit execution context is provided.
   * If you expect the marshalling to be heavy, it is suggested to provide a specialized context for those operations.
   */
  def to[B](implicit um: Unmarshaller[A, B], ec: ExecutionContext = null, mat: Materializer): Future[B] = {
    val context: ExecutionContext = if (ec == null) mat.executionContext else ec

    um(value)(context, mat)
  }
}

我們可以通過Unmarshal.to[B]把Unmarshal[A]轉換成Future[B]。註意:這一步只包括了從網上可傳輸類型到程序類型轉換這一過程,不包括具體實現時的Json轉換。下面是一些Unmarshal的用例:

import akka.actor._
import akka.stream._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._

object Unmarshalling {
  implicit val httpSys = ActorSystem("httpSystem")
  implicit val httpMat = ActorMaterializer()
  implicit val httpEC = httpSys.dispatcher

  val futInt = Unmarshal(43).to[Int]
  val futBoolean = Unmarshal("0").to[Boolean]
  val futString = Unmarshal(HttpEntity("Hello")).to[String]
  val futHello = Unmarshal(HttpRequest(method = HttpMethods.GET, entity = HttpEntity("hello")))

}

以上都是已知類型之間轉換,可能沒什麽實際用途,不像marshalling:中間層Marshalling有實際轉換的需要。Unmarshalling可以直接進行Json到自定義類型之間的轉換,如:

 val route = (path("User") & post) { entity(as[User]){ user =>
    complete(Future(s"inserting user: $user"))
  }} ~
    (path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
      complete(Future(s"update item $id: $item"))
    }}

以上是通過Directive as[???]實現的:

 /**
   * Returns the in-scope [[FromRequestUnmarshaller]] for the given type.
   *
   * @group marshalling
   */
  def as[T](implicit um: FromRequestUnmarshaller[T]) = um

這需要把FromRequestUmarshaller[T]放在可視域內,FromRequestUmarshaller[T]實際是Unmarshaller[T,B]的別名:

type FromRequestUnmarshaller[T] = Unmarshaller[HttpRequest, T]

在上篇討論我們介紹了Akka-http的Marshalling是type-class模式的。其中關鍵可以參考上篇討論。現在我們需要這些Unmarshaller的隱式實例:

trait Formats extends SprayJsonSupport with DefaultJsonProtocol
object Converters extends Formats {
  case class User(id: Int, name: String)
  case class Item(id: Int, name: String, price: Double)
  implicit val itemFormat = jsonFormat3(Item.apply)
  implicit val userFormat = jsonFormat2(User.apply)
}

object Unmarshalling {
  import Converters._
...

如果使用Json4s的實現方式,我們需要如下提供這些隱式實例:

trait JsonCodec extends Json4sSupport {
  import org.json4s.DefaultFormats
  import org.json4s.ext.JodaTimeSerializers
  implicit val serilizer = jackson.Serialization
  implicit val formats = DefaultFormats ++ JodaTimeSerializers.all
}
object JsConverters extends JsonCodec

Json4s的具體用例如下:

  import scala.collection.mutable._
  case class User(id: Int, name: String)
  class Item(id: Int, name: String, price: Double)
  object AnyPic {
    val area = 10
    val title = "a picture"
    val data = ArrayBuffer[Byte](1,2,3)
  }

  val route = (path("User") & post) { entity(as[User]){ user =>
    complete(Future(s"inserting user: $user"))
  }} ~
    (path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
      complete(Future(s"update item $id: $item"))
    }} ~
    (path("Picture") & put) { entity(as[AnyPic.type]){ pic =>
      complete(Future(s"insert picture: $pic"))
    }}

從功能上和表達靈活性來講,Json4s的實現方式要占優。

下面就是本次討論的示範源代碼:

Unmarshalling

import akka.actor._
import akka.stream._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import scala.concurrent._
import akka.http.scaladsl.marshallers.sprayjson._
import spray.json._

trait Formats extends SprayJsonSupport with DefaultJsonProtocol
object Converters extends Formats {
  case class User(id: Int, name: String)
  case class Item(id: Int, name: String, price: Double)
  implicit val itemFormat = jsonFormat3(Item.apply)
  implicit val userFormat = jsonFormat2(User.apply)
}

object Unmarshalling {
  import Converters._
  implicit val httpSys = ActorSystem("httpSystem")
  implicit val httpMat = ActorMaterializer()
  implicit val httpEC = httpSys.dispatcher

  val futInt = Unmarshal(43).to[Int]
  val futBoolean = Unmarshal("0").to[Boolean]
  val futString = Unmarshal(HttpEntity("Hello")).to[String]
  val futHello = Unmarshal(HttpRequest(method = HttpMethods.GET, entity = HttpEntity("hello")))

  val route = (path("User") & post) { entity(as[User]){ user =>
    complete(Future(s"inserting user: $user"))
  }} ~
    (path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
      complete(Future(s"update item $id: $item"))
    }}

}

Json4sUnmarshalling

import akka.actor._
import akka.stream._
import akka.http.scaladsl.server.Directives._
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson
import scala.concurrent._
trait JsonCodec extends Json4sSupport {
  import org.json4s.DefaultFormats
  import org.json4s.ext.JodaTimeSerializers
  implicit val serilizer = jackson.Serialization
  implicit val formats = DefaultFormats ++ JodaTimeSerializers.all
}
object JsConverters extends JsonCodec


object Json4sUnmarshalling {
  import JsConverters._
  implicit val httpSys = ActorSystem("httpSystem")
  implicit val httpMat = ActorMaterializer()
  implicit val httpEC = httpSys.dispatcher

  import scala.collection.mutable._
  case class User(id: Int, name: String)
  class Item(id: Int, name: String, price: Double)
  object AnyPic {
    val area = 10
    val title = "a picture"
    val data = ArrayBuffer[Byte](1,2,3)
  }

  val route = (path("User") & post) { entity(as[User]){ user =>
    complete(Future(s"inserting user: $user"))
  }} ~
    (path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
      complete(Future(s"update item $id: $item"))
    }} ~
    (path("Picture") & put) { entity(as[AnyPic.type]){ pic =>
      complete(Future(s"insert picture: $pic"))
    }}
}


Akka(34): Http:Unmarshalling,from Json