Vert.x Web模块(六)

时间:2022-07-05 18:05:23


SockJS

SockJS是一个客户端JavaScript库和协议,SockJS提供类似WebSocket的接口,此接口允许与SockJS服务器建立连接,而不论浏览器或者网络允许真实的WebSocketsSockJS通过支持浏览器与服务间的多样传输来完成这样工作。并在运行时根据浏览器和网络的能边选择其中之一。所有这一切对你都是透明的,你只简单看到类似WebSocket接口的工作方式。

为了获取更多关于SockJS的信息,请参考SockJS网站。

 

SockJS处理器

Vert.x提供一个创新的处理器叫SockJSHandler,便于在Vert.x-Web应用中使用SockJS。用SockJSHandler.create方法为每个SockJS应用创建一个处理器。在创建实例时也可以指定配置选项。配置选项由SockJSHandlerOptions类来描述。

Router router = Router.router(vertx);

 

SockJSHandlerOptions options = newSockJSHandlerOptions().setHeartbeatInterval(2000);

 

SockJSHandler sockJSHandler = SockJSHandler.create(vertx,options);

 

router.route("/myapp/*").handler(sockJSHandler);

 

处理SockJSSocket

在服务端,设置一个SockJS处理器,此处理器在每个客户端连接时都会被调用。调用时SockJSSocket类会被传入处理器。此类有着与socket类似的接口,与NetSocket或者WebSocket一样,可以读取和写入。其也实现了ReadStreamWriteStream两种流,所以可以将其泵接到其他读/写流。这是一个简单的SocketJS处理器的例子,简单地将读到的数据写回。

Router router = Router.router(vertx);

 

SockJSHandlerOptions options = newSockJSHandlerOptions().setHeartbeatInterval(2000);

 

SockJSHandler sockJSHandler = SockJSHandler.create(vertx,options);

 

sockJSHandler.socketHandler(sockJSSocket -> {

 

  // Just echo thedata back

 sockJSSocket.handler(sockJSSocket::write);

});

 

router.route("/myapp/*").handler(sockJSHandler);

 

客户端

客户端,使用SockJS客户端JavaScript库进行连接。可以从这里下载http://cdn.sockjs.org/sockjs-0.3.4.js,最小化版本可以从这里下载http://cdn.sockjs.org/sockjs-0.3.4.min.js。使用SockJS JavaScript客户端的详细信息可以参见SockJS官网https://github.com/sockjs/sockjs-client,总得来说,基本象下面代码一样使用:

var sock = new SockJS('http://mydomain.com/myapp');

 

sock.onopen = function() {

 console.log('open');

};

 

sock.onmessage = function(e) {

  console.log('message',e.data);

};

 

sock.onclose = function() {

 console.log('close');

};

 

sock.send('test');

 

sock.close();

 

配置SockJS处理器

使用SockJSHandlerOptions可以将多个选项配置给处理器。

  • insertJSESSIONID

    插入一个JSESSIONIDRcokkie,确保负载均衡器将指定SockJS会话的请求总是路由到正确的会话服务器。默认值为true.

  • sessionTimeout

    在客户端收到请求,服务器很长时间没有得到反馈时,服务 器发送一个关闭(close)事件。此处的延迟由此设置。在一个接收的连接在5秒种没有反映,默认的关闭事件将会被发送。

  • heartbeatInterval

    为了避免代理和负载均衡器关闭长时间运行的HTTP请求,需要保证连接是活的并且过一段时间发送心跳包。此设置用于设定发送心跳包的间隔。默认每隔25秒发送一个心跳包。

  • maxBytesStreaming

    多数流传输将响应保持在客户端并且不会释放投送消息的内存。这样的传输过一段时间需要进行垃圾回收。max_bytes_streaming设置了在流关闭时,单一http流请求所能传递的最小字节数。当到达此最小字节数时,客户端需要打开一个新请求。为了有效禁用流并且将流传输表现为轮询传输,请设置此值。默认值为128kb

  • libraryURL

    对于不支持跨域通信的传输,使用天然的iframe技巧。一个简单页面由SockJS服务器提供(使用外部域)并且将页面放置在不可见的iframe中。在此iframe中运行的代码不用担心跨域问题,因为由本地或运行,连接到SockJS服务器。此iframe不需要加载SockJS客户端库,此选项让你指定SockJS JavaScript库的url(如果不确定,默认地使用最新的最小化的socketJS客户端发行版)。默认值是http://cdn.sockjs.org/sockjs-0.3.4.min.js

  • disabledTransports

    这是禁用传输的列表。可能的值是WEBSOCKET,EVENT_SOURCE,HTML_FILE,JSON_P,XHR.

 

