在前面一篇讨论里我们介绍了通过http进行文件的交换。因为文件内容是以一堆bytes来表示的,而http消息的数据部分也是byte类型的,所以我们可以直接用Source[ByteString,_]来读取文件然后放进HttpEntity中。我们还提到:如果需要进行数据库数据交换的话,可以用Source[ROW,_]来表示库表行,但首先必须进行ROW -> ByteString的转换。在上期讨论我们提到过这种转换其实是ROW->Json->ByteString或者反方向的转换,在Akka-http里称之为Marshalling和Unmarshalling。Akka-http的Marshalling实现采用了type-class编程模式,需要为每一种类型与Json的转换在可视域内提供Marshaller[A,B]类型的隐式实例。Akka-http默认的Json工具库是Spray-Json,着重case class,而且要提供JsonFormat?(case-class),其中?代表case class的参数个数,用起来略显复杂。不过因为是Akka-http的配套库,在将来Akka-http的持续发展中具有一定的优势,所以我们还是用它来进行下面的示范。

下面就让我们开始写些代码吧。首先,我们用一个case class代表数据库表行结构,然后用它作为流元素来构建一个Source,如下:

  1. case class County(id: Int, name: String)
  2. val source: Source[County, NotUsed] = Source( to ).map { i => County(i, s"中国广东省地区编号 #$i") }

我们先设计服务端的数据下载部分:

  1. import akka.actor._
  2. import akka.stream._
  3. import akka.stream.scaladsl._
  4. import akka.http.scaladsl.Http
  5. import akka.http.scaladsl.server.Directives._
  6. import akka._
  7. import akka.http.scaladsl.common._
  8. import spray.json.DefaultJsonProtocol
  9. import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
  10.  
  11. trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
  12. object Converters extends MyFormats {
  13. case class County(id: Int, name: String)
  14. val source: Source[County, NotUsed] = Source( to ).map { i => County(i, s"中国广东省地区编号 #$i") }
  15. implicit val countyFormat = jsonFormat2(County)
  16. }
  17.  
  18. object HttpDBServer extends App {
  19. import Converters._
  20.  
  21. implicit val httpSys = ActorSystem("httpSystem")
  22. implicit val httpMat = ActorMaterializer()
  23. implicit val httpEC = httpSys.dispatcher
  24.  
  25. implicit val jsonStreamingSupport = EntityStreamingSupport.json()
  26. .withParallelMarshalling(parallelism = , unordered = false)
  27.  
  28. val route =
  29. path("rows") {
  30. get {
  31. complete {
  32. source
  33. }
  34. }
  35. }
  36.  
  37. val (port, host) = (,"localhost")
  38.  
  39. val bindingFuture = Http().bindAndHandle(route,host,port)
  40.  
  41. println(s"Server running at $host $port. Press any key to exit ...")
  42.  
  43. scala.io.StdIn.readLine()
  44.  
  45. bindingFuture.flatMap(_.unbind())
  46. .onComplete(_ => httpSys.terminate())
  47.  
  48. }

在上面的代码里我们直接把source放进了complete(),然后期望这个directive能通过ToEntityMarshaller[County]类实例用Spray-Json把Source[County,NotUsed]转换成Source[ByteString,NotUsed]然后放入HttpResponse的HttpEntity里。转换结果只能在客户端得到证实。我们知道HttpResponse里的Entity.dataBytes就是一个Source[ByteString,_],我们可以把它Unmarshall成Source[County,_],然后用Akka-stream来操作:

  1. case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
  2. val futSource = Unmarshal(entity).to[Source[County,NotUsed]]
  3. futSource.onSuccess {
  4. case source => source.runForeach(println)
  5. }

上面这个Unmarshal调用了下面这个FromEntityUnmarshaller[County]隐式实例:

  1. // support for as[Source[T, NotUsed]]
  2. implicit def sprayJsonSourceReader[T](implicit reader: RootJsonReader[T], support: EntityStreamingSupport): FromEntityUnmarshaller[Source[T, NotUsed]] =
  3. Unmarshaller.withMaterializer { implicit ec implicit mat e
  4. if (support.supported.matches(e.contentType)) {
  5. val frames = e.dataBytes.via(support.framingDecoder)
  6. val unmarshal = sprayJsonByteStringUnmarshaller(reader)(_)
  7. val unmarshallingFlow =
  8. if (support.unordered) Flow[ByteString].mapAsyncUnordered(support.parallelism)(unmarshal)
  9. else Flow[ByteString].mapAsync(support.parallelism)(unmarshal)
  10. val elements = frames.viaMat(unmarshallingFlow)(Keep.right)
  11. FastFuture.successful(elements)
  12. } else FastFuture.failed(Unmarshaller.UnsupportedContentTypeException(support.supported))
  13. }

