Akka-http的客户端连接模式除Connection-Level和Host-Level之外还有一种非常便利的模式:Request-Level-Api。这种模式免除了连接Connection的概念,任何时候可以直接调用singleRequest来与服务端沟通。下面我们用几个例子来示范singleRequest的用法:

  (for {
response <- Http().singleRequest(HttpRequest(method=HttpMethods.GET,uri="http://localhost:8011/message"))
message <- Unmarshal(response.entity).to[String]
} yield message).andThen {
case Success(msg) => println(s"Received message: $msg")
case Failure(err) => println(s"Error: ${err.getMessage}")
}.andThen {case _ => sys.terminate()}

这是一个GET操作:用Http().singleRequest直接把HttpRequest发送给服务端uri并获取返回的HttpResponse。我们看到,整组函数的返回类型都是Future[?],所以用for-comprehension来把所有实际运算包嵌在Future运算模式内(context)。下面这个例子是客户端上传数据示范:

 (for {
entity <- Marshal("Wata hell you doing?").to[RequestEntity]
response <- Http().singleRequest(HttpRequest(method=HttpMethods.PUT,uri="http://localhost:8011/message",entity=entity))
message <- Unmarshal(response.entity).to[String]
} yield message).andThen {
case Success(msg) => println(s"Received message: $msg")
case Failure(err) => println(s"Error: ${err.getMessage}")
}.andThen {case _ => sys.terminate()}

以上是个PUT操作。我们需要先构建数据载体HttpEntity。格式转换函数Marshal也返回Future[HttpEntity],所以也可以包含在for语句内。关注一下这个andThen,它可以连接一串多个monadic运算,在不影响上游运算结果的情况下实现一些副作用计算。值得注意的是上面这两个例子虽然表现形式很简洁,但我们无法对数据转换过程中的异常及response的状态码等进行监控。所以我们应该把整个过程拆分成两部分:先获取response,再具体处理response,包括核对状态,处理数据等:

  case class Item(id: Int, name: String, price: Double)

  def getItem(itemId: Int): Future[HttpResponse] = for {
response <- Http().singleRequest(HttpRequest(method=HttpMethods.GET,uri = s"http://localhost:8011/item/$itemId"))
} yield response def extractEntity[T](futResp: Future[HttpResponse])(implicit um: Unmarshaller[ResponseEntity,T]) = {
futResp.andThen {
case Success(HttpResponse(StatusCodes.OK, _, entity, _)) =>
Unmarshal(entity).to[T]
.onComplete {
case Success(t) => println(s"Got response entity: ${t}")
case Failure(e) => println(s"Unmarshalling failed: ${e.getMessage}")
}
case Success(_) => println("Exception in response!")
case Failure(err) => println(s"Response Failed: ${err.getMessage}")
}
}
extractEntity[Item](getItem())

现在这个extractEntity[Item](getItem(13))可以实现全过程的监控管理了。用同样的模式实现PUT操作:

  def putItem(item: Item): Future[HttpResponse] =
for {
reqEntity <- Marshal(item).to[RequestEntity]
response <- Http().singleRequest(HttpRequest(method=HttpMethods.PUT,uri="http://localhost:8011/item",entity=reqEntity))
} yield response extractEntity[Item](putItem(Item(,"Item#23", 46.0)))
.andThen { case _ => sys.terminate()}

当然,我们还是使用了前面几篇讨论里的Marshalling方式来进行数据格式的自动转换:

import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson
...
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
...
import JsConverters._ implicit val jsonStreamingSupport = EntityStreamingSupport.json()
.withParallelMarshalling(parallelism = , unordered = false)

如果我们需要对数据交换过程进行更细致的管控,用Host-Level-Api会更加适合。下面我们就针对Host-Level-Api构建一个客户端的工具库:

class PooledClient(host: String, port: Int, poolSettings: ConnectionPoolSettings)
(implicit sys: ActorSystem, mat: ActorMaterializer) { import sys.dispatcher private val cnnPool: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), Http.HostConnectionPool] =
Http().cachedHostConnectionPool[Int](host = host, port = port, settings = poolSettings)
//单一request
def requestSingleResponse(req: HttpRequest): Future[HttpResponse] = {
Source.single(req -> )
.via(cnnPool)
.runWith(Sink.head).flatMap {
case (Success(resp), _) => Future.successful(resp)
case (Failure(fail), _) => Future.failed(fail)
}
}
//组串request
def orderedResponses(reqs: Iterable[HttpRequest]): Future[Iterable[HttpResponse]] = {
Source(reqs.zipWithIndex.toMap)
.via(cnnPool)
.runFold(SortedMap[Int, Future[HttpResponse]]()) {
case (m, (Success(r), idx)) => m + (idx -> Future.successful(r))
case (m, (Failure(f), idx)) => m + (idx -> Future.failed(f))
}.flatMap { m => Future.sequence(m.values) }
}
}

下面是一种比较安全的模式:使用了queue来暂存request从而解决因发送方与接收方速率不同所产生的问题:

class QueuedRequestsClient(host: String, port: Int, poolSettings: ConnectionPoolSettings)
(qsize: Int = , overflowStrategy: OverflowStrategy = OverflowStrategy.dropNew)
(implicit sys: ActorSystem, mat: ActorMaterializer) {
import sys.dispatcher
private val cnnPool: Flow[(HttpRequest,Promise[HttpResponse]),(Try[HttpResponse],Promise[HttpResponse]),Http.HostConnectionPool] =
Http().cachedHostConnectionPool[Promise[HttpResponse]](host=host,port=port,settings=poolSettings) val queue =
Source.queue[(HttpRequest, Promise[HttpResponse])](qsize, overflowStrategy)
.via(cnnPool)
.to(Sink.foreach({
case ((Success(resp), p)) => p.success(resp)
case ((Failure(e), p)) => p.failure(e)
})).run() def queueRequest(request: HttpRequest): Future[HttpResponse] = {
val responsePromise = Promise[HttpResponse]()
queue.offer(request -> responsePromise).flatMap {
case QueueOfferResult.Enqueued => responsePromise.future
case QueueOfferResult.Dropped => Future.failed(new RuntimeException("Queue overflowed. Try again later."))
case QueueOfferResult.Failure(ex) => Future.failed(ex)
case QueueOfferResult.QueueClosed => Future.failed(new RuntimeException("Queue was closed (pool shut down) while running the request. Try again later."))
}
}
}

下面是这些工具函数的具体使用示范:

  val settings = ConnectionPoolSettings(sys)
.withMaxConnections()
.withMaxOpenRequests()
.withMaxRetries()
.withPipeliningLimit()
val pooledClient = new PooledClient("localhost",,settings) def getItemByPool(itemId: Int): Future[HttpResponse] = for {
response <- pooledClient.requestSingleResponse(HttpRequest(method=HttpMethods.GET,uri = s"http://localhost:8011/item/$itemId"))
} yield response extractEntity[Item](getItemByPool()) def getItemsByPool(itemIds: List[Int]): Future[Iterable[HttpResponse]] = {
val reqs = itemIds.map { id =>
HttpRequest(method = HttpMethods.GET, uri = s"http://localhost:8011/item/$id")
}
val rets = (for {
responses <- pooledClient.orderedResponses(reqs)
} yield responses)
rets
}
val futResps = getItemsByPool(List(,,)) futResps.andThen {
case Success(listOfResps) => {
listOfResps.foreach { r =>
r match {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[Item]
.onComplete {
case Success(t) => println(s"Got response entity: ${t}")
case Failure(e) => println(s"Unmarshalling failed: ${e.getMessage}")
}
case _ => println("Exception in response!")
}
}
}
case _ => println("Failed to get list of responses!")
} val queuedClient = new QueuedRequestsClient("localhost",,settings)() def putItemByQueue(item: Item): Future[HttpResponse] =
for {
reqEntity <- Marshal(item).to[RequestEntity]
response <- queuedClient.queueRequest(HttpRequest(method=HttpMethods.PUT,uri="http://localhost:8011/item",entity=reqEntity))
} yield response extractEntity[Item](putItemByQueue(Item(,"Item#23", 46.0)))
.andThen { case _ => sys.terminate()}

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

服务端代码:

import akka.actor._
import akka.stream._
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._ import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson
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 TestServer extends App with JsonCodec {
implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher import JsConverters._ case class Item(id: Int, name: String, price: Double)
val messages = path("message") {
get {
complete("hello, how are you?")
} ~
put {
entity(as[String]) {msg =>
complete(msg)
}
}
}
val items =
(path("item" / IntNumber) & get) { id =>
get {
complete(Item(id, s"item#$id", id * 2.0))
}
} ~
(path("item") & put) {
entity(as[Item]) {item =>
complete(item)
}
} val route = messages ~ items val (host, port) = ("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.http.scaladsl.settings.ConnectionPoolSettings
import akka.stream._
import akka.stream.scaladsl._
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._ import scala.util._
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson import scala.concurrent._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.unmarshalling._
import akka.http.scaladsl.marshalling.Marshal import scala.collection.SortedMap
import akka.http.scaladsl.common._ 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 class PooledClient(host: String, port: Int, poolSettings: ConnectionPoolSettings)
(implicit sys: ActorSystem, mat: ActorMaterializer) { import sys.dispatcher private val cnnPool: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), Http.HostConnectionPool] =
Http().cachedHostConnectionPool[Int](host = host, port = port, settings = poolSettings) def requestSingleResponse(req: HttpRequest): Future[HttpResponse] = {
Source.single(req -> )
.via(cnnPool)
.runWith(Sink.head).flatMap {
case (Success(resp), _) => Future.successful(resp)
case (Failure(fail), _) => Future.failed(fail)
}
} def orderedResponses(reqs: Iterable[HttpRequest]): Future[Iterable[HttpResponse]] = {
Source(reqs.zipWithIndex.toMap)
.via(cnnPool)
.runFold(SortedMap[Int, Future[HttpResponse]]()) {
case (m, (Success(r), idx)) => m + (idx -> Future.successful(r))
case (m, (Failure(f), idx)) => m + (idx -> Future.failed(f))
}.flatMap { m => Future.sequence(m.values) }
}
}
class QueuedRequestsClient(host: String, port: Int, poolSettings: ConnectionPoolSettings)
(qsize: Int = , overflowStrategy: OverflowStrategy = OverflowStrategy.dropNew)
(implicit sys: ActorSystem, mat: ActorMaterializer) {
import sys.dispatcher
private val cnnPool: Flow[(HttpRequest,Promise[HttpResponse]),(Try[HttpResponse],Promise[HttpResponse]),Http.HostConnectionPool] =
Http().cachedHostConnectionPool[Promise[HttpResponse]](host=host,port=port,settings=poolSettings) val queue =
Source.queue[(HttpRequest, Promise[HttpResponse])](qsize, overflowStrategy)
.via(cnnPool)
.to(Sink.foreach({
case ((Success(resp), p)) => p.success(resp)
case ((Failure(e), p)) => p.failure(e)
})).run() def queueRequest(request: HttpRequest): Future[HttpResponse] = {
val responsePromise = Promise[HttpResponse]()
queue.offer(request -> responsePromise).flatMap {
case QueueOfferResult.Enqueued => responsePromise.future
case QueueOfferResult.Dropped => Future.failed(new RuntimeException("Queue overflowed. Try again later."))
case QueueOfferResult.Failure(ex) => Future.failed(ex)
case QueueOfferResult.QueueClosed => Future.failed(new RuntimeException("Queue was closed (pool shut down) while running the request. Try again later."))
}
}
}
object ClientRequesting extends App {
import JsConverters._ implicit val sys = ActorSystem("sysClient")
implicit val mat = ActorMaterializer()
implicit val ec = sys.dispatcher implicit val jsonStreamingSupport = EntityStreamingSupport.json()
.withParallelMarshalling(parallelism = , unordered = false) case class Item(id: Int, name: String, price: Double) def extractEntity[T](futResp: Future[HttpResponse])(implicit um: Unmarshaller[ResponseEntity,T]) = {
futResp.andThen {
case Success(HttpResponse(StatusCodes.OK, _, entity, _)) =>
Unmarshal(entity).to[T]
.onComplete {
case Success(t) => println(s"Got response entity: ${t}")
case Failure(e) => println(s"Unmarshalling failed: ${e.getMessage}")
}
case Success(_) => println("Exception in response!")
case Failure(err) => println(s"Response Failed: ${err.getMessage}")
}
} (for {
response <- Http().singleRequest(HttpRequest(method=HttpMethods.GET,uri="http://localhost:8011/message"))
message <- Unmarshal(response.entity).to[String]
} yield message).andThen {
case Success(msg) => println(s"Received message: $msg")
case Failure(err) => println(s"Error: ${err.getMessage}")
} //.andThen {case _ => sys.terminate()} (for {
entity <- Marshal("Wata hell you doing?").to[RequestEntity]
response <- Http().singleRequest(HttpRequest(method=HttpMethods.PUT,uri="http://localhost:8011/message",entity=entity))
message <- Unmarshal(response.entity).to[String]
} yield message).andThen {
case Success(msg) => println(s"Received message: $msg")
case Failure(err) => println(s"Error: ${err.getMessage}")
} //.andThen {case _ => sys.terminate()} def getItem(itemId: Int): Future[HttpResponse] = for {
response <- Http().singleRequest(HttpRequest(method=HttpMethods.GET,uri = s"http://localhost:8011/item/$itemId"))
} yield response extractEntity[Item](getItem()) def putItem(item: Item): Future[HttpResponse] =
for {
reqEntity <- Marshal(item).to[RequestEntity]
response <- Http().singleRequest(HttpRequest(method=HttpMethods.PUT,uri="http://localhost:8011/item",entity=reqEntity))
} yield response extractEntity[Item](putItem(Item(,"Item#23", 46.0)))
.andThen { case _ => sys.terminate()} val settings = ConnectionPoolSettings(sys)
.withMaxConnections()
.withMaxOpenRequests()
.withMaxRetries()
.withPipeliningLimit()
val pooledClient = new PooledClient("localhost",,settings) def getItemByPool(itemId: Int): Future[HttpResponse] = for {
response <- pooledClient.requestSingleResponse(HttpRequest(method=HttpMethods.GET,uri = s"http://localhost:8011/item/$itemId"))
} yield response extractEntity[Item](getItemByPool()) def getItemsByPool(itemIds: List[Int]): Future[Iterable[HttpResponse]] = {
val reqs = itemIds.map { id =>
HttpRequest(method = HttpMethods.GET, uri = s"http://localhost:8011/item/$id")
}
val rets = (for {
responses <- pooledClient.orderedResponses(reqs)
} yield responses)
rets
}
val futResps = getItemsByPool(List(,,)) futResps.andThen {
case Success(listOfResps) => {
listOfResps.foreach { r =>
r match {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
Unmarshal(entity).to[Item]
.onComplete {
case Success(t) => println(s"Got response entity: ${t}")
case Failure(e) => println(s"Unmarshalling failed: ${e.getMessage}")
}
case _ => println("Exception in response!")
}
}
}
case _ => println("Failed to get list of responses!")
} val queuedClient = new QueuedRequestsClient("localhost",,settings)() def putItemByQueue(item: Item): Future[HttpResponse] =
for {
reqEntity <- Marshal(item).to[RequestEntity]
response <- queuedClient.queueRequest(HttpRequest(method=HttpMethods.PUT,uri="http://localhost:8011/item",entity=reqEntity))
} yield response extractEntity[Item](putItemByQueue(Item(,"Item#23", 46.0)))
.andThen { case _ => sys.terminate()} }

