Project Guide
Unlike traditional Web Servers rubris-io adopts a slightly different stance in order to be used more as a protocol library embedded in an application and allow the application to control some of the runtime aspects normally associated with web servers.
Rubris-io consists of a client side javascript file that defines the client side of the protocol and a server jar that implements the server side protocol, HTTP and WS handling and termination, and supporting functions. It does not require any other web server or framework libraries to run on the server.
Client Protocol
In order to support the HTTP -> Websocket upgrade, client heartbeating and standardisation of the packet handling from the client rubris-js leverages the widely used Engine-IO https://github.com/socketio/engine.io. This was for a number of reasons:
- It provides a standard connect/upgrade heartbeat mechanism that is used by a large number of Socket-IO libraries and applications
- The client Server protocol provided by rubris-js could be easily replaced without large scale change of the client libraries.
- Binary data over XHR and websockets is supported (unlike most other JS Websocket libraries).
- Engine-IO uses an event-emitter pattern implementation which provides a very lightweight integrated callback mechanism that can be used by rubris-js without requiring user’s client code to do anything different than using Engine-IO natively (see the client-guide for more details).
However, rubris-js does not support a couple of standard engine-io features for text protocol emulation of binary data and native JSON support (base64 encoded binary messages or jsonp).
Instead, it expects all its messages to be binary in nature as it is primarily focused on the binary websocket/long polling path and binary XHR payloads. Rubris-io’s protocol is entirely binary and does not use JSON or JSONB structures.
JSON is NOT supported as a text transport format for the messages, although it is simple to use JSON as the message body if you want to send it up and down the wire as the payload type and marshall and unmarshall it as your payload type.
Rubris also supplies its own Engine-IO compatible Java client to enable load testing and scenario testing to be driven from Java (as the existing Java client is not suited to driving load tests).
APIs
In general most of the interaction with the client is through the rubris-io protocol API Interfaces. These interfaces are asynchronous in all cases (except 1) and use callback handles as the main idiom.
The API supports protocol behaviours of
- Request/Reply single message calls (RPC).
- Shared Subscriptions (all users subscribed to the same topic share the same data) (PUSH)
- Group Shared Subscriptions (all users allocated to a group see the same data on the same topic - multiple groups with the same topic can exists independently) (PUSH)
- Private Subscriptions (Users have a private copy of the same topic Id) (CONVERSATIONS)
This leads to a general pattern of:
public interface AsynchronousHandler { public void onMessage(User user, Object message, DataHandle handle); }
Endpoints
All messaging is centered on the concept of Endpoints. An Endpoint must be unique to the type of protocol. So for instance if an RPC Endpoint name is configured it cannot be used as a subscription Endpoint (and vice versa). Endpoint names are arbitrary Strings chosen by the users of the library and there is no restriction on the number of these.
The Endpoints must be predefined as part of the configuration (see the getting started guide for details).
It is expected that the number of type of endpoints fall out of the specific use cases of functionally related behaviour. For example a trading app may have one or more MarketData shared Subscription endpoints, one or more system status endpoints, one or more Conversation endpoints for trading/RFQs and perhaps a number of RPC endpoints for more general operations that do not require multiple responses.
For subscription Endpoints a more granular concept of topics is supported. An Endpoint can have any number of topics which are both flat in namespace and private to the endpoint. Each topic can be subscribed to and unsubscribed to individually (or in bulk). The topics are not hierarchical and there is no concept of wildcards. Topics can be brought into existence dynamically by the act of subscribing; there is no need to predefine them.
For example: defining shared subscription Endpoint of “prices” we could subscribe to topics on the client side with each topic consisting of an ISIN. Each user subscribing to the same ISIN will end up subscribed to the same topic instance in the “prices” Endpoint on the server (if it is a shared endpoint).
Types and behaviours are more fully discussed in the Handlers Guide
Direct Paths
Even though the majority of behaviour runs over the protocol channels provided by rubris-io there are use cases for direct path handling of raw data. Two use cases that are prime candidates for this are: - XHR preflight authentication for SSO cookies - SSO redirect for SAML logins and landing pages.
Both of these scenarios are detailed in the SSO examples guide.
See the api guide for more details of using direct paths.
Authorisation
Rubris supports 2 levels of authorisation. - Authorising a new connection from the browser - Authorising the use of an endpoint by a user
The connection level authorisation is intentionally abstracted from the functional endpoint authorisation and it is expected that in normal usage these are actually different questions.
E.g Is the connection from the browser authorised to be allowed? (via the use of cookies or headers usually from an SSO provider) is a different question and entirely orthogonal to is the user allowed to connect to an endpoint on the server that represents a logical stream or data group?
Connection Authorisation
Authorising the connection delegates each new browser connection (whether HTTP or WS) to a connection handler configured by the user on the server which has the responsibility to accept or deny the connection.
In general one would expect this to be mediated by Cookies or Custom Headers set in the browser, but rubris-io does NOT manage sessions in a Cookie/HTTP sense, although there is a Client instance SID which is passed on all requests. This has a number of advantages, the primary one is that multiple client instances can coexist in the same browser window. Any session abstraction above this is managed by the user application.
Although this sounds a little odd for those coming from a web background it enables the user library to decide when to set session cookies, how they are generated, how long they live for, how to validate them on connection, how they are renewed etc especially if an SSO solution is in place or cross-domain XHR/WS is the common scenario. The server provides simple mechanisms for managing this and examples are given in the SSO guide.
It is important to realise that when, for instance, the server is running behind a proxy or the Amazon ELB the connection does not represent a user connection. this mechanism is useful for checking connections come from a known IP or possess some token on the initial request.
Client Authorisation
The client in this respect is essentially an Engine-IO client instance. A callback is available to authorise (or deny) the creation of a client instance in the browser. In many ways this is most similar to the Session construct (although it differs in that multiple client instances can co-exist in the same browser window scope).
This is the callback that provides a more traditional user-level authorisation.
Endpoint Authorisation
Over and above authorising the connection, the user can add to each endpoint a callback to control if the user can use the a particular endpoint.
The User
In Rubris the definition of what a user Object is and what is contained in it is entirely delegated to the user application as a function of the Client authorisation handlers.
More discussion for these is given in the Handlers guide