我们说过Akka-http是一个好的系统集成工具,集成是通过数据交换方式实现的。Http是个在网上传输和接收的规范协议。所以,在使用Akka-http之前,可能我们还是需要把Http模式的网上数据交换细节了解清楚。数据交换双方是通过Http消息类型Request和Response来实现的。在Akka-http中对应的是HttpRequest和HttpResponse。这两个类型都具备HttpEntity类型来装载需要交换的数据。首先,无论如何数据在线上的表现形式肯定是一串bytes。所以,数据交换两头Request,Response中的Entity也必须是以bytes来表达的。在Akka-http里我们把需要传输的数据转换成ByteString,通过网络发送給接收端、接收端再把收到消息Entity中的ByteString转换成目标类型的数据。这两个转换过程就是Akka-http的Marshalling和Unmarshalling过程了。我们先从HttpEntity的构建函数来了解它的定义:

object HttpEntity {
implicit def apply(string: String): HttpEntity.Strict = apply(ContentTypes.`text/plain(UTF-)`, string)
implicit def apply(bytes: Array[Byte]): HttpEntity.Strict = apply(ContentTypes.`application/octet-stream`, bytes)
implicit def apply(data: ByteString): HttpEntity.Strict = apply(ContentTypes.`application/octet-stream`, data)
def apply(contentType: ContentType.NonBinary, string: String): HttpEntity.Strict =
if (string.isEmpty) empty(contentType) else apply(contentType, ByteString(string.getBytes(contentType.charset.nioCharset)))
def apply(contentType: ContentType, bytes: Array[Byte]): HttpEntity.Strict =
if (bytes.length == ) empty(contentType) else apply(contentType, ByteString(bytes))
def apply(contentType: ContentType, data: ByteString): HttpEntity.Strict =
if (data.isEmpty) empty(contentType) else HttpEntity.Strict(contentType, data) def apply(contentType: ContentType, contentLength: Long, data: Source[ByteString, Any]): UniversalEntity =
if (contentLength == ) empty(contentType) else HttpEntity.Default(contentType, contentLength, data)
def apply(contentType: ContentType, data: Source[ByteString, Any]): HttpEntity.Chunked =
HttpEntity.Chunked.fromData(contentType, data)
...

很明显,HttpEntity可以分两大类,一种是Strict类型的,它的data是ByteString。另一种是UniversalEntity类型,它的数据dataBytes是Source[ByteString,Any]。无论如何最终在线上的还是ByteString。HttpEntity的ContentType注明了传输数据格式,有:

object ContentTypes {
val `application/json` = ContentType(MediaTypes.`application/json`)
val `application/octet-stream` = ContentType(MediaTypes.`application/octet-stream`)
val `text/plain(UTF-)` = MediaTypes.`text/plain` withCharset HttpCharsets.`UTF-`
val `text/html(UTF-)` = MediaTypes.`text/html` withCharset HttpCharsets.`UTF-`
val `text/xml(UTF-)` = MediaTypes.`text/xml` withCharset HttpCharsets.`UTF-`
val `text/csv(UTF-)` = MediaTypes.`text/csv` withCharset HttpCharsets.`UTF-` // used for explicitly suppressing the rendering of Content-Type headers on requests and responses
val NoContentType = ContentType(MediaTypes.NoMediaType)
}

注意:ContentType只是一种备注,不影响线上数据表达形式,线上的数据永远是ByteString。但是,其中的application/octet-stream类型代表数据必须是Source[ByteString,Any]。我们下面就通过客户端的例子来理解HttpEntity。下面是一个客户端测试函数:

  def runService(request: HttpRequest, rentity: RequestEntity) = {
val futResp = for {
entity <- Future.successful(rentity)
resp <- Http(sys).singleRequest(
request.copy(entity = rentity)
)
} yield resp futResp
.andThen {
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
entity.dataBytes.map(_.utf8String).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}") }
}

我们只需要对这个函数传入RequestEntity就可以了解返回Response里Entity的许多细节了。首先我们要求服务端发送一个纯字符串Hello World。服务端代码如下:

 } ~ path("text") {
get {
complete("Hello World!")
} ~

虽然complete("Hello World!")有些迷糊,不过应该complete做了些字符串到ByteString的转换。我们可以从上面这个runService函数得到证实。下面是这个例子的调用:

  val reqText = HttpRequest(uri = s"http://localhost:8011/text")
runService(reqText,HttpEntity.Empty)
.andThen{case _ => sys.terminate()}

从显示的结果可以得出runService函数中的entity.dataBytes.map(_.utf8String)已经把ByteString转换成了String,也就是说服务器端发送的Entity里的数据是ByteString。

我们再试着发送一些数据給服务端,然后让服务端把结果通过response entity返回来:

    } ~ path("text") {
get {
complete("Hello World!")
} ~
post {
withoutSizeLimit {
extractDataBytes { bytes =>
val data = bytes.runFold(ByteString())(_ ++ _)
onComplete(data) { t =>
complete(t)
}
}
}
}

我们看到服务端对request entity的操作是以ByteString进行的。客户端上传一串字符的request如下:

  val postText = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/text")
val uploadText = HttpEntity(
ContentTypes.`text/plain(UTF-)`,
// transform each number to a chunk of bytes
ByteString("hello world again")
)
runService(postText,uploadText)
.andThen{case _ => sys.terminate()}

我们可以看到放进entity里的数据是ByteString。

我们知道Akka-http是基于Akka-Stream的,具备Reactive-Stream功能特性。下面我们就示范一下如何进行stream的上传下载。首先定制一个Source:

  val numbers = Source.fromIterator(() =>
Iterator.continually(Random.nextInt()))
.map(n => ByteString(s"$n\n"))
//make conform to withoutSizeLimit constrain
val source = limitableByteSource(numbers)

服务端也是用HttpEntity来装载这个Source然后通过HttpRequest传给客户端的:

  path("random") {
get {
complete(
HttpEntity(
ContentTypes.`application/octet-stream`,
// transform each number to a chunk of bytes
source.take()
)
)
} ~

我们在客户端还是用runService来解析传过来的entity。由于接收一个大型的Source,所以需要修改一下接收方式代码:

   futResp
.andThen {
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
val futEnt = entity.dataBytes.map(_.utf8String).runForeach(println)
Await.result(futEnt, Duration.Inf) // throws if binding fails
println("End of stream!!!")
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}") }

用下面的方式调用:

  val reqRandom = HttpRequest(uri = s"http://localhost:8011/random")
runService(reqRandom,HttpEntity.Empty)
.andThen{case _ => sys.terminate()}

再示范一下在客户端用Source上传数据。服务端代码:

       post {
withoutSizeLimit {
extractDataBytes { bytes =>
val data = bytes.runFold(ByteString())(_ ++ _)
onComplete(data) { t =>
complete(t)
}
}
}
}

客户端上传数据范例:

 val numbers = Source.fromIterator(() =>
Iterator.continually(Random.nextInt()))
.map(n => ByteString(s"$n\n"))
//make conform to withoutSizeLimit constrain
val source = limitableByteSource(numbers) val bytes = HttpEntity(
ContentTypes.`application/octet-stream`,
// transform each number to a chunk of bytes
source.take()
)
val postRandom = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/random")
runService(postRandom,bytes)
.andThen{case _ => sys.terminate()}

从上面讨论我们了解了在Marshal,Unmarshal下层只是ByteString的操作和转换。下面是本次讨论示范源代码:

服务端:

import akka.actor._
import akka.stream._
import akka.stream.scaladsl._
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model._
import akka.util.ByteString
import akka.http.scaladsl.model.HttpEntity._ import scala.util.Random object ServerEntity extends App { implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher val numbers = Source.fromIterator(() =>
Iterator.continually(Random.nextInt()))
.map(n => ByteString(s"$n\n"))
//make conform to withoutSizeLimit constrain
val source = limitableByteSource(numbers) val route =
path("random") {
get {
withoutSizeLimit {
complete(
HttpEntity(
ContentTypes.`application/octet-stream`,
// transform each number to a chunk of bytes
source.take())
)
}
} ~
post {
withoutSizeLimit {
extractDataBytes { bytes =>
val data = bytes.runFold(ByteString())(_ ++ _)
onComplete(data) { t =>
complete(t)
}
}
}
}
} ~ path("text") {
get {
complete("Hello World!")
} ~
post {
withoutSizeLimit {
extractDataBytes { bytes =>
val data = bytes.runFold(ByteString())(_ ++ _)
onComplete(data) { t =>
complete(t)
}
}
}
}
} 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.HttpEntity.limitableByteSource
import akka.http.scaladsl.model._ import scala.concurrent.duration._
import akka.util.ByteString import scala.concurrent._
import scala.util._ object ClientEntity extends App { implicit val sys = ActorSystem("ClientSys")
implicit val mat = ActorMaterializer()
implicit val ec = sys.dispatcher def runService(request: HttpRequest, rentity: RequestEntity) = {
val futResp = for {
entity <- Future.successful(rentity)
resp <- Http(sys).singleRequest(
request.copy(entity = rentity)
)
} yield resp futResp
.andThen {
case Success(r@HttpResponse(StatusCodes.OK, _, entity, _)) =>
val futEnt = entity.dataBytes.map(_.utf8String).runForeach(println)
Await.result(futEnt, Duration.Inf) // throws if binding fails
println("End of stream!!!")
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}") }
} val reqText = HttpRequest(uri = s"http://localhost:8011/text")
// runService(reqText,HttpEntity.Empty)
// .andThen{case _ => sys.terminate()} val postText = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/text")
val uploadText = HttpEntity(
ContentTypes.`text/plain(UTF-)`,
// transform each number to a chunk of bytes
ByteString("hello world again")
)
// runService(postText,uploadText)
// .andThen{case _ => sys.terminate()} val reqRandom = HttpRequest(uri = s"http://localhost:8011/random")
// runService(reqRandom,HttpEntity.Empty)
// .andThen{case _ => sys.terminate()} val numbers = Source.fromIterator(() =>
Iterator.continually(Random.nextInt()))
.map(n => ByteString(s"$n\n"))
//make conform to withoutSizeLimit constrain
val source = limitableByteSource(numbers) val bytes = HttpEntity(
ContentTypes.`application/octet-stream`,
// transform each number to a chunk of bytes
source.take()
)
val postRandom = HttpRequest(HttpMethods.POST,uri = s"http://localhost:8011/random")
runService(postRandom,bytes)
.andThen{case _ => sys.terminate()} }

Akka(38): Http:Entityof ByteString-数据传输基础的更多相关文章

  1. 简述在akka中发送消息的过程

    在flink的数据传输过程中,有两类数据,一类数据是控制流数据,比如提交作业,比如连接jm,另一类数据是业务数据.flink对此采用了不同的传输机制,控制流数据的传输采用akka进行,业务类数据传输在 ...

  2. 第38章 I2S—音频播放与录音输入—零死角玩转STM32-F429系列

    第38章     I2S—音频播放与录音输入 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/ ...

  3. SQL-Teradata基础

    1.创建一个和表 pnr_1 结构一样的表 Create table pnr_2 as pnr_1 with no data  不含数据 Create table pnr_2 as pnr_1 wit ...

  4. 20145221 《Java程序设计》第十周学习总结

    20145221 <Java程序设计>第十周学习总结 网络编程 网络概述 概述 网络编程技术是当前一种主流的编程技术,随着联网趋势的逐步增强以及网络应用程序的大量出现,所以在实际的开发中网 ...

  5. EMC光纤交换机故障处理和命令分析

        主机没有Login到存储是一个比较常见的故障,故障多发于主机新上线,或者是重启后.例如在Unisphere中,显示Host状态是”Registered: Yes; Logged In: No” ...

  6. Git 总结

    详情请参考:https://git-scm.com/book/zh/v2 注意事项:#1. 多提交(相当于多保存,多^S): 在Git中任何已提交的东西几乎总是可以恢复的. 甚至那些被删除的分支中的提 ...

  7. SolrCloud集群搭建(基于zookeeper)

    1. 环境准备 1.1 三台Linux机器,x64系统 1.2 jdk1.8 1.3 Solr5.5 2. 安装zookeeper集群 2.1 分别在三台机器上创建目录 mkdir /usr/hdp/ ...

  8. 为什么Goroutine能有上百万个,Java线程却只能有上千个?

      作者|Russell Cohen   译者|张卫滨   本文通过 Java 和 Golang 在底层原理上的差异,分析了 Java 为什么只能创建数千个线程,而 Golang 可以有数百万的 Go ...

  9. c++学习书籍推荐《C++ Templates》下载

    详细讲解C++模板语言的概念. 使用C++模板的常用设计技巧. 应用例证(其中一些是“高 级”应用). 百度云及其他网盘下载地址:点我 名人推荐 如果今年你只打算买一本C++的书,那就选<C++ ...

  10. Spark Deploy 模块

    Spark Scheduler 模块的文章中,介绍到 Spark 将底层的资源管理和上层的任务调度分离开来,一般而言,底层的资源管理会使用第三方的平台,如 YARN 和 Mesos.为了方便用户测试和 ...

随机推荐

  1. jQuery Ajax跨域问题简易解决方案

    场景:由于业务需求,需要在一个页面显示另外一个页面,并且右键查看源代码看不到一条链接. 实现方式:使用iframe来显示这个首页,至于首页的地址则使用jQuery Ajax来获取.html代码如下: ...

  2. LeetCode 339. Nested List Weight Sum (嵌套列表重和)$

    Given a nested list of integers, return the sum of all integers in the list weighted by their depth. ...

  3. LeetCode 229. Majority Element II (众数之二)

    Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. The algorit ...

  4. CoreCLR源码探索(八) JIT的工作原理(详解篇)

    在上一篇我们对CoreCLR中的JIT有了一个基础的了解, 这一篇我们将更详细分析JIT的实现. JIT的实现代码主要在https://github.com/dotnet/coreclr/tree/m ...

  5. Fastify 系列教程四 (求对象、响应对象和插件)

    Fastify 系列教程: Fastify 系列教程一 (路由和日志) Fastify 系列教程二 (中间件.钩子函数和装饰器) Fastify 系列教程三 (验证.序列化和生命周期) Fastify ...

  6. python 带小数点时间格式化

    #获取带小数点的时间>>> import datetime #当前时间加3天 >>> t1 = datetime.datetime.now() + datetime ...

  7. 火狐浏览器打开html文件,中文乱码

    当html文件为: 解决方法: 将   <meta http-equiv="Content-Type" content="text/html; charset=UT ...

  8. linux云服务器常用设置

    前面的话 由于在云服务器上配置自己的网站,将Linux里的常用设置记录如下 更改shell 默认地, ubuntu系统默认的shell是dash,但更常用的shell是bash 通过下面命令可以将da ...

  9. spring boot 自己输出json数据

    @RequestMapping("/json")public void json(HttpServletResponse response, Pager pager, TruckF ...

  10. linux中必会的目录

    第1章 find命令扩展 1.1 方法一 |xargs 通过|xargs将前面命令的执行结果传给后面. [root@znix ~]# find /oldboy/ -type f -name " ...