Vert.x Java开发指南——第十一章 使用基于事件总线的跨越边界消息的实时Web特征

第十一章 使用基于事件总线的跨越边界消息的实时Web特征

版权声明:本文为博主自主翻译,转载请标明出处。 https://blog.csdn.net/elinespace/article/details/807171351
相应代码位于本指南仓库的step-10目录下

在本指南的前面部分,我们看到事件总线用于Verticle中的通讯,使用Vert.x应用内部的消息传递。开发者只需要注册一个消费者接收消息并且发送/发布消息。

SockJS事件总线桥接将这些能力扩展到客户端浏览器中。它创建了一个分布式的事件总线,不仅跨越集群中的多个Vert.x实例,还包含在浏览器中运行的客户端脚本。因此我们能创建一个巨大的分布式事件总线,包括许多浏览器和服务器,实现一个一致的基于消息的编程模型,跨越分布式应用程序的所有组件。

在本章中,我们将修改来自step-9的代码以便:

  1. 要渲染的Markdown内容被发送到服务器而不需要创建新的HTTP请求。
  2. 页面显示一个警告,当用户正在编辑一个页面,而其它用户恰好正在修改相同页面时。

11.1 准备SockJS事件总线桥接

11.1.1 服务端

SockJS是一个客户端JavaScript库和协议,提供了一个简单的类似WebSocket的接口用于链接到SockJS服务器,不管实际的浏览器或网络是否允许使用真实的WebSocket。它通过支持各种不同的浏览器与服务器之间传输方式来实现这一点,并根据它们的(指浏览器与服务器)能力在运行时选择一个。

第一步,我们需要设置SockJSHandler,它由vertx-web项目提供:

SockJSHandler sockJSHandler = SockJSHandler.create(vertx); ①
BridgeOptions bridgeOptions = new BridgeOptions()
    .addInboundPermitted(new PermittedOptions().setAddress("app.markdown")) ②
    .addOutboundPermitted(new PermittedOptions().setAddress("page.saved")); ③ sockJSHandler.bridge(bridgeOptions); ④ 
router.route("/eventbus/*").handler(sockJSHandler); ⑤

① 为vertx实例创建一个新的SockJSHandler。

② 允许在app.markdown地址上传递来自浏览器的消息。在编辑Wiki页面时,我们将使用这个地址来让服务器处理Markdown内容。

③ 允许在page.saved地址发送消息到浏览器。我们将使用这个地址通知浏览器一个wiki页面已经保存。

④ 配置处理器桥接SockJS通讯到事件总线

⑤ 使用SockJS处理器处理/eventbus路径下的所有请求。

对于大多数应用,你可能不需要客户端JavaScript可以发送任意消息到服务端的任意处理器或者到所有其它浏览器。例如:

  • 你可能在事件总线上有一个服务,允许数据被访问或者被删除,显然我们不希望恶劣的行为或者恶意的客户端能够删除数据库中的所有数据。

  • 我们不需要任意客户端可以监听任意事件总线地址。

为了应对这个问题,SockJS桥接默认拒绝所有消息通过。这就是为什么你要告诉桥接什么消息可以通过它(作为例外回复消息总是允许通过)。

11.1.2 客户端

现在服务器端已准备好接受消息,我们接下来配置客户端。

首先,SockJS库和Vert.x事件总线JavaScript客户端必须加载。作为开始,最简单的方式是从一个公共的CDN网络获得这些文件:

<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、bover,甚至webjars仓库。

然后,我们创建一个新的EventBus JavaScript对象实例:

var eb = new EventBus(window.location.protocol + "//" + window.location.host + "/eventbus");

11.2 通过事件总线发送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) {if (err === null) {
        $scope.$apply(function () { ②
            $scope.updateRendering(reply.body); ③
        }); 
    }else{
      console.warn("Error rendering Markdown content: " + JSON.stringify(err));
   }
});

① 响应处理器是一个拥有两个参数的函数:一个错误(如果有)和一个reply对象。reply对象的内容嵌在body属性中。

② 由于事件总线客户端不由AngularJS管理,$scope.$apply封装执行正确scope生命周期的回调。

③ 类似我们使用$http时做的,我们使用HTML结果调用updateRendering。

诚然,该代码非常类似于它的HTTP端点等效项(译者注:指采用HTTP的处理方式)。然而,这里的好处并不在于代码行的数量。

实际上,如果你通过事件总线与服务器通讯,桥接透明的在注册消费者之间分发传入的消息。因此,当Vert.x在集群模式下运行时,浏览器不会被绑定到单个服务器进行处理(除了SockJS链接)。此外,与服务器的链接永不关闭,因此对于HTTP/1.1,这节省了为每个请求建立TCP链接,如果在服务器和客户端之间有大量的交换,这是有用的。

11.3 当页面修改时警告用户

在许多应用中,最后提交胜出是解决冲突的方式:当两个用户同时编辑相同的资源,最后一个按下保存按钮的覆盖任何先前的变更。

在这个问题上有很多方法,如版本控制或很多关于分布式共识主题的文献。尽管如此,让我们坚持遵循一个简单的解决方案,看看当变更发生时我们可以如何通知用户,以便至少他能有机会处理这种情况。一旦内容在数据库中被修改,用户可以决定应该采取的最佳行动是什么:覆盖或者重新加载。

首先,我们需要在页面中添加一个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(); ① 
eb.onopen = function () {
    eb.registerHandler("page.saved", function (error, message) {if (message.body ③
                && $scope.pageId === message.body.id ④
                && clientUuid !== message.body.client) { ⑤ 
            $scope.$apply(function () { ⑥
                $scope.pageModified = true; ⑦ 
            });
        } 
    });
};

① 如果我们自己修改内容,我们不需要打印警告,因此我们需要一个客户端标识。

② 当从page.saved地址收到消息时,调用回调方法。

③ 检查消息体不是空。

④ 确保事件是与当前wiki页面相关的。

⑤ 检查我们不是变更的来源

⑥ 由于事件总线客户端不是由AngularJS管理,$scope.$apply封装了执行正确scope生命周期的回调。

⑦ 设置pageModified为true。

最后,当页面内容保存到数据库中时,我们必须推送消息:

dbService.rxSavePage(id, page.getString("markdown"))
    .doOnSuccess(v -> { ①
        JsonObject event = new JsonObject().put("id", id) ②
            .put("client", page.getString("client")); ③
        vertx.eventBus().publish("page.saved", event); ④ 
    })
.subscribe(v -> apiResponse(context, 200, null, null), t -> apiFailure(context, t));

① rxSavePage返回了一个Single对象。成功时(如没有数据库故障)我们发布一个事件。

② 消息包含页面标识

③ 消息包含客户端标识

④ 事件发布到page.saved地址

如果我们在同一个浏览器的两个标签(或不同浏览器)打开应用,都选择相同的页面,在其中一个进行内容更新,警告信息打印如下:

这里写图片描述

我们可以很容易的利用SockJS桥接来实现其它目的,如显示当前多少用户在给定的页面上,支持聊天框中的实时评论等。关键点是服务器和客户端都是通过事件总线上的消息传递共享相同的编程模型。

猜你喜欢

转载自blog.csdn.net/elinespace/article/details/80717135