Vert.x Web模塊(六)



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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章