Vibe has been renamed to Cettia and is no longer maintained! Use Cettia. ×
flowersinthesand wrote Vibe 3.0.0-Alpha12 released on February 5, 2015.

Preparing for your Atmosphere app to Vibe: Simple!

wrote this on

The complete support of Atmosphere 2 is still ongoing but the current Vibe may meet your requirements. Let's take a look how Atmosphere's popular chat example application can be migrated into Vibe. That application should meet the followings:

  • URI is http://localhost:8080/chat
  • If a client sends message event, a server sends broadcasts it to every client that connected to the server.

Server

src/main/java/org/atmosphere/samples/chat/Bootstrap.java substitutes src/main/webapp/WEB-INF/web.xml and src/main/java/org/atmospheres/samples/chat/Chat.java. For the sake of convenience, Java 8's lambda expressions is used here but the example is written in Java 7. $ is just unused variable.

@WebListener
public class Bootstrap implements ServletContextListener {
    private final Logger logger = LoggerFactory.getLogger(Bootstrap.class);
    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void contextInitialized(ServletContextEvent event) {
        // A
        final Server server = new DefaultServer(); 
        // B
        server.socketAction((ServerSocket socket) -> {
            logger.info("{} is opened", socket.id());
            // C
            socket.closeAction($ -> logger.info("{} is closed", socket.id()));
            // D
            socket.errorAction((Throwable t) -> logger.info("{} got an error {}", socket.id(), t));
            // E
            socket.on("heartbeat", $ -> logger.info("heartbeat sent by {}", socket.id()));
            // F
            socket.on("message", (Map<String, Object> data) -> {
                Message message = mapper.convertValue(data, Message.class);
                logger.info("{} just sent {}", message.getAuthor(), message.getMessage());
                // G
                server.all().send("message", message);
            });
        });
        // H
        new AtmosphereBridge(event.getServletContext(), "/chat").httpAction(server.httpAction()).websocketAction(server.websocketAction());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}
  • A final Server server = new DefaultServer();

Server is a facade to the Vibe API. You can instantiate Server by yourself unlike the typical usage of Atmosphere, which means you have the full right to decide how you will use it. For example, to inject Server to anywhere you want, you can delegate it to Dependency Injection framework by yourself. See the working examples.

  • B server.socketAction((ServerSocket socket) -> {});

As a controller to handle socket, Server accepts a function similar to AtmosphereHandler. Because server.socketAction allows to add several actions before and after installation, you can write a POJO controller by injecting Server like @ManagedService. For example, with the help of Spring:

@Component
public class Chat {
    @Autowired
    private Server server;

    @PostConstruct
    public init() {
        server.socketAction((ServerSocket socket) -> {});
    }
}

An action added via socketAction receives a opened ServerSocket like a method annotated with @Ready.

  • C socket.closeAction($ -> {});

ServerSocket allows to add event handlers in the form of functional interface through on method, which is convenient to use Java 8's lambda expressions. socket.closeAction is called when a socket is closed for some reason like a method annotated with @Disconnect.

  • D socket.errorAction((Throwable throwable) -> {});

Because close event is called without argument, you may wonder how socket was closed. If it was due to some error, socket.errorAction is executed with Throwable in question before close event.

  • E socket.on("heartbeat", $ -> {});

The heartbeat plays an important role in maintaining a connection in Vibe as well as Atmosphere. In Vibe, it's implemented using custom event. You can hear heartbeat by adding heartbeat event handler like a method annotated with @Heartbeat.

  • F socket.on("message", (Map<String, Object> data) -> {});

The unit of to be received and sent of Atmosphere is a plain string but that of Vibe is an event object. Therefore, the message event has no special meaning and is just yet another custom event like heartbeat. That event handler is called when a client sends a message event like a method annotated with @Message. Event data's type depends on the event format and here because the event format is JSON and the client is supposed to send an object to message event, Map<String, Object> is the corresponding type of message event and vice versa. You can use any event name except reserved ones like close and error.

Though object is exchanged, using a plain map is inconvenient. A kind of Encoder and Decoder will be provided in upcoming release. Until then, you can convert data type using library like Jackson or Apache Commons BeanUtils.