这个隐式实例是由Spray-Jason提供的,在SprayJsonSupport.scala里。
下面是这部分客户端的完整代码:

  1. import akka.actor._
  2. import akka.stream._
  3. import akka.stream.scaladsl._
  4. import akka.http.scaladsl.Http
  5. import akka.http.scaladsl.model._
  6. import scala.util._
  7. import akka._
  8. import akka.http.scaladsl.common._
  9. import spray.json.DefaultJsonProtocol
  10. import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
  11. import akka.http.scaladsl.unmarshalling.Unmarshal
  12.  
  13. trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
  14. object Converters extends MyFormats {
  15. case class County(id: Int, name: String)
  16. implicit val countyFormat = jsonFormat2(County)
  17. }
  18.  
  19. object HttpDBClient extends App {
  20. import Converters._
  21.  
  22. implicit val sys = ActorSystem("ClientSys")
  23. implicit val mat = ActorMaterializer()
  24. implicit val ec = sys.dispatcher
  25.  
  26. implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json()
  27.  
  28. def downloadRows(request: HttpRequest) = {
  29. val futResp = Http(sys).singleRequest(request)
  30. futResp
  31. .andThen {
  32. case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
  33. val futSource = Unmarshal(entity).to[Source[County,NotUsed]]
  34. futSource.onSuccess {
  35. case source => source.runForeach(println)
  36. }
  37. case Success(r@HttpResponse(code, _, _, _)) =>
  38. println(s"download request failed, response code: $code")
  39. r.discardEntityBytes()
  40. case Success(_) => println("Unable to download rows!")
  41. case Failure(err) => println(s"download failed: ${err.getMessage}")
  42.  
  43. }
  44. }
  45. downloadRows(HttpRequest(HttpMethods.GET,uri = s"http://localhost:8011/rows"))
  46.  
  47. scala.io.StdIn.readLine()
  48.  
  49. sys.terminate()
  50.  
  51. }

以上我们已经实现了客户端从服务端下载一段数据库表行,然后以Akka-stream的操作方式来处理下载数据。那么反向交换即从客户端上传一段表行的话就需要把一个Source[T,_]转换成Source[ByteString,_]然后放进HttpRequest的HttpEntity里。服务端收到数据后又要进行反向的转换即把Request.Entity.dataBytes从Source[ByteString,_]转回Source[T,_]。Akka-http在客户端没有提供像complete这样的强大的自动化功能。我们可能需要自定义并提供像ToRequestMarshaller[Source[T,_]]这样的隐式实例。但Akka-http的Marshalling-type-class是个非常复杂的系统。如果我们的目的是简单提供一个Source[ByteString,_],我们是否可以直接调用Spray-Json的函数来进行ROW->Son->ByteString转换呢?如下:

  1. import akka.util.ByteString
  2. import akka.http.scaladsl.model.HttpEntity.limitableByteSource
  3.  
  4. val source: Source[County,NotUsed] = Source( to ).map {i => County(i, s"广西壮族自治区地市县编号 #$i")}
  5. def countyToByteString(c: County) = {
  6. ByteString(c.toJson.toString)
  7. }
  8. val flowCountyToByteString : Flow[County,ByteString,NotUsed] = Flow.fromFunction(countyToByteString)
  9.  
  10. val rowBytes = limitableByteSource(source via flowCountyToByteString)
  11.  
  12. val request = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/rows")
  13. val data = HttpEntity(
  14. ContentTypes.`application/json`,
  15. rowBytes
  16. )
  17.  
  18. 我们直接用toJson函数进行County->Json转换实现了flowCountyToByteStringtoJasonSpray-Json提供的一个函数:
  19. package json {
  20.  
  21. case class DeserializationException(msg: String, cause: Throwable = null, fieldNames: List[String] = Nil) extends RuntimeException(msg, cause)
  22. class SerializationException(msg: String) extends RuntimeException(msg)
  23.  
  24. private[json] class PimpedAny[T](any: T) {
  25. def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any)
  26. }
  27.  
  28. private[json] class PimpedString(string: String) {
  29. @deprecated("deprecated in favor of parseJson", "1.2.6")
  30. def asJson: JsValue = parseJson
  31. def parseJson: JsValue = JsonParser(string)
  32. }
  33. }

