Nimbus

Nimbus

  • Docs
  • Getting Started
  • Help

›Deploying Resources

Documentation

  • Introduction
  • Deployment

Deploying Resources

  • File Storage Bucket
  • Document Store
  • Key-Value Store
  • Relational Database
  • Functions

    • HTTP Function
    • WebSocket Function
    • Document Store Function
    • Key-Value Store Function
    • Notification Function
    • Queue Function
    • Basic Function
    • File Storage Function
    • After Deployment Function
    • Environment Variables

Clients

  • Document Store Client
  • Key-Value Store Client
  • File Storage Client
  • WebSocket Management Client
  • Basic Serverless Function Client
  • Notification Topic Client
  • Queue Client
  • Relational Database Client
  • Environment Variable Client

Local Deployment

  • Unit Testing
  • Local Server

WebSocket Function

WebSocket functions allow you to build up a WebSocket API for realtime communication with clients. You can only have one WebSocket API in a nimbus project (i.e. only one endpoint).

Basic Usage

Using the WebSocketServerlessFunction annotation a topic where the function will be triggered is specified. There are three important topics, these are:

  • $default - Used when the topic selection expression produces a value that does not match any of the other route keys in your API routes. This can be used, for example, to implement a generic error handling mechanism.
  • $connect - The associated route is used when a client first connects to your WebSocket API.
  • $disconnect - The associated route is used when a client disconnects from your API. This call is made on a best-effort basis.

The route selection is done via the request.body.topic expression. This means to select what topic is triggered the message send from the client must contain a field called "topic" where its value is the topic. For example to trigger a function that is listening to the newUser topic a message like this would have to be sent:

{
  "topic": "newUser",
  "username": "user",
  "password": "1234"
}

A corresponding backend function for this could look like:

class WebSocketHandlers {
    
    @WebSocketServerlessFunction(topic="newUser")
    public void newUser(NewUser newUser, WebSocketEvent event) {
        String connectionId = event.getRequestContext().getConnectionId();
        System.out.println("ConnectionId " + connectionId + " registered as new user: " + newUser);
    }
    
    class NewUser {
        public String username;
        public String password;
    }
}

Method Details

Parameters

A WebSocket serverless function method can have at most two parameters. One of these is a WebSocketEvent type, which contains the header and query parameters, as well as the connection id. The second method parameter available is a custom user type which will be read and deserialized from the JSON body of the request. This is done using the jackson library, so any customisation you want can be done using jackson annotations in the target class. An example with both parameters is shown above.

The order of the parameters supplied to the method does not matter, and one or both can be left out.

Return type

The return type is unused in the cloud, instead to send a message back to the client you should use the ServerlessFunctionWebSocketClient. This class allows you to send messages to a ConnectionId. It is important to note that messages cannot be sent to a client until after the $connect function has returned.

Connection Method Details

To accept a client connection the function must return successfully (i.e. no exceptions). If you want to refuse a client connection this function must throw an exception (this is caught by nimbus and translated to a refused connection). You also cannot sent messages to a client until this method has returned successfully.

Example WebChat Server

Here is an example WebChat server implemented using nimbus. It is very basic, allowing users to send messages to each other.

public class WebSocketApi {

    private ServerlessFunctionWebSocketClient webSocketClient = ClientBuilder.getServerlessFunctionWebSocketClient();
    private DocumentStoreClient<UserDetail> userDetails = ClientBuilder.getDocumentStoreClient(UserDetail.class);
    private KeyValueStoreClient<String, ConnectionDetail> connectionDetails = ClientBuilder.getKeyValueStoreClient(String.class, ConnectionDetail.class);

    @WebSocketServerlessFunction(topic = "$connect")
    @UsesDocumentStore(dataModel = UserDetail.class)
    @UsesKeyValueStore(dataModel = ConnectionDetail.class)
    public void onConnect(WebSocketEvent event) throws Exception {
        String connectionId = event.getRequestContext().getConnectionId();
        String username = event.getQueryStringParameters().get("user");
        UserDetail validUser = userDetails.get(username);
        if (validUser != null) {
            ConnectionDetail connectionDetail = new ConnectionDetail(username);
            System.out.println("Adding " + connectionDetail.getUsername() + " with connection " + connectionId);
            connectionDetails.put(connectionId, connectionDetail);
            if (validUser.getCurrentWebsocket() != null) {
                connectionDetails.delete(validUser.getCurrentWebsocket());
            }
            validUser.setCurrentWebsocket(connectionId);
            userDetails.put(validUser);
        } else {
            throw new Exception("Not a valid user");
        }
    }

    @WebSocketServerlessFunction(topic = "$disconnect")
    @UsesDocumentStore(dataModel = UserDetail.class)
    @UsesKeyValueStore(dataModel = ConnectionDetail.class)
    public void onDisconnect(WebSocketEvent event) {
        String connectionId = event.getRequestContext().getConnectionId();
        ConnectionDetail disconnectedUser = connectionDetails.get(connectionId);
        if (disconnectedUser != null) {
            UserDetail validUser = userDetails.get(disconnectedUser.getUsername());
            if (validUser != null) {
                validUser.setCurrentWebsocket(null);
                userDetails.put(validUser);
            }
        }
        connectionDetails.delete(event.getRequestContext().getConnectionId());
    }

    @WebSocketServerlessFunction(topic = "sendMessage")
    @UsesServerlessFunctionWebSocketClient
    @UsesDocumentStore(dataModel = UserDetail.class)
    @UsesKeyValueStore(dataModel = ConnectionDetail.class)
    public void onMessage(WebSocketMessage message, WebSocketEvent event) {
        UserDetail userDetail = userDetails.get(message.getRecipient());
        ConnectionDetail connectionDetail = connectionDetails.get(event.getRequestContext().getConnectionId());
        if (userDetail != null && connectionDetail != null) {
            System.out.println();
            webSocketClient.sendToConnectionConvertToJson(userDetail.getCurrentWebsocket(),
                    new Message(message.getMessage(), connectionDetail.getUsername()));
        }
    }

    @AfterDeployment
    @UsesDocumentStore(dataModel = UserDetail.class)
    public void setupBasicUsers() {
        UserDetail thomas = new UserDetail("thomas", null);
        UserDetail bob = new UserDetail("bob", null);

        userDetails.put(thomas);
        userDetails.put(bob);
    }
}

It is recommended to keep track of connection ids that one of the stores are used.

Deployment Information

Many of these methods can be written to build up a WebSocket api. Once it has been deployed the base URL will be reported.

Any logging lines or print statements will appear in the cloud providers log service (e.g. AWS CloudWatch).

Annotation Specification

@WebSocketServerlessFunction

Required parameters

  • topic - The topic that will trigger the function

Optional Parameters

  • timeout - How long the function is allowed to run for before timing out, in seconds. Defaults to 10.
  • memory - The amount of memory the function runs with, in MB. Defaults to 1024.
  • stages - The stages that the function is deployed to.
← HTTP FunctionDocument Store Function →
  • Basic Usage
  • Method Details
    • Parameters
    • Return type
  • Deployment Information
  • Annotation Specification
    • @WebSocketServerlessFunction
Nimbus
Docs
Getting StartedDocumentation
More
GitHubStar
Copyright © 2019 Thomas Allerton