  • G server.all().send("message", message);

To broadcast an event to every client, you can use server.all().send(event, data) like using Broadcaster.broadcast or using an return value of a method annotated with @Message. Otherwise instead you can handle each socket using server.all(socket -> {}):

socket.on("message", data -> server.all(socket -> socket.send("message", data)));

Though, notifying something of every client is not common. Practically, you will utilize tag a lot to handle a group of socket:

socket.tag("room").on("message", data -> server.byTag("room").send("message", data));

Also it's possible to attach callbacks receiving the result of event processing when sending event and to handle those callbacks when receiving event:

socket.send("/account/find", "donghwan", account -> System.out.println(account), reason -> System.err.println(reason));
socket.on("/account/find", reply -> {
    try {
        reply.resolve(entityManager.find(Account.class, reply.data()));
    } catch(Exception e) {
        reply.reject(e.getMessage());
    }
});

For details, see the reference documentation.

  • H new AtmosphereBridge(servletContext, "/chat").httpAction(server.httpAction()).websocketAction(server.websocketAction());

This part provides a bridge between Server and the underlying platform, Atmosphere. It installs and configures Atmosphere automatically so you don't need to manipulate web.xml (With Servlet 3, Atmosphere can be installed like this). Thankfully, Atmosphere provides a solid abstraction layer for Servlet containers and Java WebSocket API implementations Vibe use it as a platform.

You can replace the platform with others like Vert.x without modifying the code. See the working examples.

Client

Most code in src/main/webapp/javascript/application.js are not modified except socket handling.

$(function () {
    "use strict";

    var content = $('#content');
    var input = $('#input');
    var status = $('#status');
    var myName = false;
    var author = null;
    var logged = false;

    // A
    var socket = vibe.open("/chat");
    // B
    socket.on("connecting", function() {
        content.html($('<p>', {text: 'Connecting to the server'}));
    });
    // C
    socket.on("open", function() {
        content.html($('<p>', {text: 'Connection opened'}));
        input.removeAttr('disabled').focus();
        status.text('Choose name:');
    });
    // D
    socket.on("error", function(error) {
        content.html($('<p>', {text: 'There was an error in connection ' + error.message}));
        logged = false;
    });
    // E
    socket.on("close", function() {
        content.html($('<p>', {text: 'Connection closed'}));
        input.attr('disabled', 'disabled');
    });
    // F
    socket.on("waiting", function(delay, attempts) {
        content.html($('<p>', {text: 'Trying to reconnect ' + delay + '. Reconnection attempts ' + attempts}));
    });
    // G
    socket.on("message", function(data) {
        if (!logged && myName) {
            logged = true;
            status.text(myName + ': ').css('color', 'blue');
        } else {
            var me = data.author == author;
            var date = typeof(data.time) == 'string' ? parseInt(data.time) : data.time;
            addMessage(data.author, data.message, me ? 'blue' : 'black', new Date(date));
        }
    });

    input.keydown(function(e) {
        if (e.keyCode === 13) {
            var msg = $(this).val();
            if (author == null) {
                author = msg;
            }
            // H
            socket.send("message", {author: author, message: msg});
            $(this).val('');
            if (myName === false) {
                myName = msg;
            }
        }
    });

    function addMessage(author, message, color, datetime) {
        content.append('<p><span style="color:' + color + '">' + author + '</span> @ ' +
            + (datetime.getHours() < 10 ? '0' + datetime.getHours() : datetime.getHours()) + ':'
            + (datetime.getMinutes() < 10 ? '0' + datetime.getMinutes() : datetime.getMinutes())
            + ': ' + message + '</p>');
    }
});
  • A vibe.open("/chat");

vibe.open opens a socket to a given URI like atmosphere.subscribe(request). It takes an option object as a second argument, but since options like transport and heartbeat are set by the server in handshaking, you don't need to set them explicitly. Actually, you don't even need to be aware of what 'transport' is when using Vibe.

  • B socket.on("connecting", () => {})

connecting event is fired when the transport is selected and starts connecting to the server. This is a first event of socket's life cycle where you can handle socket.

  • C socket.on("open", () => {})

open event is fired when the connection is established successfully and communication is possible like request.onOpen.