假设服务端收到数据后以Akka-stream方式再转换成一个List返回,我们用下面的方法来测试功能:

  1. def uploadRows(request: HttpRequest, dataEntity: RequestEntity) = {
  2. val futResp = Http(sys).singleRequest(
  3. request.copy(entity = dataEntity)
  4. )
  5. futResp
  6. .andThen {
  7. case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
  8. entity.dataBytes.map(_.utf8String).runForeach(println)
  9. case Success(r@HttpResponse(code, _, _, _)) =>
  10. println(s"Upload request failed, response code: $code")
  11. r.discardEntityBytes()
  12. case Success(_) => println("Unable to Upload file!")
  13. case Failure(err) => println(s"Upload failed: ${err.getMessage}")
  14.  
  15. }
  16. }

服务端接收数据处理方法如下:

  1. post {
  2. withoutSizeLimit {
  3. entity(asSourceOf[County]) { source =>
  4. val futofNames: Future[List[String]] =
  5. source.runFold(List[String](""))((acc, b) => acc ++ List(b.name))
  6. complete {
  7. futofNames
  8. }
  9. }
  10. }
  11. }

考虑到在数据转换的过程中可能会出现异常。需要异常处理方法来释放backpressure:

  1. def postExceptionHandler: ExceptionHandler =
  2. ExceptionHandler {
  3. case _: RuntimeException =>
  4. extractRequest { req =>
  5. req.discardEntityBytes()
  6. complete((StatusCodes.InternalServerError.intValue,"Upload Failed!"))
  7. }
  8. }
  9.  
  10. post {
  11. withoutSizeLimit {
  12. handleExceptions(postExceptionHandler) {
  13. entity(asSourceOf[County]) { source =>
  14. val futofNames: Future[List[String]] =
  15. source.runFold(List[String](""))((acc, b) => acc ++ List(b.name))
  16. complete {
  17. futofNames
  18. }
  19. }
  20. }
  21. }
  22. }

在客户端试运行返回结果显示:

  1. uploadRows(request,data)
  2.  
  3. ["","广西壮族自治区地市县编号 #1","广西壮族自治区地市县编号 #2","广西壮族自治区地市县编号 #3","广西壮族自治区地市县编号 #4","广西壮族自治区地市县编号 #5"]

正是我们期待的结果。

下面是本次讨论的示范代码:

服务端:

  1. import akka.actor._
  2. import akka.stream._
  3. import akka.stream.scaladsl._
  4. import akka.http.scaladsl.Http
  5. import akka._
  6. import akka.http.scaladsl.common._
  7. import spray.json.DefaultJsonProtocol
  8. import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
  9. import scala.concurrent._
  10. import akka.http.scaladsl.server._
  11. import akka.http.scaladsl.server.Directives._
  12. import akka.http.scaladsl.model._
  13.  
  14. trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
  15. object Converters extends MyFormats {
  16. case class County(id: Int, name: String)
  17. val source: Source[County, NotUsed] = Source( to ).map { i => County(i, s"中国广东省地区编号 #$i") }
  18. implicit val countyFormat = jsonFormat2(County)
  19. }
  20.  
  21. object HttpDBServer extends App {
  22. import Converters._
  23.  
  24. implicit val httpSys = ActorSystem("httpSystem")
  25. implicit val httpMat = ActorMaterializer()
  26. implicit val httpEC = httpSys.dispatcher
  27.  
  28. implicit val jsonStreamingSupport = EntityStreamingSupport.json()
  29. .withParallelMarshalling(parallelism = , unordered = false)
  30.  
  31. def postExceptionHandler: ExceptionHandler =
  32. ExceptionHandler {
  33. case _: RuntimeException =>
  34. extractRequest { req =>
  35. req.discardEntityBytes()
  36. complete((StatusCodes.InternalServerError.intValue,"Upload Failed!"))
  37. }
  38. }
  39.  
  40. val route =
  41. path("rows") {
  42. get {
  43. complete {
  44. source
  45. }
  46. } ~
  47. post {
  48. withoutSizeLimit {
  49. handleExceptions(postExceptionHandler) {
  50. entity(asSourceOf[County]) { source =>
  51. val futofNames: Future[List[String]] =
  52. source.runFold(List[String](""))((acc, b) => acc ++ List(b.name))
  53. complete {
  54. futofNames
  55. }
  56. }
  57. }
  58. }
  59. }
  60. }
  61.  
  62. val (port, host) = (,"localhost")
  63.  
  64. val bindingFuture = Http().bindAndHandle(route,host,port)
  65.  
  66. println(s"Server running at $host $port. Press any key to exit ...")
  67.  
  68. scala.io.StdIn.readLine()
  69.  
  70. bindingFuture.flatMap(_.unbind())
  71. .onComplete(_ => httpSys.terminate())
  72.  
  73. }

