WARNING: This server provides a static reference view of the NetKernel documentation. Links to dynamic content do not work. For the best experience we recommend you install NetKernel and view the documentation in the live system .

WebSockets

WebSockets allow a Browser to initiate an HTTP request to a server and request that the server keep the connection open. The open connection becomes a bi-directional asynchronous message "pipe" through which the browser, or the server, may send the other party messages.

An HTML5 browser provides a Javascript WebSocket object to which handler functions may be bound for "onopen", "onclose", "onmessage" and "onerror" events.

Both the Client and Server-sides of a WebSocket are free to close it at any time.

The messages you can send are not prescribed. Essentially you can consider them as UTF-8 Strings (at least today and for the foreseeable future). You're free to implement any message format and application level message exchange protocol you like. (as we'll see below, it can be quite convenient to use JSON as the message exchange format, but if you've got a hankering to send CSV you're more than welcome).

The initiating HTTP request which is held open can be considered as something like a meta-data envelope and provides all the usual web headers as well as Cookies etc.

WebSocket Step 1: Create the raw WebSocket

When the HTTPTransport receives a WebSocket request, it looks like a regular HTTP request and has a URL of http://host/path. (it looks like a regular http request to get through proxy's etc as discussed in the primer section above)

However it includes a header requesting that the protocol be upgraded to a WebSocket. The NetKernelWebSocketHandler will upgrade the http request to a WebSocket.

As in the standard case the HTTPTransport issues a request into the NK fulcrum address space, only it now includes not only the HTTPServlet request (for the external envelope HTTP headers etc) it also includes a WebSocket object.

As with a Client-side JavaScript WebSocket, the server-side WebSocket has methods for open, message, disconnect events. It also has an Outbound interface through which a message can be sent from the server to the client. The server can also close the socket at any time.

If you want direct access to this to-the-metal POJO you're welcome to deal with it directly in an endpoint of your own. However, as with the regular HTTP request, the HTTPBridge will help out. It normalizes the raw WebSocket and provide a uniform Resource Oriented interaction model.

WebSocket Step 2: HTTPBridge creates a WebSocket Transport and spawns a Fulcrum

In this figure we see the HTTPBridge has received a request with a raw WebSocket associated with it. The bridge creates a new space "WebSocket Fulcrum" and populates that space with a WebSocketTransport.

Raw WebSocket events are translated by the WebSocketTransport into ROC requests into this new WebSocketFulcrum.

httpRequest:/

Just like with a regular HTTP request, the WebSocketFulcrum space still provides the full httpRequest:/ resource set. It therefore allows you to access all of the initial HTTP state provided with the initiating HTTP request (eg httpRequest:/header/xxx, or httpRequest:/param/yyy).

Importantly this means that any authentication or correlated Cookie state that has been established for the containing HTTP envelope, is accessible to your endpoint.

So if you have a GateKeeper or Session overlay around your application, they do not need to be changed, they can continue to use the httpRequest:/ to determine authorization headers and correlate cookie session state etc.

In short implementing WebSockets in your secured web application is seamless, the external architecture stays the same.

One thing that is different is that a WebSocket has no httpResponse: space - since at the HTTP level, there is nothing to write a response to. The protocol was switched when the HTTPTranport upgraded the connection to a WebSocket. Therefore the HTTPBridge, having spawned the new WebSocketTransport, now doesn't issue any request into the application space, it just returns a response to the HTTPBridge.

Spawned and Ready: WebSocket Applications Start Here

At this point, you can consider that the HTTPTransport and HTTPBridge's work is done. So what does the WebSocketTransport do?

WebSocket Step 3: WebSocket ROC Requests

This diagram shows the relationship between the new WSFulcrum its WSTransport and your application space.

As we've already discussed a WebSocket is asynchronous and can generate events for "onopen", "onmessage" and "ondisconnect". Plus, the serverside can issue messages and disconnect the socket at any time.

The table below shows how the WebSocket events are mapped to ROC requests that you can handle with your application endpoint.