  • D socket.on("error", error => {})

error event is fired when there is an error in connection with error object in question like request.onError, request.onClientTimeout and request.onTransportFailure. Unlike Atmosphere, there's nothing you should do for connection in this event.

  • E socket.on("close", () => {})

close event is fired when the connection has been closed, has been regarded as closed or could not be opened like request.onClose. Regardless of whether the connection is closed normally or abnormally, this event is always fired on disconnection. If you've disabled reconnection, this event is a destination of socket's life cycle. Here, since reconnection is enabled by default, next waiting event will be fired.

  • F socket.on("waiting", (delay, attempts) => {})

waiting event is fired when the reconnection is scheduled with the reconnection delay in milliseconds and the total number of reconnection attempts like request.onReconnect. The socket connects to the server after the reconnection delay and then new life cycle starts and connecting event is fired.

  • G socket.on("message", data => {})

As mentioned in above, message event is just yet another custom event you can use and event data, data, is a JSON object not string as the server sent an object so that you don't need to parse it to JSON.

Also you may notice that message event handler actually does two things: setting username and broadcasting a message. That was necessary in Atmosphere since request.onMessage is the only handler receiving message from server. But in Vibe, you can split message event into two other events for separation of concerns using custom event.

  • H socket.send("message", {author: author, message: msg})

As mentioned in above, you can send an event with object data so that you don't need to format object to JSON. If you formatted it to string, the server will receive that string not object. Like server, client can attach callbacks in sending event and handle those callbacks in receiving events as well.

vibe.open(uri).on("open", () => {
    this.send("/account/find", "donghwan", account => console.log(account), reason => console.error(reason));
    this.on("/account/find", (id, reply) => {
        try {
            reply.resolve(entityManager.find("account", id));
        } catch(e) {
            reply.reject(e.message);
        }
    });
});

Getting this example

You can find this example on GitHub.


Vibe 3.0.0-Alpha3 released

wrote this on

I'm happy to to release Vibe Protocol 3.0.0-Alpha3, Vibe JavaScript Client 3.0.0-Alpha3, Vibe Java Server 3.0.0-Alpha3 and Vibe Java Platform 3.0.0-Alpha2. The main focus of this release is error handling, a consistent way to deal with error.

In next phase, we will complete the life cycle of ServerHttpExchange through explicit read method, reading request by chunk and handling binary. Then, we will be able to create bridge for other platforms like Netty. If you have any feedback, please let us know and we really appreciate them.


Vibe 3.0.0-Alpha2 released

wrote this on

It's my pleasure to announce the second alpha of Vibe Protocol, Vibe JavaScript Client and Vibe Java Server. The main focus of this release is handshaking, an entry point for authentication and protocol negotiation.

In Alpha3, we will focus on how to capture and handle exception thrown all over the place. If you have any feedback, please let us know and we really appreciate them.


Writing Chat Application

wrote this on

This guide walks you through the process of building a very simple chat application meeting the followings:

  • URI is http://localhost:8080/vibe.
  • If a client sends echo event, a server sends it back to the client that sent it.
  • If a client sends chat event, a server broadcasts it to every client that connected to the server.

Here we are going to use Vibe Java Server and Vibe JavaScript Client and also to look at how flexible it is to integrate with other technologies like dependency injection and clustering.

Setting up environment

Server

You need to have Java 7 or later installed and also decide application platform to embed Vibe. In Java, there are lots of many kinds of frameworks and platforms where you can build an application and you have probably already chosen the platform and tried to find out if Vibe is available on that platform.

To write true Java Server which is able to run on any platform seamlessly in Java, a simple abstraction layer for such platforms, Vibe Java Platform, is created. Therefore, you can run Vibe application on a platform you like without effort if Vibe Java Platform supports it and with some effort if not. Now Atmosphere, Vert.x, Servlet, Java WebSocket API and Play framework are supported. Here we are going to use Atmosphere with Jetty but complete examples are provided per each platform.

Let's set up a basic build script, pom.xml, using Maven.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.atmosphere</groupId>
    <artifactId>simple-chat</artifactId>
    <version>0.1.0</version>
    <dependencies>
        <dependency>
            <groupId>org.atmosphere</groupId>
            <artifactId>vibe-server</artifactId>
            <version>3.0.0-Alpha1</version>
        </dependency>
        <dependency>
            <groupId>org.atmosphere</groupId>
            <artifactId>vibe-platform-server-atmosphere2</artifactId>
            <version>3.0.0-Alpha1</version>
        </dependency>
        <dependency>
            <groupId>org.atmosphere</groupId>
            <artifactId>atmosphere-runtime</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.2.3.v20140905</version>
            </plugin>
        </plugins>
    </build>
</project>

And then you can run this application on Jetty using mvn jetty:run. It will bind the application to localhost and at port 8080.

Client

You need a web browser. Because JavaScript Client follows jQuery 1.x's browser support policy, almost all browsers are available including Internet Explorer 6. Also we are going to use the browser's JavaScript console to communicate with the server interactively instead of writing static HTML. Though, if you need an editor, you can start editing from a prepared JS Bin.

This guide already loaded JavaScript Client, vibe.js, open a JavaScript console and type the following to confirm it's loaded. Technically, this way is a kind of cross-origin connection. Because cross-origin connection is more limited than same-origin connection, you won't have no problem to use same-origin connection. Though, if you want to test it, connect to http://localhost:8080/, open JavaScript console and load the script by copy and paste of contents of vibe.js.

vibe

Instead of browser's JavaScript console, if you have Node.js installed, you can run client on Node.js console as well. Type the following on system console to install JavaScript Client.

npm install vibe-client

And then load it to Node.js console.

var vibe = require("vibe-client");

URI design

Generally you would try to embed Vibe in your application. If you have done something using URI like filtering and authentication, the final form of URI may be an issue because it may be not negotiable and restrict your idea.

Here is the first line of HTTP request to receive an event from the server and the first line of HTTP request to send an event to the server when using sse transport and http://localhost:8080/vibe URI:

GET /vibe?id=d009d9f3-088f-4977-8ec0-99576f84cb4c&_=32fb5d&when=open&transport=sse&heartbeat=20000 HTTP/1.1
POST /vibe?id=d009d9f3-088f-4977-8ec0-99576f84cb4c&_=6a5e3d HTTP/1.1

As you can see, only query string component of URI is used to pass necessary information so you can safely manipulate path component of URI and utilize it for your application. In other words, you should be aware of reserved request parameters if you are going to manipulate query string.

Opening socket

Let's write a simple server and client focusing what happens when a socket is opened and closed. Now we generate network traffic so that you can observe what happens in network by using packet analyzer like Fiddler or Wireshark.

Server

Create a src/main/java/simple/Bootstrap.java that is a server application and use mvn jetty:run to run the application.

package simple;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.atmosphere.vibe.platform.Action;
import org.atmosphere.vibe.platform.server.atmosphere2.AtmosphereBridge;
import org.atmosphere.vibe.server.DefaultServer;
import org.atmosphere.vibe.server.Server;
import org.atmosphere.vibe.server.ServerSocket;

@WebListener
public class Bootstrap implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        final Server server = new DefaultServer();
        server.socketAction(new Action<ServerSocket>() {
            @Override
            public void on(final ServerSocket socket) {
                System.out.println("client connected");
                socket.on("close", new VoidAction() {
                    @Override
                    public void on() {
                        System.out.println("client disconnected");
                    }
                });
            }
        });
        new AtmosphereBridge(event.getServletContext(), "/vibe").httpAction(server.httpAction()).websocketAction(server.websocketAction());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}

How to install Vibe is simply to feed HTTP exchange and WebSocket into Server using the bridge for the specific platform. Even you can pass HTTP exchange and WebSocket created by different servers to one Server such as from Jetty listening on port 8080 and Tomcat listening on port 8090.

Here once Jetty starts up, public void contextInitialized(ServletContextEvent event) is executed. Then AtmosphereBridge installs Atmosphere under the path of /vibe and delegates HTTP exchange and WebSocket to Server. If you are going to use other platform, only this portion is needed to be changed according to the platform usage.

Server consumes HTTP exchange and WebSocket and produces and manages ServerSocket. When a client connects successfully and a corresponding socket is opened, actions added via socketAction(Action<ServerSocket> action) are executed with it.

ServerSocket has a special event, close, which is fired when the socket has been closed. We will cover it in next topic in detail.

