WebSocket is a capability introduced with HTML5, providing an asynchronous, bidirectional, full-duplex connection over which messages can be sent between agents, commonly a browser client and a server application. Compatible browsers provide a JavaScript interface that allows connections to be set up, maintained, messages exchanged, and connections closed down, using a callable and event-based interface.
WASD provides a WebSocket compatible scripting environment, one that is activated in the same fashion as an equivalent CGI/CGIplus/RTE and has an identical CGI environment (variables, streams, etc.) but which uses a unique HTTP response and communicates with its client using the WebSocket protocol.
Client supplied data is available to the script via the WEBSOCKET_INPUT mailbox and data from the script supplied via the WEBSOCKET_OUTPUT mailbox (indicated via CGI variables). Communication using a WebSocket requires the use of a framing protocol while WEBSOCKET_INPUT and WEBSOCKET_OUTPUT are opaque octet-streams providing communication to and from the WebSocket application. CGI variables WEBSOCKET_INPUT_MRS and WEBSOCKET_OUTPUT_MRS indicate the respective mailbox capacity.
The WASD server largely acts as a conduit for the WebSocket octet-stream. It provides the upgrade from HTTP to WebSocket protocol handshake and then connects the bidirectional data stream to the WebSocket application activated in WASD's scripting environment which then is required to perform all of the protocol requirements, etc. The baseline WASD implementation is via the wsLIB library (see below). The complexity and potential extensibility of the WebSocket protocol means this decoupling of server infrastructure and protocol implementation offers a number of advantages, including more straight-forward updates and bug fixing (just the library), and alternate, concurrent implementations.
Long-lived WebSocket scripts by default have timeouts and other limits set to infinite. If control is required it must be exercised using the appropriate mapping SETings or DCL callouts.
A WASD RawSocket is an analogue to the WebSocket, providing a bidirectional, asynchronous, opaque data stream input and output on a per-service basis. See 5.8 - WASD "Raw"Socket.
A single WASD WebSocket server application (script) can support multiple clients by using some form of multi-threading such as AST-based I/O, POSIX Threads, multi-thread interpreter environment, etc. The WASD wsLIB library (5.3 - WebSocket Library) supports native AST concurrency.
A WebSocket connection to a script is maintained by the WEBSOCKET_INPUT and WEBSOCKET_OUTPUT channels remaining connected to the script. If the script closes them (or the image or process exits, etc.) the WebSocket connection is closed. WebSocket requests are maintained as long as the script maintains them, for a CGIplus script, until it exits. If a CGIplus script requires to disconnect from a WebSocket client without exiting it must do so explicitly (by using the wsLIB close function (and associated WebSocket protocol close handshake), closing C streams, deassigning channels, etc.)
Of course this is the underlying mechanism allowing a single CGIplus script to maintain connections with multiple WebSocket clients. Provided the script remains connected to the WebSocket IPC mailboxes and processes that I/O asynchronously a single script can concurrently handle multiple clients. The script just processes each request it is given, adding the new client to the existing group (and removing them as the IPC indicates they disconnect).
Obviously the script must remain resident via CGIplus or RTE.
BYTLM
WebSocket scripting environments have the potential to consume significantly more BYTLM than those for HTTP scripting. The potentially large number of mailboxes associated with each scripting process (two per WebSocket connection) means that server and scripting account(s) BYTLM and associated quotas will need to be increased appropriately.
The server will continue to provide requests to the script for as long as it appears idle (i.e. the CGIplus sentinal EOF is returned even though concurrent processing may continue). Obviously a single scripting process cannot accept an unlimited number of concurrent WebSockets. When a script decides it can process no more it should not return the sentinal EOF from the most recent request until it is in a position to process more, when it then provides the EOF and the server again will supply another request.
The original request is access logged at request run-down (when the WebSocket is finally closed either because the client disconnected or the script closed its connection to the WEBSOCKET_.. mailboxes). The access log status is 101 (Switching Protocols) and the bytes rx and tx reflect the total for the duration.
WebSocket server applications are essentially CGIplus scripts and so have similar programming considerations (see 3 - CGIplus).
A WebSocket application however is typically long-lived and involves significant interaction between the participants. Either party can initiate independent communication with the other according to the required business logic.
A WASD WebSocket application relies on asynchronous I/O and other events to provide the communication granularity required for application interaction. The following pseudo-code shows the structure of one such hypothetical application. It accepts multiple, concurrent requests in it's main loop, creates the required WebSocket protocol supporting data structure, and then services application requirements in two event loops.
The first reads from the remote client and processes according to the business logic of client-initiated processing, asynchronously and/or synchronously writing data to the client. The second loop pushes data asynchronously to the client based on the application business logic providing those events. The close event occurs when the client or application close the WebSocket, or are otherwise disconnected, and finalises the request.
begin { initialise loop { wait for next client request open the WebSocket begin asynchronous read from client signal ready for another request } } read event from client { business logic asynchronous write to client next asynchronous read from client } push event to client { business logic asynchronous write to client } close event { business logic close the WebSocket }
A second model for request synchronisation allows the initialisation to specify a routine to be called when another request is available, making all processing event-driven. In all other respects the processing is the same as above.
begin { initialise set routine for request acceptance hibernate (or otherwise not exit) } request acceptance { open the WebSocket business logic begin asynchronous read from client signal ready for another request } read event from client { business logic asynchronous write to client next asynchronous read from client } push event to client { business logic asynchronous write to client } close event { business logic close the WebSocket }
These basic structures are seen in all the WebSocket example applications.
wsLIB is a C code module that provides the basic infrastructure for building WebSocket applications to run as WASD scripting under both the models decribed above.
It abstracts much of the required functionality into a few callable functions using optional string descriptors so as to minimise dependency on the C language and on knowing the internals of the library data structure. The list of functions and associated parameters would unnecessarily clutter this document and so WebSocket application designers and programmers are referred to the descriptive prologue in the library code module itself (see below). While wsLIB usage is relatively straight-forward, the detail of any multi-threaded, asynchronous application can be daunting and so the example WebSocket applications (scripts) should be used as a wsLIB reference and tutorial.
wsLIB also contains routines for synchronising request acceptance and accessing CGI variables associated with that request. These variables are available for the period from request acceptance to the issuing of the CGIplus sentinal EOF indicating to the server the script is ready to accept another request. Any CGI variable values required during ongoing processing must be copied to request-specific storage. Again, the example WebSocket applications (scripts) should be used as a CGI variable processing reference and tutorial.
The library contains WATCH points. Network [x]Data and [x]Script provide a useful combination of traffic data. The library function WsLibWatchScript() allows WebSocket applications (scripts) to provide additional WATCHable information via the [x]Script item.
WASD_ROOT:[SRC.WEBSOCKET]WSLIB.C
The WASD WebSocket implementation provides a number of scripting examples illustrating WebSocket programming basics and the use of the WASD wsLIB library. All of these illustrate multi-client support using asynchonous I/O. Each has a server component (the C code) and a client component (the HTML file containing the JavaScript code).
The following examples concentrate on the server C code as this is WASD-specific. Any WebSocket reference can adequitely cover the essentials of the client JavaScript implementation.
Does this browser support WebSockets?
NO!
This almost has to be the classic example of asynchronous, bidirectional communications without HTTP kludges. Each connected client can enter a message and it is distributed to all connected clients.
WASD_ROOT:[SRC.WEBSOCKET]WS_CHAT.C
Each connected client can enter a message which is then returned to them.
WASD_ROOT:[SRC.WEBSOCKET]WS_ECHO.C
The HTML/JavaScript/WebSocket client end connects to the script. Each mouse movement is then reported to the script. These data are distributed to all connected clients. This provides an asynchronous update facility from all clients to all clients.
The script is implemented using VMS I/O-driven ASTs. The code is also interesting because it implements all required functionality explicitly; no WebSocket library functions are employed.
WASD_ROOT:[SRC.WEBSOCKET]WS_MOUSE.C
WebSocket server applications are essentially CGIplus scripts and so are mapped and activated in the same fashion as any other CGIplus script (3.3 - Other Considerations).
Throttle mapping rules may be applied to WebSocket requests. There is however, a fundamental difference between request throttling and WebSocket throttling though. HTTP request throttling applies control to the entire life of the response. WebSocket throttling applies only to establishing connection to the underlying server application. Once the script responds to accept the connection or reject it throttling is concluded.
Long-lived WebSocket connections are considered less suitable to full life-cycle throttling and should use internal mechanisms to control resource utilisation (i.e. using the delayed sentinal EOF mechanism described in 5.1 - Multi-Client WebSocket Applications). Essentially it is used to limit the impact concurrent requests have on the number of supporting script processes allowed to be instantiated to support the application.
For example, the rule
set /cgi-bin/ws_application throttle=1will only allow one new request at a time attempt to connect to and/or create a WebSocket application script. This will effectively limit the number of supporting processes to one however many clients wish to connect.
To support concurrent requests distributed across multiple application scripts specify the throttle value as the number of separate scripts
set /cgi-bin/ws_application throttle=5and if each script is to support a maximum number of individual connections then have it delay the EOF sentinal (described above) to block the server selecting it for the next request. Requests will be allocated until all processes have blocked after which they will be queued.
To return a "too busy" 503 to clients (almost) immediately upon all processes become full and blocking (maximum application concurrency has been reached) then set the "t/o-busy" value to 1 second.
set /cgi-bin/ws_application throttle=5,,,,,1
Unconditionally disconnects all WebSocket applications.
$ HTTPD /DO=WEBSOCKET=DISCONNECT
For VMS V8.2 and later, more selective disconnects are possible. Disconnects WebSocket applications with connect number, with matching script names, and with matching scripting account usernames, respectively.
$ HTTPD /DO=WEBSOCKET=DISCONNECT=number $ HTTPD /DO=WEBSOCKET=DISCONNECT=SCRIPT=pattern $ HTTPD /DO=WEBSOCKET=DISCONNECT=USER=pattern
CGI variable WEBSOCKET_VERSION provides the WebSocket protocol version number negotiated by the server at connection establishment.
At the time of writing the WebSocket protocol has just gone to IETF Draft RFC and has during development been very volatile and may continue to be so as it evolves. WASD supports the current base protocol number and any higher. At some time in the future it may be necessary to constrain that to a supported version number or set of numbers. Defining the logical name WASD_WEBSOCKET_VERSION to be one or more comma-separated numbers will limit the supported protocol versions. For example
$ DEFINE /TABLE=WASD_TABLE WASD_WEBSOCKET_VERSION "10, 9, 8"limits requests to protocol version 10 (current), 9 (earlier) and 8 (earliest). Logical name is only tested once for each server startup (the first WebSocket request received). This logical name only controls server handshake support and behaviour. The underlying WebSocket library used by the application (e.g. wsLIB.c) supports version idiosyncracies for other aspects.
This string is also used as the list of versions reported in a 426 (upgrade required) response when a client makes a request using an unsupported version.
The raw WebSocket throughput of a platform (hardware plus VMS plus TCP/IP stack plus WASD and optionally network infrastructure) can be measured using the WSB utility. Measures of raw message and byte throughput for a series of messages of various sizes can provide useful information on the underlying maximum messaging characteristics of that platform.
The following example shows usage on an Alpha XP1000:
$ WSB == "$WASD_EXE:WSB" $ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=0 %WSB-I-STATS, 1 total connections Duration: 0.303 seconds Tx: 1000 msgs at 3303/S, 0 bytes at 0 B/S Rx: 1000 msgs at 3303/S, 0 bytes at 0 B/S Total: 2000 msgs at 6607/S, 0 bytes at 0 B/S $ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=16 %WSB-I-STATS, 1 total connections Duration: 0.349 seconds Tx: 1000 msgs at 2869/S, 16.0 kbytes at 45.9 kB/S Rx: 1000 msgs at 2869/S, 16.0 kbytes at 45.9 kB/S Total: 2000 msgs at 5737/S, 32.0 kbytes at 91.8 kB/S $ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=64 %WSB-I-STATS, 1 total connections Duration: 0.359 seconds Tx: 1000 msgs at 2783/S, 64.0 kbytes at 178.1 kB/S Rx: 1000 msgs at 2783/S, 64.0 kbytes at 178.1 kB/S Total: 2000 msgs at 5566/S, 128.0 kbytes at 356.2 kB/S $ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=256 %WSB-I-STATS, 1 total connections Duration: 0.607 seconds Tx: 1000 msgs at 1646/S, 256.0 kbytes at 421.5 kB/S Rx: 1000 msgs at 1646/S, 256.0 kbytes at 421.5 kB/S Total: 2000 msgs at 3293/S, 512.0 kbytes at 843.0 kB/S $ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=1024 %WSB-I-STATS, 1 total connections Duration: 0.659 seconds Tx: 1000 msgs at 1517/S, 1.0 Mbytes at 1.6 MB/S Rx: 1000 msgs at 1517/S, 1.0 Mbytes at 1.6 MB/S Total: 2000 msgs at 3034/S, 2.0 Mbytes at 3.1 MB/S $ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=65k %WSB-I-STATS, 1 total connections Duration: 10.279 seconds Tx: 1000 msgs at 97/S, 65.0 Mbytes at 6.3 MB/S Rx: 1000 msgs at 97/S, 65.0 Mbytes at 6.3 MB/S Total: 2000 msgs at 195/S, 130.0 Mbytes at 12.6 MB/S
For more information see the description in the prologue of the program. (A zero-size message is legitimate with the WebSocket protocol.)
The WASD RawSocket is a variant of, and is heavily based on, the WASD WebSocket infrastructure. It allows a service (listening host and port) to accept a connection and immediately activate a configured WASD CGIplus script to service that connection. Full-duplex, asynchronous, bidirectional input/output is supported. While being referred to as a "socket", of course it is not network socket communication (BSD or any other abstraction), but a read/write abstraction via mailbox I/O. Input is a stream of octets; output a stream of octets. The streams are opaque in this context and depend entirely on the abstraction (protocol) being implemented by the script. The "RawSocket" is just WASD nomenclature for WebSocket-style scripting without the WebSocket protocol.
NOTE
A RawSocket application is not intended to use a (standard web) browser as a client. Of course the application could (perhaps, also) behave as a web server if at activation it (also) expected to receive, parse and respond to an HTTP request, and therefore (also) be used by a web browser client.
The script when activated enters a CGIplus wait-service-wait loop using the standard CGIplus mechanism. When a request activates the script it issues a "100 Continue" CGI/HTTP header to signal the server that the request is accepted and now being processed. This header is NOT relayed to the client. The activation parameters (normally referred to as request parameters) are available via the usual CGI variables. The script can then read from and write to the RAWSOCKET_INPUT and RAWSOCKET_OUTPUT devices respectively. As with WebSocket applications, a single RawSocket application (script) can concurrently support multiple clients. The end-of-script is indicated by issuing an EOF to the mailbox. The script activation can be as long-lived as required and the server will not run a request down for any timeout reason.
RawSocket applications share the same lifecycle as described for WebSockets in 5.2 - WebSocket Application.
rawLIB is a C code module that provides a similar API and functionality to the wsLIB library described in 5.3 - WebSocket Library.
WASD_ROOT:[SRC.WEBSOCKET]RAWLIB.C
The WASD RawSocket implementation provides a number of scripting examples illustrating programming basics and the use of the WASD rawLIB library. All of these illustrate multi-client support using asynchonouse I/O. All use telnet as a client interface although this is only for convenience. RawSocket is completely protocol agnostic.
This almost has to be the classic example of asynchronous, bidirectional communications. Each connected client can enter a message and it is distributed to all connected clients.
WASD_ROOT:[SRC.WEBSOCKET]RAW_CHAT.C
Each connected client can enter a message which is then returned to them.
WASD_ROOT:[SRC.WEBSOCKET]RAW_ECHO.C
This example implements a simple-minded telnet server. Definitely not intended for production.
WASD_ROOT:[SRC.WEBSOCKET]RAW_PTD.C
RawSocket server applications are essentially CGIplus scripts and so are mapped and activated in the same fashion as any other CGIplus script (3.3 - Other Considerations). They are a little unique in that there is generally a one-to-one relationship between a script and a service. The service is flagged as implementing a RawSocket and that service is mapped directly to a script.
# WASD_CONFIG_SERVICE [[http:*:1234]] [ServiceRawSocket] enabled # WASD_CONFIG_MAP [[*:1234]] map * /cgiplus-bin/raw_script
It is possible to have conditional mapping based on the (rather limited) "request" parameters (WATCH is useful for understanding what request data is available). For example, the script activated may be varied on the client address.
# WASD_CONFIG_MAP [[*:1234]] if (remote-addr:131.185.10.0/24) map * /cgiplus-bin/raw_one else map * /cgiplus-bin/raw_two endif