WS Event ROC Verb ROC Request Primary Argument Response
ONOPEN NEW ws:/PATH (where PATH is the same as the PATH in the envelope HTTP request, (ie the same as in the client-side WebSocket URL). WebSocket Session - a POJO to which you can call getRemote().sendString() or disconnect()

Additionally, if the WebSocket has specified a protocol, then the NetKernel request header WS_PROTOCOL will be provided on the NEW request and has the value of the external HTTP "Sec-WebSocket-Protocol" header.
Optional: a String unique identifier for this WebSocket. If a null is returned, the WebSocket transport will create its own unique identifier. (Hereafter we'll refer to this identifier as ''XYZ'')
ONMESSAGE SINK ws:/PATH+socketid@XYZ String. The received message as a unicode String. ---
ONDISCONNECT DELETE ws:/PATH+socketid@XYZ +status@STATUS+reason@REASON An integer websocket close status argument is passed. The close status start at 1000 for normal close and extend upwards. A string reason message argument may also be provided. A boolean true if the DELETE is successful.

wsResponse:/

Whenever the WSTransport issues a request your receiving endpoint can deal with it in any way it chooses to support the application model it implements.

You'll see in the demos you can do request-response patterns, pub-sub patterns and server-side event generation patterns. All these are possible and are up to you to implement and boil down to how you choose to deal with the NEW,SINK,DELETE requests that originate from the WSTransport.

But if from the server side, you want to send a message or disconnect the socket, you have two choices.

Firstly, you have contextual access to the WebSocket. Whenever you are handling a NEW,SINK or DELETE request from the transport you can SINK to the wsResponse:/ space

  • wsResponse:/message - SINK a String to this resource to send a message back to the same WebSocket
  • wsResponse:/disconnect - SINK a boolean true, to force the current WebSocket to close. (Note this will also trigger the WSTransport to issue a DELETE ws:/PATH+socketid@XYZ request back to you so you can clean up any state you might have for that socket)

In addition, you can see that WebSockets almost by definition require you to hold server-side state. This state is correlated using the unique socketid@XYZ argument. When the NEW request comes in when the WebSocket first connects, you are free to hold on to a reference to the Outbound POJO (presumably you'd store it under the socketid you create for the NEW response - see the groupTherapy demo for example).

You can therefore implement your endpoint with an ROC interface that allows you to send or disconnect a given socketid based on any application generated state change you choose.

If this all sounds too detailed, don't worry, look at the code in the two demo endpoints and you'll see that its actually really pretty simple stuff to deal with.

WebSockets means many Fulcrums

You're probably comfortable with the idea of the Fulcrum pattern as the host space for a transport. You'll probably also usually have relatively few in your system and set up your imports so that your applications are exposed to those fulcrums (and their transport originated root requests).

This diagram shows that with the WebSocket model, every external WebSocket from a remote client means there is an independent fulcrum with a transport for that WebSocket.

This is probably not how you've thought about ROC spaces before. But essentially this is an example of exposing your application simultaneously to many many fulcrums (as many as you like).

But you don't need to worry about this. Spaces are very cheap. From the Kernel/ROC system point of view a space is as cheap to instantiate as a request.

Message Format: JSON Resource Model

The WebSocket standard does not prescribe the format of your messages.

JSON is very easy to create and process in the client-side Javsacript code. HTML5 now provides the JSON object which offers stringify() and parse() methods to quickly serialize and parse JSON serializations.

In the demo's you'll see examples like this..

    var data={ type: "foo", message: "bar" }
    myWebSocket.send(JSON.stringify(data));

Where JSON.stringify() serializes the JSON data for the wire like this...

{"type":"foo", "message":"bar"}

Which is the message received by the server-side WebSocket.

With NK's json:core library, its just as easy to deal with on the server-side.

Just import urn:org:netkernel:json:core to your application space and then in your endpoint you can just source the message as a JSONObject like this, which will automatically transrept it...

json=context.sourcePrimary(JSONObject.class)
json.type     //The "type" value
json.message  //The "message" value

WebSockets, ROC and Caching

You'll see that by placing the WebSocket into a transport, its interaction with the ROC domain is through simple requests. One obvious pattern you'd be likely to want to have is to consider the message exchange as triggering internal requests for ROC resources.

The benefit should be obvious - you can have tens or tens of thousands of open websockets all asynchronously firing into your endpoint. This endpoint can then issue a request for some internal ROC service to fulfil a response or to do a pub-sub dispatch etc.

Whatever way you choose to create outbound messages, they are resources, they can be cached and the cost of creating the message (maybe a DB call?) can be ammortized across all of the outbound sockets!

With WebSockets the Web is no longer web-like. But with WebSockets on NK its resource oriented on the inside but not on the outside!

Security Considerations

One final word of caution. Keep your wits about you. WebSockets aren't like HTTP requests - they can send you any message they like and there is no authentication on a message - that will always have happened with the initialisation and is external to you.

I don't know if there are any Javascript specific WebSocket security weaknesses - but I can imagine that injecting arbitrary messages to an open WebSocket would be a real high priority vector of attack for the black-hat hackers.

So one thing to consider. You would never want to have your application's message protocol include a resource identifier within it directly.

For example, don't have message formats like this...

{ "request" : "active:sqlQuery..." }

And then have your endpoint code do this...

req=context.sourcePrimary(JSONObject.class).request
resp=context.source(req)

You can very happily issue resource requests to fulfil a message. Just don't do a pattern where you literally request what the client tells you to do! As your Gran would have said "You wouldn't put your head in the oven if you were asked to!"