Client

Open your JavaScript console and type the following:

var socket = vibe.open("http://localhost:8080/vibe");
socket.on("open", function() {
    console.log("opened");
});
socket.on("close", function() {
    console.log("closed");
});

And then check server console and client console. You'll see both consoles display the client has connected to the server. To see when close event is fired, you need to shut down the server by Ctrl + C or close the socket by socket.close(). If you shut down and restart the server, you may realize the socket tries to connect to the server again automatically. Unlike server, when you handle client, you need to be aware of the following socket life cycle.

  • preparing - As an initial state of life cycle, it's internally used during reinitializing socket state and selecting transport.
  • connecting - The selected transport starts connecting to the server and the connecting event is fired.
  • opened - The connection is established successfully and communication is possible. The open event is fired.
  • closed - The connection has been closed, has been regarded as closed or could not be opened. The close event is fired.
  • waiting - The socket waits out the reconnection delay. The waiting event is fired.

Note that you can determine these state by socket.state(). Reconnection is clearly necessary in production but annoying in development. To suppress reconnection, set reconnect option to false i.e. vibe.open("/vibe", {reconnect: false}).

Exchanging event

Now we have a connection and can implement echo event. Let's send and receive data via the connection. In Vibe, the unit of data to be sent and received between client and server is an event object. Any event name can be used except reserved ones, hence you can create a socket handler by using URI as event name just like creating HTTP request handler by using request URI.

Server

ServerSocket provides on(String event, Action<T> action) to receive an event and send(String event, Object data) to send an event.

socket.on("echo", new Action<String>() {
    @Override
    public void on(String data) {
        System.out.println("on echo event: " + data);
        socket.send("echo", data);
    }
});

Here, in receiving event, the allowed Java types for the type, T, depends on the event format. By default it's JSON. Therefore, in this case:

Number String Boolean Array Object null
Integer or Double String Boolean List<T> Map<String, T> null or Void

When sending event, you can send any type of data but it will be serialized by the underlying event format in their own way.

Client

A socket returned by vibe.open provides on(event: string, onEvent: (data?: any) => void) to receive an event and send(event: string, data?: any) to send an event.

var socket = vibe.open("http://localhost:8080/vibe", {transports: ["sse"], reconnect: false});
socket.on("open", function() {
    this.send("echo", "Hello there?");
});
socket.on("echo", function(data) {
    console.log("on echo event: " + data);
});

In this time, we set transports to ["sse"] to see how echo does work at the HTTP protocol level and reconnection to false to suppress auto-reconnection. Transport is a full-duplex message channel underlying the socket but is not public interface so you don't need to be aware of that. To send event to the server, a connection have to be established before so open event is safe place to send event.

When you types the code to the console, you will see an echo event with Hello there? data is sent back.

Tracking event

You may wonder how event object not plain text is sent and received via the connection based on HTTP not WebSocket. For the full answer of that question, see Anatomy of Vibe Protocol. In short,

  • client user sends an event where type is echo and data is Hello there?.
  • client creates an event object, {id:"0",type:"echo",data:"Hello there?",reply:false}.
  • client's event format serializes it to text, {"id":"0","type":"echo","data":"Hello there?","reply":false}.
  • client's sse transport formats it to data={"id":"0","type":"echo","data":"Hello there?","reply":false}.
  • server's sse transport parses it to {"id":"0","type":"echo","data":"Hello there?","reply":false}.
  • server's event format deserializes it to a map, {id=0, type=echo, data=Hello there?, reply=false}.
  • server creates an event object, {id=0, type=echo, data=Hello there?, reply=false}.
  • server user receives an event where type is echo and data is Hello there?.
  • server user sends an event where type is echo and data is Hello there?.
  • server creates an event object, {id=1, type=echo, data=Hello there?, reply=false}.
  • server's event format serializes it to text, {"id":"1","type":"echo","data":"Hello there?","reply":false}.
  • server's sse transport formats it to data: {"id":"1","type":"echo","data":"Hello there?","reply":false}\n\n.
  • client's sse transport parses it to {"id":"1","type":"echo","data":"Hello there?","reply":false}.
  • client's event format deserializes it to an object literal, {id:"1",type:"echo",data:"Hello there?",reply:false}.
  • client creates an event object, {id:"1",type:"echo",data:"Hello there?",reply:false}.
  • client user receives an event where type is echo and data is Hello there?.

