📜 ⬆️ ⬇️

New in Caché 2013.1 DBMS: Integrated WebSockets Support

In one of the previous articles , work with WebSocket was already considered using the example of its own server implementation of this protocol over ordinary sockets.

In Caché 2013.1, the CSP Gateway now includes support for the HTML 5 specification for WebSocket connections between a web server and an HTML 5 compatible browser. This feature is available for Apache 2.2 and higher, and for IIS 8.0, which is part of Windows Server 2012.

Since Apache 2.4 is already built into Caché 2013.1, we’ll run our examples on it.
For the implementation of the client part, the ZEN framework was used, but you can convert examples to CSP technology or any other.
')
So let's get started.

Theory


As mentioned above, now all the internal logic for WebSocket support on the server side of the Caché DBMS takes over. The programmer is only required to create his own class, inheriting it from the % CSP.WebSocket class, and override several methods in it.
The object of class % CSP.WebSocket serves as an event handler for communication between the client and server using the WebSocket protocol. All WebSocket servers are inherited from % CSP.WebSocket .
A detailed description of the methods and properties of the % CSP.WebSocket class can be found in the class reference.
Here I will note briefly some of them:
Method / PropertyDescription
Read ()get data from client
Write ()send data to customer
OnPreServer ()PreServer event handler: called before starting the WebSocket server
OnPostServer ()event handler PostServer: called after stopping WebSocket server
Server ()actually the websocket server itself
EndServer ()stop websocket server
AtEndthe property is true (1) when, while reading, the WebSocket server reaches the end of the current data frame
SharedConnectionthe property determines whether the information exchange between the client and the WebSocket server will occur over a dedicated CSP-Gateway connection or through a pool of shared connections (not yet used)

Practice


Let's look at a simple example of how all this can be used in practice.
First, create a ZEN page in the USER Studio - the demo.WebSocket class - and inherit it from the % CSP.WebSocket class:
Class demo.WebSocket Extends ( % ZEN.Component.page , % CSP.WebSocket )
{

XData Contents [ XMLNamespace = " www.intersystems.com/zen" ]
{
< page xmlns = " www.intersystems.com/zen" title = "" >
</ page >
}

}

Connect the zenCSLM.js file to support working with JSON:
Parameter JSINCLUDES = "zenCSLM.js" ;

We implement methods for connecting the client via WebSocket and processing the corresponding events:
/// This client event, if present, is the fired when the page is loaded.
ClientMethod onloadHandler () [ Language = javascript]
{
ws = null;

url = ((window.location.protocol == 'https:' ) ? 'wss:' : 'ws:' ) + '//' + window.location.host + window.location.pathname;

wsCtor = window [ 'MozWebSocket' ] ? MozWebSocket : window [ 'WebSocket' ] ? WebSocket : null;

if (zenIsMissing (wsCtor)) zenAlert ( 'WebSocket is NOT supported by your browser!' );
}

ClientMethod start () [ Language = javascript]
{
if ( ! zenIsMissing (wsCtor)) {
if (zenIsMissing (ws)) {
ws = new wsCtor (url);

ws.onopen = function () {
zenAlert ( 'onopen \ n \ nreadyState:' , ws.readyState, '\ nbinaryType:' , ws.binaryType, '\ nbufferedAmount:' , ws.bufferedAmount);
};

ws.onmessage = function (e) {
zenAlert (e.data);
};

ws.onclose = function (e) {
zenAlert ( 'onclose \ n \ nwasClean:' , e.wasClean, '\ ncode:' , e.code, '\ nreason:' , e.reason);
ws = null;
};

ws.onerror = function (e) {
zenAlert ( 'onerror' );
};
}
}
}

Since the WebSocket server implementation is on the same page, the connection string will be identical except for the protocol. That is, if the url of our ZEN-page looks like
localhost:57772/csp/user/demo.WebSocket.cls
, it will be for the WebSocket server
ws://localhost:57772/csp/user/demo.WebSocket.cls

Now let's add some interface to our page: a few buttons to connect, disconnect and send data to the server:
XData Contents [ XMLNamespace = " www.intersystems.com/zen" ]
{
< page xmlns = " www.intersystems.com/zen" title = "" >
< text id = "txt" label = "Text to send" value = "World" />
< button caption = "1.Connect" onclick = "zenPage.start ();" />
< button caption = "2.Send text" onclick = "if (! zenIsMissing (ws)) ws.send (zenGetProp ('txt', 'value'));" />
< button caption = "3.Send a long line" onclick = "zenPage.sendLongStr (100000);" />
< button caption = "4.Send JSON" onclick = "zenPage.sendJSON ();" />
< button caption = "5. Turn off" onclick = "if (! zenIsMissing (ws)) ws.close ();" />
</ page >
}

The sendLongStr method code is as follows:
ClientMethod sendLongStr ( N ) [ Language = javascript]
{
if (zenIsMissing (ws)) return ;

var s = 'a' ;
for ( var i = 1 ; i < N; i ++ ) s + = 'a' ;
ws.send (s);
}

