Configuration

In this lesson, we will dive into configuration parameters associated with WebSocket endpoints. Simply put, Configuration is nothing but a bunch of (meta) data associated with an endpoint (server or client). You will learn about

  • Server endpoint configuration (for both annotated and programmatic endpoints)

  • Client endpoint configuration (for both annotated and programmatic endpoints)

  • (Server & client) Endpoint Configurators

From an API perspective, a WebSocket endpoint Configuration is represented by the EndpointConfig interface which is extended by ServerEndpointConfig and ClientEndpointConfig for server and client respectively

Server configuration

Before we dive into the details, here is a quick snapshot of the related interfaces

Configuring annotated server endpoints

Annotated server endpoints are configured implicitly via the elements of the @ServerEndpoint annotation. The WebSocket container picks up the value from the annotation elements and creates an instance of EndpointConfig behind the scenes

//annotated server endpoint with all its configuration elements

@ServerEnpdoint(
    value = "/chat/",
    configurator = ChatEndpointConfigurator.class, //discussed later
    decoders = JSONToChatObjectDecoder.class,
    encoders = ChatObjectToJSONEncoder.class,
    subprotocols = {"chat"}
)
public class ChatServer {
    //business logic...
}

The EndpointConfig instance is automatically injected (at run time by the WebSocket container) as a parameter of the @OnOpen method

//server endpoint configuration in action

@OnOpen
public void onOpenCallback(Session session, EndpointConfig epConfig){
    ServerEndpointConfig serverConfig = (ServerEndpointConfig) epConfig;
    Map<String, Object> globalPropertiesMap = serverConfig.getUserProperties();
    ......
}

Configuring programmatic server endpoints

Programmatic endpoints need (explicit) coding as far as configuration is concerned. This is because of the fact that programmatic endpoints are deployed differently and need an instance of ServerEndpointConfig

Don't worry about the deployment aspect since it's covered in detail in the next lesson

Here is where the fluent builder ServerEndpointConfig.Builder comes into picture. Let's look at an example which demonstrates it's usage

ServerEndpointConfig serverConfig = ServerEndpointConfig.Builder
    .create(StockTrackerEndpoint.class , "/pop-stocks/").
    .configurator(StockTrackerConfigurator.getInstance()) //discussed later
    .decoders(JSONToStockTickerObject.class)
    .encoders(StockTickerObjectToJSON.class)
    .build();

An instance of ServerEndpointConfig is made available in the onOpen method of the javax.websocket.Endpoint (as a parameter)

public class ProgrammaticChatClient extends Endpoint {
    @Override
    public void onOpen(Session session, EndpointConfig config){
      ServerEndpointConfig serverConfig = (ServerEndpointConfig) epConfig;
      .....
    }
}

Client configuration

You must have built a fair understanding about WebSocket clients from the WebSocket Client API lesson. They too have configuration parameters associated with them which are used while connecting to WebSocket server endpoints. Before we dive into the details, here is a quick snapshot of the related interfaces

Configuring annotated client endpoints

Annotated client endpoints are configured implicitly via the elements of the @ClientEndpoint annotation

@ClientEndpoint(
    configurator = ChatClientEndpointConfigurator.class, //discussed later
    decoders = JSONToChatObjectDecoder.class,
    encoders = ChatObjectToJSONEncoder.class,
    subprotocols = {"chat"}
)
public class ChatClient {
 //business logic...
}

This instance is automatically injected (at runtime) as a parameter of the @OnOpen method

//server endpoint configuration in action

@OnOpen
public void onOpenCallback(Session session, EndpointConfig epConfig){
  ClientEndpointConfig clientConfig = (ClientEndpointConfig) epConfig;
  ......
}

Configuring programmatic client endpoints

Just like their server side counterparts, configuration for programmatic clients can be coded using a fluent builder API - ClientEndpointConfig