The above event format is JSON which is the default one. By replacing it with one supporting binary, you can use binary data but not now. The event format will be exposed in the future release.

Broadcasting event

Let's implement chat event which should be broadcasted to every client that connected to the server.

Server

One of the patterns you need to be aware of to use Java Server is "select sockets and do something". Focus on which socket you need and how you handle a given socket. In this situation to broadcast chat event to every client, sockets we need are all opened socket and what we need to do for a socket is to send chat event. It can be done like the following:

socket.on("chat", new Action<String>() {
    @Override
    public void on(String data) {
        System.out.println("on chat event: " + chat);
        server.all(new Action<ServerSocket>() {
            @Override
            public void on(ServerSocket s) {
                s.send("chat", data);
            }
        });
    }
});

There are 3 methods to select sockets: all(Action<ServerSocket> action) for all of the socket in the server, byId(String id, Action<ServerSocket> action) for a socket associated with the given id and byTag(String[] names, Action<ServerSocket> action) for a group of socket tagged with the given tag. In any cases, the given socket is opened where I/O operations are possible so you don't need to mind the state unlike in client.

As we can separate use case into the target of operation and operation dealing with a single target, we can reuse operation. For example, spending 6 lines to only send an event decreases readability unless Java 8's Lambda Expressions is available. In this case, you can use Sentence, which is a fluent interface to deal with a group of sockets. Let's refactor the above chat action to use Sentence:

socket.on("chat", new Action<String>() {
    @Override
    public void on(String data) {
        System.out.println("on chat event: " + chat);
        server.all().send("chat", data);
    }
});

all(), byId(String id) and byTag(String... names) return a Sentence. Another advantage to use Sentence is that it internally uses actions implementing Serializable, which don't need to modify the code in clustering. Details are explained in the clustering topic.

Client

Open a console and type the followings:

var socket1 = vibe.open("http://localhost:8080/vibe", {reconnect: false});
socket1.on("open", function() {
    this.send("chat", "Hi?");
});
socket1.on("chat", function(data) {
    console.log("on echo event on socket1: " + data);
});
var socket2 = vibe.open("http://localhost:8080/vibe", {reconnect: false});
socket2.on("open", function() {
    this.send("chat", "Hello?");
});
socket2.on("echo", function(data) {
    console.log("on echo event on socket2: " + data);
});

You'll see socket1 and socket2 receive the same chat event.

Integration

We just finished writing a simple chat application but to introduce Vibe to a real project still need to confirm that Vibe is easy to integrate other technologies.

Dependency Injection

If you are familiar with Dependency Injection framework like Spring and Guice, you may realize how definite it is to use Vibe with Dependency Injection. Registers a Server as a singleton component and inject it wherever you need to handle socket.

The following codes are for Spring but the same pattern can be applied to other frameworks.

src/main/java/simple/Bootstrap.java

package simple;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.annotation.WebListener;

import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;

@WebListener
public class Bootstrap extends ContextLoaderListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        ServletContext servletContext = event.getServletContext();
        servletContext.setInitParameter(CONTEXT_CLASS_PARAM, AnnotationConfigWebApplicationContext.class.getName());
        servletContext.setInitParameter(CONFIG_LOCATION_PARAM, this.getClass().getPackage().getName());
        super.contextInitialized(event);
    }
}

Now Server doesn't need to be installed here. Let's configure Spring here instead and install Server in configuration class.

src/main/java/simple/SpringConfig.java

package simple;

import javax.servlet.ServletContext;

import org.atmosphere.vibe.platform.server.atmosphere2.AtmosphereBridge;
import org.atmosphere.vibe.server.DefaultServer;
import org.atmosphere.vibe.server.Server;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
@ComponentScan(basePackages = { "simple" })
public class SpringConfig {
    @Autowired
    private ServletContext servletContext;

    @Bean
    public Server server() {
        Server server = new DefaultServer();
        new AtmosphereBridge(servletContext, "/vibe").httpAction(server.httpAction()).websocketAction(server.websocketAction());
        return server;
    }
}

