因为我了解Akka-http的主要目的不是为了有关Web-Server的编程,而是想实现一套系统集成的api,所以也需要考虑由服务端主动向客户端发送指令的应用场景。比如一个零售店管理平台的服务端在完成了某些数据更新后需要通知各零售门市客户端下载最新数据。虽然Akka-http也提供对websocket协议的支持,但websocket的网络连接是双向恒久的,适合频繁的问答交互式服务端与客户端的交流,消息结构也比较零碎。而我们面临的可能是批次型的大量数据库数据交换,只需要简单的服务端单向消息就行了,所以websocket不太合适,而Akka-http的SSE应该比较适合我们的要求。SSE模式的基本原理是服务端统一集中发布消息,各客户端持久订阅服务端发布的消息并从消息的内容中筛选出属于自己应该执行的指令,然后进行相应的处理。客户端接收SSE是在一个独立的线程里不断进行的,不会影响客户端当前的运算流程。当收到有用的消息后就会调用一个业务功能函数作为后台异步运算任务。

服务端的SSE发布是以Source[ServerSentEvent,NotUsed]来实现的。ServerSentEvent类型定义如下:

/**
* Representation of a server-sent event. According to the specification, an empty data field designates an event
* which is to be ignored which is useful for heartbeats.
*
* @param data data, may span multiple lines
* @param eventType optional type, must not contain \n or \r
* @param id optional id, must not contain \n or \r
* @param retry optional reconnection delay in milliseconds
*/
final case class ServerSentEvent(
data: String,
eventType: Option[String] = None,
id: Option[String] = None,
retry: Option[Int] = None) {...}

这个类型的参数代表事件消息的数据结构。用户可以根据实际需要充分利用这个数据结构来传递消息。服务端是通过complete以SeverSentEvent类为元素的Source来进行SSE的,如下:

    import akka.http.scaladsl.marshalling.sse.EventStreamMarshalling._
complete {
Source
.tick(.seconds, .seconds, NotUsed)
.map( _ => processToServerSentEvent)
.keepAlive(.second, () => ServerSentEvent.heartbeat)
}

以上代码代表服务端定时运算processToServerSentEvent返回ServerSentEvent类型结果后发布给所有订阅的客户端。我们用一个函数processToServerSentEvent模拟重复运算的业务功能:

  private def processToServerSentEvent: ServerSentEvent = {
Thread.sleep() //processing delay
ServerSentEvent(SyncFiles.fileToSync)
}