客户端:

  1. import akka.actor._
  2. import akka.stream._
  3. import akka.stream.scaladsl._
  4. import akka.http.scaladsl.Http
  5. import akka.http.scaladsl.model._
  6. import scala.util._
  7. import akka._
  8. import akka.http.scaladsl.common._
  9. import spray.json.DefaultJsonProtocol
  10. import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
  11. import akka.http.scaladsl.unmarshalling._
  12.  
  13. trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
  14. object Converters extends MyFormats {
  15. case class County(id: Int, name: String)
  16. implicit val countyFormat = jsonFormat2(County)
  17. }
  18.  
  19. object HttpDBClient extends App {
  20. import Converters._
  21.  
  22. implicit val sys = ActorSystem("ClientSys")
  23. implicit val mat = ActorMaterializer()
  24. implicit val ec = sys.dispatcher
  25.  
  26. implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json()
  27.  
  28. def downloadRows(request: HttpRequest) = {
  29. val futResp = Http(sys).singleRequest(request)
  30. futResp
  31. .andThen {
  32. case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
  33. val futSource = Unmarshal(entity).to[Source[County,NotUsed]]
  34. futSource.onSuccess {
  35. case source => source.runForeach(println)
  36. }
  37. case Success(r@HttpResponse(code, _, _, _)) =>
  38. println(s"download request failed, response code: $code")
  39. r.discardEntityBytes()
  40. case Success(_) => println("Unable to download rows!")
  41. case Failure(err) => println(s"download failed: ${err.getMessage}")
  42.  
  43. }
  44. }
  45. downloadRows(HttpRequest(HttpMethods.GET,uri = s"http://localhost:8011/rows"))
  46.  
  47. import akka.util.ByteString
  48. import akka.http.scaladsl.model.HttpEntity.limitableByteSource
  49.  
  50. val source: Source[County,NotUsed] = Source( to ).map {i => County(i, s"广西壮族自治区地市县编号 #$i")}
  51. def countyToByteString(c: County) = {
  52. ByteString(c.toJson.toString)
  53. }
  54. val flowCountyToByteString : Flow[County,ByteString,NotUsed] = Flow.fromFunction(countyToByteString)
  55.  
  56. val rowBytes = limitableByteSource(source via flowCountyToByteString)
  57.  
  58. val request = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/rows")
  59. val data = HttpEntity(
  60. ContentTypes.`application/json`,
  61. rowBytes
  62. )
  63.  
  64. def uploadRows(request: HttpRequest, dataEntity: RequestEntity) = {
  65. val futResp = Http(sys).singleRequest(
  66. request.copy(entity = dataEntity)
  67. )
  68. futResp
  69. .andThen {
  70. case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
  71. entity.dataBytes.map(_.utf8String).runForeach(println)
  72. case Success(r@HttpResponse(code, _, _, _)) =>
  73. println(s"Upload request failed, response code: $code")
  74. r.discardEntityBytes()
  75. case Success(_) => println("Unable to Upload file!")
  76. case Failure(err) => println(s"Upload failed: ${err.getMessage}")
  77.  
  78. }
  79. }
  80.  
  81. uploadRows(request,data)
  82.  
  83. scala.io.StdIn.readLine()
  84.  
  85. sys.terminate()
  86.  
  87. }