As Spring injects ServletContext needed to install Vibe on Atmosphere, installation has been done in configuration class. Of course, you can do that elsewhere by injecting Server.

src/main/java/simple/Clock.java

package simple;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class Clock {
    @Autowired
    private Server server;

    @Scheduled(fixedRate = 5000)
    public void tick() {
        server.all().send("chat", "tick: " + System.currentTimeMillis());
    }
}

This bean sends an chat event with the current server time in milliseconds to all the clients connected to the server every 5 seconds. Like this, any bean in the server can send event to client in real-time.

Clustering

What you need to cluster application is Message Oriented Middleware supporting publish and subscribe model. "select sockets and do something" is still valid here so that you don't need to know if this application is going to be clustered or not.

What you need to do that is to use ClusteredServer and to add an action to publishAction(Action<Map<String,Object>> action) that publishes a given message to all nodes including the one issued in the cluster and to pass a given message to messageAction() if one of nodes publishes a message while subscribing. Here the "message" to be published to all Server in the cluster contains the method call information of all, byId and byTag occurred in a sepcific Server. And also it is supposed to be serialized and deserialized so you should use an action implementing Serializable or Sentence.

The full code with Hazelcast is like the following:

src/main/java/simple/Bootstrap.java

package simple;

import java.util.Map;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.atmosphere.vibe.platform.Action;
import org.atmosphere.vibe.platform.server.atmosphere2.AtmosphereBridge;
import org.atmosphere.vibe.server.ClusteredServer;
import org.atmosphere.vibe.server.ServerSocket;

import com.hazelcast.config.Config;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.ITopic;
import com.hazelcast.core.Message;
import com.hazelcast.core.MessageListener;
import com.hazelcast.instance.HazelcastInstanceFactory;

@WebListener
public class Bootstrap implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        HazelcastInstance hazelcast = HazelcastInstanceFactory.newHazelcastInstance(new Config());
        final ClusteredServer server = new ClusteredServer();
        final ITopic<Map<String, Object>> topic = hazelcast.getTopic("vibe");

        topic.addMessageListener(new MessageListener<Map<String, Object>>() {
            @Override
            public void onMessage(Message<Map<String, Object>> message) {
                System.out.println("receiving a message: " + message.getMessageObject());
                server.messageAction().on(message.getMessageObject());
            }
        });
        server.publishAction(new Action<Map<String, Object>>() {
            @Override
            public void on(Map<String, Object> message) {
                System.out.println("publishing a message: " + message);
                topic.publish(message);
            }
        });

        server.socketAction(new Action<ServerSocket>() {
            @Override
            public void on(final ServerSocket socket) {
                System.out.println("on socket: " + socket.uri());
                socket.on("echo", new Action<Object>() {
                    @Override
                    public void on(Object data) {
                        System.out.println("on echo event: " + data);
                        socket.send("echo", data);
                    }
                });
                socket.on("chat", new Action<Object>() {
                    @Override
                    public void on(Object data) {
                        System.out.println("on chat event: " + data);
                        server.all().send("chat", data);
                    }
                });
            }
        });

        new AtmosphereBridge(event.getServletContext(), "/vibe").httpAction(server.httpAction()).websocketAction(server.websocketAction());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}

Getting this example

You can find this example as well as example per each platform on GitHub.


Anatomy of Vibe Protocol

wrote this on

Because there is no documented specification for the protocol now, you may wonder how Vibe protocol is and will be organized. Here's an overview of Vibe protocol including features on the roadmap. The following blocks build the protocol:

  • Handshaking - A process to negotiate the protocol.
    • The client sends an HTTP request with a special URI for handshaking and then the server responds with a newly issued socket id and socket options like transports. Then, the client determines whether to connect or not and which transport, event format, and so on to use. If all is well, the chosen client-side starts a connection and the corresponding server-side transport accepts it.
  • Event format - A separated event serializer/deserializer per each socket.
    • The unit of data to be sent/received is an event object. The serializer is used to format a event object given by user to text or binary for transport and the decesrializer is used to parse a text or binary given by transport to event object for user. The default one is JSON so binary data is not supported but you can use it by replacing JSON with one supporting binary like BSON.
  • Transport - A full-duplex message channel.
    • It's based on specific network technology and consists a pair of a set of rules to provide a consistent view of full duplex connection, one for the client and the other one for the server. The rules defines what open, close, message and is and workarounds to fix quirks. By choosing a network technology and defining rules, you can make and use your own transport as well.
  • Extension - An additional feature built on protocol.
    • It's to make it easy to use useful implementation-specific features in other implementations by making it specification at the protocol level. Like transport, an extension includes a pair of a set of rules, one for the client and the other one for server. Generally, it is built on a view provided by transport so doesn't affect what the underlying transport is.