这个函数模拟发布事件数据是某种业务运算结果,在这里代表客户端需要下载文件名称。我们用客户端request来模拟设定这个文件名称:

  object SyncFiles {
var fileToSync: String = ""
}
private def route = {
import Directives._
import akka.http.scaladsl.marshalling.sse.EventStreamMarshalling._ def syncRequests =
pathPrefix("sync") {
pathSingleSlash {
post {
parameter("file") { filename =>
complete {
SyncFiles.fileToSync = filename
s"set download file to : $filename"
}
}
}
}
}

客户端订阅SSE的方式如下:

    import akka.http.scaladsl.unmarshalling.sse.EventStreamUnmarshalling._
import system.dispatcher Http()
.singleRequest(Get("http://localhost:8011/events"))
.flatMap(Unmarshal(_).to[Source[ServerSentEvent, NotUsed]])
.foreach(_.runForeach(se => downloadFiles(se.data)))

每当客户端收到SSE后即运行downloadFiles(filename)函数。downloadFiles函数定义:

  def downloadFiles(file: String) = {
Thread.sleep() //process delay
if (file != "")
println(s"Try to download $file")
}

下面是客户端程序的测试运算步骤:

    scala.io.StdIn.readLine()
println("do some thing ...")
Http().singleRequest(
HttpRequest(method=HttpMethods.POST,uri = "http://localhost:8011/sync/?file=Orders")
).onSuccess {
case msg => println(msg)
} scala.io.StdIn.readLine()
println("do some other things ...")
Http().singleRequest(
HttpRequest(method=HttpMethods.POST,uri = "http://localhost:8011/sync/?file=Items")
).onSuccess {
case msg => println(msg)
}

运算结果:

do some thing ...
HttpResponse( OK,List(Server: akka-http/10.0., Date: Fri, Dec :: GMT),HttpEntity.Strict(text/plain; charset=UTF-,set download file to : Orders),HttpProtocol(HTTP/1.1))
Try to download Orders
Try to download Orders do some other things ...
HttpResponse( OK,List(Server: akka-http/10.0., Date: Fri, Dec :: GMT),HttpEntity.Strict(text/plain; charset=UTF-,set download file to : Items),HttpProtocol(HTTP/1.1))
Try to download Orders
Try to download Orders
Try to download Items
Try to download Items Try to download Items Process finished with exit code

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

服务端:

import akka.NotUsed
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Source
import scala.concurrent.duration.DurationInt
import akka.http.scaladsl.model.sse.ServerSentEvent object SSEServer { def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
Http().bindAndHandle(route, "localhost", ) scala.io.StdIn.readLine()
system.terminate()
} object SyncFiles {
var fileToSync: String = ""
}
private def route = {
import Directives._
import akka.http.scaladsl.marshalling.sse.EventStreamMarshalling._ def syncRequests =
pathPrefix("sync") {
pathSingleSlash {
post {
parameter("file") { filename =>
complete {
SyncFiles.fileToSync = filename
s"set download file to : $filename"
}
}
}
}
} def events =
path("events") {
get {
complete {
Source
.tick(.seconds, .seconds, NotUsed)
.map( _ => processToServerSentEvent)
.keepAlive(.second, () => ServerSentEvent.heartbeat)
}
}
} syncRequests ~ events
} private def processToServerSentEvent: ServerSentEvent = {
Thread.sleep() //processing delay
ServerSentEvent(SyncFiles.fileToSync)
}
}

客户端:

import akka.NotUsed
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.client.RequestBuilding.Get
import akka.http.scaladsl.model.HttpMethods
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Source
import akka.http.scaladsl.model.sse.ServerSentEvent
import akka.http.scaladsl.model._ object SSEClient { def downloadFiles(file: String) = {
Thread.sleep() //process delay
if (file != "")
println(s"Try to download $file")
} def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer() import akka.http.scaladsl.unmarshalling.sse.EventStreamUnmarshalling._
import system.dispatcher Http()
.singleRequest(Get("http://localhost:8011/events"))
.flatMap(Unmarshal(_).to[Source[ServerSentEvent, NotUsed]])
.foreach(_.runForeach(se => downloadFiles(se.data))) scala.io.StdIn.readLine()
println("do some thing ...")
Http().singleRequest(
HttpRequest(method=HttpMethods.POST,uri = "http://localhost:8011/sync/?file=Orders")
).onSuccess {
case msg => println(msg)
} scala.io.StdIn.readLine()
println("do some other things ...")
Http().singleRequest(
HttpRequest(method=HttpMethods.POST,uri = "http://localhost:8011/sync/?file=Items")
).onSuccess {
case msg => println(msg)
} scala.io.StdIn.readLine()
system.terminate()
}
}

我的博客即将同步至腾讯云+社区。邀大家一同入驻http://cloud.tencent.com/developer/support-plan