SockJS 事件总线桥

随同编译进Vert.x-WebSockJSsocket处理器叫事件总线桥,此处理器有效扩展服务端vert.x事件总线到客户端JavaScript中。这就创建了分布式事件总线,此事件总线不能跨越多个服务端Vert.x实例,但包括了运行在浏览器端的客户端JavaScript。因此,可以创建大的分布式总线压缩一些浏览器与服务器,只要服务器可连接,浏览器不必连接到相同的服务器。这是通过提供一个客户端JavaScript(vertx-eventbus.js)实现的,此JavaScript库提供了与服务端Vert.x事件总线相似的API,使用此API,可以发送和发布信息到事件总线并且可以注册处理器接收消息。此JavaScript库使用JavaScript SockJS客户端在SockJS连接上建立传输通道,在某个服务器端的处理器(SockJSHandler)终此连接。

一个特殖的SockJS socket处理器会添加到SockJSHandler,此处理器处理SockJS数据,或者将数据桥接到/自服务器端事件总线。为了激活格桥接,只要在SockJS处理器上简单调用bridge方法。

Router router =Router.router(vertx);

 

SockJSHandler sockJSHandler =SockJSHandler.create(vertx);

BridgeOptions options = newBridgeOptions();

sockJSHandler.bridge(options);

 

router.route("/eventbus/*").handler(sockJSHandler);

在客户端JavaScript中,需要使用 'vertx-eventbus.js`库创建到事件总线的连接发送和接收消息。

<scriptsrc="http://cdn.sockjs.org/sockjs-0.3.4.min.js"></script>

<scriptsrc='vertx-eventbus.js'></script>

 

<script>

 

var eb = newEventBus('http://localhost:8080/eventbus');

 

eb.onopen = function() {

 

 // set a handler to receive a message

 eb.registerHandler('some-address', function(error, message) {

    console.log('received a message: ' +JSON.stringify(message));

 });

 

 // send a message

 eb.send('some-address', {name: 'tim', age: 587});

 

}

 

</script>

此例子做的第一件事是创建一个事件总线实例:

var eb = new EventBus('http://localhost:8080/eventbus');

构造函数的参数是需要连接到的URI。因为我们创建带eventbus前缀的桥将会连到那里。在连接打开前,它实际还不能做任何事。打开连接时onopen处理器会被调用。

可以用依赖管理器获取客户端包。

Maven(pom.xml)

<dependency>

 <groupId>io.vertx</groupId>

 <artifactId>vertx-web</artifactId>

 <version>3.2.1</version>

 <classifier>client</classifier>

</dependency>

Gradle ( build.gradle):

compile 'io.vertx:vertx-web:3.2.1:client'

此库也可以用NPMBower获取。

注意,在3.0.03.1.0版本之间API已经改变了。请查看changelog。前一个版本的客户端被兼容仍然能被使用,但是新和客户端提供了更多特性并与vert.x事件总线API更接近。

 

保护SockJS

如果像上面例子一样打开一个没有保护的桥,并通过期试图发送消息,你将发现消息神奇地消失了。这发生了什么?

对于大多数应用,你或许不想客户端JavaScript发送一些消息到任何服务器端处理器或者其他所有的浏览器。例如,你在事件总线上有一个服务,此服务可访问或删除数据。我们不想错误行为或者恶意客户端删除所有数据库所有数据。我们也不想任意客户端侦听任意事件总线地址。为了处理上面问题,一个SockJS桥将默认拒绝传输任何消息。这取决于你告诉桥什么消息可以通过传输。对于总是能够通过的响应消息这是一个异常。换句话说,桥充当了防火墙角色,默认是拒绝所有(deny-all)策略。什么消息可以在桥上传输,只需对桥进行简单配置。