Let's see how the above blocks affect HTTP request and response in an example of exchanging an event. An explanation for extension is excluded because it is usually built on transport.

Java Server 1

Server server = new DefaultServer();
server.socketAction(new Action<ServerSocket>() {
    @Override
    public void on(final ServerSocket socket) {
        socket.on("echo", new Action<String>() {
            @Override
            public void on(String data) {
                socket.send("echo", data);
            }
        });
    }
});

JavaScript Client

vibe.open("http://localhost:8080/vibe", {transports: ["sse"]})
.on("open", function() {
    this.send("echo", "An echo message");
})
.on("echo", function(data) {
    console.log("on echo event:", data);
});

  • 1: In case of Java Server, mapping Server to a specific URI, http://localhost:8080/vibe, is done in integrating with a platform. For details, see quick start guide and working examples.

First, in 3.0.0-Alpha1, handshaking is not yet implemented so the protocol negotiation is assumed already done by setting options by user. In other means, the client and the server have agreed to use sse transport and JSON event format in advance.

HTTP request/response for the client to receive data from the server

GET /vibe?id=d009d9f3-088f-4977-8ec0-99576f84cb4c&_=32fb5d&when=open&transport=sse&heartbeat=20000 HTTP/1.1
Cache-Control: no-cache
Accept: text/event-stream
Host: localhost:8080
Connection: keep-alive

HTTP/1.1 200 OK
Date: Tue, 07 Oct 2014 11:24:06 GMT
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
access-control-allow-origin: *
access-control-allow-credentials: true
Content-Type: text/event-stream; charset=utf-8
Transfer-Encoding: chunked
Server: Jetty(9.2.2.v20140723)

801


47
data: {"id":"1","type":"echo","data":"An echo message","reply":false}

It's initiated by vibe.open("http://localhost:8080/vibe", {transports: ["sse"]}).

  • Accept: text/event-stream from the request and Content-Type: text/event-stream; charset=utf-8 from the response are required by Server-Sent Events, which is a underlying technology standard of sse transport. As Server-Sent Events is based on HTTP, HTTP is also underlying technology.
  • 801 and two whitespace lines are a special padding for old browser's Server-Sent Events implementation to be aware that the connection is opened, which is a kind of rule of server-side sse transport.
  • After socket.send("echo", data); is executed, an event where type is echo and data is data is created and passed to the serializer of JSON event format. It creates a text, {"id":"1","type":"echo","data":"An echo message","reply":false}. Then sse transport formats that text by wrapping data: and \n\n to conform the event-stream format from its specification and writes it to HTTP response. Likewise, the client's sse transport extracts message following its specification and gives it the deserializer of JSON event format. Then it parses the text to event object and fires echo event with data so finally console.log("on echo event:", data); is executed.

HTTP request/response for the client to send data to the server

POST /vibe?id=d009d9f3-088f-4977-8ec0-99576f84cb4c&_=6a5e3d HTTP/1.1
content-type: text/plain; charset=utf-8
Host: localhost:8080
Connection: keep-alive
Transfer-Encoding: chunked

44
data={"id":"0","type":"echo","data":"An echo message","reply":false}
0

HTTP/1.1 200 OK
Date: Tue, 07 Oct 2014 11:24:06 GMT
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
access-control-allow-origin: *
access-control-allow-credentials: true
Content-Length: 0
Server: Jetty(9.2.2.v20140723)

It's initiated by this.send("echo", "An echo message");.

How it is processed is the same with the above one. Because sse transport is only able to establish read-only connection, to simulate a full-duplex connection, a separate HTTP request with POST is used every time it sends an event to the server. This is also a kind of rule of client-side sse transport.