Akka(43): Http:SSE-Server Sent Event - 服务端主推消息的更多相关文章

  1. 1.使用SignalR实现页面即时刷新(服务端主动推送)

    模块功能说明: 实现技术:sqlserver,MVC,WebAPI,ADO.NET,SignalR(服务器主动推送) 特殊车辆管理--->移动客户端采集数据存入数据库---->只要数据库数 ...

  2. 使用SignalR实现页面即时刷新(服务端主动推送)

    模块功能说明: 实现技术:sqlserver,MVC,WebAPI,ADO.NET,SignalR(服务器主动推送) 特殊车辆管理--->移动客户端采集数据存入数据库---->只要数据库数 ...

  3. java Socket通信,客户端与服务端相互发消息

    1.通信过程 网络分为应用层,http.ssh.telnet就是属于这一类,建立在传输层的基础上.其实就是定义了各自的编码解码格式,分层如下: 2.Socket连接 上述通信都要先在传输层有建立连接的 ...

  4. netty-2.客户端与服务端互发消息

    (原) 第二篇,客户端与服务端互发消息 与第一篇的例子类似,这里服务端需要三个类,客户端也需要三个类. 服务端关键代码如下:MyServer与上一个例子中的TestServer 差多,这里只列举不同的 ...

  5. 使用SignalR从服务端主动推送警报日志到各种终端(桌面、移动、网页)

    微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. 使用SignalR从服务端主动推送警报日志到各种终端(桌面.移动.网页) 阅读导航 本文背景 ...

  6. SSE技术详解:使用 HTTP 做服务端数据推送应用的技术

    SSE ( Server-sent Events )是 WebSocket 的一种轻量代替方案,使用 HTTP 协议. 严格地说,HTTP 协议是没有办法做服务器推送的,但是当服务器向客户端声明接下来 ...

  7. 在 Windows Server 上搭建 *** 服务端(转载加亲测)

    转载自:https://diveng.io/build-shadowsocks-server-on-windows-server.html 下面的教程建议大家使用第一种方法安装,说是比较简单.我则使用 ...

  8. GraphQL-- 使用Apollo Server搭建Node服务端

    一.关于Apollo Server Apollo Server是一种使用JS创建GraphQL服务端的一个方案.它的兼容性比较好,可以很好地和GraphQL客户端进行兼容.同时它可以 独立作为服务端进 ...

  9. SignalR 实现web浏览器客户端与服务端的推送功能

    SignalR 是一个集成的客户端与服务器库,基于浏览器的客户端和基于 ASP.NET 的服务器组件可以借助它来进行双向多步对话. 换句话说,该对话可不受限制地进行单个无状态请求/响应数据交换:它将继 ...

随机推荐

  1. git使用教程之git分支

    1 分支简介 让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流. 你将经历如下步骤: 开发某个网站. 为实现某个新的需求,创建一个分支. 在这个分支上开展工作. 正在此 ...

  2. mac nexus搭建本地maven服务器

    1.在保证安装jdk 1.7+,去nexus官网下载nexus(http://www.sonatype.com/download-oss-sonatype) 最新版本 2.解压zip文件,配置nexu ...

  3. BZOJ-2330-[SCOI2011]糖果(差分约束)

    Description 幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果.但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的 ...

  4. Python 解LeetCode:367. Valid Perfect Square

    题目描述:给出一个正整数,不使用内置函数,如sqrt(),判断这个数是不是一个数的平方. 思路:直接使用二分法,貌似没啥好说的.代码如下: class Solution(object): def is ...

  5. sql 1.1 1.1.1 1.10.1 排序

    解决思路:计算每位的权重,得到序号完整的权重值,使用权重值进行排序! 创建sql 函数如下: ALTER FUNCTION [dbo].[SequenceToOrderNum] ( @Sequence ...

  6. robotframework自动化系列:修改流程

    在上一小节中,新增流程操作已经完成.那么接下来就是修改的流程操作了,对于一个页面的修改如何操作呢? 针对修改修改操作是在一个已经存在的账号直接修改还是新添加一个账号再修改呢?从用例设计健壮性出发,我们 ...

  7. 设计模式的征途—11.外观(Facade)模式

    在软件开发中,有时候为了完成一项较为复杂的功能,一个类需要和多个其他业务类交互,而这些需要交互的业务类经常会作为一个完整的整体出现,由于涉及的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服 ...

  8. Cordic算法——verilog实现

    上两篇博文Cordic算法--圆周系统之旋转模式.Cordic算法--圆周系统之向量模式做了理论分析和实现,但是所用到的变量依然是浮点型,而cordic真正的用处是基于FPGA等只能处理定点的平台.只 ...

  9. ssm+maven 框架整合

    开篇简言:maven的存在让ssm框架简单了一个量级,BaseDao的存在又让项目简化了一个量级,希望看到这篇文章的都能深入的了解上面两项,好了,进入主题,构造一个ssm框架 1.准备工具  mave ...

  10. SSH框架基础

    首先,SSH不是一个框架,而是多个框架(struts+spring+hibernate)的集成,是目前较流行的一种Web应用程序开源集成框架,用于构建灵活.易于扩展的多层Web应用程序. 集成SSH框 ...