在调用桥时,可用传入BridgeOptions对出站和入站允许的流量进行匹配。每个匹配是一个PermittedOptions对象:

setAddress

这代表了一个确切的消息发送到的地址。如果想让消息基于此确切地址,可以使用此字段。

setAddressReges

这是一个匹配地址的正则表达式。如果让想消息基于正则表达式地址,可以使用此字段,如果设置了address字段,将忽视此字段。

setMatch

此允许消息基于其消息结构。匹配的任何字段必须存在于消息中,同时值是允许的。此仅能用于JSON消息。

 

如果是传入的消息(如,由客户端javaScript发送到服务器)在服务器收到时,Vert.x-Web会查看所有入站许可匹配。如果匹配,将允许通过。

 

如是是出站肖息在被发送到客户端之前,Vert.x-Web会查看出站许可匹配,如果匹配,将允许通过。

实际匹配规则如下:

如果设置了address字段,消息地址必须与指定的address地址精确匹配才被认为是匹配的。

如果不设置address字段而是设置addressRegex字段,消息地址的格式必须与address_re中的正则表达式匹配。

如果设置了match字段,消息结构必须匹配,结构匹配是检查看消息是否有存在匹配对象的所有字段和值。

这是一个例子:

Router router =Router.router(vertx);

 

SockJSHandler sockJSHandler =SockJSHandler.create(vertx);

 

 

// Let through any messages sentto 'demo.orderMgr' from the client

PermittedOptionsinboundPermitted1 = new PermittedOptions().setAddress("demo.orderMgr");

 

// Allow calls to the address'demo.persistor' from the client as long as the messages

// have an action field withvalue 'find' and a collection field with value

// 'albums'

PermittedOptions inboundPermitted2= new PermittedOptions().setAddress("demo.persistor")

    .setMatch(newJsonObject().put("action", "find")

        .put("collection","albums"));

 

// Allow through any message witha field `wibble` with value `foo`.

PermittedOptionsinboundPermitted3 = new PermittedOptions().setMatch(newJsonObject().put("wibble", "foo"));

 

// First let's define what we'regoing to allow from server -> client

 

// Let through any messagescoming from address 'ticker.mystock'

PermittedOptionsoutboundPermitted1 = new PermittedOptions().setAddress("ticker.mystock");

 

// Let through any messages fromaddresses starting with "news." (e.g. news.europe, news.usa, etc)

PermittedOptionsoutboundPermitted2 = newPermittedOptions().setAddressRegex("news\\..+");

 

// Let's define what we're goingto allow from client -> server

BridgeOptions options = newBridgeOptions().

    addInboundPermitted(inboundPermitted1).

    addInboundPermitted(inboundPermitted1).

    addInboundPermitted(inboundPermitted3).

    addOutboundPermitted(outboundPermitted1).

    addOutboundPermitted(outboundPermitted2);

 

sockJSHandler.bridge(options);

 

router.route("/eventbus/*").handler(sockJSHandler);

 

消息必须的认证信息

事件总线桥可用Vert.x-Web认证功能配置成要求消息必须认证,不论是桥的入站还是出站。

为了完成此工作,如前节所描述的,需要添加额外的match字段,此字段段决定针对匹配的消息必须提供什么权利。用setRequiredAuthority设置登录用户需要特定访问消息的权利。

这是一个例子:

PermittedOptions inboundPermitted= new PermittedOptions().setAddress("demo.orderService");

 

// But only if the user is loggedin and has the authority "place_orders"

inboundPermitted.setRequiredAuthority("place_orders");

 

BridgeOptions options = newBridgeOptions().addInboundPermitted(inboundPermitted);

对于被授权的用户,首先需要登录,然后需要特定权利。为了处理登录和确切授权,可以为Vert.x配置正常的认证处理器(auth handler)。例如:

Router router =Router.router(vertx);

 

// Let through any messages sentto 'demo.orderService' from the client

PermittedOptions inboundPermitted= new PermittedOptions().setAddress("demo.orderService");

 

// But only if the user is loggedin and has the authority "place_orders"

inboundPermitted.setRequiredAuthority("place_orders");

 

SockJSHandler sockJSHandler =SockJSHandler.create(vertx);

sockJSHandler.bridge(newBridgeOptions().

        addInboundPermitted(inboundPermitted));

 

// Now set up some basic authhandling:

 

router.route().handler(CookieHandler.create());

router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));

 

AuthHandler basicAuthHandler =BasicAuthHandler.create(authProvider);

 

router.route("/eventbus/*").handler(basicAuthHandler);

 

 

router.route("/eventbus/*").handler(sockJSHandler);

 

处理事件总线桥事件

如果在桥的事件出现后想得到事件通知,可以在调用bridge方法时提供一个处理器。不论什么时间桥事件出下,事件将会传给处理器进行处理,用BridgeEvet实例对桥事件进行描述。事件可以是下面任一类型:

SOCKET_CREATED

在创建SockJS socket时,产生此事件

SOCKET_CLOSED

SockJS socket关闭时,产生此事件

SEND

在一个消息试图被从客户端发送到服务器时,产生此事件。

PUBLISH

在一个消息被图被从客户端发布到服务器时,产生此事件。

RECEIVE

在消息从服务器传送到客户端时,产生RECEIVE事件。

REGISTER

在一个客户端试图注册一个处理器时,产生此事件。

UNREGISTER

在一个客户端试图解注册一个处理器时,产生此事件。

 

用事件的type方法获取事件类型,并且用getRawMessage方法查看原生事件消息。

原事消息是一个JSON对象,俱有以下结构:

{

 "type":"send"|"publish"|"receive"|"register"|"unregister",

 "address": the event bus address beingsent/published/registered/unregistered

 "body": the body of the message

}

 

事件也是一个Future对象。在完成事件处理,可以用true来结束Future,以便进一步进行处理。如果不想让事件被处理,可以用false来结束Future。这对于过滤桥上传输的消息或者进行细粒度的授权和度量很有用。这是一个例子,在这个例子中,我们拒绝了所有包售“Armadillos”单词的所有消息。

Router router =Router.router(vertx);

 

// Let through any messages sentto 'demo.orderMgr' from the client

PermittedOptions inboundPermitted= new PermittedOptions().setAddress("demo.someService");

 

SockJSHandler sockJSHandler =SockJSHandler.create(vertx);

BridgeOptions options = newBridgeOptions().addInboundPermitted(inboundPermitted);

 

sockJSHandler.bridge(options, be-> {

 if (be.type() == BridgeEventType.PUBLISH || be.type() ==BridgeEventType.RECEIVE) {

    if(be.getRawMessage().getString("body").equals("armadillos")){

      // Reject it

      be.complete(false);

      return;

    }

 }

 be.complete(true);

});

 

router.route("/eventbus").handler(sockJSHandler);

也可以修改原始信息,例如改变消息体。对于从客户端流入的消息,也可以向消息添加头,这是一个例子:

Router router =Router.router(vertx);

 

// Let through any messages sentto 'demo.orderService' from the client

PermittedOptions inboundPermitted= new PermittedOptions().setAddress("demo.orderService");

 

SockJSHandler sockJSHandler =SockJSHandler.create(vertx);

BridgeOptions options = newBridgeOptions().addInboundPermitted(inboundPermitted);

 

sockJSHandler.bridge(options, be-> {

 if (be.type() == BridgeEventType.PUBLISH || be.type() ==BridgeEventType.SEND) {

    // Add some headers

    JsonObject headers = newJsonObject().put("header1", "val").put("header2","val2");

    JsonObject rawMessage = be.getRawMessage();

    rawMessage.put("headers",headers);

    be.setRawMessage(rawMessage);

 }

 be.complete(true);

});

 

router.route("/eventbus").handler(sockJSHandler);