在本指南的较早部分,我们看到事件总线正在使用Vert.x应用程序中的消息传递进行通信。开发人员只需注册消费者即可接收消息并发送/发布消息。
SockJS事件总线桥将这些功能在Web浏览器中扩展到客户端。它创建了一个分布式事件总线,它不仅跨越集群中的多个Vert.x实例,还包括在(许多)浏览器中运行的客户端JavaScript。因此,我们可以创建包含许多浏览器和服务器的大型分布式事件总线,从而在分布式应用程序的所有组成部分中建立一致的基于消息的编程模型。
在本章中,我们将修改代码,step-9
以便:
要呈现的Markdown内容将被发送到服务器,而不会创建新的HTTP请求
该页面在用户正在编辑刚被其他用户修改的页面时显示警告。
设置SockJS事件总线桥
在服务器上
注意
|
SockJS是一个客户端JavaScript库和协议,它提供了一个简单的类似WebSocket的接口,用于连接到SockJS服务器,而不管实际的浏览器或网络是否允许真正的WebSocket。它通过在浏览器和服务器之间支持各种不同的传输,并根据其功能在运行时选择一个来实现。 |
SockJSHandler sockJSHandler = SockJSHandler.create(vertx); (1)
BridgeOptions bridgeOptions = new BridgeOptions()
.addInboundPermitted(new PermittedOptions().setAddress("app.markdown")) (2)
.addOutboundPermitted(new PermittedOptions().setAddress("page.saved")); (3)
sockJSHandler.bridge(bridgeOptions); (4)
router.route("/eventbus/*").handler(sockJSHandler); (5)
SockJSHandler
为此vertx
实例创建一个新的。允许从
app.markdown
地址上的浏览器传递消息。在编辑维基页面时,我们将使用此地址让服务器处理Markdown内容。允许将消息发送到
page.saved
地址上的浏览器。我们将使用此地址通知浏览器Wiki页面已被修改。配置处理程序以将SockJS流量桥接到事件总线。
/eventbus
使用SockJS处理程序处理路径下的所有请求。
警告
|
对于大多数应用程序,您可能不希望客户端JavaScript能够将任何消息发送到服务器端的任何处理程序或所有其他浏览器。例如:
为了解决这个问题,一个SockJS桥将默认拒绝任何消息。这就是为什么你要告诉桥梁什么样的信息可以通过(因为总是允许通过的例外回复信息)。 |
在客户端上
现在服务器已准备好接受消息,我们将配置客户端。
首先,必须加载SockJS库和Vert.x事件总线JavaScript客户端。最简单的入门方法是从公共内容交付网络获取文件:
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"
integrity="sha256-KWJavOowudybFMUCd547Wvd/u8vUg/2g0uSWYU5Ae+w="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vertx/3.4.1/vertx-eventbus.min.js"
integrity="sha256-EX8Kk2SwcSUXBJ4RORlETHNwHWEw+57C/YDLnbiIl3U="
crossorigin="anonymous"></script>
注意
|
事件总线客户端可以预先下载并与应用程序捆绑在一起。这是发表在Maven ,npm ,bower 甚至是webjars 仓库。 |
然后,我们创建一个新的EventBus
Javascript对象实例:
var eb = new EventBus(window.location.protocol + "//" + window.location.host + "/eventbus");
通过事件总线发送Markdown内容进行处理
SockJS桥现在正在运行。为了处理服务器端的Markdown内容,我们需要注册一个用户。消费者处理发送到该app.markdown
地址的消息:
vertx.eventBus().<String>consumer("app.markdown", msg -> {
String html = Processor.process(msg.body());
msg.reply(html);
});
这里没有什么新东西,我们之前已经创建了事件总线消费者。现在我们来看看客户端代码中发生了什么:
eb.send("app.markdown", text, function (err, reply) { (1)
if (err === null) {
$scope.$apply(function () { (2)
$scope.updateRendering(reply.body); (3)
});
} else {
console.warn("Error rendering Markdown content: " + JSON.stringify(err));
}
});
的回复处理程序是采取两个参数的函数:误差(如果有的话)和
reply
对象。的reply
对象的内容嵌套在内部body
属性。由于事件总线客户端不是由AngularJS管理,因此
$scope.$apply
将回调包装以执行适当的范围生命周期。正如我们在处理时所做的那样
$http
,我们updateRendering
使用HTML结果进行调用。
诚然,代码非常类似于它的HTTP端点。然而,这里的好处并不在于代码行数。
事实上,如果您通过事件总线与服务器进行通信,桥接器会在注册用户之间透明地分发传入消息。因此,当Vert.x以集群模式运行时,浏览器不会连接到单个服务器进行处理(除了SockJS连接)。更重要的是与服务器的连接永远不会关闭,因此使用HTTP / 1.1可以节省为每个请求建立TCP连接,如果您在服务器和客户端之间进行了大量交换,这可能很有用。
当页面被修改时警告用户
在许多应用程序中,最后一次提交胜利原则是如何解决冲突:当两个用户同时编辑同一资源时,最后一个按下保存按钮将覆盖以前的任何更改。
有关这个问题的方法有很多,比如实体版本化或关于分布式共识主题的大量文献。尽管如此,让我们坚持一个简单的解决方案,看看我们如何通知用户何时做出改变,至少他(她)能够有机会处理这种情况。只要内容在数据库中被更改,用户就可以决定采取何种最佳措施:覆盖或重新加载。
首先,我们需要在页面中添加一条alert alert-warning
消息div
。但是我们希望它仅在pageModified
范围变量设置为时才显示true
。
<div class="col-md-12">
<div class="alert alert-warning ng-class:{'invisible': !pageModified};" role="alert">
The page has been modified by another user.
<a href="#" ng-click="load(pageId)">Reload</a>
</div>
</div>
现在,pageModified
必须true
将此页面保存时设置为。让我们注册一个page.saved
地址的事件总线处理程序:
var clientUuid = generateUUID(); (1)
eb.onopen = function () {
eb.registerHandler("page.saved", function (error, message) { (2)
if (message.body (3)
&& $scope.pageId === message.body.id (4)
&& clientUuid !== message.body.client) { (5)
$scope.$apply(function () { (6)
$scope.pageModified = true; (7)
});
}
});
};
如果我们自己修改了内容,我们不想打印警告,因此我们需要客户端标识符。
当
page.saved
地址上收到消息时,将调用回调。检查身体是否不空。
确保这个事件与当前的wiki页面有关。
检查我们是不是变化的起源。
由于事件总线客户端不是由AngularJS管理,所以
$scope.$apply
将回调包装以执行适当的范围生命周期。设置
pageModified
为true。
最终,当页面内容保存在数据库中时,我们必须推送消息:
dbService.rxSavePage(id, page.getString("markdown"))
.doOnComplete(() -> { (1)
JsonObject event = new JsonObject()
.put("id", id) (2)
.put("client", page.getString("client")); (3)
vertx.eventBus().publish("page.saved", event); (4)
})
.subscribe(() -> apiResponse(context, 200, null, null), t -> apiFailure(context, t));
rxSavePage
返回一个Single<Void>
对象。成功(即没有数据库失败),我们发布一个事件。该消息包含页面标识符。
该消息包含客户端标识符。
该活动发布在
page.saved
地址上。
如果我们在同一浏览器(或不同浏览器)内的两个选项卡中打开该应用程序,请选择两个页面上的同一页面,并将其中的内容更新为一个,则将显示警告消息:
我们可以很容易地将SockJS桥用于其他目的,例如显示给定页面上有多少用户,在聊天框中支持实时评论等。关键在于服务器端和客户端都共享相同的编程模型通过传递事件总线上的消息。
结论
这是本指南的结尾。让我们花点时间重述前几节中的重要内容,然后指出更多有用的资源。
概要
我们通过使用Vert.x构建wiki Web应用程序来开始本指南。虽然第一次迭代是典型的“快速和肮脏的快速原型”,但它表明,可以快速轻松地构建这样一个应用程序,其中包含Web模板的服务器端呈现以及关系数据库的持久性。
接下来的步骤显示了如何通过连续重构来改进设计:首先将每个技术关注点分离为独立的Verticle,然后提取Vert.x服务以获得API清洁,最后引入JUnit测试以获取异步代码。
然后,我们开始使用Web客户端API来消费第三方HTTP / JSON服务,这进一步简化了Vert.x核心中HTTP客户端的使用。相反,我们还看到了如何使用优雅的Vert.x Web模块公开HTTP / JSON Web API。
从那里可以很容易地扩展构建API网关的方法,为许多服务提供外观。如果你要建立这样的网关,我们建议你利用:
Vert.x RxJava支持来描述服务消费数据流,以及
Vert.x断路器模块能够始终如一地处理潜在的服务故障。
访问控制,身份验证和安全性往往被忽视或作为后果。我们看到Vert.x提供了一个简单的可插入身份验证机制来利用数据库,文件或LDAP目录。SSL网络加密非常容易为服务器或客户端设置。最后,Vert.x支持JWT令牌,这是一种非常有用且分散的Web API验证方案。
Vert.x核心API依赖于回调,因为它是处理异步事件的最通用的方式。Vert.x提供了一个简单的promise / future API。虽然Vert.x未来是可组合的,但它们应局限于有限的用途,例如处理垂直部署和初始化。我们看到了Vert.x如何支持RxJava,我们鼓励您将它用于您自己的Verticle。更重要的是,RxJava是JVM上最受欢迎的反应式编程库,因此您可以轻松找到第三方库,以便始终集成到端到端的反应式应用程序中。
现代Web应用程序往往有服务器暴露只是 HTTP / JSON API,以及依赖于用户界面客户端的Web框架。我们看到了如何使用AngularJS来实现这一目标,以便将我们的wiki变成单页面应用程序。
最后,我们看到扩展应用程序的事件总线的优雅性,以允许Web应用程序使用SockJS桥接器从浏览器发送和接收事件。虽然它最初看起来可能只是一个小功能,但实际上它极大地简化了实时 Web应用程序功能的开发。在使用HTTP端点的情况下,SockJS桥实际上也很有用:发送消息然后在事件总线上获得响应有时可能比做HTTP调用更简单,让服务器处理HTTP请求并转发它到事件总线上的Verticle,并最终通过编码JSON响应来终止HTTP交换。
走得更远
该Vert.x网站当然是对一切事物的权威Vert.x.
我们没有在本指南中介绍过许多功能和模块,例如:
使用Hazelcast,Infinispan,Apache Ignite或Apache Zookeeper进行集群,
代码如何与其他支持的语言一起使用,
使用gRPC可能(但不一定)通过HTTP / 2进行公开和消费
使用诸如MongoDB或Redis之类的NoSQL数据库,
通过SMTP发送电子邮件,
与AMQP,Stomp,Kafka,MQTT或RabbitMQ进行消息传递,
使用来自定制和热门提供商的OAuth2身份验证,
用于编写阻塞式代码的Vert.x同步,后者在运行时转换为光纤非阻塞代码,
发布和发现注册管理机构的微服务,例如在OpenShift等云环境中进行部署时,
暴露指标和健康检查。
此列表并非详尽无遗:Vert.x是一个工具包,因此您可以决定项目所需的成分大小。
您也可能发现浏览Vert.x精心策划的社区项目列表非常有用,因为它超出了项目支持的范围。
如果您正在开发微服务,我们建议您阅读由ClémentEscoffier撰写的“Java中构建反应式微服务”一书。
这就是所有人!
我们希望您喜欢阅读本指南,结果证明它对于您使用Vert.x进行异步编程的过程非常有用。
无需直接通过电子邮件或通过Vert.x项目用户组与作者取得联系。当然,我们赞赏赞誉,但我们非常赞赏任何可以改进此内容的建设性反馈。
非常感谢你!
The------------------------End