The code of the sendJSON method:
ClientMethod sendJSON () [ Language = javascript]
{
if (zenIsMissing (ws)) return ;

var obj = {
"firstName" : "Ivan" ,
"lastName" : "Ivanov" ,
"address" : {
"streetAddress" : "Moskovskoye sh., 101, ap. 101" ,
"city" : "Leningrad" ,
"postalCode" : 101101
},
"phoneNumbers" : [
"812 123-1234" ,
"916 123-4567"
]
};

ws.send (ZLM.jsonStringify (obj));
}

It's time to implement our own WebSocket server itself. To do this, override the OnPreServer , OnPostServer and Server methods, as shown below:
Method OnPreServer () As% Status
{
Do $ system .Process . Undefined (2)

Set ^ tmp ( $ Increment (^ tmp), "OnPreServer" ) = ""
Quit $$$ OK
}

Method OnPostServer () As% Status
{
Set ^ tmp ( $ Increment (^ tmp), "OnPostServer" ) = ""
Quit $$$ OK
}

Method Server () As% Status
{
For {

Set len = 32656
Set data = $ ZConvert (.. Read (. Len,. Status ), "I" , "UTF8" )

If $$$ ISOK ( status ) {

If data = "World" {
Do .. Write ( $ ZConvert ( "Hello," _ data _ "!" , "O" , "UTF8" ))
} ElseIf data = "bye" {

; forcibly shutting down the WebSocket server and exiting an infinite loop
; on the client, this will work onclose
Do .. EndServer ()
Quit

} Else {

#Dim obj As % RegisteredObject = $$$ NULLOREF

Set ^ tmp = $ Increment (^ tmp)

; convert string to object
If $$$ ISOK ( ## class ( % ZEN.Auxiliary.jsonProvider ). % ConvertJSONToObject ( data ,,. Obj )) {

; if there is no error, save the property values
Set ^ tmp (^ tmp, "Server" , "firstName" ) = obj . firstName
Set ^ tmp (^ tmp, "Server" , "lastName" ) = obj . lastName
Set ^ tmp (^ tmp, "Server" , "address.streetAddress" ) = obj . address . streetAddress
Set ^ tmp (^ tmp, "Server" , "address.city" ) = obj . address . city
Set ^ tmp (^ tmp, "Server" , "address.postalCode" ) = obj . address . postalCode
Set ^ tmp (^ tmp, "Server" , "phoneNumbers.1" ) = obj . phoneNumbers . GetAt (1)
Set ^ tmp (^ tmp, "Server" , "phoneNumbers.2" ) = obj . phoneNumbers . GetAt (2)

; Change the name
Set obj . lastName = "Sidorov"

; Add another phone
Do obj . phoneNumbers . Insert ( "111 111-1111" )

; Add two more new properties to the object.
Set obj . name = "Vasya"
Set obj . street = "Mira St. 17"

; Convert the modified object to a string and send it back to the client.
Do .. Write (.. Write2Str (. Obj ))

} Else {

; save data when getting a long string
Set ^ tmp (^ tmp, "Server" , "longStr" ) = .. AtEnd _ ":" _ $ Length ( data ) _ ":" _ len
}
}
} Else {
Quit :( $$$ GETERRORCODE ( status ) = $$$ CSPWebSocketClosed )
}
}
Quit $$$ OK
}

The utility method Write2Str is used to assemble into a string, the output of the Write command, data:
ClassMethod Write2Str ( ByRef obj ) As% String [ Private ]
{
Try {
Set tIO = $ IO , tXDEV = "| XDEV |" _ + $ Job
Do {

// For $$$ IsUnicode use UTF-8
Open tXDEV :( $ ZF (-6, $$$ XSLTLibrary , 12): "" : "S" : / HOSTNAME = "XSLT" : / IOT = $ Select ( $$$ IsUnicode : "UTF8" , 1: " RAW " ): / IBU = 16384: / OBU = 16384)
Use tXDEV

Quit : $$$ ISERR ( obj . % ToJSON (, "aeloiwu" ))

// Flush any remaining output
Write * -3

// Now read back a string (up to the maximum possible length, 32k or ~ 4MB for long strings)
Set s = ""
While (1) {
#Dim tChunk As % String
Read tChunk : 0
Quit : ' $ Length ( tChunk )
Set s = s _ tChunk
}

} While (0)
} Catch {}

Close tXDEV
Use tIO
Quit s
}

It remains to compile our class (Ctrl + F7) and open it for viewing in a browser (F5).
After consistently pressing the buttons, the contents of the global ^ tmp will be as follows:

^tmp=7
^tmp(1,"OnPreServer")=""
^tmp(2,"Server","longStr")="0:32656:32656"
^tmp(3,"Server","longStr")="0:32656:32656"
^tmp(4,"Server","longStr")="0:32656:32656"
^tmp(5,"Server","longStr")="1:2032:2032"
^tmp(6,"Server","address.city")=""
^tmp(6,"Server","address.postalCode")=101101
^tmp(6,"Server","address.streetAddress")=" ., 101, .101"
^tmp(6,"Server","firstName")=""
^tmp(6,"Server","lastName")=""
^tmp(6,"Server","phoneNumbers.1")="812 123-1234"
^tmp(6,"Server","phoneNumbers.2")="916 123-4567"
^tmp(7,"OnPostServer")=""

Download the demo.WebSocket class source .

Source: https://habr.com/ru/post/181113/


All Articles