Akka(41): Http:DBTable-rows streaming - 数据库表行交换
在前面一篇讨论里我们介绍了通过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,如下:
case class County(id: Int, name: String)
val source: Source[County, NotUsed] = Source( to ).map { i => County(i, s"中国广东省地区编号 #$i") }
我们先设计服务端的数据下载部分:
import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka._
import akka.http.scaladsl.common._
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
object Converters extends MyFormats {
case class County(id: Int, name: String)
val source: Source[County, NotUsed] = Source( to ).map { i => County(i, s"中国广东省地区编号 #$i") }
implicit val countyFormat = jsonFormat2(County)
} object HttpDBServer extends App {
import Converters._ implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher implicit val jsonStreamingSupport = EntityStreamingSupport.json()
.withParallelMarshalling(parallelism = , unordered = false) val route =
path("rows") {
get {
complete {
source
}
}
} val (port, host) = (,"localhost") val bindingFuture = Http().bindAndHandle(route,host,port) println(s"Server running at $host $port. Press any key to exit ...") scala.io.StdIn.readLine() bindingFuture.flatMap(_.unbind())
.onComplete(_ => httpSys.terminate()) }
在上面的代码里我们直接把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来操作:
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
val futSource = Unmarshal(entity).to[Source[County,NotUsed]]
futSource.onSuccess {
case source => source.runForeach(println)
}
上面这个Unmarshal调用了下面这个FromEntityUnmarshaller[County]隐式实例:
// support for as[Source[T, NotUsed]]
implicit def sprayJsonSourceReader[T](implicit reader: RootJsonReader[T], support: EntityStreamingSupport): FromEntityUnmarshaller[Source[T, NotUsed]] =
Unmarshaller.withMaterializer { implicit ec ⇒ implicit mat ⇒ e ⇒
if (support.supported.matches(e.contentType)) {
val frames = e.dataBytes.via(support.framingDecoder)
val unmarshal = sprayJsonByteStringUnmarshaller(reader)(_)
val unmarshallingFlow =
if (support.unordered) Flow[ByteString].mapAsyncUnordered(support.parallelism)(unmarshal)
else Flow[ByteString].mapAsync(support.parallelism)(unmarshal)
val elements = frames.viaMat(unmarshallingFlow)(Keep.right)
FastFuture.successful(elements)
} else FastFuture.failed(Unmarshaller.UnsupportedContentTypeException(support.supported))
}
这个隐式实例是由Spray-Jason提供的,在SprayJsonSupport.scala里。
下面是这部分客户端的完整代码:
import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import scala.util._
import akka._
import akka.http.scaladsl.common._
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.unmarshalling.Unmarshal trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
object Converters extends MyFormats {
case class County(id: Int, name: String)
implicit val countyFormat = jsonFormat2(County)
} object HttpDBClient extends App {
import Converters._ implicit val sys = ActorSystem("ClientSys")
implicit val mat = ActorMaterializer()
implicit val ec = sys.dispatcher implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json() def downloadRows(request: HttpRequest) = {
val futResp = Http(sys).singleRequest(request)
futResp
.andThen {
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
val futSource = Unmarshal(entity).to[Source[County,NotUsed]]
futSource.onSuccess {
case source => source.runForeach(println)
}
case Success(r@HttpResponse(code, _, _, _)) =>
println(s"download request failed, response code: $code")
r.discardEntityBytes()
case Success(_) => println("Unable to download rows!")
case Failure(err) => println(s"download failed: ${err.getMessage}") }
}
downloadRows(HttpRequest(HttpMethods.GET,uri = s"http://localhost:8011/rows")) scala.io.StdIn.readLine() sys.terminate() }
以上我们已经实现了客户端从服务端下载一段数据库表行,然后以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转换呢?如下:
import akka.util.ByteString
import akka.http.scaladsl.model.HttpEntity.limitableByteSource val source: Source[County,NotUsed] = Source( to ).map {i => County(i, s"广西壮族自治区地市县编号 #$i")}
def countyToByteString(c: County) = {
ByteString(c.toJson.toString)
}
val flowCountyToByteString : Flow[County,ByteString,NotUsed] = Flow.fromFunction(countyToByteString) val rowBytes = limitableByteSource(source via flowCountyToByteString) val request = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/rows")
val data = HttpEntity(
ContentTypes.`application/json`,
rowBytes
) 我们直接用toJson函数进行County->Json转换实现了flowCountyToByteString。toJason是Spray-Json提供的一个函数:
package json { case class DeserializationException(msg: String, cause: Throwable = null, fieldNames: List[String] = Nil) extends RuntimeException(msg, cause)
class SerializationException(msg: String) extends RuntimeException(msg) private[json] class PimpedAny[T](any: T) {
def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any)
} private[json] class PimpedString(string: String) {
@deprecated("deprecated in favor of parseJson", "1.2.6")
def asJson: JsValue = parseJson
def parseJson: JsValue = JsonParser(string)
}
}
假设服务端收到数据后以Akka-stream方式再转换成一个List返回,我们用下面的方法来测试功能:
def uploadRows(request: HttpRequest, dataEntity: RequestEntity) = {
val futResp = Http(sys).singleRequest(
request.copy(entity = dataEntity)
)
futResp
.andThen {
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
entity.dataBytes.map(_.utf8String).runForeach(println)
case Success(r@HttpResponse(code, _, _, _)) =>
println(s"Upload request failed, response code: $code")
r.discardEntityBytes()
case Success(_) => println("Unable to Upload file!")
case Failure(err) => println(s"Upload failed: ${err.getMessage}")
}
}
服务端接收数据处理方法如下:
post {
withoutSizeLimit {
entity(asSourceOf[County]) { source =>
val futofNames: Future[List[String]] =
source.runFold(List[String](""))((acc, b) => acc ++ List(b.name))
complete {
futofNames
}
}
}
}
考虑到在数据转换的过程中可能会出现异常。需要异常处理方法来释放backpressure:
def postExceptionHandler: ExceptionHandler =
ExceptionHandler {
case _: RuntimeException =>
extractRequest { req =>
req.discardEntityBytes()
complete((StatusCodes.InternalServerError.intValue,"Upload Failed!"))
}
} post {
withoutSizeLimit {
handleExceptions(postExceptionHandler) {
entity(asSourceOf[County]) { source =>
val futofNames: Future[List[String]] =
source.runFold(List[String](""))((acc, b) => acc ++ List(b.name))
complete {
futofNames
}
}
}
}
}
在客户端试运行返回结果显示:
uploadRows(request,data) ["","广西壮族自治区地市县编号 #1","广西壮族自治区地市县编号 #2","广西壮族自治区地市县编号 #3","广西壮族自治区地市县编号 #4","广西壮族自治区地市县编号 #5"]
正是我们期待的结果。
下面是本次讨论的示范代码:
服务端:
import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.http.scaladsl.Http
import akka._
import akka.http.scaladsl.common._
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import scala.concurrent._
import akka.http.scaladsl.server._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model._ trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
object Converters extends MyFormats {
case class County(id: Int, name: String)
val source: Source[County, NotUsed] = Source( to ).map { i => County(i, s"中国广东省地区编号 #$i") }
implicit val countyFormat = jsonFormat2(County)
} object HttpDBServer extends App {
import Converters._ implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher implicit val jsonStreamingSupport = EntityStreamingSupport.json()
.withParallelMarshalling(parallelism = , unordered = false) def postExceptionHandler: ExceptionHandler =
ExceptionHandler {
case _: RuntimeException =>
extractRequest { req =>
req.discardEntityBytes()
complete((StatusCodes.InternalServerError.intValue,"Upload Failed!"))
}
} val route =
path("rows") {
get {
complete {
source
}
} ~
post {
withoutSizeLimit {
handleExceptions(postExceptionHandler) {
entity(asSourceOf[County]) { source =>
val futofNames: Future[List[String]] =
source.runFold(List[String](""))((acc, b) => acc ++ List(b.name))
complete {
futofNames
}
}
}
}
}
} val (port, host) = (,"localhost") val bindingFuture = Http().bindAndHandle(route,host,port) println(s"Server running at $host $port. Press any key to exit ...") scala.io.StdIn.readLine() bindingFuture.flatMap(_.unbind())
.onComplete(_ => httpSys.terminate()) }
客户端:
import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import scala.util._
import akka._
import akka.http.scaladsl.common._
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.unmarshalling._ trait MyFormats extends SprayJsonSupport with DefaultJsonProtocol
object Converters extends MyFormats {
case class County(id: Int, name: String)
implicit val countyFormat = jsonFormat2(County)
} object HttpDBClient extends App {
import Converters._ implicit val sys = ActorSystem("ClientSys")
implicit val mat = ActorMaterializer()
implicit val ec = sys.dispatcher implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json() def downloadRows(request: HttpRequest) = {
val futResp = Http(sys).singleRequest(request)
futResp
.andThen {
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
val futSource = Unmarshal(entity).to[Source[County,NotUsed]]
futSource.onSuccess {
case source => source.runForeach(println)
}
case Success(r@HttpResponse(code, _, _, _)) =>
println(s"download request failed, response code: $code")
r.discardEntityBytes()
case Success(_) => println("Unable to download rows!")
case Failure(err) => println(s"download failed: ${err.getMessage}") }
}
downloadRows(HttpRequest(HttpMethods.GET,uri = s"http://localhost:8011/rows")) import akka.util.ByteString
import akka.http.scaladsl.model.HttpEntity.limitableByteSource val source: Source[County,NotUsed] = Source( to ).map {i => County(i, s"广西壮族自治区地市县编号 #$i")}
def countyToByteString(c: County) = {
ByteString(c.toJson.toString)
}
val flowCountyToByteString : Flow[County,ByteString,NotUsed] = Flow.fromFunction(countyToByteString) val rowBytes = limitableByteSource(source via flowCountyToByteString) val request = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/rows")
val data = HttpEntity(
ContentTypes.`application/json`,
rowBytes
) def uploadRows(request: HttpRequest, dataEntity: RequestEntity) = {
val futResp = Http(sys).singleRequest(
request.copy(entity = dataEntity)
)
futResp
.andThen {
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
entity.dataBytes.map(_.utf8String).runForeach(println)
case Success(r@HttpResponse(code, _, _, _)) =>
println(s"Upload request failed, response code: $code")
r.discardEntityBytes()
case Success(_) => println("Unable to Upload file!")
case Failure(err) => println(s"Upload failed: ${err.getMessage}") }
} uploadRows(request,data) scala.io.StdIn.readLine() sys.terminate() }
Akka(41): Http:DBTable-rows streaming - 数据库表行交换的更多相关文章
- mssql 数据库表行转列,列转行 比较经典
--行列互转 /******************************************************************************************** ...
- restapi(2)- generic restful CRUD:通用的restful风格数据库表维护工具
研究关于restapi的初衷是想搞一套通用的平台数据表维护http工具.前面谈过身份验证和使用权限.文件的上传下载,这次来到具体的数据库表维护.我们在这篇示范里设计一套通用的对平台每一个数据表的标准维 ...
- MS SQL查询所有表行数,获取所有数据库名,表名,字段名
1.获取所有数据库名 --SELECT Name FROM Master..SysDatabases ORDER BY Name -- 2.获取所有表名: --SELECT Name NAMEtemp ...
- SAP中的数据库表索引
数据库表中的索引可以加快查询的速度.索引是数据库表字段的有序副本.附加的字段包含指向真实数据库表行的指针.排序可以使访问表行的速度变快,例如,可以使用二分搜索.数据库表至少有一个主索引,由它的key字 ...
- 将Hive统计分析结果导入到MySQL数据库表中(一)——Sqoop导入方式
https://blog.csdn.net/niityzu/article/details/45190787 交通流的数据分析,需求是对于海量的城市交通数据,需要使用MapReduce清洗后导入到HB ...
- 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 ...
- C# 操作数据库表和数据库
<1>c#创建数据库表: private void CreatTable(string name) //创建数据库源数据表,name为表名 { con.ConnectionStr ...
- 《项目经验》--通过js获取前台数据向一般处理程序传递Json数据,并解析Json数据,将前台传来的Json数据写入数据库表中
先看一下我要实现的功能界面: 这个界面的功能在图中已有展现,课程分配(教师教授哪门课程)在之前的页面中已做好.这个页面主要实现的是授课,即给老师教授的课程分配学生.此页面实现功能的步骤已在页面 ...
- asp.net 对数据库表增加,删除,编辑更新修改
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using ...
随机推荐
- emacs elpy代码补全功能
emcas中使用elpy编辑python代码,经常需要格式化代码 格式化代码方法C-c C-r f (elpy-format-code) 使用这个快捷键时,需要保证两点, 1.已经安装了yapf或者a ...
- 读书笔记-你不知道的JS上-词法作用域
JS引擎 编译与执行 Javascript引擎会在词法分析和代码生成阶段对运行性能进行优化,包含对冗余元素进行优化(例如对语句在不影响结果的情况下进行重新组合). 对于Javascript来说,大部分 ...
- 在windows下,将mysql离线数据文件导入本地mysql数据库
1. 查看mysql路径 SELECT @@basedir AS basePath FROM DUAL 其实mysql5.6 的数据文件在 C:\ProgramData\MySQL\MySQL Ser ...
- WCF 内置跟踪日志
Web.config 配置文件修改: <system.serviceModel> <diagnostics> <messageLogging logEntireMessa ...
- mysql别名的使用
在项目中遇到别名的问题,抽时间整理了一下 在sql中,合理的使用别名可以让sql更容易写并且提高可读性.别名使用 as 来表示,可以分为表别名和列别名. 别名应该是先定义后使用才对,所以首先要了解sq ...
- Grunt打包seajs项目
在使用seajs时,常常将若干脚本分为多次require进来,这样开发中比较方便,但是,会增加http请求次数,在生产环境中需要进行打包合并.压缩等操作. 以Grunt构建工具为例,对一个seajs项 ...
- hadoop的webUI查看Live Nodes为1
开起了两个节点,而且jps查看确实开启了,但是用web端50070查看却一直显示为1 经过排查,将虚拟机直接copy一份,但是之前配置好hadoop环境的namenode格式化(format)生成的文 ...
- git(2)----Git的常用撤销技巧与解决冲突方法
git checkout . #本地所有修改的.没有的提交的,都返回到原来的状态 git stash #把所有没有提交的修改暂存到stash里面.可用git stash pop回复. git rese ...
- 项目实战4—haproxy 负载均衡和ACL控制
haproxy实现高级负载均衡实战 环境:随着公司业务的发展,公司负载均衡服务已经实现四层负载均衡,但业务的复杂程度提升,公司要求把mobile手机站点作为单独的服务提供,不在和pc站点一起提供服务 ...
- mysql步骤详解
一.配置MySQL数据库 1.解压绿色版mysql,并改名为mysql5.7,如下图 对比一下下图5.6以前的版本,少data目录(存放数据)和my-default.ini文件(配置信息) 二.安装服 ...