|1WebSocket|
|^ 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 |link|WASD "Raw"Socket||.
|2Multi-Client WebSocket Applications|
|^ 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
(|link|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.
|note|
|0BYTLM|
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.
|!note|
|^ 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.
|2WebSocket Application|
|^ WebSocket server applications are essentially CGIplus scripts and so have
similar programming considerations (see |link|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.
|code|
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
}
|!code|
|^ 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.
|code|
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
}
|!code|
|^ These basic structures are seen in all the WebSocket example applications.
|2WebSocket Library|
|^ |*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.
|^+ |link%=|/wasd_root/src/websocket/wslib.c|\
WASD_ROOT:[SRC.WEBSOCKET]WSLIB.C|
|2WebSocket Application Examples|
|^ 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).
|^+ |link%=|/wasd_root/src/websocket/*.*|WASD_ROOT:[SRC.WEBSOCKET]|
|^ 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.
|style|._doesws { width:70%; font-size:120%; margin-bottom:0.2em; }|
|9WebSocket - does this browser support?|
|note><|
|0Does this browser support WebSocket?|
|asis|
Y E S
Y E S ... but not a recent enough version
N O !
)
||||
|!note|
|3Chat|
|^ 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.
|^+ |link%=|/wasd_root/src/websocket/ws_chat.c|\
WASD_ROOT:[SRC.WEBSOCKET]WS_CHAT.C|
|3Echo|
|^ Each connected client can enter a message which is then returned to them.
|^+ |link%=|/wasd_root/src/websocket/ws_echo.c|\
WASD_ROOT:[SRC.WEBSOCKET]WS_ECHO.C|
|3Mouse|
|^ 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.
|^+ |link%=|/wasd_root/src/websocket/ws_mouse.c|\
WASD_ROOT:[SRC.WEBSOCKET]WS_MOUSE.C|
|2WebSocket Configuration|
|^ WebSocket server applications are essentially CGIplus scripts and so are
mapped and activated in the same fashion as any other CGIplus script
(|link|Other Considerations||).
|3WebSocket Throttle|
|^ 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
|link|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
|code|
set /cgi-bin/ws_application throttle=1
|!code|
will 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
|code|
set /cgi-bin/ws_application throttle=5
|!code|
and 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.
|code|
set /cgi-bin/ws_application throttle=5,,,,,1
|!code|
|3WebSocket Command-Line|
|^ Unconditionally disconnects all WebSocket applications.
|code|
$ HTTPD /DO=WEBSOCKET=DISCONNECT
|!code|
|^ 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.
|code|
$ HTTPD /DO=WEBSOCKET=DISCONNECT=|/number||
$ HTTPD /DO=WEBSOCKET=DISCONNECT=SCRIPT=|/pattern||
$ HTTPD /DO=WEBSOCKET=DISCONNECT=USER=|/pattern||
|!code|
|3WebSocket Version|
|^ 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
|code|
$ DEFINE /TABLE=WASD_TABLE WASD_WEBSOCKET_VERSION "10, 9, 8"
|!code|
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.
|2WebSocket Throughput|
|^ 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:
|code|
$ 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
|!code|
|^ For more information see the description in the prologue of the program.
(A zero-size message is legitimate with the WebSocket protocol.)
|2WebSocket References|
|bullet|
|item| |%http://en.wikipedia.org/wiki/WebSockets||
|^- Wikipedia overview of WebSockets
|item| |%https://html.spec.whatwg.org/multipage/comms.html#network||
|^- The WebSocket API (JavaScript)
|item| |%http://tools.ietf.org/html/rfc6455||
|^- The WebSocket protocol
|item| |%https://trac.tools.ietf.org/wg/hybi/trac/wiki/FAQ||
|^- The IETF WebSocket FAQ
|item| |%http://websocketstest.com/||
|^- Real-Time Web[socket] Test
|!bullet|
|2WASD "Raw"Socket|
|^ 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.
|!note|
|^ 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.
|3RawSocket Application|
|^ RawSocket applications share the same lifecycle as described for WebSockets
in |link|Websocket Application||.
|3RawSocket Library|
|^ |*rawLIB| is a C code module that provides a similar API and functionality
to the wsLIB library described in |link|Websocket Library||.
|^+ |link%=|/wasd_root/src/websocket/rawlib.c|\
WASD_ROOT:[SRC.WEBSOCKET]RAWLIB.C|
|3RawSocket Application Examples|
|^ 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.
|^+ |link%=|/wasd_root/src/websocket/*.*|\
WASD_ROOT:[SRC.WEBSOCKET]|
|4Chat|
|^ 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.
|^+ |link%=|/wasd_root/src/websocket/raw_char.c|\
WASD_ROOT:[SRC.WEBSOCKET]RAW_CHAT.C|
|4Echo|
|^ Each connected client can enter a message which is then returned to them.
|^+ |link%=|/wasd_root/src/websocket/raw_echo.c|\
WASD_ROOT:[SRC.WEBSOCKET]RAW_ECHO.C|
|4Terminal Server|
|^ This example implements a simple-minded telnet server. Definitely not
intended for production.
|^+ |link%=|/wasd_root/src/websocket/raw_ptd.c|\
WASD_ROOT:[SRC.WEBSOCKET]RAW_PTD.C|
|3RawSocket Configuration|
|^ RawSocket server applications are essentially CGIplus scripts and so are
mapped and activated in the same fashion as any other CGIplus script
(|link|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.
|code|
# WASD_CONFIG_SERVICE
[[http:*:1234]]
[ServiceRawSocket] enabled
# WASD_CONFIG_MAP
[[*:1234]]
map * /cgiplus-bin/raw_script
|!code|
|^ 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.
|code|
# WASD_CONFIG_MAP
[[*:1234]]
if (remote-addr:131.185.10.0/24)
map * /cgiplus-bin/raw_one
else
map * /cgiplus-bin/raw_two
endif
|!code|