Akka(37): Http:客户端操作模式的更多相关文章

  1. HDFS的Java客户端操作代码(HDFS的查看、创建)

    1.HDFS的put上传文件操作的java代码: package Hdfs; import java.io.FileInputStream; import java.io.FileNotFoundEx ...

  2. arm9的操作模式,寄存器,寻址方式

    工作模式 Arm有7种工作模式: 名称 简称 简介 User Usr 正常用户程序执行的模式(linux下用户程序就是在这一模式执行的.) FIQ Fiq 快速中断模式 IRQ Irq 普通中断模式 ...

  3. IdentityServer4 (1) 客户端授权模式(Client Credentials)

    写在前面 1.源码(.Net Core 2.2) git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.git 2.相关章节 2.1. ...

  4. PYTHON工业互联网应用实战12—客户端操作

    本章节我们将实现与admin里类似的列操作"下达"功能,演示客户端是如何实现操作功能,同时,演示也会强调一点,何时合并你的功能代码,避免相同功能使用不同的代码段来实现,在企业开发中 ...

  5. ListView多选操作模式——上下文操作模式

    1.什么叫上下文操作模式 2.如何进入上下文操作模式 1.ListView自身带了单选.多选模式,可通过listview.setChoiceMode来设置: listview.setChoiceMod ...

  6. java web 获取客户端操作系统信息

    package com.java.basic.pattern; import java.util.regex.Matcher; import java.util.regex.Pattern; /** ...

  7. Hadoop系列007-HDFS客户端操作

    title: Hadoop系列007-HDFS客户端操作 date: 2018-12-6 15:52:55 updated: 2018-12-6 15:52:55 categories: Hadoop ...

  8. pytthon—day8 读写模式的结合、文件操作模式、with完成文本文件复制、游标操作

    一.读写模式的结合 w:写指没有新建文件,有文件就清空 w=open('1.txt','w',encoding='utf-8') w.write('000\n') 在写入数据时,需要及时处理内存空间, ...

  9. 使用Java客户端操作elasticsearch(二)

    承接上文,使用Java客户端操作elasticsearch,本文主要介绍 常见的配置 和Sniffer(集群探测) 的使用. 常见的配置 前面已介绍过,RestClientBuilder支持同时提供一 ...

随机推荐

  1. WPF DataGrid自定义样式

    微软的WPF DataGrid中有很多的属性和样式,你可以调整,以寻找合适的(如果你是一名设计师).下面,找到我的小抄造型的网格.它不是100%全面,但它可以让你走得很远,有一些非常有用的技巧和陷阱. ...

  2. Cygwin-添加到右键菜单脚本--一键安装、卸载

    平时习惯用一些linux命令来完成工作,在Windows上有cygwin和gitbash两个选择.这两个我都装了. 相对来说cygwin支持的功能更多一些,但是它没有默认绑定到右键菜单.为此,我想到用 ...

  3. 【转载】十条jQuery代码片段助力Web开发效率提升

    文章转载自 51CTO http://www.51cto.com/ 原文链接:http://developer.51cto.com/art/201604/509093.htm原文摘要:JQuery是继 ...

  4. JAVA基础-反射

    一.反射的介绍 JAVA反射机制是在运行状态中,能够获取任意一个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法.这种动态获取的以及动态调用对象的方法的功能称为java语言的反射机制. ...

  5. 从零开始搭建框架SSM+Redis+Mysql(二)之MAVEN项目搭建

    从零开始搭建框架SSM+Redis+Mysql(二)之MAVEN项目搭建 废话不说,直接撸步骤!!! 1.创建主项目:ncc-parent 选择maven创建项目,注意在创建项目中,packing选择 ...

  6. win10 uwp 圆角按钮

    本文讲的是如何做圆角按钮,我们在UWP本来的按钮都是矩形,圆角Radius没有,所以本文就用简单方法去做圆角按钮. 我们按钮需要圆角,而自带没有,其实做一个很简单,把原来的按钮变为背景透明,然后使用矩 ...

  7. JavaWeb之Maven配置

    Maven和C#的nuget类似,可以通过设置就能引入框架等第三方,方便又省事.Java中使用Maven来管理第三方.今天尝试着配置了一下. 一.JDK的安装 关于JDK的安装可以查看百度经验,设置P ...

  8. banner幻灯片

    emmm本来想用js写的,但是感觉难就换了jquery. 1.把图片的位置.透明度.宽高.z-index信息用数组datas保存起来,移动的时候直接把位置等信息赋值给li. 2.点击next,即显示下 ...

  9. RabbitMQ 笔记-基本概念

    ConnectionFactory.Connection.Channel ConnectionFactory.Connection.Channel,这三个都是RabbitMQ对外提供的API中最基本的 ...

  10. 【ASP.NET MVC 学习笔记】- 07 使用 Entity Framework

    本文参考:http://www.cnblogs.com/willick/p/3304534.html 1.ORM(Object Relation Mapping)工具,是为了解决“关系数据库”和“面向 ...