ClientEndpointConfig cec = ClientEndpointConfig.Builder
    .configurator(ChatClientConfigurator.getInstance()) //discussed later
    .decoders(JSONToStockTickerObject.class)
    .encoders(StockTickerObjectToJSON.class)
    .build();

This configuration object is used while initiating connection to a WebSocket endpoint. Please refer to the WebSocket Client API chapter for code samples

Additional notes

  • As you might have already noticed, there is not much of a difference b/w annotated client and server side configurations, except for the fact that a client endpoint does not have the concept of a path or a URL where its listening for connections - that's something that a server endpoint does

  • An EndpointConfig instance provides the capability to store (global) properties which are common to all instances of an endpoint. It does so by providing a getUserProperties() method which exposes a mutable Map

The big picture

Annotated and programmatic endpoint configuration are handled differently, but the end result is the same. Below is a table which illustrates this point for both server and client endpoints

  • For server endpoints, the table shows the mapping b/w corresponding element of the @ServerEndpoint annotation, the corresponding method in ServerEndpointConfig as well as appropriate the method in the ServerEndpointConfig.Builder, and

  • In case of client endpoints, the table shows the mapping b/w corresponding element of the @ClientEndpoint annotation, the corresponding method in ClientEndpointConfig as well as appropriate the method in the ClientEndpointConfig.Builder

Configurators

Basics

Configurators (which in my opinion could have been named differently) are applicable to both server and client side WebSocket endpoints. These are components which can intercept handshake phase of the WebSocket connection lifecycle. They can be used to implement a bunch of things such as

  • customizing the WebSocket handshake process

  • plugging in a custom implementation for producing endpoint instances

  • implementing common logic which can be used by all endpoint instances which are configured using the Configuration with which the Configurator is associated

If the developer does not override (provide a custom implementation) of a Configurator a default one is internally used by the container

Server side

The table below provides an overview. It lists out the methods of a ServerEndpointConfig.Configurator which needs to be overridden to provide custom behavior

The catch If you choose to customize the endpoint creation process, (Java EE) container services like dependency injection might not available since the container default convention is being overridden

Let's look at en example

//custom configurator

public class CustomServerEndpointConfigurator extends ServerEndpointConfig.Configurator {

    @Override
    public <T> T getEndpointInstance(Class<T> endpointClass){
        //override the default behavior by providing a 'Singleton'
        return (T) StockTickerEndpoint.getInstance();
    }

    @Override
    public boolean checkOrigin(String originHeaderValue){
        //just audit this
        audit(originHeaderValue);
        return true;
    }

    private String user;

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response){
        //introspect the request headers
        System.out.println(request);

        //the authenticated user
        this.user = request.getUserPrincipal().getName();

    }

    @Override
    public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested){
        //invoke default implementation
        return super.getNegotiatedExtensions(installed, requested);
    }

    @Override
    public String getNegotiatedSubprotocol(List<String> supported, List<String> requested){
        //invoke default implementation
        return super.getNegotiatedSubprotocol(supported, requested);
    }
}
//declaring the custom configuration

@ServerEndpoint(value = "/letschat" , configurator = CustomServerEndpointConfigurator.class)
public class AnnotatedServerEndpointExample {
  //call back life cycle method(s) implementation...
}

Client side

Client configurators are similar in sprirt to their server counterparts. They slightly less complicated and just define hooks for inercepting the phases before and after the handshake

//custom configurator

public class CustomClientEndpointConfigurator extends ClientEndpointConfig.Configurator {

    @Override
    public void beforeRequest(Map<String,List<String>> headers){
        //mutate the header
        String token = ...;
        headers.put("X-token" , Arrays.asList(token));
    }


    @Override
    public void afterResponse(HandshakeResponse hr){
        //introspect the handshake response
        System.out.println(hr.getHeaders());
    }
}
//declaring the client configuration

@ClientEndpoint(configurator = CustomClientEndpointConfigurator.class)
public class AnnotatedClientEndpointExample {
  //call back life cycle method(s) implementation...
}

Let's move on...

.. and take a closer look at the Deployment related aspects

Last updated