Akka(41): Http:DBTable-rows streaming - 数据库表行交换的更多相关文章

  1. mssql 数据库表行转列,列转行 比较经典

    --行列互转 /******************************************************************************************** ...

  2. restapi(2)- generic restful CRUD:通用的restful风格数据库表维护工具

    研究关于restapi的初衷是想搞一套通用的平台数据表维护http工具.前面谈过身份验证和使用权限.文件的上传下载,这次来到具体的数据库表维护.我们在这篇示范里设计一套通用的对平台每一个数据表的标准维 ...

  3. MS SQL查询所有表行数,获取所有数据库名,表名,字段名

    1.获取所有数据库名 --SELECT Name FROM Master..SysDatabases ORDER BY Name -- 2.获取所有表名: --SELECT Name NAMEtemp ...

  4. SAP中的数据库表索引

    数据库表中的索引可以加快查询的速度.索引是数据库表字段的有序副本.附加的字段包含指向真实数据库表行的指针.排序可以使访问表行的速度变快,例如,可以使用二分搜索.数据库表至少有一个主索引,由它的key字 ...

  5. 将Hive统计分析结果导入到MySQL数据库表中(一)——Sqoop导入方式

    https://blog.csdn.net/niityzu/article/details/45190787 交通流的数据分析,需求是对于海量的城市交通数据,需要使用MapReduce清洗后导入到HB ...

  6. 019-zabbix数据库表详解

    https://www.cnblogs.com/yaoyaojcy/p/10367945.html 1. 查看目前zabbix系统所有数据表: 1 2 3 4 5 6 7 8 9 10 11 12 1 ...

  7. C# 操作数据库表和数据库

    <1>c#创建数据库表: private void CreatTable(string name)      //创建数据库源数据表,name为表名 { con.ConnectionStr ...

  8. 《项目经验》--通过js获取前台数据向一般处理程序传递Json数据,并解析Json数据,将前台传来的Json数据写入数据库表中

      先看一下我要实现的功能界面:   这个界面的功能在图中已有展现,课程分配(教师教授哪门课程)在之前的页面中已做好.这个页面主要实现的是授课,即给老师教授的课程分配学生.此页面实现功能的步骤已在页面 ...

  9. asp.net 对数据库表增加,删除,编辑更新修改

    using System; using System.Collections.Generic; using System.Configuration; using System.Data; using ...

随机推荐

  1. 暑假练习赛 003 A Spider Man

    A - Spider Man Crawling in process... Crawling failed Time Limit:2000MS     Memory Limit:262144KB    ...

  2. 「Vue」起步 - vue-router路由与页面间导航

    vue-router 我们知道路由定义了一系列访问的地址规则,路由引擎根据这些规则匹配找到对应的处理页面,然后将请求转发给页进行处理.可以说所有的后端开发都是这样做的,而前端路由是不存在"请 ...

  3. Intellij导入子项目时,maven列表子项目灰色不可用---解决方法

    导入子项目的module时,左侧project目录中有一个module图标右下角没有小蓝点,maven管理列表该module为灰色(表明未被管理),尝试几次后终于找到解决方案. 贴一张调好过后的图 第 ...

  4. java 通过eclipse编辑器用mysql尝试 连接数据库

    注:本人学的是Oracle,用mysql连接数据库是一次尝试. 一.下载JDBC mysql驱动,导入jar包     我自己下载的是connector-java-6.0.6.jar,如下图所示,JD ...

  5. 史上最全的IntelliJIdea快捷键

    Ctrl+Shift+方向键Up/Down 代码向上/下移动. Ctrl+X 删除行 Ctrl+Y 也是删除行,不知道有啥区别 Ctrl+D 复制行 Ctrl+Alt+L 格式化代码 Ctrl+N 查 ...

  6. sql2012笔记

    收缩数据库日志文件1.数据库右键-->Options-->Revovery model =Full 改成 Simple2.数据库右键-->Tasks-->Shrink--> ...

  7. solr索引库的创建

    solr索引库的创建 一.找到你安装的[solrhome]目录(我的是这个) 二.进入该目录 三.选择其中任意一个索引库复制一份到该目录下并更名为要创建的索引库名称 四.进入[myindex]目录下, ...

  8. 使用composer更新thinkphp5或则yii2的版本

    更新thinkphp5或则yii2的版本,我目前采用的是用composer去更新,小伙伴们如果有其他更好的办法更新,可以直接评论给我,不胜感激啊. 如果还没有安装 Composer ,你可以按 Com ...

  9. WebService学习总结

    因为最近开发的项目需求中涉及到了webservice,正好对这块知识以前学过但是又忘记了,于是想着从新学习下,整理一个笔记,便于后面的复习.于是有了本文,下面开始介绍webservice. 一.简介 ...

  10. Unity3D游戏xlua轻量级热修复框架

    这是什么东西 前阵子刚刚集成xlua到项目,目的只有一个:对线上游戏C#逻辑有Bug的地方执行修复,通过考察了xlua和tolua,最终选择了xlua,原因如下: 1)项目已经到了后期,线上版本迭代了 ...